From 1cff666b5206c28e3116eea7edb36b65135775a1 Mon Sep 17 00:00:00 2001 From: Shawn Tabrizi Date: Wed, 4 Sep 2024 11:26:26 -0400 Subject: [PATCH 001/125] Create a Basic Proving Trie for the Runtime (#3881) This PR will introduce a `BasicProvingTrie` type, which makes it easy to construct and prove data in a base-16 merkle trie within the runtime. Data into the merkle trie only require that they implement `Encode` / `Decode`. A FRAME compatible `TrieError` was created and added to `DispatchError`. Expected usage is to construct the merkle trie with all data offline, and then place only the merkle root of that trie on-chain. Also offchain, a user is given a compact merkle proof of some data they want to prove exists on the blockchain. Then in the runtime, you can call `verify_single_value_proof` or `verify_proof` with the root, proof, and the keys and values you want to verify exists in the merkle trie. Closes https://github.com/paritytech/polkadot-sdk/issues/3880 Contributes to #5400 --------- Co-authored-by: Ankan <10196091+Ank4n@users.noreply.github.com> Co-authored-by: Oliver Tale-Yazdi --- Cargo.lock | 1 + prdoc/pr_3881.prdoc | 15 + substrate/primitives/runtime/Cargo.toml | 2 + substrate/primitives/runtime/src/lib.rs | 16 + .../primitives/runtime/src/proving_trie.rs | 388 ++++++++++++++++++ 5 files changed, 422 insertions(+) create mode 100644 prdoc/pr_3881.prdoc create mode 100644 substrate/primitives/runtime/src/proving_trie.rs diff --git a/Cargo.lock b/Cargo.lock index b3eefe1826c..508fc2e8922 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20741,6 +20741,7 @@ dependencies = [ "sp-state-machine", "sp-std 14.0.0", "sp-tracing 16.0.0", + "sp-trie", "sp-weights", "substrate-test-runtime-client", "tracing", diff --git a/prdoc/pr_3881.prdoc b/prdoc/pr_3881.prdoc new file mode 100644 index 00000000000..4cf6425e73a --- /dev/null +++ b/prdoc/pr_3881.prdoc @@ -0,0 +1,15 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Introduce a Generic Proving Trie + +doc: + - audience: Runtime Dev + description: | + This PR introduces a Proving Trie object which can be used inside the runtime. This can allow + for things like airdrops where a single hash is stored on chain representing the whole airdrop + and individuals present a proof of their inclusion in the airdrop. + +crates: + - name: sp-runtime + bump: major diff --git a/substrate/primitives/runtime/Cargo.toml b/substrate/primitives/runtime/Cargo.toml index 55e9f4b3199..800bf4bd073 100644 --- a/substrate/primitives/runtime/Cargo.toml +++ b/substrate/primitives/runtime/Cargo.toml @@ -32,6 +32,7 @@ sp-arithmetic = { workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-std = { workspace = true } +sp-trie = { workspace = true } sp-weights = { workspace = true } docify = { workspace = true } tracing = { workspace = true, features = ["log"], default-features = false } @@ -69,6 +70,7 @@ std = [ "sp-state-machine/std", "sp-std/std", "sp-tracing/std", + "sp-trie/std", "sp-weights/std", "tracing/std", ] diff --git a/substrate/primitives/runtime/src/lib.rs b/substrate/primitives/runtime/src/lib.rs index fd10dee2a7c..ba1ea376972 100644 --- a/substrate/primitives/runtime/src/lib.rs +++ b/substrate/primitives/runtime/src/lib.rs @@ -90,6 +90,7 @@ pub mod generic; pub mod legacy; mod multiaddress; pub mod offchain; +pub mod proving_trie; pub mod runtime_logger; mod runtime_string; #[cfg(feature = "std")] @@ -103,6 +104,8 @@ pub use crate::runtime_string::*; // Re-export Multiaddress pub use multiaddress::MultiAddress; +use proving_trie::TrieError; + /// Re-export these since they're only "kind of" generic. pub use generic::{Digest, DigestItem}; @@ -592,6 +595,8 @@ pub enum DispatchError { Unavailable, /// Root origin is not allowed. RootNotAllowed, + /// An error with tries. + Trie(TrieError), } /// Result of a `Dispatchable` which contains the `DispatchResult` and additional information about @@ -697,6 +702,12 @@ impl From for DispatchError { } } +impl From for DispatchError { + fn from(e: TrieError) -> DispatchError { + Self::Trie(e) + } +} + impl From<&'static str> for DispatchError { fn from(err: &'static str) -> DispatchError { Self::Other(err) @@ -721,6 +732,7 @@ impl From for &'static str { Corruption => "State corrupt", Unavailable => "Resource unavailable", RootNotAllowed => "Root not allowed", + Trie(e) => e.into(), } } } @@ -768,6 +780,10 @@ impl traits::Printable for DispatchError { Corruption => "State corrupt".print(), Unavailable => "Resource unavailable".print(), RootNotAllowed => "Root not allowed".print(), + Trie(e) => { + "Trie error: ".print(); + <&'static str>::from(*e).print(); + }, } } } diff --git a/substrate/primitives/runtime/src/proving_trie.rs b/substrate/primitives/runtime/src/proving_trie.rs new file mode 100644 index 00000000000..688bf81e0d7 --- /dev/null +++ b/substrate/primitives/runtime/src/proving_trie.rs @@ -0,0 +1,388 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Types for a compact base-16 merkle trie used for checking and generating proofs within the +//! runtime. The `sp-trie` crate exposes all of these same functionality (and more), but this +//! library is designed to work more easily with runtime native types, which simply need to +//! implement `Encode`/`Decode`. It also exposes a runtime friendly `TrieError` type which can be +//! use inside of a FRAME Pallet. +//! +//! Proofs are created with latest substrate trie format (`LayoutV1`), and are not compatible with +//! proofs using `LayoutV0`. + +use crate::{Decode, DispatchError, Encode, MaxEncodedLen, TypeInfo}; +#[cfg(feature = "serde")] +use crate::{Deserialize, Serialize}; + +use sp_std::vec::Vec; +use sp_trie::{ + trie_types::{TrieDBBuilder, TrieDBMutBuilderV1, TrieError as SpTrieError}, + LayoutV1, MemoryDB, Trie, TrieMut, VerifyError, +}; + +type HashOf = ::Out; + +/// A runtime friendly error type for tries. +#[derive(Eq, PartialEq, Clone, Copy, Encode, Decode, Debug, TypeInfo, MaxEncodedLen)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum TrieError { + /* From TrieError */ + /// Attempted to create a trie with a state root not in the DB. + InvalidStateRoot, + /// Trie item not found in the database, + IncompleteDatabase, + /// A value was found in the trie with a nibble key that was not byte-aligned. + ValueAtIncompleteKey, + /// Corrupt Trie item. + DecoderError, + /// Hash is not value. + InvalidHash, + /* From VerifyError */ + /// The statement being verified contains multiple key-value pairs with the same key. + DuplicateKey, + /// The proof contains at least one extraneous node. + ExtraneousNode, + /// The proof contains at least one extraneous value which should have been omitted from the + /// proof. + ExtraneousValue, + /// The proof contains at least one extraneous hash reference the should have been omitted. + ExtraneousHashReference, + /// The proof contains an invalid child reference that exceeds the hash length. + InvalidChildReference, + /// The proof indicates that an expected value was not found in the trie. + ValueMismatch, + /// The proof is missing trie nodes required to verify. + IncompleteProof, + /// The root hash computed from the proof is incorrect. + RootMismatch, + /// One of the proof nodes could not be decoded. + DecodeError, +} + +impl From> for TrieError { + fn from(error: SpTrieError) -> Self { + match error { + SpTrieError::InvalidStateRoot(..) => Self::InvalidStateRoot, + SpTrieError::IncompleteDatabase(..) => Self::IncompleteDatabase, + SpTrieError::ValueAtIncompleteKey(..) => Self::ValueAtIncompleteKey, + SpTrieError::DecoderError(..) => Self::DecoderError, + SpTrieError::InvalidHash(..) => Self::InvalidHash, + } + } +} + +impl From> for TrieError { + fn from(error: VerifyError) -> Self { + match error { + VerifyError::DuplicateKey(..) => Self::DuplicateKey, + VerifyError::ExtraneousNode => Self::ExtraneousNode, + VerifyError::ExtraneousValue(..) => Self::ExtraneousValue, + VerifyError::ExtraneousHashReference(..) => Self::ExtraneousHashReference, + VerifyError::InvalidChildReference(..) => Self::InvalidChildReference, + VerifyError::ValueMismatch(..) => Self::ValueMismatch, + VerifyError::IncompleteProof => Self::IncompleteProof, + VerifyError::RootMismatch(..) => Self::RootMismatch, + VerifyError::DecodeError(..) => Self::DecodeError, + } + } +} + +impl From for &'static str { + fn from(e: TrieError) -> &'static str { + match e { + TrieError::InvalidStateRoot => "The state root is not in the database.", + TrieError::IncompleteDatabase => "A trie item was not found in the database.", + TrieError::ValueAtIncompleteKey => + "A value was found with a key that is not byte-aligned.", + TrieError::DecoderError => "A corrupt trie item was encountered.", + TrieError::InvalidHash => "The hash does not match the expected value.", + TrieError::DuplicateKey => "The proof contains duplicate keys.", + TrieError::ExtraneousNode => "The proof contains extraneous nodes.", + TrieError::ExtraneousValue => "The proof contains extraneous values.", + TrieError::ExtraneousHashReference => "The proof contains extraneous hash references.", + TrieError::InvalidChildReference => "The proof contains an invalid child reference.", + TrieError::ValueMismatch => "The proof indicates a value mismatch.", + TrieError::IncompleteProof => "The proof is incomplete.", + TrieError::RootMismatch => "The root hash computed from the proof is incorrect.", + TrieError::DecodeError => "One of the proof nodes could not be decoded.", + } + } +} + +/// A helper structure for building a basic base-16 merkle trie and creating compact proofs for that +/// trie. Proofs are created with latest substrate trie format (`LayoutV1`), and are not compatible +/// with proofs using `LayoutV0`. +pub struct BasicProvingTrie +where + Hashing: sp_core::Hasher, +{ + db: MemoryDB, + root: HashOf, + _phantom: core::marker::PhantomData<(Key, Value)>, +} + +impl BasicProvingTrie +where + Hashing: sp_core::Hasher, + Key: Encode, + Value: Encode + Decode, +{ + /// Create a new instance of a `ProvingTrie` using an iterator of key/value pairs. + pub fn generate_for(items: I) -> Result + where + I: IntoIterator, + { + let mut db = MemoryDB::default(); + let mut root = Default::default(); + + { + let mut trie = TrieDBMutBuilderV1::new(&mut db, &mut root).build(); + for (key, value) in items.into_iter() { + key.using_encoded(|k| value.using_encoded(|v| trie.insert(k, v))) + .map_err(|_| "failed to insert into trie")?; + } + } + + Ok(Self { db, root, _phantom: Default::default() }) + } + + /// Access the underlying trie root. + pub fn root(&self) -> &HashOf { + &self.root + } + + /// Query a value contained within the current trie. Returns `None` if the + /// nodes within the current `MemoryDB` are insufficient to query the item. + pub fn query(&self, key: Key) -> Option { + let trie = TrieDBBuilder::new(&self.db, &self.root).build(); + key.using_encoded(|s| trie.get(s)) + .ok()? + .and_then(|raw| Value::decode(&mut &*raw).ok()) + } + + /// Create a compact merkle proof needed to prove all `keys` and their values are in the trie. + /// Returns `None` if the nodes within the current `MemoryDB` are insufficient to create a + /// proof. + /// + /// This function makes a proof with latest substrate trie format (`LayoutV1`), and is not + /// compatible with `LayoutV0`. + /// + /// When verifying the proof created by this function, you must include all of the keys and + /// values of the proof, else the verifier will complain that extra nodes are provided in the + /// proof that are not needed. + pub fn create_proof(&self, keys: &[Key]) -> Result>, DispatchError> { + sp_trie::generate_trie_proof::, _, _, _>( + &self.db, + self.root, + &keys.into_iter().map(|k| k.encode()).collect::>>(), + ) + .map_err(|err| TrieError::from(*err).into()) + } + + /// Create a compact merkle proof needed to prove a single key and its value are in the trie. + /// Returns `None` if the nodes within the current `MemoryDB` are insufficient to create a + /// proof. + /// + /// This function makes a proof with latest substrate trie format (`LayoutV1`), and is not + /// compatible with `LayoutV0`. + pub fn create_single_value_proof(&self, key: Key) -> Result>, DispatchError> { + self.create_proof(&[key]) + } +} + +/// Verify the existence or non-existence of `key` and `value` in a given trie root and proof. +/// +/// Proofs must be created with latest substrate trie format (`LayoutV1`). +pub fn verify_single_value_proof( + root: HashOf, + proof: &[Vec], + key: Key, + maybe_value: Option, +) -> Result<(), DispatchError> +where + Hashing: sp_core::Hasher, + Key: Encode, + Value: Encode, +{ + sp_trie::verify_trie_proof::, _, _, _>( + &root, + proof, + &[(key.encode(), maybe_value.map(|value| value.encode()))], + ) + .map_err(|err| TrieError::from(err).into()) +} + +/// Verify the existence or non-existence of multiple `items` in a given trie root and proof. +/// +/// Proofs must be created with latest substrate trie format (`LayoutV1`). +pub fn verify_proof( + root: HashOf, + proof: &[Vec], + items: &[(Key, Option)], +) -> Result<(), DispatchError> +where + Hashing: sp_core::Hasher, + Key: Encode, + Value: Encode, +{ + let items_encoded = items + .into_iter() + .map(|(key, maybe_value)| (key.encode(), maybe_value.as_ref().map(|value| value.encode()))) + .collect::, Option>)>>(); + + sp_trie::verify_trie_proof::, _, _, _>(&root, proof, &items_encoded) + .map_err(|err| TrieError::from(err).into()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::traits::BlakeTwo256; + use sp_core::H256; + use sp_std::collections::btree_map::BTreeMap; + + // A trie which simulates a trie of accounts (u32) and balances (u128). + type BalanceTrie = BasicProvingTrie; + + // The expected root hash for an empty trie. + fn empty_root() -> H256 { + sp_trie::empty_trie_root::>() + } + + fn create_balance_trie() -> BalanceTrie { + // Create a map of users and their balances. + let mut map = BTreeMap::::new(); + for i in 0..100u32 { + map.insert(i, i.into()); + } + + // Put items into the trie. + let balance_trie = BalanceTrie::generate_for(map).unwrap(); + + // Root is changed. + let root = *balance_trie.root(); + assert!(root != empty_root()); + + // Assert valid keys are queryable. + assert_eq!(balance_trie.query(6u32), Some(6u128)); + assert_eq!(balance_trie.query(9u32), Some(9u128)); + assert_eq!(balance_trie.query(69u32), Some(69u128)); + // Invalid key returns none. + assert_eq!(balance_trie.query(6969u32), None); + + balance_trie + } + + #[test] + fn empty_trie_works() { + let empty_trie = BalanceTrie::generate_for(Vec::new()).unwrap(); + assert_eq!(*empty_trie.root(), empty_root()); + } + + #[test] + fn basic_end_to_end_single_value() { + let balance_trie = create_balance_trie(); + let root = *balance_trie.root(); + + // Create a proof for a valid key. + let proof = balance_trie.create_single_value_proof(6u32).unwrap(); + + // Assert key is provable, all other keys are invalid. + for i in 0..200u32 { + if i == 6 { + assert_eq!( + verify_single_value_proof::( + root, + &proof, + i, + Some(u128::from(i)) + ), + Ok(()) + ); + // Wrong value is invalid. + assert_eq!( + verify_single_value_proof::( + root, + &proof, + i, + Some(u128::from(i + 1)) + ), + Err(TrieError::RootMismatch.into()) + ); + } else { + assert!(verify_single_value_proof::( + root, + &proof, + i, + Some(u128::from(i)) + ) + .is_err()); + assert!(verify_single_value_proof::( + root, + &proof, + i, + None:: + ) + .is_err()); + } + } + } + + #[test] + fn basic_end_to_end_multi_value() { + let balance_trie = create_balance_trie(); + let root = *balance_trie.root(); + + // Create a proof for a valid and invalid key. + let proof = balance_trie.create_proof(&[6u32, 69u32, 6969u32]).unwrap(); + let items = [(6u32, Some(6u128)), (69u32, Some(69u128)), (6969u32, None)]; + + assert_eq!(verify_proof::(root, &proof, &items), Ok(())); + } + + #[test] + fn proof_fails_with_bad_data() { + let balance_trie = create_balance_trie(); + let root = *balance_trie.root(); + + // Create a proof for a valid key. + let proof = balance_trie.create_single_value_proof(6u32).unwrap(); + + // Correct data verifies successfully + assert_eq!( + verify_single_value_proof::(root, &proof, 6u32, Some(6u128)), + Ok(()) + ); + + // Fail to verify proof with wrong root + assert_eq!( + verify_single_value_proof::( + Default::default(), + &proof, + 6u32, + Some(6u128) + ), + Err(TrieError::RootMismatch.into()) + ); + + // Fail to verify proof with wrong data + assert_eq!( + verify_single_value_proof::(root, &[], 6u32, Some(6u128)), + Err(TrieError::IncompleteProof.into()) + ); + } +} -- GitLab From d5346e72e54b040670b60475b90a3c467f03b08c Mon Sep 17 00:00:00 2001 From: Shawn Tabrizi Date: Thu, 5 Sep 2024 03:41:11 -0400 Subject: [PATCH 002/125] fix link check (#5590) PR Doc #5443 broke the link checker because it includes a link to this private repo: https://github.com/paritytech/devops/issues/3502 This PR adds the `prdoc` folder to the excluded paths of the link checker to ensure that historical PR Docs do not break the pipeline. I think this makes sense over whitelisting the github link because we probably expect that in a long enough time period, links from old PR docs will start to break, but I don't think we intend to update those old PR docs, or start whitelisting lots of urls. --- .config/lychee.toml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.config/lychee.toml b/.config/lychee.toml index b7bb6f0ce49..b1f08de3334 100644 --- a/.config/lychee.toml +++ b/.config/lychee.toml @@ -18,7 +18,10 @@ accept = [ "429", ] -exclude_path = ["./target"] +exclude_path = [ + "./prdoc", + "./target", +] exclude = [ # Place holders (no need to fix these): -- GitLab From cf330ccd58e62b8033acdd0e7b14f33d7a480c09 Mon Sep 17 00:00:00 2001 From: PG Herveou Date: Thu, 5 Sep 2024 10:14:50 +0200 Subject: [PATCH 003/125] [pallet-revive] refactor uapi with better types (#5555) start using better type for address, code_hash, and salt in runtime and the uapi crate fix https://github.com/paritytech/polkadot-sdk/issues/5575 --- prdoc/pr_5555.prdoc | 15 ++++ substrate/frame/revive/fixtures/build.rs | 1 + .../frame/revive/fixtures/contracts/call.rs | 2 +- .../fixtures/contracts/call_return_code.rs | 2 +- .../contracts/call_runtime_and_call.rs | 2 +- .../contracts/call_with_flags_and_value.rs | 2 +- .../fixtures/contracts/call_with_limit.rs | 2 +- .../fixtures/contracts/caller_contract.rs | 14 ++-- .../contracts/chain_extension_temp_storage.rs | 7 +- .../fixtures/contracts/common/src/lib.rs | 12 +++ .../contracts/create_storage_and_call.rs | 2 +- .../create_storage_and_instantiate.rs | 7 +- .../create_transient_storage_and_call.rs | 2 +- .../fixtures/contracts/delegate_call.rs | 2 +- .../fixtures/contracts/delegate_call_lib.rs | 5 +- .../contracts/delegate_call_simple.rs | 2 +- .../contracts/destroy_and_transfer.rs | 16 ++-- .../frame/revive/fixtures/contracts/drain.rs | 2 +- .../contracts/instantiate_return_code.rs | 4 +- .../contracts/locking_delegate_dependency.rs | 2 +- .../fixtures/contracts/read_only_call.rs | 2 +- .../revive/fixtures/contracts/recurse.rs | 7 +- .../fixtures/contracts/self_destruct.rs | 8 +- .../contracts/self_destructing_constructor.rs | 2 +- .../fixtures/contracts/set_code_hash.rs | 2 +- .../contracts/transfer_return_code.rs | 2 +- .../frame/revive/src/benchmarking/mod.rs | 26 +++--- substrate/frame/revive/src/debug.rs | 2 +- substrate/frame/revive/src/lib.rs | 8 +- substrate/frame/revive/src/storage.rs | 6 +- .../frame/revive/src/tests/test_debug.rs | 20 ++--- substrate/frame/revive/src/wasm/runtime.rs | 69 +++++++-------- substrate/frame/revive/src/weights.rs | 24 ++---- substrate/frame/revive/uapi/src/host.rs | 47 +++++------ .../frame/revive/uapi/src/host/riscv32.rs | 84 +++++++++---------- 35 files changed, 211 insertions(+), 201 deletions(-) create mode 100644 prdoc/pr_5555.prdoc diff --git a/prdoc/pr_5555.prdoc b/prdoc/pr_5555.prdoc new file mode 100644 index 00000000000..630345b9b5a --- /dev/null +++ b/prdoc/pr_5555.prdoc @@ -0,0 +1,15 @@ +title: Make salt optional + +doc: + - audience: Runtime Dev + description: | + Remove address_len and salt_len from uapi as both are now fixed size + +crates: + - name: pallet-revive + bump: patch + - name: pallet-revive-uapi + bump: patch + - name: pallet-revive-fixtures + bump: patch + diff --git a/substrate/frame/revive/fixtures/build.rs b/substrate/frame/revive/fixtures/build.rs index ed981a75467..944ae246c1b 100644 --- a/substrate/frame/revive/fixtures/build.rs +++ b/substrate/frame/revive/fixtures/build.rs @@ -159,6 +159,7 @@ mod build { fn post_process(input_path: &Path, output_path: &Path) -> Result<()> { let mut config = polkavm_linker::Config::default(); config.set_strip(true); + config.set_optimize(false); let orig = fs::read(input_path).with_context(|| format!("Failed to read {:?}", input_path))?; let linked = polkavm_linker::program_from_elf(config, orig.as_ref()) diff --git a/substrate/frame/revive/fixtures/contracts/call.rs b/substrate/frame/revive/fixtures/contracts/call.rs index 73f427650c2..93687441fa5 100644 --- a/substrate/frame/revive/fixtures/contracts/call.rs +++ b/substrate/frame/revive/fixtures/contracts/call.rs @@ -31,7 +31,7 @@ pub extern "C" fn deploy() {} pub extern "C" fn call() { input!( callee_input: [u8; 4], - callee_addr: [u8; 20], + callee_addr: &[u8; 20], ); // Call the callee diff --git a/substrate/frame/revive/fixtures/contracts/call_return_code.rs b/substrate/frame/revive/fixtures/contracts/call_return_code.rs index e8f995cffc7..29b77c343fe 100644 --- a/substrate/frame/revive/fixtures/contracts/call_return_code.rs +++ b/substrate/frame/revive/fixtures/contracts/call_return_code.rs @@ -33,7 +33,7 @@ pub extern "C" fn deploy() {} pub extern "C" fn call() { input!( 100, - callee_addr: [u8; 20], + callee_addr: &[u8; 20], input: [u8], ); diff --git a/substrate/frame/revive/fixtures/contracts/call_runtime_and_call.rs b/substrate/frame/revive/fixtures/contracts/call_runtime_and_call.rs index f3d2ece2132..7cd46849655 100644 --- a/substrate/frame/revive/fixtures/contracts/call_runtime_and_call.rs +++ b/substrate/frame/revive/fixtures/contracts/call_runtime_and_call.rs @@ -31,7 +31,7 @@ pub extern "C" fn call() { input!( 512, callee_input: [u8; 4], - callee_addr: [u8; 20], + callee_addr: &[u8; 20], call: [u8], ); diff --git a/substrate/frame/revive/fixtures/contracts/call_with_flags_and_value.rs b/substrate/frame/revive/fixtures/contracts/call_with_flags_and_value.rs index 15c1124eeae..c3204c29281 100644 --- a/substrate/frame/revive/fixtures/contracts/call_with_flags_and_value.rs +++ b/substrate/frame/revive/fixtures/contracts/call_with_flags_and_value.rs @@ -31,7 +31,7 @@ pub extern "C" fn deploy() {} pub extern "C" fn call() { input!( 256, - callee_addr: [u8; 20], + callee_addr: &[u8; 20], flags: u32, value: u64, forwarded_input: [u8], diff --git a/substrate/frame/revive/fixtures/contracts/call_with_limit.rs b/substrate/frame/revive/fixtures/contracts/call_with_limit.rs index 985df672411..a941aa9a342 100644 --- a/substrate/frame/revive/fixtures/contracts/call_with_limit.rs +++ b/substrate/frame/revive/fixtures/contracts/call_with_limit.rs @@ -32,7 +32,7 @@ pub extern "C" fn deploy() {} pub extern "C" fn call() { input!( 256, - callee_addr: [u8; 20], + callee_addr: &[u8; 20], ref_time: u64, proof_size: u64, forwarded_input: [u8], diff --git a/substrate/frame/revive/fixtures/contracts/caller_contract.rs b/substrate/frame/revive/fixtures/contracts/caller_contract.rs index dceab813f88..3b83f208d62 100644 --- a/substrate/frame/revive/fixtures/contracts/caller_contract.rs +++ b/substrate/frame/revive/fixtures/contracts/caller_contract.rs @@ -28,7 +28,7 @@ pub extern "C" fn deploy() {} #[no_mangle] #[polkavm_derive::polkavm_export] pub extern "C" fn call() { - input!(code_hash: [u8; 32],); + input!(code_hash: &[u8; 32],); // The value to transfer on instantiation and calls. Chosen to be greater than existential // deposit. @@ -73,7 +73,6 @@ pub extern "C" fn call() { // Deploy the contract successfully. let mut callee = [0u8; 20]; - let callee = &mut &mut callee[..]; api::instantiate( code_hash, @@ -82,17 +81,16 @@ pub extern "C" fn call() { None, // No deposit limit. &value, &input, - Some(callee), + Some(&mut callee), None, &salt, ) .unwrap(); - assert_eq!(callee.len(), 20); // Call the new contract and expect it to return failing exit code. let res = api::call( uapi::CallFlags::empty(), - callee, + &callee, 0u64, // How much ref_time weight to devote for the execution. 0 = all. 0u64, // How much proof_size weight to devote for the execution. 0 = all. None, // No deposit limit. @@ -105,7 +103,7 @@ pub extern "C" fn call() { // Fail to call the contract due to insufficient ref_time weight. let res = api::call( uapi::CallFlags::empty(), - callee, + &callee, 1u64, // Too little ref_time weight. 0u64, // How much proof_size weight to devote for the execution. 0 = all. None, // No deposit limit. @@ -118,7 +116,7 @@ pub extern "C" fn call() { // Fail to call the contract due to insufficient proof_size weight. let res = api::call( uapi::CallFlags::empty(), - callee, + &callee, 0u64, // How much ref_time weight to devote for the execution. 0 = all. 1u64, // too little proof_size weight None, // No deposit limit. @@ -132,7 +130,7 @@ pub extern "C" fn call() { let mut output = [0u8; 4]; api::call( uapi::CallFlags::empty(), - callee, + &callee, 0u64, // How much ref_time weight to devote for the execution. 0 = all. 0u64, // How much proof_size weight to devote for the execution. 0 = all. None, // No deposit limit. diff --git a/substrate/frame/revive/fixtures/contracts/chain_extension_temp_storage.rs b/substrate/frame/revive/fixtures/contracts/chain_extension_temp_storage.rs index c7596e44dab..bb5c1ccbc1d 100644 --- a/substrate/frame/revive/fixtures/contracts/chain_extension_temp_storage.rs +++ b/substrate/frame/revive/fixtures/contracts/chain_extension_temp_storage.rs @@ -20,7 +20,7 @@ #![no_std] #![no_main] -use common::{input, output}; +use common::input; use uapi::{HostFn, HostFnImpl as api}; #[no_mangle] @@ -47,12 +47,13 @@ pub extern "C" fn call() { input[8] = 1u8; // Read the contract address. - output!(addr, [0u8; 32], api::address,); + let mut addr = [0u8; 20]; + api::address(&mut addr); // call self api::call( uapi::CallFlags::ALLOW_REENTRY, - addr, + &addr, 0u64, // How much ref_time to devote for the execution. 0 = all. 0u64, // How much proof_size to devote for the execution. 0 = all. None, // No deposit limit. diff --git a/substrate/frame/revive/fixtures/contracts/common/src/lib.rs b/substrate/frame/revive/fixtures/contracts/common/src/lib.rs index 6631af8292f..947247e9cf7 100644 --- a/substrate/frame/revive/fixtures/contracts/common/src/lib.rs +++ b/substrate/frame/revive/fixtures/contracts/common/src/lib.rs @@ -100,11 +100,23 @@ macro_rules! input { input!(@inner $input, $cursor + $n, $($rest)*); }; + // Match an array reference of the given size. + // e.g input!(var1: &[u8; 32], ); + (@inner $input:expr, $cursor:expr, $var:ident: &[u8; $n:expr], $($rest:tt)*) => { + let $var: &[u8; $n] = &$input[$cursor..$cursor+$n].try_into().unwrap(); + input!(@inner $input, $cursor + $n, $($rest)*); + }; + // Size of a u8 slice. (@size $size:expr, $var:ident: [u8; $n:expr], $($rest:tt)*) => { input!(@size $size + $n, $($rest)*) }; + // Size of an array reference. + (@size $size:expr, $var:ident: &[u8; $n:expr], $($rest:tt)*) => { + input!(@size $size + $n, $($rest)*) + }; + // Entry point, with the buffer and it's size specified first. // e.g input!(buffer, 512, var1: u32, var2: [u8], ); ($buffer:ident, $size:expr, $($rest:tt)*) => { diff --git a/substrate/frame/revive/fixtures/contracts/create_storage_and_call.rs b/substrate/frame/revive/fixtures/contracts/create_storage_and_call.rs index 7a0b497079c..28d161791e5 100644 --- a/substrate/frame/revive/fixtures/contracts/create_storage_and_call.rs +++ b/substrate/frame/revive/fixtures/contracts/create_storage_and_call.rs @@ -32,7 +32,7 @@ pub extern "C" fn call() { input!( buffer, input: [u8; 4], - callee: [u8; 20], + callee: &[u8; 20], deposit_limit: [u8; 8], ); diff --git a/substrate/frame/revive/fixtures/contracts/create_storage_and_instantiate.rs b/substrate/frame/revive/fixtures/contracts/create_storage_and_instantiate.rs index 53b9afba778..d87c2e8cd35 100644 --- a/substrate/frame/revive/fixtures/contracts/create_storage_and_instantiate.rs +++ b/substrate/frame/revive/fixtures/contracts/create_storage_and_instantiate.rs @@ -31,14 +31,13 @@ pub extern "C" fn deploy() {} pub extern "C" fn call() { input!( input: [u8; 4], - code_hash: [u8; 32], + code_hash: &[u8; 32], deposit_limit: [u8; 8], ); let value = 10_000u64.to_le_bytes(); let salt = [0u8; 32]; let mut address = [0u8; 20]; - let address = &mut &mut address[..]; api::instantiate( code_hash, @@ -47,12 +46,12 @@ pub extern "C" fn call() { Some(deposit_limit), &value, input, - Some(address), + Some(&mut address), None, &salt, ) .unwrap(); // Return the deployed contract address. - api::return_value(uapi::ReturnFlags::empty(), address); + api::return_value(uapi::ReturnFlags::empty(), &address); } diff --git a/substrate/frame/revive/fixtures/contracts/create_transient_storage_and_call.rs b/substrate/frame/revive/fixtures/contracts/create_transient_storage_and_call.rs index 8788542a0c5..753490cf26b 100644 --- a/substrate/frame/revive/fixtures/contracts/create_transient_storage_and_call.rs +++ b/substrate/frame/revive/fixtures/contracts/create_transient_storage_and_call.rs @@ -35,7 +35,7 @@ pub extern "C" fn call() { buffer, len: u32, input: [u8; 4], - callee: [u8; 20], + callee: &[u8; 20], ); let rounds = len as usize / BUFFER.len(); diff --git a/substrate/frame/revive/fixtures/contracts/delegate_call.rs b/substrate/frame/revive/fixtures/contracts/delegate_call.rs index d03ddab1bc5..9fd155408af 100644 --- a/substrate/frame/revive/fixtures/contracts/delegate_call.rs +++ b/substrate/frame/revive/fixtures/contracts/delegate_call.rs @@ -28,7 +28,7 @@ pub extern "C" fn deploy() {} #[no_mangle] #[polkavm_derive::polkavm_export] pub extern "C" fn call() { - input!(code_hash: [u8; 32],); + input!(code_hash: &[u8; 32],); let mut key = [0u8; 32]; key[0] = 1u8; diff --git a/substrate/frame/revive/fixtures/contracts/delegate_call_lib.rs b/substrate/frame/revive/fixtures/contracts/delegate_call_lib.rs index 921543dced0..c5525423a9e 100644 --- a/substrate/frame/revive/fixtures/contracts/delegate_call_lib.rs +++ b/substrate/frame/revive/fixtures/contracts/delegate_call_lib.rs @@ -44,6 +44,7 @@ pub extern "C" fn call() { assert_eq!(value_transferred, 1337); // Assert that ALICE is the caller of the contract. - output!(caller, [0u8; 20], api::caller,); - assert_eq!(&caller[..], &[1u8; 20]); + let mut caller = [0u8; 20]; + api::caller(&mut caller); + assert_eq!(caller, [1u8; 20]); } diff --git a/substrate/frame/revive/fixtures/contracts/delegate_call_simple.rs b/substrate/frame/revive/fixtures/contracts/delegate_call_simple.rs index cf3351c52fd..20f8ec3364e 100644 --- a/substrate/frame/revive/fixtures/contracts/delegate_call_simple.rs +++ b/substrate/frame/revive/fixtures/contracts/delegate_call_simple.rs @@ -28,7 +28,7 @@ pub extern "C" fn deploy() {} #[no_mangle] #[polkavm_derive::polkavm_export] pub extern "C" fn call() { - input!(code_hash: [u8; 32],); + input!(code_hash: &[u8; 32],); // Delegate call into passed code hash. let input = [0u8; 0]; diff --git a/substrate/frame/revive/fixtures/contracts/destroy_and_transfer.rs b/substrate/frame/revive/fixtures/contracts/destroy_and_transfer.rs index b86e761d53e..4959a5e2e0c 100644 --- a/substrate/frame/revive/fixtures/contracts/destroy_and_transfer.rs +++ b/substrate/frame/revive/fixtures/contracts/destroy_and_transfer.rs @@ -27,11 +27,10 @@ const VALUE: [u8; 8] = [0, 0, 1u8, 0, 0, 0, 0, 0]; #[no_mangle] #[polkavm_derive::polkavm_export] pub extern "C" fn deploy() { - input!(code_hash: [u8; 32],); + input!(code_hash: &[u8; 32],); let input = [0u8; 0]; let mut address = [0u8; 20]; - let address = &mut &mut address[..]; let salt = [47u8; 32]; api::instantiate( @@ -41,27 +40,28 @@ pub extern "C" fn deploy() { None, // No deposit limit. &VALUE, &input, - Some(address), + Some(&mut address), None, &salt, ) .unwrap(); // Return the deployed contract address. - api::set_storage(StorageFlags::empty(), &ADDRESS_KEY, address); + api::set_storage(StorageFlags::empty(), &ADDRESS_KEY, &address); } #[no_mangle] #[polkavm_derive::polkavm_export] pub extern "C" fn call() { let mut callee_addr = [0u8; 20]; - let callee_addr = &mut &mut callee_addr[..]; - api::get_storage(StorageFlags::empty(), &ADDRESS_KEY, callee_addr).unwrap(); + let callee = &mut &mut callee_addr[..]; + api::get_storage(StorageFlags::empty(), &ADDRESS_KEY, callee).unwrap(); + assert!(callee.len() == 20); // Calling the destination contract with non-empty input data should fail. let res = api::call( uapi::CallFlags::empty(), - callee_addr, + &callee_addr, 0u64, // How much ref_time weight to devote for the execution. 0 = all. 0u64, // How much proof_size weight to devote for the execution. 0 = all. None, // No deposit limit. @@ -74,7 +74,7 @@ pub extern "C" fn call() { // Call the destination contract regularly, forcing it to self-destruct. api::call( uapi::CallFlags::empty(), - callee_addr, + &callee_addr, 0u64, // How much ref_time weight to devote for the execution. 0 = all. 0u64, // How much proof_size weight to devote for the execution. 0 = all. None, // No deposit limit. diff --git a/substrate/frame/revive/fixtures/contracts/drain.rs b/substrate/frame/revive/fixtures/contracts/drain.rs index f5c8681c938..b46d4f7c841 100644 --- a/substrate/frame/revive/fixtures/contracts/drain.rs +++ b/substrate/frame/revive/fixtures/contracts/drain.rs @@ -39,6 +39,6 @@ pub extern "C" fn call() { // Try to self-destruct by sending more balance to the 0 address. // The call will fail because a contract transfer has a keep alive requirement. - let res = api::transfer(&[0u8; 32], &balance.to_le_bytes()); + let res = api::transfer(&[0u8; 20], &balance.to_le_bytes()); assert!(matches!(res, Err(uapi::ReturnErrorCode::TransferFailed))); } diff --git a/substrate/frame/revive/fixtures/contracts/instantiate_return_code.rs b/substrate/frame/revive/fixtures/contracts/instantiate_return_code.rs index 90884f1a2a6..a81ffea943d 100644 --- a/substrate/frame/revive/fixtures/contracts/instantiate_return_code.rs +++ b/substrate/frame/revive/fixtures/contracts/instantiate_return_code.rs @@ -28,7 +28,7 @@ pub extern "C" fn deploy() {} #[no_mangle] #[polkavm_derive::polkavm_export] pub extern "C" fn call() { - input!(buffer, 36, code_hash: [u8; 32],); + input!(buffer, 36, code_hash: &[u8; 32],); let input = &buffer[32..]; let err_code = match api::instantiate( @@ -41,7 +41,7 @@ pub extern "C" fn call() { input, None, None, - &[0u8; 0], // Empty salt. + &[0u8; 32], // Salt. ) { Ok(_) => 0u32, Err(code) => code as u32, diff --git a/substrate/frame/revive/fixtures/contracts/locking_delegate_dependency.rs b/substrate/frame/revive/fixtures/contracts/locking_delegate_dependency.rs index 2f3e5ae148c..2efacb4e683 100644 --- a/substrate/frame/revive/fixtures/contracts/locking_delegate_dependency.rs +++ b/substrate/frame/revive/fixtures/contracts/locking_delegate_dependency.rs @@ -30,7 +30,7 @@ const ETH_ALICE: [u8; 20] = [1u8; 20]; fn load_input(delegate_call: bool) { input!( action: u32, - code_hash: [u8; 32], + code_hash: &[u8; 32], ); match action { diff --git a/substrate/frame/revive/fixtures/contracts/read_only_call.rs b/substrate/frame/revive/fixtures/contracts/read_only_call.rs index ef8bc95f009..7476b7a8366 100644 --- a/substrate/frame/revive/fixtures/contracts/read_only_call.rs +++ b/substrate/frame/revive/fixtures/contracts/read_only_call.rs @@ -31,7 +31,7 @@ pub extern "C" fn deploy() {} pub extern "C" fn call() { input!( 256, - callee_addr: [u8; 20], + callee_addr: &[u8; 20], callee_input: [u8], ); diff --git a/substrate/frame/revive/fixtures/contracts/recurse.rs b/substrate/frame/revive/fixtures/contracts/recurse.rs index f4dfd6c965d..c15784b7f24 100644 --- a/substrate/frame/revive/fixtures/contracts/recurse.rs +++ b/substrate/frame/revive/fixtures/contracts/recurse.rs @@ -20,7 +20,7 @@ #![no_std] #![no_main] -use common::{input, output}; +use common::input; use uapi::{HostFn, HostFnImpl as api}; #[no_mangle] @@ -33,7 +33,8 @@ pub extern "C" fn call() { input!(calls_left: u32, ); // own address - output!(addr, [0u8; 32], api::address,); + let mut addr = [0u8; 20]; + api::address(&mut addr); if calls_left == 0 { return @@ -41,7 +42,7 @@ pub extern "C" fn call() { api::call( uapi::CallFlags::ALLOW_REENTRY, - addr, + &addr, 0u64, // How much ref_time to devote for the execution. 0 = all. 0u64, // How much deposit_limit to devote for the execution. 0 = all. None, // No deposit limit. diff --git a/substrate/frame/revive/fixtures/contracts/self_destruct.rs b/substrate/frame/revive/fixtures/contracts/self_destruct.rs index b0e004018f1..0e1e4d30e6f 100644 --- a/substrate/frame/revive/fixtures/contracts/self_destruct.rs +++ b/substrate/frame/revive/fixtures/contracts/self_destruct.rs @@ -18,7 +18,7 @@ #![no_std] #![no_main] -use common::{input, output}; +use common::input; use uapi::{HostFn, HostFnImpl as api}; const ETH_DJANGO: [u8; 20] = [4u8; 20]; @@ -36,10 +36,12 @@ pub extern "C" fn call() { input!(input, 4,); if !input.is_empty() { - output!(addr, [0u8; 20], api::address,); + let mut addr = [0u8; 20]; + api::address(&mut addr); + api::call( uapi::CallFlags::ALLOW_REENTRY, - addr, + &addr, 0u64, // How much ref_time to devote for the execution. 0 = all. 0u64, // How much proof_size to devote for the execution. 0 = all. None, // No deposit limit. diff --git a/substrate/frame/revive/fixtures/contracts/self_destructing_constructor.rs b/substrate/frame/revive/fixtures/contracts/self_destructing_constructor.rs index 28bcef97823..3285aecbe78 100644 --- a/substrate/frame/revive/fixtures/contracts/self_destructing_constructor.rs +++ b/substrate/frame/revive/fixtures/contracts/self_destructing_constructor.rs @@ -24,7 +24,7 @@ use uapi::{HostFn, HostFnImpl as api}; #[no_mangle] #[polkavm_derive::polkavm_export] pub extern "C" fn deploy() { - api::terminate(&[0u8; 32]); + api::terminate(&[0u8; 20]); } #[no_mangle] diff --git a/substrate/frame/revive/fixtures/contracts/set_code_hash.rs b/substrate/frame/revive/fixtures/contracts/set_code_hash.rs index e3cf4becfb9..75995d7bb8a 100644 --- a/substrate/frame/revive/fixtures/contracts/set_code_hash.rs +++ b/substrate/frame/revive/fixtures/contracts/set_code_hash.rs @@ -28,7 +28,7 @@ pub extern "C" fn deploy() {} #[no_mangle] #[polkavm_derive::polkavm_export] pub extern "C" fn call() { - input!(addr: [u8; 32],); + input!(addr: &[u8; 32],); api::set_code_hash(addr).unwrap(); // we return 1 after setting new code_hash diff --git a/substrate/frame/revive/fixtures/contracts/transfer_return_code.rs b/substrate/frame/revive/fixtures/contracts/transfer_return_code.rs index d3f6a1dd3a0..3e1f2757c27 100644 --- a/substrate/frame/revive/fixtures/contracts/transfer_return_code.rs +++ b/substrate/frame/revive/fixtures/contracts/transfer_return_code.rs @@ -28,7 +28,7 @@ pub extern "C" fn deploy() {} #[no_mangle] #[polkavm_derive::polkavm_export] pub extern "C" fn call() { - let ret_code = match api::transfer(&[0u8; 32], &100u64.to_le_bytes()) { + let ret_code = match api::transfer(&[0u8; 20], &100u64.to_le_bytes()) { Ok(_) => 0u32, Err(code) => code as u32, }; diff --git a/substrate/frame/revive/src/benchmarking/mod.rs b/substrate/frame/revive/src/benchmarking/mod.rs index 3ffd53e3561..2c528562284 100644 --- a/substrate/frame/revive/src/benchmarking/mod.rs +++ b/substrate/frame/revive/src/benchmarking/mod.rs @@ -532,17 +532,17 @@ mod benchmarks { #[benchmark(pov_mode = Measured)] fn seal_caller() { let len = H160::len_bytes(); - build_runtime!(runtime, memory: [len.to_le_bytes(), vec![0u8; len as _], ]); + build_runtime!(runtime, memory: [vec![0u8; len as _], ]); let result; #[block] { - result = runtime.bench_caller(memory.as_mut_slice(), 4, 0); + result = runtime.bench_caller(memory.as_mut_slice(), 0); } assert_ok!(result); assert_eq!( - ::decode(&mut &memory[4..]).unwrap(), + ::decode(&mut &memory[..]).unwrap(), T::AddressMapper::to_address(&runtime.ext().caller().account_id().unwrap()) ); } @@ -567,17 +567,17 @@ mod benchmarks { fn seal_code_hash() { let contract = Contract::::with_index(1, WasmModule::dummy(), vec![]).unwrap(); let len = ::max_encoded_len() as u32; - build_runtime!(runtime, memory: [len.to_le_bytes(), vec![0u8; len as _], contract.account_id.encode(), ]); + build_runtime!(runtime, memory: [vec![0u8; len as _], contract.account_id.encode(), ]); let result; #[block] { - result = runtime.bench_code_hash(memory.as_mut_slice(), 4 + len, 4, 0); + result = runtime.bench_code_hash(memory.as_mut_slice(), len, 0); } assert_ok!(result); assert_eq!( - ::decode(&mut &memory[4..]).unwrap(), + ::decode(&mut &memory[..]).unwrap(), contract.info().unwrap().code_hash ); } @@ -585,16 +585,16 @@ mod benchmarks { #[benchmark(pov_mode = Measured)] fn seal_own_code_hash() { let len = ::max_encoded_len() as u32; - build_runtime!(runtime, contract, memory: [len.to_le_bytes(), vec![0u8; len as _], ]); + build_runtime!(runtime, contract, memory: [vec![0u8; len as _], ]); let result; #[block] { - result = runtime.bench_own_code_hash(memory.as_mut_slice(), 4, 0); + result = runtime.bench_own_code_hash(memory.as_mut_slice(), 0); } assert_ok!(result); assert_eq!( - ::decode(&mut &memory[4..]).unwrap(), + ::decode(&mut &memory[..]).unwrap(), contract.info().unwrap().code_hash ); } @@ -629,15 +629,15 @@ mod benchmarks { #[benchmark(pov_mode = Measured)] fn seal_address() { let len = H160::len_bytes(); - build_runtime!(runtime, memory: [len.to_le_bytes(), vec![0u8; len as _], ]); + build_runtime!(runtime, memory: [vec![0u8; len as _], ]); let result; #[block] { - result = runtime.bench_address(memory.as_mut_slice(), 4, 0); + result = runtime.bench_address(memory.as_mut_slice(), 0); } assert_ok!(result); - assert_eq!(::decode(&mut &memory[4..]).unwrap(), runtime.ext().address()); + assert_eq!(::decode(&mut &memory[..]).unwrap(), runtime.ext().address()); } #[benchmark(pov_mode = Measured)] @@ -1581,11 +1581,9 @@ mod benchmarks { offset(value_len), // input_data_ptr i, // input_data_len SENTINEL, // address_ptr - 0, // address_len_ptr SENTINEL, // output_ptr 0, // output_len_ptr offset(i), // salt_ptr - 32, // salt_len ); } diff --git a/substrate/frame/revive/src/debug.rs b/substrate/frame/revive/src/debug.rs index 00e893b94f8..d1fc0823e03 100644 --- a/substrate/frame/revive/src/debug.rs +++ b/substrate/frame/revive/src/debug.rs @@ -64,7 +64,7 @@ impl Tracing for () { type CallSpan = (); fn new_call_span(contract_address: &H160, entry_point: ExportedFunction, input_data: &[u8]) { - log::trace!(target: LOG_TARGET, "call {entry_point:?} account: {contract_address:?}, input_data: {input_data:?}") + log::trace!(target: LOG_TARGET, "call {entry_point:?} address: {contract_address:?}, input_data: {input_data:?}") } } diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 9a99b01776c..393acc8c985 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -824,7 +824,7 @@ pub mod pallet { /// must be supplied. #[pallet::call_index(1)] #[pallet::weight( - T::WeightInfo::instantiate(data.len() as u32, 32).saturating_add(*gas_limit) + T::WeightInfo::instantiate(data.len() as u32).saturating_add(*gas_limit) )] pub fn instantiate( origin: OriginFor, @@ -855,7 +855,7 @@ pub mod pallet { dispatch_result( output.result.map(|result| result.result), output.gas_consumed, - T::WeightInfo::instantiate(data_len, 32), + T::WeightInfo::instantiate(data_len), ) } @@ -888,7 +888,7 @@ pub mod pallet { /// - The `deploy` function is executed in the context of the newly-created account. #[pallet::call_index(2)] #[pallet::weight( - T::WeightInfo::instantiate_with_code(code.len() as u32, data.len() as u32, 32) + T::WeightInfo::instantiate_with_code(code.len() as u32, data.len() as u32) .saturating_add(*gas_limit) )] pub fn instantiate_with_code( @@ -921,7 +921,7 @@ pub mod pallet { dispatch_result( output.result.map(|result| result.result), output.gas_consumed, - T::WeightInfo::instantiate_with_code(code_len, data_len, 32), + T::WeightInfo::instantiate_with_code(code_len, data_len), ) } diff --git a/substrate/frame/revive/src/storage.rs b/substrate/frame/revive/src/storage.rs index 91b7b904d2b..ef7ce2db32c 100644 --- a/substrate/frame/revive/src/storage.rs +++ b/substrate/frame/revive/src/storage.rs @@ -83,16 +83,16 @@ impl ContractInfo { /// This returns an `Err` if an contract with the supplied `account` already exists /// in storage. pub fn new( - account: &H160, + address: &H160, nonce: T::Nonce, code_hash: sp_core::H256, ) -> Result { - if >::contains_key(account) { + if >::contains_key(address) { return Err(Error::::DuplicateContract.into()) } let trie_id = { - let buf = ("bcontract_trie_v1", account, nonce).using_encoded(T::Hashing::hash); + let buf = ("bcontract_trie_v1", address, nonce).using_encoded(T::Hashing::hash); buf.as_ref() .to_vec() .try_into() diff --git a/substrate/frame/revive/src/tests/test_debug.rs b/substrate/frame/revive/src/tests/test_debug.rs index 7885d681e48..1e94d5cafb8 100644 --- a/substrate/frame/revive/src/tests/test_debug.rs +++ b/substrate/frame/revive/src/tests/test_debug.rs @@ -28,7 +28,7 @@ use std::cell::RefCell; #[derive(Clone, PartialEq, Eq, Debug)] struct DebugFrame { - contract_account: sp_core::H160, + contract_address: sp_core::H160, call: ExportedFunction, input: Vec, result: Option>, @@ -41,7 +41,7 @@ thread_local! { pub struct TestDebug; pub struct TestCallSpan { - contract_account: sp_core::H160, + contract_address: sp_core::H160, call: ExportedFunction, input: Vec, } @@ -50,20 +50,20 @@ impl Tracing for TestDebug { type CallSpan = TestCallSpan; fn new_call_span( - contract_account: &crate::H160, + contract_address: &crate::H160, entry_point: ExportedFunction, input_data: &[u8], ) -> TestCallSpan { DEBUG_EXECUTION_TRACE.with(|d| { d.borrow_mut().push(DebugFrame { - contract_account: *contract_account, + contract_address: *contract_address, call: entry_point, input: input_data.to_vec(), result: None, }) }); TestCallSpan { - contract_account: *contract_account, + contract_address: *contract_address, call: entry_point, input: input_data.to_vec(), } @@ -90,7 +90,7 @@ impl CallSpan for TestCallSpan { fn after_call(self, output: &ExecReturnValue) { DEBUG_EXECUTION_TRACE.with(|d| { d.borrow_mut().push(DebugFrame { - contract_account: self.contract_account, + contract_address: self.contract_address, call: self.call, input: self.input, result: Some(output.data.clone()), @@ -131,18 +131,18 @@ mod run_tests { .addr } - fn constructor_frame(contract_account: &H160, after: bool) -> DebugFrame { + fn constructor_frame(contract_address: &H160, after: bool) -> DebugFrame { DebugFrame { - contract_account: *contract_account, + contract_address: *contract_address, call: ExportedFunction::Constructor, input: vec![], result: if after { Some(vec![]) } else { None }, } } - fn call_frame(contract_account: &H160, args: Vec, after: bool) -> DebugFrame { + fn call_frame(contract_address: &H160, args: Vec, after: bool) -> DebugFrame { DebugFrame { - contract_account: *contract_account, + contract_address: *contract_address, call: ExportedFunction::Call, input: args, result: if after { Some(vec![]) } else { None }, diff --git a/substrate/frame/revive/src/wasm/runtime.rs b/substrate/frame/revive/src/wasm/runtime.rs index 533baf8d2c8..51c72349384 100644 --- a/substrate/frame/revive/src/wasm/runtime.rs +++ b/substrate/frame/revive/src/wasm/runtime.rs @@ -453,7 +453,7 @@ impl Token for RuntimeCosts { DelegateCallBase => T::WeightInfo::seal_delegate_call(), CallTransferSurcharge => cost_args!(seal_call, 1, 0), CallInputCloned(len) => cost_args!(seal_call, 0, len), - Instantiate { input_data_len } => T::WeightInfo::seal_instantiate(input_data_len, 32), + Instantiate { input_data_len } => T::WeightInfo::seal_instantiate(input_data_len), HashSha256(len) => T::WeightInfo::seal_hash_sha2_256(len), HashKeccak256(len) => T::WeightInfo::seal_hash_keccak_256(len), HashBlake256(len) => T::WeightInfo::seal_hash_blake2_256(len), @@ -661,6 +661,27 @@ impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { memory.write(out_len_ptr, &buf_len.encode()) } + /// Same as `write_sandbox_output` but for static size output. + pub fn write_fixed_sandbox_output( + &mut self, + memory: &mut M, + out_ptr: u32, + buf: &[u8], + allow_skip: bool, + create_token: impl FnOnce(u32) -> Option, + ) -> Result<(), DispatchError> { + if allow_skip && out_ptr == SENTINEL { + return Ok(()) + } + + let buf_len = buf.len() as u32; + if let Some(costs) = create_token(buf_len) { + self.charge_gas(costs)?; + } + + memory.write(out_ptr, buf) + } + /// Computes the given hash function on the supplied input. /// /// Reads from the sandboxed input buffer into an intermediate buffer. @@ -1010,7 +1031,6 @@ impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { input_data_ptr: u32, input_data_len: u32, address_ptr: u32, - address_len_ptr: u32, output_ptr: u32, output_len_ptr: u32, salt_ptr: u32, @@ -1041,11 +1061,10 @@ impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { ); if let Ok((address, output)) = &instantiate_outcome { if !output.flags.contains(ReturnFlags::REVERT) { - self.write_sandbox_output( + self.write_fixed_sandbox_output( memory, address_ptr, - address_len_ptr, - &address.encode(), + &address.as_bytes(), true, already_charged, )?; @@ -1169,12 +1188,12 @@ pub mod env { fn transfer( &mut self, memory: &mut M, - account_ptr: u32, + address_ptr: u32, value_ptr: u32, ) -> Result { self.charge_gas(RuntimeCosts::Transfer)?; let mut callee = H160::zero(); - memory.read_into_buf(account_ptr, callee.as_bytes_mut())?; + memory.read_into_buf(address_ptr, callee.as_bytes_mut())?; let value: BalanceOf<::T> = memory.read_as(value_ptr)?; let result = self.ext.transfer(&callee, value); match result { @@ -1258,11 +1277,9 @@ pub mod env { input_data_ptr: u32, input_data_len: u32, address_ptr: u32, - address_len_ptr: u32, output_ptr: u32, output_len_ptr: u32, salt_ptr: u32, - _salt_len: u32, ) -> Result { self.instantiate( memory, @@ -1273,7 +1290,6 @@ pub mod env { input_data_ptr, input_data_len, address_ptr, - address_len_ptr, output_ptr, output_len_ptr, salt_ptr, @@ -1320,13 +1336,12 @@ pub mod env { /// Stores the address of the caller into the supplied buffer. /// See [`pallet_revive_uapi::HostFn::caller`]. #[api_version(0)] - fn caller(&mut self, memory: &mut M, out_ptr: u32, out_len_ptr: u32) -> Result<(), TrapReason> { + fn caller(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { self.charge_gas(RuntimeCosts::Caller)?; let caller = ::AddressMapper::to_address(self.ext.caller().account_id()?); - Ok(self.write_sandbox_output( + Ok(self.write_fixed_sandbox_output( memory, out_ptr, - out_len_ptr, caller.as_bytes(), false, already_charged, @@ -1349,18 +1364,16 @@ pub mod env { fn code_hash( &mut self, memory: &mut M, - account_ptr: u32, + addr_ptr: u32, out_ptr: u32, - out_len_ptr: u32, ) -> Result { self.charge_gas(RuntimeCosts::CodeHash)?; let mut address = H160::zero(); - memory.read_into_buf(account_ptr, address.as_bytes_mut())?; + memory.read_into_buf(addr_ptr, address.as_bytes_mut())?; if let Some(value) = self.ext.code_hash(&address) { - self.write_sandbox_output( + self.write_fixed_sandbox_output( memory, out_ptr, - out_len_ptr, &value.encode(), false, already_charged, @@ -1374,18 +1387,12 @@ pub mod env { /// Retrieve the code hash of the currently executing contract. /// See [`pallet_revive_uapi::HostFn::own_code_hash`]. #[api_version(0)] - fn own_code_hash( - &mut self, - memory: &mut M, - out_ptr: u32, - out_len_ptr: u32, - ) -> Result<(), TrapReason> { + fn own_code_hash(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { self.charge_gas(RuntimeCosts::OwnCodeHash)?; let code_hash_encoded = &self.ext.own_code_hash().encode(); - Ok(self.write_sandbox_output( + Ok(self.write_fixed_sandbox_output( memory, out_ptr, - out_len_ptr, code_hash_encoded, false, already_charged, @@ -1411,18 +1418,12 @@ pub mod env { /// Stores the address of the current contract into the supplied buffer. /// See [`pallet_revive_uapi::HostFn::address`]. #[api_version(0)] - fn address( - &mut self, - memory: &mut M, - out_ptr: u32, - out_len_ptr: u32, - ) -> Result<(), TrapReason> { + fn address(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { self.charge_gas(RuntimeCosts::Address)?; let address = self.ext.address(); - Ok(self.write_sandbox_output( + Ok(self.write_fixed_sandbox_output( memory, out_ptr, - out_len_ptr, address.as_bytes(), false, already_charged, diff --git a/substrate/frame/revive/src/weights.rs b/substrate/frame/revive/src/weights.rs index 6a0d27529d8..7974cc1260e 100644 --- a/substrate/frame/revive/src/weights.rs +++ b/substrate/frame/revive/src/weights.rs @@ -65,8 +65,8 @@ pub trait WeightInfo { fn on_runtime_upgrade_in_progress() -> Weight; fn on_runtime_upgrade() -> Weight; fn call_with_code_per_byte(c: u32, ) -> Weight; - fn instantiate_with_code(c: u32, i: u32, s: u32, ) -> Weight; - fn instantiate(i: u32, s: u32, ) -> Weight; + fn instantiate_with_code(c: u32, i: u32) -> Weight; + fn instantiate(i: u32) -> Weight; fn call() -> Weight; fn upload_code_determinism_enforced(c: u32, ) -> Weight; fn upload_code_determinism_relaxed(c: u32, ) -> Weight; @@ -115,7 +115,7 @@ pub trait WeightInfo { fn seal_transfer() -> Weight; fn seal_call(t: u32, i: u32, ) -> Weight; fn seal_delegate_call() -> Weight; - fn seal_instantiate(i: u32, s: u32, ) -> Weight; + fn seal_instantiate(i: u32) -> Weight; fn seal_hash_sha2_256(n: u32, ) -> Weight; fn seal_hash_keccak_256(n: u32, ) -> Weight; fn seal_hash_blake2_256(n: u32, ) -> Weight; @@ -382,7 +382,7 @@ impl WeightInfo for SubstrateWeight { /// The range of component `c` is `[0, 125952]`. /// The range of component `i` is `[0, 1048576]`. /// The range of component `s` is `[0, 1048576]`. - fn instantiate_with_code(c: u32, i: u32, s: u32, ) -> Weight { + fn instantiate_with_code(c: u32, i: u32) -> Weight { // Proof Size summary in bytes: // Measured: `323` // Estimated: `6262` @@ -393,7 +393,6 @@ impl WeightInfo for SubstrateWeight { // Standard Error: 22 .saturating_add(Weight::from_parts(2_143, 0).saturating_mul(i.into())) // Standard Error: 22 - .saturating_add(Weight::from_parts(2_210, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(8_u64)) .saturating_add(T::DbWeight::get().writes(7_u64)) } @@ -415,7 +414,7 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(193), added: 2668, mode: `Measured`) /// The range of component `i` is `[0, 1048576]`. /// The range of component `s` is `[0, 1048576]`. - fn instantiate(i: u32, s: u32, ) -> Weight { + fn instantiate(i: u32) -> Weight { // Proof Size summary in bytes: // Measured: `560` // Estimated: `4017` @@ -424,7 +423,6 @@ impl WeightInfo for SubstrateWeight { // Standard Error: 32 .saturating_add(Weight::from_parts(934, 0).saturating_mul(i.into())) // Standard Error: 32 - .saturating_add(Weight::from_parts(920, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(8_u64)) .saturating_add(T::DbWeight::get().writes(5_u64)) } @@ -978,7 +976,7 @@ impl WeightInfo for SubstrateWeight { /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) /// The range of component `i` is `[0, 983040]`. /// The range of component `s` is `[0, 983040]`. - fn seal_instantiate(i: u32, s: u32, ) -> Weight { + fn seal_instantiate(i: u32) -> Weight { // Proof Size summary in bytes: // Measured: `676` // Estimated: `4132` @@ -987,7 +985,6 @@ impl WeightInfo for SubstrateWeight { // Standard Error: 24 .saturating_add(Weight::from_parts(581, 0).saturating_mul(i.into())) // Standard Error: 24 - .saturating_add(Weight::from_parts(915, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(5_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -1375,7 +1372,7 @@ impl WeightInfo for () { /// The range of component `c` is `[0, 125952]`. /// The range of component `i` is `[0, 1048576]`. /// The range of component `s` is `[0, 1048576]`. - fn instantiate_with_code(c: u32, i: u32, s: u32, ) -> Weight { + fn instantiate_with_code(c: u32, i: u32) -> Weight { // Proof Size summary in bytes: // Measured: `323` // Estimated: `6262` @@ -1386,7 +1383,6 @@ impl WeightInfo for () { // Standard Error: 22 .saturating_add(Weight::from_parts(2_143, 0).saturating_mul(i.into())) // Standard Error: 22 - .saturating_add(Weight::from_parts(2_210, 0).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(8_u64)) .saturating_add(RocksDbWeight::get().writes(7_u64)) } @@ -1408,7 +1404,7 @@ impl WeightInfo for () { /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(193), added: 2668, mode: `Measured`) /// The range of component `i` is `[0, 1048576]`. /// The range of component `s` is `[0, 1048576]`. - fn instantiate(i: u32, s: u32, ) -> Weight { + fn instantiate(i: u32) -> Weight { // Proof Size summary in bytes: // Measured: `560` // Estimated: `4017` @@ -1417,7 +1413,6 @@ impl WeightInfo for () { // Standard Error: 32 .saturating_add(Weight::from_parts(934, 0).saturating_mul(i.into())) // Standard Error: 32 - .saturating_add(Weight::from_parts(920, 0).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(8_u64)) .saturating_add(RocksDbWeight::get().writes(5_u64)) } @@ -1971,7 +1966,7 @@ impl WeightInfo for () { /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) /// The range of component `i` is `[0, 983040]`. /// The range of component `s` is `[0, 983040]`. - fn seal_instantiate(i: u32, s: u32, ) -> Weight { + fn seal_instantiate(i: u32) -> Weight { // Proof Size summary in bytes: // Measured: `676` // Estimated: `4132` @@ -1980,7 +1975,6 @@ impl WeightInfo for () { // Standard Error: 24 .saturating_add(Weight::from_parts(581, 0).saturating_mul(i.into())) // Standard Error: 24 - .saturating_add(Weight::from_parts(915, 0).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(5_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } diff --git a/substrate/frame/revive/uapi/src/host.rs b/substrate/frame/revive/uapi/src/host.rs index 6eb662363f7..f52ea957402 100644 --- a/substrate/frame/revive/uapi/src/host.rs +++ b/substrate/frame/revive/uapi/src/host.rs @@ -40,12 +40,10 @@ pub enum HostFnImpl {} pub trait HostFn: private::Sealed { /// Stores the address of the current contract into the supplied buffer. /// - /// If the available space in `output` is less than the size of the value a trap is triggered. - /// /// # Parameters /// /// - `output`: A reference to the output data buffer to write the address. - fn address(output: &mut &mut [u8]); + fn address(output: &mut [u8; 20]); /// Lock a new delegate dependency to the contract. /// @@ -56,7 +54,7 @@ pub trait HostFn: private::Sealed { /// /// - `code_hash`: The code hash of the dependency. Should be decodable as an `T::Hash`. Traps /// otherwise. - fn lock_delegate_dependency(code_hash: &[u8]); + fn lock_delegate_dependency(code_hash: &[u8; 32]); /// Stores the *free* balance of the current account into the supplied buffer. /// @@ -105,7 +103,7 @@ pub trait HostFn: private::Sealed { /// - [NotCallable][`crate::ReturnErrorCode::NotCallable] fn call( flags: CallFlags, - callee: &[u8], + callee: &[u8; 20], ref_time_limit: u64, proof_size_limit: u64, deposit: Option<&[u8]>, @@ -166,8 +164,6 @@ pub trait HostFn: private::Sealed { /// Stores the address of the caller into the supplied buffer. /// - /// If the available space in `output` is less than the size of the value a trap is triggered. - /// /// If this is a top-level call (i.e. initiated by an extrinsic) the origin address of the /// extrinsic will be returned. Otherwise, if this call is initiated by another contract then /// the address of the contract will be returned. @@ -178,7 +174,7 @@ pub trait HostFn: private::Sealed { /// # Parameters /// /// - `output`: A reference to the output data buffer to write the caller address. - fn caller(output: &mut &mut [u8]); + fn caller(output: &mut [u8; 20]); /// Checks whether the caller of the current contract is the origin of the whole call stack. /// @@ -216,15 +212,13 @@ pub trait HostFn: private::Sealed { /// /// # Parameters /// - /// - `account_id`: The address of the contract.Should be decodable as an `T::AccountId`. Traps - /// otherwise. + /// - `addr`: The address of the contract. /// - `output`: A reference to the output data buffer to write the code hash. /// - /// /// # Errors /// /// - [CodeNotFound][`crate::ReturnErrorCode::CodeNotFound] - fn code_hash(account_id: &[u8], output: &mut [u8]) -> Result; + fn code_hash(addr: &[u8; 20], output: &mut [u8; 32]) -> Result; /// Checks whether there is a value stored under the given key. /// @@ -281,7 +275,7 @@ pub trait HostFn: private::Sealed { /// - [CodeNotFound][`crate::ReturnErrorCode::CodeNotFound] fn delegate_call( flags: CallFlags, - code_hash: &[u8], + code_hash: &[u8; 32], input_data: &[u8], output: Option<&mut &mut [u8]>, ) -> Result; @@ -405,28 +399,27 @@ pub trait HostFn: private::Sealed { /// - [TransferFailed][`crate::ReturnErrorCode::TransferFailed] /// - [CodeNotFound][`crate::ReturnErrorCode::CodeNotFound] fn instantiate( - code_hash: &[u8], + code_hash: &[u8; 32], ref_time_limit: u64, proof_size_limit: u64, deposit: Option<&[u8]>, value: &[u8], input: &[u8], - address: Option<&mut &mut [u8]>, + address: Option<&mut [u8; 20]>, output: Option<&mut &mut [u8]>, - salt: &[u8], + salt: &[u8; 32], ) -> Result; /// Checks whether a specified address belongs to a contract. /// /// # Parameters /// - /// - `account_id`: The address to check. Should be decodable as an `T::AccountId`. Traps - /// otherwise. + /// - `address`: The address to check /// /// # Return /// /// Returns `true` if the address belongs to a contract. - fn is_contract(account_id: &[u8]) -> bool; + fn is_contract(address: &[u8; 20]) -> bool; /// Stores the minimum balance (a.k.a. existential deposit) into the supplied buffer. /// The data is encoded as `T::Balance`. @@ -443,7 +436,7 @@ pub trait HostFn: private::Sealed { /// # Parameters /// /// - `output`: A reference to the output data buffer to write the code hash. - fn own_code_hash(output: &mut [u8]); + fn own_code_hash(output: &mut [u8; 32]); /// Load the latest block timestamp into the supplied buffer /// @@ -462,7 +455,7 @@ pub trait HostFn: private::Sealed { /// /// - `code_hash`: The code hash of the dependency. Should be decodable as an `T::Hash`. Traps /// otherwise. - fn unlock_delegate_dependency(code_hash: &[u8]); + fn unlock_delegate_dependency(code_hash: &[u8; 32]); /// Cease contract execution and save a data buffer as a result of the execution. /// @@ -510,7 +503,7 @@ pub trait HostFn: private::Sealed { /// # Errors /// /// - [CodeNotFound][`crate::ReturnErrorCode::CodeNotFound] - fn set_code_hash(code_hash: &[u8]) -> Result; + fn set_code_hash(code_hash: &[u8; 32]) -> Result; /// Set the value at the given key in the contract storage. /// @@ -554,14 +547,13 @@ pub trait HostFn: private::Sealed { /// /// # Parameters /// - /// - `account_id`: The address of the account to transfer funds to. Should be decodable as an - /// `T::AccountId`. Traps otherwise. + /// - `address`: The address of the account to transfer funds to. /// - `value`: The value to transfer. Should be decodable as a `T::Balance`. Traps otherwise. /// /// # Errors /// /// - [TransferFailed][`crate::ReturnErrorCode::TransferFailed] - fn transfer(account_id: &[u8], value: &[u8]) -> Result; + fn transfer(address: &[u8; 20], value: &[u8]) -> Result; /// Remove the calling account and transfer remaining **free** balance. /// @@ -571,15 +563,14 @@ pub trait HostFn: private::Sealed { /// /// # Parameters /// - /// - `beneficiary`: The address of the beneficiary account, Should be decodable as an - /// `T::AccountId`. + /// - `beneficiary`: The address of the beneficiary account /// /// # Traps /// /// - The contract is live i.e is already on the call stack. /// - Failed to send the balance to the beneficiary. /// - The deletion queue is full. - fn terminate(beneficiary: &[u8]) -> !; + fn terminate(beneficiary: &[u8; 20]) -> !; /// Stores the value transferred along with this call/instantiate into the supplied buffer. /// The data is encoded as `T::Balance`. diff --git a/substrate/frame/revive/uapi/src/host/riscv32.rs b/substrate/frame/revive/uapi/src/host/riscv32.rs index 0b7130015f1..c8218bb8f73 100644 --- a/substrate/frame/revive/uapi/src/host/riscv32.rs +++ b/substrate/frame/revive/uapi/src/host/riscv32.rs @@ -58,7 +58,7 @@ mod sys { out_ptr: *mut u8, out_len_ptr: *mut u32, ) -> ReturnCode; - pub fn transfer(account_ptr: *const u8, value_ptr: *const u8) -> ReturnCode; + pub fn transfer(address_ptr: *const u8, value_ptr: *const u8) -> ReturnCode; pub fn call(ptr: *const u8) -> ReturnCode; pub fn delegate_call( flags: u32, @@ -72,17 +72,13 @@ mod sys { pub fn terminate(beneficiary_ptr: *const u8); pub fn input(out_ptr: *mut u8, out_len_ptr: *mut u32); pub fn seal_return(flags: u32, data_ptr: *const u8, data_len: u32); - pub fn caller(out_ptr: *mut u8, out_len_ptr: *mut u32); + pub fn caller(out_ptr: *mut u8); pub fn is_contract(account_ptr: *const u8) -> ReturnCode; - pub fn code_hash( - account_ptr: *const u8, - out_ptr: *mut u8, - out_len_ptr: *mut u32, - ) -> ReturnCode; - pub fn own_code_hash(out_ptr: *mut u8, out_len_ptr: *mut u32); + pub fn code_hash(address_ptr: *const u8, out_ptr: *mut u8) -> ReturnCode; + pub fn own_code_hash(out_ptr: *mut u8); pub fn caller_is_origin() -> ReturnCode; pub fn caller_is_root() -> ReturnCode; - pub fn address(out_ptr: *mut u8, out_len_ptr: *mut u32); + pub fn address(out_ptr: *mut u8); pub fn weight_to_fee( ref_time: u64, proof_size: u64, @@ -198,17 +194,20 @@ fn ptr_or_sentinel(data: &Option<&[u8]>) -> *const u8 { impl HostFn for HostFnImpl { fn instantiate( - code_hash: &[u8], + code_hash: &[u8; 32], ref_time_limit: u64, proof_size_limit: u64, deposit_limit: Option<&[u8]>, value: &[u8], input: &[u8], - mut address: Option<&mut &mut [u8]>, + mut address: Option<&mut [u8; 20]>, mut output: Option<&mut &mut [u8]>, - salt: &[u8], + salt: &[u8; 32], ) -> Result { - let (address_ptr, mut address_len) = ptr_len_or_sentinel(&mut address); + let address = match address { + Some(ref mut data) => data.as_mut_ptr(), + None => crate::SENTINEL as _, + }; let (output_ptr, mut output_len) = ptr_len_or_sentinel(&mut output); let deposit_limit_ptr = ptr_or_sentinel(&deposit_limit); #[repr(packed)] @@ -220,13 +219,11 @@ impl HostFn for HostFnImpl { deposit_limit: *const u8, value: *const u8, input: *const u8, - input_len: usize, + input_len: u32, address: *const u8, - address_len: *mut u32, output: *mut u8, output_len: *mut u32, salt: *const u8, - salt_len: usize, } let args = Args { code_hash: code_hash.as_ptr(), @@ -235,21 +232,15 @@ impl HostFn for HostFnImpl { deposit_limit: deposit_limit_ptr, value: value.as_ptr(), input: input.as_ptr(), - input_len: input.len(), - address: address_ptr, - address_len: &mut address_len as *mut _, + input_len: input.len() as _, + address, output: output_ptr, output_len: &mut output_len as *mut _, salt: salt.as_ptr(), - salt_len: salt.len(), }; let ret_code = { unsafe { sys::instantiate(&args as *const Args as *const _) } }; - if let Some(ref mut address) = address { - extract_from_slice(address, address_len as usize); - } - if let Some(ref mut output) = output { extract_from_slice(output, output_len as usize); } @@ -259,7 +250,7 @@ impl HostFn for HostFnImpl { fn call( flags: CallFlags, - callee: &[u8], + callee: &[u8; 20], ref_time_limit: u64, proof_size_limit: u64, deposit_limit: Option<&[u8]>, @@ -279,7 +270,7 @@ impl HostFn for HostFnImpl { deposit_limit: *const u8, value: *const u8, input: *const u8, - input_len: usize, + input_len: u32, output: *mut u8, output_len: *mut u32, } @@ -291,7 +282,7 @@ impl HostFn for HostFnImpl { deposit_limit: deposit_limit_ptr, value: value.as_ptr(), input: input.as_ptr(), - input_len: input.len(), + input_len: input.len() as _, output: output_ptr, output_len: &mut output_len as *mut _, }; @@ -311,7 +302,7 @@ impl HostFn for HostFnImpl { fn delegate_call( flags: CallFlags, - code_hash: &[u8], + code_hash: &[u8; 32], input: &[u8], mut output: Option<&mut &mut [u8]>, ) -> Result { @@ -336,8 +327,8 @@ impl HostFn for HostFnImpl { ret_code.into() } - fn transfer(account_id: &[u8], value: &[u8]) -> Result { - let ret_code = unsafe { sys::transfer(account_id.as_ptr(), value.as_ptr()) }; + fn transfer(address: &[u8; 20], value: &[u8]) -> Result { + let ret_code = unsafe { sys::transfer(address.as_ptr(), value.as_ptr()) }; ret_code.into() } @@ -415,7 +406,7 @@ impl HostFn for HostFnImpl { ret_code.into() } - fn terminate(beneficiary: &[u8]) -> ! { + fn terminate(beneficiary: &[u8; 20]) -> ! { unsafe { sys::terminate(beneficiary.as_ptr()) } panic!("terminate does not return"); } @@ -458,8 +449,16 @@ impl HostFn for HostFnImpl { ret_code.into() } + fn address(output: &mut [u8; 20]) { + unsafe { sys::address(output.as_mut_ptr()) } + } + + fn caller(output: &mut [u8; 20]) { + unsafe { sys::caller(output.as_mut_ptr()) } + } + impl_wrapper_for! { - caller, block_number, address, balance, + block_number, balance, value_transferred,now, minimum_balance, weight_left, } @@ -512,8 +511,8 @@ impl HostFn for HostFnImpl { ret_code.into() } - fn is_contract(account_id: &[u8]) -> bool { - let ret_val = unsafe { sys::is_contract(account_id.as_ptr()) }; + fn is_contract(address: &[u8; 20]) -> bool { + let ret_val = unsafe { sys::is_contract(address.as_ptr()) }; ret_val.into_bool() } @@ -522,28 +521,25 @@ impl HostFn for HostFnImpl { ret_val.into_bool() } - fn set_code_hash(code_hash: &[u8]) -> Result { + fn set_code_hash(code_hash: &[u8; 32]) -> Result { let ret_val = unsafe { sys::set_code_hash(code_hash.as_ptr()) }; ret_val.into() } - fn code_hash(account_id: &[u8], output: &mut [u8]) -> Result { - let mut output_len = output.len() as u32; - let ret_val = - unsafe { sys::code_hash(account_id.as_ptr(), output.as_mut_ptr(), &mut output_len) }; + fn code_hash(address: &[u8; 20], output: &mut [u8; 32]) -> Result { + let ret_val = unsafe { sys::code_hash(address.as_ptr(), output.as_mut_ptr()) }; ret_val.into() } - fn own_code_hash(output: &mut [u8]) { - let mut output_len = output.len() as u32; - unsafe { sys::own_code_hash(output.as_mut_ptr(), &mut output_len) } + fn own_code_hash(output: &mut [u8; 32]) { + unsafe { sys::own_code_hash(output.as_mut_ptr()) } } - fn lock_delegate_dependency(code_hash: &[u8]) { + fn lock_delegate_dependency(code_hash: &[u8; 32]) { unsafe { sys::lock_delegate_dependency(code_hash.as_ptr()) } } - fn unlock_delegate_dependency(code_hash: &[u8]) { + fn unlock_delegate_dependency(code_hash: &[u8; 32]) { unsafe { sys::unlock_delegate_dependency(code_hash.as_ptr()) } } -- GitLab From 5e0ec3e0231b43392f569cc4c685b91cb481a3ec Mon Sep 17 00:00:00 2001 From: Przemek Rzad Date: Thu, 5 Sep 2024 10:47:15 +0200 Subject: [PATCH 004/125] Update and test the `getting-started` script (#5446) Here are some changes to the `getting-started.sh` scripts we have advertised on top of the readme. ### Changes to the script 1. Change `echo` to a more portable `printf`. On my machine, the script printed a literal `\n` string if run with `bash`. If I changed it to `echo -e`, then it printed a literal `-e` if run with `sh`. Changed it to `printf` which is more portable. --- 2. Template selection The script proceeded to clone and build the `minimal` template, which is not always what we want. Added a selection prompt where the user can select one of the 3 templates, and choose if it should be built&run or not. The user can also select no template at all - that way, we have a starter of a dependencies-installation script. --- 3. Added some missing dependencies for some of the systems. ### A workflow testing the script I propose a workflow, that will test the script using the [expect](https://core.tcl-lang.org/expect/index) tool. For each OS mentioned in the script (macOS, Ubuntu, Debian, Arch, Fedora, OpenSUSE) we go through the script twice, and after that build and run the template binary. I'm using docker containers, so we start from scratch and make sure the scripts installs all required dependencies - with the exception of macOS, which I can't run from scratch in a container. The jobs use a selected combination of OSes, shell interpreters (`bash` or `sh`), and templates. There is too much combinations to run them all, but I have [run it once](https://github.com/paritytech-stg/polkadot-sdk/actions/runs/10509533645) in staging to make sure all pass. I'm adding a cron schedule because it can break without any code changes in this repository (e.g. new `latest` release of a container). --------- Co-authored-by: Oliver Tale-Yazdi --- .github/workflows/check-getting-started.yml | 296 ++++++++++++++++++++ scripts/getting-started.sh | 110 +++++--- 2 files changed, 367 insertions(+), 39 deletions(-) create mode 100644 .github/workflows/check-getting-started.yml diff --git a/.github/workflows/check-getting-started.yml b/.github/workflows/check-getting-started.yml new file mode 100644 index 00000000000..b43db33c63b --- /dev/null +++ b/.github/workflows/check-getting-started.yml @@ -0,0 +1,296 @@ +name: Check the getting-started.sh script + +# This workflow aims to make sure that the `getting-started.sh` script +# is functional and allows to build the templates +# on different operating systems. +# +# There are two jobs inside. +# One for systems that can run in a docker container, and one for macOS. +# +# Each job consists of: +# 1. Some necessary prerequisites for the workflow itself. +# 2. A first pass of the script, which will install dependencies and clone a template. +# 3. A second pass of the script, to make sure the behaviour is as expected. +# 4. Building the template - making sure it's buildable and runnable. +# +# The script is interacted with using the `expect` tool, which is available on all relevant systems. +# The steps are not re-used between macOS and other systems, +# because they are very similar but a little different. +# Additionally, macOS does NOT start from scratch here - for example, we have homebrew already installed. +# +# There are many combinations of systems, shells and templates. +# We test a selected handful of combinations here. + +on: + pull_request: + paths: + - '.github/workflows/check-getting-started.yml' + - 'scripts/getting-started.sh' + schedule: + - cron: '0 5 * * *' + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + check-getting-started: + strategy: + fail-fast: true + matrix: + include: + - name: ubuntu + container: ubuntu + template: minimal + shell: bash + - name: debian + container: debian + template: parachain + shell: sh + - name: arch + container: archlinux + template: solochain + shell: sh + - name: fedora + container: fedora + template: parachain + shell: sh + - name: opensuse + container: opensuse/tumbleweed + template: solochain + shell: sh + runs-on: arc-runners-polkadot-sdk-beefy + container: ${{ matrix.container }}:latest + steps: + # A minimal amount of prerequisites required before we can run the actual getting-started script, + # which will install the rest of requirements. + - name: Install ubuntu/debian prerequisites + run: apt update && apt install -y expect sudo git + if: contains(matrix.name, 'ubuntu') || contains(matrix.name, 'debian') + - name: Install arch prerequisites + run: pacman -Syu --needed --noconfirm expect sudo git + if: contains(matrix.name, 'arch') + - name: Install fedora prerequisites + run: dnf --assumeyes install expect sudo git + if: contains(matrix.name, 'fedora') + - name: Install opensuse prerequisites + run: zypper install --no-confirm expect sudo git + if: contains(matrix.name, 'opensuse') + + - name: Checkout + uses: actions/checkout@v4 + + - name: Set additional expect flags if necessary + run: | + # Add a debug flag to expect, if github is re-run with debug logging enabled. + [ "${{ runner.debug }}" = "1" ] && EXPECT_FLAGS="-d" || EXPECT_FLAGS="" + echo "EXPECT_FLAGS=${EXPECT_FLAGS}" >> $GITHUB_ENV + + - name: Check the first run of the script + run: | + expect $EXPECT_FLAGS -c ' + set timeout 240 + + spawn ${{ matrix.shell }} scripts/getting-started.sh + + expect_after { + timeout { puts stderr "Timed out on an expect"; exit 1 } + eof { puts stderr "EOF received on an expect"; exit 1 } + } + + expect -nocase "Detected ${{ matrix.name }}" + + expect "Rust is not installed. Install it?" { + send "y\r" + expect "Proceed with standard installation (default - just press enter)" { + send "\r" + expect "Rust is installed now" + } + } + + expect "Setup the Rust environment" { + send "y\r" + } + + expect "start with one of the templates" { + send "y\r" + } + + expect -re "(.)\\) ${{ matrix.template }} template" { + send "$expect_out(1,string)\r" + } + + expect "compile the node?" { + send "n\r" + } + + expect eof + ' + timeout-minutes: 15 + + - name: Check the second run of the script + run: | + expect $EXPECT_FLAGS -c ' + set timeout 120 + + spawn ${{ matrix.shell }} scripts/getting-started.sh + + expect_after { + timeout { puts stderr "Timed out on an expect"; exit 1 } + eof { puts stderr "EOF received on an expect"; exit 1 } + } + + expect "Rust already installed" {} + + expect "Setup the Rust environment" { + send "n\r" + } + + expect "start with one of the templates" { + send "y\r" + } + + expect -re "(.)\\) ${{ matrix.template }} template" { + send "$expect_out(1,string)\r" + expect "directory already exists" {} + } + + expect "compile the node?" { + send "n\r" + } + + expect eof + ' + timeout-minutes: 15 + + - name: Compile the node outside of the script + run: | + . "$HOME/.cargo/env" + cd ${{ matrix.template }}-template + cargo build --release + timeout-minutes: 120 + + - name: Check that the binary is executable + run: | + . "$HOME/.cargo/env" + cd ${{ matrix.template }}-template + cargo run --release -- --help + timeout-minutes: 5 + + check-getting-started-macos: + strategy: + fail-fast: true + matrix: + include: + - template: parachain + shell: sh + - template: solochain + shell: bash + runs-on: macos-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set additional expect flags if necessary + run: | + # Add a debug flag to expect, if github is re-run with debug logging enabled. + [ "${{ runner.debug }}" = "1" ] && EXPECT_FLAGS="-d" || EXPECT_FLAGS="" + echo "EXPECT_FLAGS=${EXPECT_FLAGS}" >> $GITHUB_ENV + + - name: Check the first run of the script + run: | + expect $EXPECT_FLAGS -c ' + set timeout 120 + + spawn ${{ matrix.shell }} scripts/getting-started.sh + + expect_after { + timeout { puts stderr "Timed out on an expect"; exit 1 } + eof { puts stderr "EOF received on an expect"; exit 1 } + } + + expect -nocase "Detected macOS" + + expect "Homebrew already installed" + + expect "Install cmake" { + send "y\r" + } + + expect "Rust already installed" {} + + expect "Setup the Rust environment" { + send "y\r" + } + + expect "start with one of the templates" { + send "y\r" + } + + expect -re "(.)\\) ${{ matrix.template }} template" { + send "$expect_out(1,string)\r" + } + + expect "compile the node?" { + send "n\r" + } + + expect eof + ' + timeout-minutes: 15 + + - name: Check the second run of the script + run: | + expect $EXPECT_FLAGS -c ' + set timeout 120 + + spawn ${{ matrix.shell }} scripts/getting-started.sh + + expect_after { + timeout { puts stderr "Timed out on an expect"; exit 1 } + eof { puts stderr "EOF received on an expect"; exit 1 } + } + + expect "Homebrew already installed" + + expect "Install cmake" { + send "y\r" + } + + expect "Rust already installed" {} + + expect "Setup the Rust environment" { + send "n\r" + } + + expect "start with one of the templates" { + send "y\r" + } + + expect -re "(.)\\) ${{ matrix.template }} template" { + send "$expect_out(1,string)\r" + expect "directory already exists" {} + } + + expect "compile the node?" { + send "n\r" + } + + expect eof + ' + timeout-minutes: 15 + + - name: Compile the node outside of the script + run: | + . "$HOME/.cargo/env" + cd ${{ matrix.template }}-template + cargo build --release + timeout-minutes: 120 + + - name: Check that the binary is executable + run: | + . "$HOME/.cargo/env" + cd ${{ matrix.template }}-template + cargo run --release -- --help + timeout-minutes: 5 diff --git a/scripts/getting-started.sh b/scripts/getting-started.sh index 57806280914..d5fd545481d 100755 --- a/scripts/getting-started.sh +++ b/scripts/getting-started.sh @@ -4,30 +4,41 @@ set -e prompt() { while true; do - echo "$1 [y/N]" + printf "$1 [y/N]\n" read yn case $yn in [Yy]* ) return 0;; # Yes, return 0 (true) [Nn]* ) return 1;; # No, return 1 (false) "" ) return 1;; # Default to no if user just presses Enter - * ) echo "Please answer yes or no.";; + * ) printf "Please answer yes or no.\n";; esac done } prompt_default_yes() { while true; do - echo "$1 [Y/n]" + printf "$1 [Y/n]\n" read yn case $yn in [Yy]* ) return 0;; # Yes, return 0 (true) [Nn]* ) return 1;; # No, return 1 (false) "" ) return 0;; # Default to yes if user just presses Enter - * ) echo "Please answer yes or no.";; + * ) printf "Please answer yes or no.\n";; esac done } +clone_and_enter_template() { + template="$1" # minimal, solochain, or parachain + if [ -d "${template}-template" ]; then + printf "\n✅︎ ${template}-template directory already exists. -> Entering.\n" + else + printf "\n↓ Let's grab the ${template} template from github.\n" + git clone --quiet https://github.com/paritytech/polkadot-sdk-${template}-template.git ${template}-template + fi + cd ${template}-template +} + cat </dev/null 2>&1; then - echo "\n✅︎🍺 Homebrew already installed." + printf "\n✅︎🍺 Homebrew already installed.\n" else - if prompt_default_yes "\n🍺 Homebrew is not installed. Install it?"; then - echo "🍺 Installing Homebrew." + if prompt_default_yes "\n🍺 Homebrew is not installed. Install it?\n"; then + printf "🍺 Installing Homebrew.\n" /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)" else - echo "❌ Cannot continue without homebrew. Aborting." + printf "❌ Cannot continue without homebrew. Aborting.\n" exit 1 fi fi brew update if command -v git >/dev/null 2>&1; then - echo "\n✅︎🍺 git already installed." + printf "\n✅︎🍺 git already installed.\n" else - if prompt_default_yes "\n🍺 git seems to be missing but we will need it; install git?"; then + if prompt_default_yes "\n🍺 git seems to be missing but we will need it; install git?\n"; then brew install git else - echo "❌ Cannot continue without git. Aborting." + printf "❌ Cannot continue without git. Aborting.\n" exit 1 fi fi @@ -75,73 +86,94 @@ if [ "$os_name" = "Darwin" ]; then if prompt "\n🍺 Install cmake, openssl and protobuf?"; then brew install cmake openssl protobuf else - echo "🍺 Assuming cmake, openssl and protobuf are present." + printf "🍺 Assuming cmake, openssl and protobuf are present.\n" fi elif [ "$os_name" = "Linux" ]; then # find the distro name in the release files distro=$( cat /etc/*-release | tr '[:upper:]' '[:lower:]' | grep -Poi '(debian|ubuntu|arch|fedora|opensuse)' | uniq | head -n 1 ) if [ "$distro" = "ubuntu" ]; then - echo "\n🐧 Detected Ubuntu. Using apt to install dependencies." - sudo apt install --assume-yes git clang curl libssl-dev protobuf-compiler + printf "\n🐧 Detected Ubuntu. Using apt to install dependencies.\n" + sudo apt -qq update + sudo apt -qq install --assume-yes git clang curl libssl-dev protobuf-compiler make elif [ "$distro" = "debian" ]; then - echo "\n🐧 Detected Debian. Using apt to install dependencies." - sudo apt install --assume-yes git clang curl libssl-dev llvm libudev-dev make protobuf-compiler + printf "\n🐧 Detected Debian. Using apt to install dependencies.\n" + sudo apt -qq update + sudo apt -qq install --assume-yes git clang curl libssl-dev llvm libudev-dev make protobuf-compiler elif [ "$distro" = "arch" ]; then - echo "\n🐧 Detected Arch Linux. Using pacman to install dependencies." + printf "\n🐧 Detected Arch Linux. Using pacman to install dependencies.\n" pacman -Syu --needed --noconfirm curl git clang make protobuf elif [ "$distro" = "fedora" ]; then - echo "\n🐧 Detected Fedora. Using dnf to install dependencies." - sudo dnf update - sudo dnf install clang curl git openssl-devel make protobuf-compiler + printf "\n🐧 Detected Fedora. Using dnf to install dependencies.\n" + sudo dnf update --assumeyes + sudo dnf install --assumeyes clang curl git openssl-devel make protobuf-compiler perl elif [ "$distro" = "opensuse" ]; then - echo "\n🐧 Detected openSUSE. Using zypper to install dependencies." - sudo zypper install clang curl git openssl-devel llvm-devel libudev-devel make protobuf + printf "\n🐧 Detected openSUSE. Using zypper to install dependencies.\n" + sudo zypper install --no-confirm clang gcc gcc-c++ curl git openssl-devel llvm-devel libudev-devel make awk protobuf-devel else - if prompt "\n🐧 Unknown Linux distribution. Unable to install dependencies. Continue anyway?"; then - echo "\n🐧 Proceeding with unknown linux distribution..." + if prompt "\n🐧 Unknown Linux distribution. Unable to install dependencies. Continue anyway?\n"; then + printf "\n🐧 Proceeding with unknown linux distribution...\n" else exit 1 fi fi else - echo "❌ Unknown operating system. Aborting." + printf "❌ Unknown operating system. Aborting.\n" exit 1 fi # Check if rust is installed +[ -f "$HOME/.cargo/env" ] && . "$HOME/.cargo/env" if command -v rustc >/dev/null 2>&1; then - echo "\n✅︎🦀 Rust already installed." + printf "\n✅︎🦀 Rust already installed.\n" else if prompt_default_yes "\n🦀 Rust is not installed. Install it?"; then - echo "🦀 Installing via rustup." + printf "🦀 Installing via rustup.\n" curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh + . "$HOME/.cargo/env" else - echo "Aborting." + printf "Aborting.\n" exit 1 fi fi # Ensure that we have wasm support if prompt_default_yes "\n🦀 Setup the Rust environment (e.g. WASM support)?"; then - echo "🦀 Setting up Rust environment." + printf "🦀 Setting up Rust environment.\n" rustup default stable rustup update rustup target add wasm32-unknown-unknown rustup component add rust-src fi -if [ -d "minimal-template" ]; then - echo "\n✅︎ minimal-template directory already exists. -> Entering." -else - echo "\n↓ Let's grab the minimal template from github." - git clone https://github.com/paritytech/polkadot-sdk-minimal-template.git minimal-template +if ! prompt "\nWould you like to start with one of the templates?"; then + printf "⚡ All done, the environment is ready for hacking.\n" + exit 0 +fi + +while true; do + printf "\nWhich template would you like to start with?\n" + printf "1) minimal template\n" + printf "2) parachain template\n" + printf "3) solochain template\n" + printf "q) cancel\n" + read -p "#? " template + case $template in + [1]* ) clone_and_enter_template minimal; break;; + [2]* ) clone_and_enter_template parachain; break;; + [3]* ) clone_and_enter_template solochain; break;; + [qQ]* ) printf "Canceling, not using a template.\n"; exit 0;; + * ) printf "Selection not recognized.\n";; + esac +done + +if ! prompt_default_yes "\n⚙️ Let's compile the node? It might take a while."; then + printf "⚡ Script finished, you can continue in the ${template}-template directory.\n" + exit 0 fi -cd minimal-template -echo "\n⚙️ Let's compile the node." cargo build --release -if prompt_default_yes "\n🚀 Everything ready to go, let's run the node?"; then +if prompt_default_yes "\n🚀 Everything ready to go, let's run the node?\n"; then cargo run --release -- --dev fi -- GitLab From 6a1b200c1277512ef93ea9e64957baea684fbe61 Mon Sep 17 00:00:00 2001 From: Alexander Samusev <41779041+alvicsam@users.noreply.github.com> Date: Thu, 5 Sep 2024 12:01:36 +0200 Subject: [PATCH 005/125] [ci] Move test-linux-stable to GHA (#5571) PR moves jobs `test-linux-stable`, `test-linux-stable-int` and `test-linux-stable-runtime-benchmarks` to github actions cc https://github.com/paritytech/ci_cd/issues/1006 --- .github/workflows/tests-linux-stable.yml | 52 +++++--- .gitlab-ci.yml | 21 ---- .gitlab/pipeline/test.yml | 151 ++--------------------- 3 files changed, 43 insertions(+), 181 deletions(-) diff --git a/.github/workflows/tests-linux-stable.yml b/.github/workflows/tests-linux-stable.yml index 4a13f5318f7..997d7622f0c 100644 --- a/.github/workflows/tests-linux-stable.yml +++ b/.github/workflows/tests-linux-stable.yml @@ -6,39 +6,51 @@ on: branches: - master pull_request: - types: [opened, synchronize, reopened, ready_for_review, labeled] + types: [opened, synchronize, reopened, ready_for_review] merge_group: concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true jobs: - changes: - # TODO: remove once migration is complete or this workflow is fully stable - if: contains(github.event.label.name, 'GHA-migration') - permissions: - pull-requests: read - uses: ./.github/workflows/reusable-check-changed-files.yml + # changes: + # # TODO: remove once migration is complete or this workflow is fully stable + # if: contains(github.event.label.name, 'GHA-migration') + # permissions: + # pull-requests: read + # uses: ./.github/workflows/reusable-check-changed-files.yml set-image: # GitHub Actions allows using 'env' in a container context. # However, env variables don't work for forks: https://github.com/orgs/community/discussions/44322 # This workaround sets the container image for each job using 'set-image' job output. - needs: changes - if: ${{ needs.changes.outputs.rust }} runs-on: ubuntu-latest outputs: IMAGE: ${{ steps.set_image.outputs.IMAGE }} + RUNNER: ${{ steps.set_runner.outputs.RUNNER }} + OLDLINUXRUNNER: ${{ steps.set_runner.outputs.OLDLINUXRUNNER }} steps: - name: Checkout uses: actions/checkout@v4 - id: set_image run: cat .github/env >> $GITHUB_OUTPUT + # By default we use spot machines that can be terminated at any time. + # Merge queues use persistent runners to avoid kicking off from queue when the runner is terminated. + - id: set_runner + run: | + # Run merge queues on persistent runners + if [[ $GITHUB_REF_NAME == *"gh-readonly-queue"* ]]; then + echo "RUNNER=arc-runners-polkadot-sdk-beefy-persistent" >> $GITHUB_OUTPUT + echo "OLDLINUXRUNNER=oldlinux-persistent" >> $GITHUB_OUTPUT + else + echo "RUNNER=arc-runners-polkadot-sdk-beefy" >> $GITHUB_OUTPUT + echo "OLDLINUXRUNNER=oldlinux" >> $GITHUB_OUTPUT + fi test-linux-stable-int: - needs: [set-image, changes] - if: ${{ needs.changes.outputs.rust }} - runs-on: arc-runners-polkadot-sdk-beefy + needs: [set-image] + # if: ${{ needs.changes.outputs.rust }} + runs-on: ${{ needs.set-image.outputs.RUNNER }} timeout-minutes: 60 container: image: ${{ needs.set-image.outputs.IMAGE }} @@ -57,9 +69,9 @@ jobs: # https://github.com/paritytech/ci_cd/issues/864 test-linux-stable-runtime-benchmarks: - needs: [set-image, changes] - if: ${{ needs.changes.outputs.rust }} - runs-on: arc-runners-polkadot-sdk-beefy + needs: [set-image] + # if: ${{ needs.changes.outputs.rust }} + runs-on: ${{ needs.set-image.outputs.RUNNER }} timeout-minutes: 60 container: image: ${{ needs.set-image.outputs.IMAGE }} @@ -75,15 +87,19 @@ jobs: run: forklift cargo nextest run --workspace --features runtime-benchmarks benchmark --locked --cargo-profile testnet test-linux-stable: - needs: [set-image, changes] - if: ${{ needs.changes.outputs.rust }} + needs: [set-image] + # if: ${{ needs.changes.outputs.rust }} runs-on: ${{ matrix.runners }} timeout-minutes: 60 strategy: fail-fast: false matrix: partition: [1/3, 2/3, 3/3] - runners: [arc-runners-polkadot-sdk-beefy, oldlinux] + runners: + [ + "${{ needs.set-image.outputs.RUNNER }}", + "${{ needs.set-image.outputs.OLDLINUXRUNNER }}", + ] container: image: ${{ needs.set-image.outputs.IMAGE }} # needed for tests that use unshare syscall diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 56d426218c8..5b581c45fb8 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -268,22 +268,6 @@ remove-cancel-pipeline-message: PR_NUM: "${CI_COMMIT_REF_NAME}" trigger: project: "parity/infrastructure/ci_cd/pipeline-stopper" -# need to copy jobs this way because otherwise gitlab will wait -# for all 3 jobs to finish instead of cancelling if one fails -cancel-pipeline-test-linux-stable1: - extends: .cancel-pipeline-template - needs: - - job: "test-linux-stable 1/3" - -cancel-pipeline-test-linux-stable2: - extends: .cancel-pipeline-template - needs: - - job: "test-linux-stable 2/3" - -cancel-pipeline-test-linux-stable3: - extends: .cancel-pipeline-template - needs: - - job: "test-linux-stable 3/3" cancel-pipeline-cargo-check-benches1: extends: .cancel-pipeline-template @@ -295,11 +279,6 @@ cancel-pipeline-cargo-check-benches2: needs: - job: "cargo-check-benches 2/2" -cancel-pipeline-test-linux-stable-int: - extends: .cancel-pipeline-template - needs: - - job: test-linux-stable-int - cancel-pipeline-cargo-check-each-crate-1: extends: .cancel-pipeline-template needs: diff --git a/.gitlab/pipeline/test.yml b/.gitlab/pipeline/test.yml index 83af0e6b7b2..ca3a2394fb3 100644 --- a/.gitlab/pipeline/test.yml +++ b/.gitlab/pipeline/test.yml @@ -35,7 +35,7 @@ codecov-start: - .pipeline-stopper-artifacts - .run-immediately script: - - !reference [ .codecov-check, script ] + - !reference [.codecov-check, script] - > if [ "$CI_COMMIT_REF_NAME" != "master" ]; then codecovcli -v create-commit -t ${CODECOV_TOKEN} -r paritytech/polkadot-sdk --commit-sha ${CI_COMMIT_SHA} --fail-on-error --pr ${CI_COMMIT_REF_NAME} --git-service github; @@ -57,7 +57,7 @@ codecov-finish: needs: - test-linux-stable-codecov script: - - !reference [ .codecov-check, script ] + - !reference [.codecov-check, script] - codecovcli -v create-report-results -t ${CODECOV_TOKEN} -r paritytech/polkadot-sdk --commit-sha ${CI_COMMIT_SHA} --git-service github - codecovcli -v get-report-results -t ${CODECOV_TOKEN} -r paritytech/polkadot-sdk --commit-sha ${CI_COMMIT_SHA} --git-service github - codecovcli -v send-notifications -t ${CODECOV_TOKEN} -r paritytech/polkadot-sdk --commit-sha ${CI_COMMIT_SHA} --git-service github @@ -83,7 +83,7 @@ test-linux-stable-codecov: parallel: 2 script: # tools - - !reference [ .codecov-check, script ] + - !reference [.codecov-check, script] - rustup component add llvm-tools-preview - mkdir -p target/coverage/result/ # Place real test call here @@ -110,119 +110,6 @@ test-linux-stable-codecov: codecovcli -v do-upload -f target/coverage/result/report-${CI_NODE_INDEX}.lcov --disable-search -t ${CODECOV_TOKEN} -r paritytech/polkadot-sdk --commit-sha ${CI_COMMIT_SHA} --fail-on-error --git-service github; fi -test-linux-stable: - stage: test - extends: - - .docker-env - - .common-refs - - .run-immediately - - .pipeline-stopper-artifacts - variables: - RUST_TOOLCHAIN: stable - # Enable debug assertions since we are running optimized builds for testing - # but still want to have debug assertions. - RUSTFLAGS: "-Cdebug-assertions=y -Dwarnings" - parallel: 3 - script: - # Build all but only execute 'runtime' tests. - - echo "Node index - ${CI_NODE_INDEX}. Total amount - ${CI_NODE_TOTAL}" - - > - time cargo nextest run \ - --workspace \ - --locked \ - --release \ - --no-fail-fast \ - --features try-runtime,experimental,riscv,ci-only-tests \ - --partition count:${CI_NODE_INDEX}/${CI_NODE_TOTAL} - # Upload tests results to Elasticsearch - - echo "Upload test results to Elasticsearch" - - cat target/nextest/default/junit.xml | xq . > target/nextest/default/junit.json - - > - curl -v -XPOST --http1.1 \ - -u ${ELASTIC_USERNAME}:${ELASTIC_PASSWORD} \ - https://elasticsearch.parity-build.parity.io/unit-tests/_doc/${CI_JOB_ID} \ - -H 'Content-Type: application/json' \ - -d @target/nextest/default/junit.json || echo "failed to upload junit report" - # run runtime-api tests with `enable-staging-api` feature on the 1st node - - if [ ${CI_NODE_INDEX} == 1 ]; then time cargo nextest run -p sp-api-test --features enable-staging-api; fi - artifacts: - when: always - paths: - - target/nextest/default/junit.xml - reports: - junit: target/nextest/default/junit.xml - timeout: 90m - -test-linux-oldkernel-stable: - extends: test-linux-stable - tags: - - oldkernel-vm - -# https://github.com/paritytech/ci_cd/issues/864 -test-linux-stable-runtime-benchmarks: - stage: test - extends: - - .docker-env - - .common-refs - - .run-immediately - - .pipeline-stopper-artifacts - variables: - RUST_TOOLCHAIN: stable - # Enable debug assertions since we are running optimized builds for testing - # but still want to have debug assertions. - RUSTFLAGS: "-Cdebug-assertions=y -Dwarnings" - script: - - time cargo nextest run --workspace --features runtime-benchmarks benchmark --locked --cargo-profile testnet - -# can be used to run all tests -# test-linux-stable-all: -# stage: test -# extends: -# - .docker-env -# - .common-refs -# - .run-immediately -# variables: -# RUST_TOOLCHAIN: stable -# # Enable debug assertions since we are running optimized builds for testing -# # but still want to have debug assertions. -# RUSTFLAGS: "-Cdebug-assertions=y -Dwarnings" -# parallel: 3 -# script: -# # Build all but only execute 'runtime' tests. -# - echo "Node index - ${CI_NODE_INDEX}. Total amount - ${CI_NODE_TOTAL}" -# - > -# time cargo nextest run \ -# --workspace \ -# --locked \ -# --release \ -# --no-fail-fast \ -# --features runtime-benchmarks,try-runtime \ -# --partition count:${CI_NODE_INDEX}/${CI_NODE_TOTAL} -# # todo: add flacky-test collector - -# takes about 1,5h without cache -# can be used to check that nextest works correctly -# test-linux-stable-polkadot: -# stage: test -# timeout: 2h -# extends: -# - .docker-env -# - .common-refs -# - .run-immediately -# - .collect-artifacts-short -# variables: -# RUST_TOOLCHAIN: stable -# # Enable debug assertions since we are running optimized builds for testing -# # but still want to have debug assertions. -# RUSTFLAGS: "-Cdebug-assertions=y -Dwarnings" -# script: -# - mkdir -p artifacts -# - time cargo test --workspace -# --locked -# --profile testnet -# --features=runtime-benchmarks,runtime-metrics,try-runtime -- -# --skip upgrade_version_checks_should_work - test-doc: stage: test extends: @@ -316,7 +203,7 @@ cargo-check-benches: git merge --verbose --no-edit FETCH_HEAD; fi fi' - - !reference [ .forklift-cache, before_script ] + - !reference [.forklift-cache, before_script] parallel: 2 script: - mkdir -p ./artifacts/benches/$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA @@ -360,7 +247,7 @@ node-bench-regression-guard: artifacts: true variables: CI_IMAGE: "paritytech/node-bench-regression-guard:latest" - before_script: [ "" ] + before_script: [""] script: - if [ $(ls -la artifacts/benches/ | grep master | wc -l) == 0 ]; then echo "Couldn't find master artifacts"; @@ -371,7 +258,7 @@ node-bench-regression-guard: - echo "In case of this job failure, check your pipeline's cargo-check-benches" - "node-bench-regression-guard --reference artifacts/benches/master-* --compare-with artifacts/benches/$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA" - after_script: [ "" ] + after_script: [""] # if this fails run `bot update-ui` in the Pull Request or "./scripts/update-ui-tests.sh" locally # see ./docs/contributor/CONTRIBUTING.md#ui-tests @@ -443,26 +330,6 @@ test-frame-examples-compile-to-wasm: # FIXME allow_failure: true -test-linux-stable-int: - stage: test - extends: - - .docker-env - - .common-refs - - .run-immediately - - .pipeline-stopper-artifacts - variables: - # Enable debug assertions since we are running optimized builds for testing - # but still want to have debug assertions. - RUSTFLAGS: "-C debug-assertions -D warnings" - RUST_BACKTRACE: 1 - WASM_BUILD_NO_COLOR: 1 - WASM_BUILD_RUSTFLAGS: "-C debug-assertions -D warnings" - # Ensure we run the UI tests. - RUN_UI_TESTS: 1 - script: - - WASM_BUILD_NO_COLOR=1 - time cargo test -p staging-node-cli --release --locked -- --ignored - # more information about this job can be found here: # https://github.com/paritytech/substrate/pull/6916 check-tracing: @@ -533,9 +400,9 @@ cargo-check-each-crate-macos: # - .collect-artifacts before_script: # skip timestamp script, the osx bash doesn't support printf %()T - - !reference [ .job-switcher, before_script ] - - !reference [ .rust-info-script, script ] - - !reference [ .pipeline-stopper-vars, script ] + - !reference [.job-switcher, before_script] + - !reference [.rust-info-script, script] + - !reference [.pipeline-stopper-vars, script] variables: SKIP_WASM_BUILD: 1 script: -- GitLab From 8d9ebcd51a8021f77d6ae16ef9d6388f55d0598f Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Thu, 5 Sep 2024 12:37:25 +0200 Subject: [PATCH 006/125] Clear other messages before dry-run to get only the ones produced during (#5581) The dry-run shows in `forwarded_xcms` all the messages in the queues at the time of calling the API. Each time the API is called, the result could be different. You could get messages even if you dry-run something that doesn't send a message, like a `System::remark`. This PR fixes this by clearing the message queues before doing the dry-run, so the only messages left are the ones the users of the API actually care about. --------- Co-authored-by: Adrian Catangiu --- .../modules/xcm-bridge-hub-router/src/lib.rs | 4 +++ .../modules/xcm-bridge-hub-router/src/mock.rs | 4 +++ bridges/modules/xcm-bridge-hub/src/mock.rs | 4 +++ cumulus/pallets/parachain-system/src/lib.rs | 4 +++ cumulus/pallets/xcmp-queue/src/lib.rs | 5 ++++ cumulus/primitives/utility/src/lib.rs | 4 +++ polkadot/runtime/common/src/xcm_sender.rs | 5 ++++ polkadot/xcm/pallet-xcm/src/lib.rs | 6 +++- polkadot/xcm/xcm-builder/src/routing.rs | 13 ++++++++ .../xcm/xcm-builder/src/universal_exports.rs | 4 +++ prdoc/pr_5581.prdoc | 30 +++++++++++++++++++ 11 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 prdoc/pr_5581.prdoc diff --git a/bridges/modules/xcm-bridge-hub-router/src/lib.rs b/bridges/modules/xcm-bridge-hub-router/src/lib.rs index 860c1a83878..7ba524e95b1 100644 --- a/bridges/modules/xcm-bridge-hub-router/src/lib.rs +++ b/bridges/modules/xcm-bridge-hub-router/src/lib.rs @@ -408,6 +408,10 @@ impl, I: 'static> SendXcm for Pallet { } impl, I: 'static> InspectMessageQueues for Pallet { + fn clear_messages() { + ViaBridgeHubExporter::::clear_messages() + } + fn get_messages() -> Vec<(VersionedLocation, Vec>)> { ViaBridgeHubExporter::::get_messages() } diff --git a/bridges/modules/xcm-bridge-hub-router/src/mock.rs b/bridges/modules/xcm-bridge-hub-router/src/mock.rs index f4806eb55b2..bb265e1925a 100644 --- a/bridges/modules/xcm-bridge-hub-router/src/mock.rs +++ b/bridges/modules/xcm-bridge-hub-router/src/mock.rs @@ -130,6 +130,10 @@ impl SendXcm for TestToBridgeHubSender { } impl InspectMessageQueues for TestToBridgeHubSender { + fn clear_messages() { + SENT_XCM.with(|q| q.borrow_mut().clear()); + } + fn get_messages() -> Vec<(VersionedLocation, Vec>)> { SENT_XCM.with(|q| { (*q.borrow()) diff --git a/bridges/modules/xcm-bridge-hub/src/mock.rs b/bridges/modules/xcm-bridge-hub/src/mock.rs index d32e0989dfd..aff3526b558 100644 --- a/bridges/modules/xcm-bridge-hub/src/mock.rs +++ b/bridges/modules/xcm-bridge-hub/src/mock.rs @@ -295,6 +295,10 @@ impl SendXcm for TestExportXcmWithXcmOverBridge { } } impl InspectMessageQueues for TestExportXcmWithXcmOverBridge { + fn clear_messages() { + todo!() + } + fn get_messages() -> Vec<(VersionedLocation, Vec>)> { todo!() } diff --git a/cumulus/pallets/parachain-system/src/lib.rs b/cumulus/pallets/parachain-system/src/lib.rs index bf136dc0644..882dcb68fbb 100644 --- a/cumulus/pallets/parachain-system/src/lib.rs +++ b/cumulus/pallets/parachain-system/src/lib.rs @@ -1540,6 +1540,10 @@ impl UpwardMessageSender for Pallet { } impl InspectMessageQueues for Pallet { + fn clear_messages() { + PendingUpwardMessages::::kill(); + } + fn get_messages() -> Vec<(VersionedLocation, Vec>)> { use xcm::prelude::*; diff --git a/cumulus/pallets/xcmp-queue/src/lib.rs b/cumulus/pallets/xcmp-queue/src/lib.rs index 8c4446a925d..732ee94f3e1 100644 --- a/cumulus/pallets/xcmp-queue/src/lib.rs +++ b/cumulus/pallets/xcmp-queue/src/lib.rs @@ -1008,6 +1008,11 @@ impl SendXcm for Pallet { } impl InspectMessageQueues for Pallet { + fn clear_messages() { + // Best effort. + let _ = OutboundXcmpMessages::::clear(u32::MAX, None); + } + fn get_messages() -> Vec<(VersionedLocation, Vec>)> { use xcm::prelude::*; diff --git a/cumulus/primitives/utility/src/lib.rs b/cumulus/primitives/utility/src/lib.rs index 3ebcb44fa43..e568c79bb6a 100644 --- a/cumulus/primitives/utility/src/lib.rs +++ b/cumulus/primitives/utility/src/lib.rs @@ -99,6 +99,10 @@ where impl InspectMessageQueues for ParentAsUmp { + fn clear_messages() { + T::clear_messages(); + } + fn get_messages() -> Vec<(VersionedLocation, Vec>)> { T::get_messages() } diff --git a/polkadot/runtime/common/src/xcm_sender.rs b/polkadot/runtime/common/src/xcm_sender.rs index dace785a535..37fe7f0b59e 100644 --- a/polkadot/runtime/common/src/xcm_sender.rs +++ b/polkadot/runtime/common/src/xcm_sender.rs @@ -141,6 +141,11 @@ where } impl InspectMessageQueues for ChildParachainRouter { + fn clear_messages() { + // Best effort. + let _ = dmp::DownwardMessageQueues::::clear(u32::MAX, None); + } + fn get_messages() -> Vec<(VersionedLocation, Vec>)> { dmp::DownwardMessageQueues::::iter() .map(|(para_id, messages)| { diff --git a/polkadot/xcm/pallet-xcm/src/lib.rs b/polkadot/xcm/pallet-xcm/src/lib.rs index 6451901279b..05d9046ab19 100644 --- a/polkadot/xcm/pallet-xcm/src/lib.rs +++ b/polkadot/xcm/pallet-xcm/src/lib.rs @@ -2457,10 +2457,14 @@ impl Pallet { ::RuntimeOrigin: From, { crate::Pallet::::set_record_xcm(true); - frame_system::Pallet::::reset_events(); // To make sure we only record events from current call. + // Clear other messages in queues... + Router::clear_messages(); + // ...and reset events to make sure we only record events from current call. + frame_system::Pallet::::reset_events(); let result = call.dispatch(origin.into()); crate::Pallet::::set_record_xcm(false); let local_xcm = crate::Pallet::::recorded_xcm(); + // Should only get messages from this call since we cleared previous ones. let forwarded_xcms = Router::get_messages(); let events: Vec<::RuntimeEvent> = frame_system::Pallet::::read_events_no_consensus() diff --git a/polkadot/xcm/xcm-builder/src/routing.rs b/polkadot/xcm/xcm-builder/src/routing.rs index 03ef780ef03..fc2de89d212 100644 --- a/polkadot/xcm/xcm-builder/src/routing.rs +++ b/polkadot/xcm/xcm-builder/src/routing.rs @@ -62,6 +62,10 @@ impl SendXcm for WithUniqueTopic { } } impl InspectMessageQueues for WithUniqueTopic { + fn clear_messages() { + Inner::clear_messages() + } + fn get_messages() -> Vec<(VersionedLocation, Vec>)> { Inner::get_messages() } @@ -149,12 +153,21 @@ impl EnsureDelivery for Tuple { /// Inspects messages in queues. /// Meant to be used in runtime APIs, not in runtimes. pub trait InspectMessageQueues { + /// Clear the queues at the beginning of Runtime API call, so that subsequent + /// `Self::get_messages()` will return only messages generated by said Runtime API. + fn clear_messages(); /// Get queued messages and their destinations. fn get_messages() -> Vec<(VersionedLocation, Vec>)>; } #[impl_trait_for_tuples::impl_for_tuples(30)] impl InspectMessageQueues for Tuple { + fn clear_messages() { + for_tuples!( #( + Tuple::clear_messages(); + )* ); + } + fn get_messages() -> Vec<(VersionedLocation, Vec>)> { let mut messages = Vec::new(); diff --git a/polkadot/xcm/xcm-builder/src/universal_exports.rs b/polkadot/xcm/xcm-builder/src/universal_exports.rs index 8aa9602fcc2..30e0b7c72b0 100644 --- a/polkadot/xcm/xcm-builder/src/universal_exports.rs +++ b/polkadot/xcm/xcm-builder/src/universal_exports.rs @@ -340,6 +340,10 @@ impl InspectMessageQueues for SovereignPaidRemoteExporter { + fn clear_messages() { + Router::clear_messages() + } + fn get_messages() -> Vec<(VersionedLocation, Vec>)> { Router::get_messages() } diff --git a/prdoc/pr_5581.prdoc b/prdoc/pr_5581.prdoc new file mode 100644 index 00000000000..e33690939d8 --- /dev/null +++ b/prdoc/pr_5581.prdoc @@ -0,0 +1,30 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Clear other messages before dry-running + +doc: + - audience: Runtime Dev + description: | + The DryRunApi.dry_run_call and DryRunApi.dry_run_xcm functions used to populate + `forwarded_xcms` with all the existing messages in the queues at the time. + Now, existing (irrelevant) messages are cleared when dry-running, meaning only the + messages produced by the dry-run call (or xcm) will be returned in `forwarded_xcms`. + +crates: + - name: pallet-xcm + bump: minor + - name: staging-xcm-builder + bump: major + - name: pallet-xcm-bridge-hub-router + bump: minor + - name: cumulus-pallet-parachain-system + bump: minor + - name: cumulus-pallet-xcmp-queue + bump: minor + - name: cumulus-primitives-utility + bump: minor + - name: polkadot-runtime-common + bump: minor + - name: pallet-xcm-bridge-hub + bump: minor -- GitLab From f6fd5bc5a644b2c4ed986e372b43ab8f049b7fc6 Mon Sep 17 00:00:00 2001 From: Przemek Rzad Date: Thu, 5 Sep 2024 13:26:46 +0200 Subject: [PATCH 007/125] Make the docs logo readable on light backgrounds (#5389) Currently, the logo on the docs site blends into the background on the light theme: [Here](https://internals.rust-lang.org/t/feedback-on-new-rust-documentation-logo-background/12787) is a discussion related to this. There doesn't seem to be a perfect way to handle this situation. --- Silent because the docs crate is not part of the regular release process. --------- Co-authored-by: ordian --- docs/sdk/assets/theme.css | 5 +++++ docs/sdk/src/lib.rs | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/sdk/assets/theme.css b/docs/sdk/assets/theme.css index a488e15c36b..1f47a8ef5b0 100644 --- a/docs/sdk/assets/theme.css +++ b/docs/sdk/assets/theme.css @@ -9,9 +9,14 @@ body.sdk-docs { nav.sidebar>div.sidebar-crate>a>img { width: 190px; + height: 52px; } nav.sidebar { flex: 0 0 250px; } } + +html[data-theme="light"] .sidebar-crate > .logo-container > img { + content: url("https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/docs/images/Polkadot_Logo_Horizontal_Pink_Black.png"); +} diff --git a/docs/sdk/src/lib.rs b/docs/sdk/src/lib.rs index 6dc87858530..35f73c290bf 100644 --- a/docs/sdk/src/lib.rs +++ b/docs/sdk/src/lib.rs @@ -27,7 +27,7 @@ #![warn(rustdoc::private_intra_doc_links)] #![doc(html_favicon_url = "https://polkadot.com/favicon.ico")] #![doc( - html_logo_url = "https://europe1.discourse-cdn.com/standard21/uploads/polkadot2/original/1X/eb57081e2bb7c39e5fcb1a98b443e423fa4448ae.svg" + html_logo_url = "https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/docs/images/Polkadot_Logo_Horizontal_Pink_White.png" )] #![doc(issue_tracker_base_url = "https://github.com/paritytech/polkadot-sdk/issues")] -- GitLab From a947cb831d8d56228e7aa84355410c50864a816e Mon Sep 17 00:00:00 2001 From: Alexandru Gheorghe <49718502+alexggh@users.noreply.github.com> Date: Thu, 5 Sep 2024 15:37:27 +0300 Subject: [PATCH 008/125] Add benchmark for the number of minimum cpu cores (#5127) Fixes: https://github.com/paritytech/polkadot-sdk/issues/5122. This PR extends the existing single core `benchmark_cpu` to also build a score of the entire processor by spawning `EXPECTED_NUM_CORES(8)` threads and averaging their throughput. This is better than simply checking the number of cores, because also covers multi-tenant environments where the OS sees a high number of available CPUs, but because it has to share it with the rest of his neighbours its total throughput does not satisfy the minimum requirements. ## TODO - [x] Obtain reference values on the reference hardware. --------- Signed-off-by: Alexandru Gheorghe --- .../polkadot-parachain-lib/src/command.rs | 5 +- .../polkadot-parachain-lib/src/common/spec.rs | 4 +- polkadot/cli/src/command.rs | 2 +- polkadot/node/service/src/lib.rs | 31 ++++- prdoc/pr_5127.prdoc | 25 ++++ substrate/bin/node/cli/src/service.rs | 4 +- substrate/client/sysinfo/src/lib.rs | 22 ++- substrate/client/sysinfo/src/sysinfo.rs | 128 ++++++++++++++---- .../benchmarking-cli/src/machine/hardware.rs | 25 +++- .../frame/benchmarking-cli/src/machine/mod.rs | 8 +- .../src/machine/reference_hardware.json | 5 + templates/parachain/node/src/command.rs | 5 +- templates/parachain/node/src/service.rs | 2 +- 13 files changed, 217 insertions(+), 49 deletions(-) create mode 100644 prdoc/pr_5127.prdoc diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/command.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/command.rs index 320511ece5e..43fb551f80d 100644 --- a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/command.rs +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/command.rs @@ -258,7 +258,10 @@ pub fn run(cmd_config: RunConfig) -> Result<() let hwbench = (!cli.no_hardware_benchmarks) .then_some(config.database.path().map(|database_path| { let _ = std::fs::create_dir_all(database_path); - sc_sysinfo::gather_hwbench(Some(database_path)) + sc_sysinfo::gather_hwbench( + Some(database_path), + &SUBSTRATE_REFERENCE_HARDWARE, + ) })) .flatten(); diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/spec.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/spec.rs index 8e19cf304b0..0c0230296eb 100644 --- a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/spec.rs +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/spec.rs @@ -80,7 +80,9 @@ where fn warn_if_slow_hardware(hwbench: &sc_sysinfo::HwBench) { // Polkadot para-chains should generally use these requirements to ensure that the relay-chain // will not take longer than expected to import its blocks. - if let Err(err) = frame_benchmarking_cli::SUBSTRATE_REFERENCE_HARDWARE.check_hardware(hwbench) { + if let Err(err) = + frame_benchmarking_cli::SUBSTRATE_REFERENCE_HARDWARE.check_hardware(hwbench, false) + { log::warn!( "⚠️ The hardware does not meet the minimal requirements {} for role 'Authority' find out more at:\n\ https://wiki.polkadot.network/docs/maintain-guides-how-to-validate-polkadot#reference-hardware", diff --git a/polkadot/cli/src/command.rs b/polkadot/cli/src/command.rs index 62d99122c30..168a645430e 100644 --- a/polkadot/cli/src/command.rs +++ b/polkadot/cli/src/command.rs @@ -230,7 +230,7 @@ where let hwbench = (!cli.run.no_hardware_benchmarks) .then_some(config.database.path().map(|database_path| { let _ = std::fs::create_dir_all(&database_path); - sc_sysinfo::gather_hwbench(Some(database_path)) + sc_sysinfo::gather_hwbench(Some(database_path), &SUBSTRATE_REFERENCE_HARDWARE) })) .flatten(); diff --git a/polkadot/node/service/src/lib.rs b/polkadot/node/service/src/lib.rs index ab91c6b9b92..a8e7fc16eb4 100644 --- a/polkadot/node/service/src/lib.rs +++ b/polkadot/node/service/src/lib.rs @@ -763,6 +763,7 @@ pub fn new_full< use polkadot_availability_recovery::FETCH_CHUNKS_THRESHOLD; use polkadot_node_network_protocol::request_response::IncomingRequest; use sc_network_sync::WarpSyncConfig; + use sc_sysinfo::Metric; let is_offchain_indexing_enabled = config.offchain_worker.indexing_enabled; let role = config.role; @@ -1080,13 +1081,31 @@ pub fn new_full< if let Some(hwbench) = hwbench { sc_sysinfo::print_hwbench(&hwbench); - match SUBSTRATE_REFERENCE_HARDWARE.check_hardware(&hwbench) { + match SUBSTRATE_REFERENCE_HARDWARE.check_hardware(&hwbench, role.is_authority()) { Err(err) if role.is_authority() => { - log::warn!( - "⚠️ The hardware does not meet the minimal requirements {} for role 'Authority' find out more at:\n\ - https://wiki.polkadot.network/docs/maintain-guides-how-to-validate-polkadot#reference-hardware", - err - ); + if err + .0 + .iter() + .any(|failure| matches!(failure.metric, Metric::Blake2256Parallel { .. })) + { + log::warn!( + "⚠️ Starting January 2025 the hardware will fail the minimal physical CPU cores requirements {} for role 'Authority',\n\ + find out more when this will become mandatory at:\n\ + https://wiki.polkadot.network/docs/maintain-guides-how-to-validate-polkadot#reference-hardware", + err + ); + } + if err + .0 + .iter() + .any(|failure| !matches!(failure.metric, Metric::Blake2256Parallel { .. })) + { + log::warn!( + "⚠️ The hardware does not meet the minimal requirements {} for role 'Authority' find out more at:\n\ + https://wiki.polkadot.network/docs/maintain-guides-how-to-validate-polkadot#reference-hardware", + err + ); + } }, _ => {}, } diff --git a/prdoc/pr_5127.prdoc b/prdoc/pr_5127.prdoc new file mode 100644 index 00000000000..c08f4e7fb8f --- /dev/null +++ b/prdoc/pr_5127.prdoc @@ -0,0 +1,25 @@ +title: Add benchmark to check upcoming minimum required hw cores + +doc: + - audience: Node Operator + description: | + Add benchmark that checks hardware satisifies the minimum required hardware cores + for a validators. The new minimum requirements are schedule to come into effect + in January 2025, for more details see: https://polkadot.subsquare.io/referenda/1051. + + +crates: + - name: sc-sysinfo + bump: major + - name: frame-benchmarking-cli + bump: major + - name: staging-node-cli + bump: patch + - name: polkadot-service + bump: patch + - name: polkadot-parachain-lib + bump: patch + - name: polkadot-cli + bump: patch + - name: parachain-template-node + bump: patch diff --git a/substrate/bin/node/cli/src/service.rs b/substrate/bin/node/cli/src/service.rs index 4f63473f632..1b345a23f27 100644 --- a/substrate/bin/node/cli/src/service.rs +++ b/substrate/bin/node/cli/src/service.rs @@ -416,7 +416,7 @@ pub fn new_full_base::Hash>>( let hwbench = (!disable_hardware_benchmarks) .then_some(config.database.path().map(|database_path| { let _ = std::fs::create_dir_all(&database_path); - sc_sysinfo::gather_hwbench(Some(database_path)) + sc_sysinfo::gather_hwbench(Some(database_path), &SUBSTRATE_REFERENCE_HARDWARE) })) .flatten(); @@ -553,7 +553,7 @@ pub fn new_full_base::Hash>>( if let Some(hwbench) = hwbench { sc_sysinfo::print_hwbench(&hwbench); - match SUBSTRATE_REFERENCE_HARDWARE.check_hardware(&hwbench) { + match SUBSTRATE_REFERENCE_HARDWARE.check_hardware(&hwbench, false) { Err(err) if role.is_authority() => { log::warn!( "⚠️ The hardware does not meet the minimal requirements {} for role 'Authority'.", diff --git a/substrate/client/sysinfo/src/lib.rs b/substrate/client/sysinfo/src/lib.rs index 7065c9b997e..7bc30ae0221 100644 --- a/substrate/client/sysinfo/src/lib.rs +++ b/substrate/client/sysinfo/src/lib.rs @@ -27,10 +27,10 @@ mod sysinfo; mod sysinfo_linux; pub use sysinfo::{ - benchmark_cpu, benchmark_disk_random_writes, benchmark_disk_sequential_writes, - benchmark_memory, benchmark_sr25519_verify, gather_hwbench, gather_sysinfo, - serialize_throughput, serialize_throughput_option, Metric, Requirement, Requirements, - Throughput, + benchmark_cpu, benchmark_cpu_parallelism, benchmark_disk_random_writes, + benchmark_disk_sequential_writes, benchmark_memory, benchmark_sr25519_verify, gather_hwbench, + gather_sysinfo, serialize_throughput, serialize_throughput_option, Metric, Requirement, + Requirements, Throughput, }; /// The operating system part of the current target triplet. @@ -48,6 +48,12 @@ pub struct HwBench { /// The CPU speed, as measured in how many MB/s it can hash using the BLAKE2b-256 hash. #[serde(serialize_with = "serialize_throughput")] pub cpu_hashrate_score: Throughput, + /// The parallel CPU speed, as measured in how many MB/s it can hash in parallel using the + /// BLAKE2b-256 hash. + #[serde(serialize_with = "serialize_throughput")] + pub parallel_cpu_hashrate_score: Throughput, + /// The number of expected cores used for computing the parallel CPU speed. + pub parallel_cpu_cores: usize, /// Memory bandwidth in MB/s, calculated by measuring the throughput of `memcpy`. #[serde(serialize_with = "serialize_throughput")] pub memory_memcpy_score: Throughput, @@ -65,6 +71,7 @@ pub struct HwBench { pub disk_random_write_score: Option, } +#[derive(Copy, Clone, Debug)] /// Limit the execution time of a benchmark. pub enum ExecutionLimit { /// Limit by the maximal duration. @@ -132,7 +139,12 @@ pub fn print_sysinfo(sysinfo: &sc_telemetry::SysInfo) { /// Prints out the results of the hardware benchmarks in the logs. pub fn print_hwbench(hwbench: &HwBench) { - log::info!("🏁 CPU score: {}", hwbench.cpu_hashrate_score); + log::info!( + "🏁 CPU single core score: {}, parallelism score: {} with expected cores: {}", + hwbench.cpu_hashrate_score, + hwbench.parallel_cpu_hashrate_score, + hwbench.parallel_cpu_cores, + ); log::info!("🏁 Memory score: {}", hwbench.memory_memcpy_score); if let Some(score) = hwbench.disk_sequential_write_score { diff --git a/substrate/client/sysinfo/src/sysinfo.rs b/substrate/client/sysinfo/src/sysinfo.rs index 37b35fcb910..4bcdd673455 100644 --- a/substrate/client/sysinfo/src/sysinfo.rs +++ b/substrate/client/sysinfo/src/sysinfo.rs @@ -22,16 +22,18 @@ use sc_telemetry::SysInfo; use sp_core::{sr25519, Pair}; use sp_io::crypto::sr25519_verify; +use core::f64; use derive_more::From; use rand::{seq::SliceRandom, Rng, RngCore}; use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer}; use std::{ - fmt, - fmt::{Display, Formatter}, + borrow::Cow, + fmt::{self, Display, Formatter}, fs::File, io::{Seek, SeekFrom, Write}, ops::{Deref, DerefMut}, path::{Path, PathBuf}, + sync::{Arc, Barrier}, time::{Duration, Instant}, }; @@ -42,6 +44,8 @@ pub enum Metric { Sr25519Verify, /// Blake2-256 hashing algorithm. Blake2256, + /// Blake2-256 hashing algorithm executed in parallel + Blake2256Parallel { num_cores: usize }, /// Copying data in RAM. MemCopy, /// Disk sequential write. @@ -85,20 +89,22 @@ impl Metric { /// The category of the metric. pub fn category(&self) -> &'static str { match self { - Self::Sr25519Verify | Self::Blake2256 => "CPU", + Self::Sr25519Verify | Self::Blake2256 | Self::Blake2256Parallel { .. } => "CPU", Self::MemCopy => "Memory", Self::DiskSeqWrite | Self::DiskRndWrite => "Disk", } } /// The name of the metric. It is always prefixed by the [`self.category()`]. - pub fn name(&self) -> &'static str { + pub fn name(&self) -> Cow<'static, str> { match self { - Self::Sr25519Verify => "SR25519-Verify", - Self::Blake2256 => "BLAKE2-256", - Self::MemCopy => "Copy", - Self::DiskSeqWrite => "Seq Write", - Self::DiskRndWrite => "Rnd Write", + Self::Sr25519Verify => Cow::Borrowed("SR25519-Verify"), + Self::Blake2256 => Cow::Borrowed("BLAKE2-256"), + Self::Blake2256Parallel { num_cores } => + Cow::Owned(format!("BLAKE2-256-Parallel-{}", num_cores)), + Self::MemCopy => Cow::Borrowed("Copy"), + Self::DiskSeqWrite => Cow::Borrowed("Seq Write"), + Self::DiskRndWrite => Cow::Borrowed("Rnd Write"), } } } @@ -253,6 +259,10 @@ pub struct Requirement { deserialize_with = "deserialize_throughput" )] pub minimum: Throughput, + /// Check this requirement only for relay chain validator nodes. + #[serde(default)] + #[serde(skip_serializing_if = "core::ops::Not::not")] + pub validator_only: bool, } #[inline(always)] @@ -343,8 +353,18 @@ fn clobber_value(input: &mut T) { pub const DEFAULT_CPU_EXECUTION_LIMIT: ExecutionLimit = ExecutionLimit::Both { max_iterations: 4 * 1024, max_duration: Duration::from_millis(100) }; -// This benchmarks the CPU speed as measured by calculating BLAKE2b-256 hashes, in bytes per second. +// This benchmarks the single core CPU speed as measured by calculating BLAKE2b-256 hashes, in bytes +// per second. pub fn benchmark_cpu(limit: ExecutionLimit) -> Throughput { + benchmark_cpu_parallelism(limit, 1) +} + +// This benchmarks the entire CPU speed as measured by calculating BLAKE2b-256 hashes, in bytes per +// second. It spawns multiple threads to measure the throughput of the entire CPU and averages the +// score obtained by each thread. If we have at least `refhw_num_cores` available then the +// average throughput should be relatively close to the single core performance as measured by +// calling this function with refhw_num_cores equal to 1. +pub fn benchmark_cpu_parallelism(limit: ExecutionLimit, refhw_num_cores: usize) -> Throughput { // In general the results of this benchmark are somewhat sensitive to how much // data we hash at the time. The smaller this is the *less* B/s we can hash, // the bigger this is the *more* B/s we can hash, up until a certain point @@ -359,20 +379,38 @@ pub fn benchmark_cpu(limit: ExecutionLimit) -> Throughput { // but without hitting its theoretical maximum speed. const SIZE: usize = 32 * 1024; - let mut buffer = Vec::new(); - buffer.resize(SIZE, 0x66); - let mut hash = Default::default(); + let ready_to_run_benchmark = Arc::new(Barrier::new(refhw_num_cores)); + let mut benchmark_threads = Vec::new(); - let run = || -> Result<(), ()> { - clobber_slice(&mut buffer); - hash = sp_crypto_hashing::blake2_256(&buffer); - clobber_slice(&mut hash); + // Spawn a thread for each expected core and average the throughput for each of them. + for _ in 0..refhw_num_cores { + let ready_to_run_benchmark = ready_to_run_benchmark.clone(); - Ok(()) - }; + let handle = std::thread::spawn(move || { + let mut buffer = Vec::new(); + buffer.resize(SIZE, 0x66); + let mut hash = Default::default(); - benchmark("CPU score", SIZE, limit.max_iterations(), limit.max_duration(), run) - .expect("benchmark cannot fail; qed") + let run = || -> Result<(), ()> { + clobber_slice(&mut buffer); + hash = sp_crypto_hashing::blake2_256(&buffer); + clobber_slice(&mut hash); + + Ok(()) + }; + ready_to_run_benchmark.wait(); + benchmark("CPU score", SIZE, limit.max_iterations(), limit.max_duration(), run) + .expect("benchmark cannot fail; qed") + }); + benchmark_threads.push(handle); + } + + let average_score = benchmark_threads + .into_iter() + .map(|thread| thread.join().map(|throughput| throughput.as_kibs()).unwrap_or(0.0)) + .sum::() / + refhw_num_cores as f64; + Throughput::from_kibs(average_score) } /// A default [`ExecutionLimit`] that can be used to call [`benchmark_memory`]. @@ -624,10 +662,25 @@ pub fn benchmark_sr25519_verify(limit: ExecutionLimit) -> Throughput { /// Optionally accepts a path to a `scratch_directory` to use to benchmark the /// disk. Also accepts the `requirements` for the hardware benchmark and a /// boolean to specify if the node is an authority. -pub fn gather_hwbench(scratch_directory: Option<&Path>) -> HwBench { +pub fn gather_hwbench(scratch_directory: Option<&Path>, requirements: &Requirements) -> HwBench { + let cpu_hashrate_score = benchmark_cpu(DEFAULT_CPU_EXECUTION_LIMIT); + let (parallel_cpu_hashrate_score, parallel_cpu_cores) = requirements + .0 + .iter() + .filter_map(|req| { + if let Metric::Blake2256Parallel { num_cores } = req.metric { + Some((benchmark_cpu_parallelism(DEFAULT_CPU_EXECUTION_LIMIT, num_cores), num_cores)) + } else { + None + } + }) + .next() + .unwrap_or((cpu_hashrate_score, 1)); #[allow(unused_mut)] let mut hwbench = HwBench { - cpu_hashrate_score: benchmark_cpu(DEFAULT_CPU_EXECUTION_LIMIT), + cpu_hashrate_score, + parallel_cpu_hashrate_score, + parallel_cpu_cores, memory_memcpy_score: benchmark_memory(DEFAULT_MEMORY_EXECUTION_LIMIT), disk_sequential_write_score: None, disk_random_write_score: None, @@ -659,9 +712,17 @@ pub fn gather_hwbench(scratch_directory: Option<&Path>) -> HwBench { impl Requirements { /// Whether the hardware requirements are met by the provided benchmark results. - pub fn check_hardware(&self, hwbench: &HwBench) -> Result<(), CheckFailures> { + pub fn check_hardware( + &self, + hwbench: &HwBench, + is_rc_authority: bool, + ) -> Result<(), CheckFailures> { let mut failures = Vec::new(); for requirement in self.0.iter() { + if requirement.validator_only && !is_rc_authority { + continue + } + match requirement.metric { Metric::Blake2256 => if requirement.minimum > hwbench.cpu_hashrate_score { @@ -671,6 +732,14 @@ impl Requirements { found: hwbench.cpu_hashrate_score, }); }, + Metric::Blake2256Parallel { .. } => + if requirement.minimum > hwbench.parallel_cpu_hashrate_score { + failures.push(CheckFailure { + metric: requirement.metric, + expected: requirement.minimum, + found: hwbench.parallel_cpu_hashrate_score, + }); + }, Metric::MemCopy => if requirement.minimum > hwbench.memory_memcpy_score { failures.push(CheckFailure { @@ -732,6 +801,13 @@ mod tests { assert!(benchmark_cpu(DEFAULT_CPU_EXECUTION_LIMIT) > Throughput::from_mibs(0.0)); } + #[test] + fn test_benchmark_parallel_cpu() { + assert!( + benchmark_cpu_parallelism(DEFAULT_CPU_EXECUTION_LIMIT, 8) > Throughput::from_mibs(0.0) + ); + } + #[test] fn test_benchmark_memory() { assert!(benchmark_memory(DEFAULT_MEMORY_EXECUTION_LIMIT) > Throughput::from_mibs(0.0)); @@ -781,6 +857,8 @@ mod tests { fn hwbench_serialize_works() { let hwbench = HwBench { cpu_hashrate_score: Throughput::from_gibs(1.32), + parallel_cpu_hashrate_score: Throughput::from_gibs(1.32), + parallel_cpu_cores: 4, memory_memcpy_score: Throughput::from_kibs(9342.432), disk_sequential_write_score: Some(Throughput::from_kibs(4332.12)), disk_random_write_score: None, @@ -788,6 +866,6 @@ mod tests { let serialized = serde_json::to_string(&hwbench).unwrap(); // Throughput from all of the benchmarks should be converted to MiBs. - assert_eq!(serialized, "{\"cpu_hashrate_score\":1351,\"memory_memcpy_score\":9,\"disk_sequential_write_score\":4}"); + assert_eq!(serialized, "{\"cpu_hashrate_score\":1351,\"parallel_cpu_hashrate_score\":1351,\"parallel_cpu_cores\":4,\"memory_memcpy_score\":9,\"disk_sequential_write_score\":4}"); } } diff --git a/substrate/utils/frame/benchmarking-cli/src/machine/hardware.rs b/substrate/utils/frame/benchmarking-cli/src/machine/hardware.rs index 555e848f8cc..ee1d490b854 100644 --- a/substrate/utils/frame/benchmarking-cli/src/machine/hardware.rs +++ b/substrate/utils/frame/benchmarking-cli/src/machine/hardware.rs @@ -51,17 +51,36 @@ mod tests { assert_eq!( *SUBSTRATE_REFERENCE_HARDWARE, Requirements(vec![ - Requirement { metric: Metric::Blake2256, minimum: Throughput::from_mibs(1000.00) }, + Requirement { + metric: Metric::Blake2256, + minimum: Throughput::from_mibs(1000.00), + validator_only: false + }, + Requirement { + metric: Metric::Blake2256Parallel { num_cores: 8 }, + minimum: Throughput::from_mibs(1000.00), + validator_only: true, + }, Requirement { metric: Metric::Sr25519Verify, minimum: Throughput::from_kibs(637.619999744), + validator_only: false }, Requirement { metric: Metric::MemCopy, minimum: Throughput::from_gibs(11.4925205078125003), + validator_only: false, + }, + Requirement { + metric: Metric::DiskSeqWrite, + minimum: Throughput::from_mibs(950.0), + validator_only: false, + }, + Requirement { + metric: Metric::DiskRndWrite, + minimum: Throughput::from_mibs(420.0), + validator_only: false }, - Requirement { metric: Metric::DiskSeqWrite, minimum: Throughput::from_mibs(950.0) }, - Requirement { metric: Metric::DiskRndWrite, minimum: Throughput::from_mibs(420.0) }, ]) ); } diff --git a/substrate/utils/frame/benchmarking-cli/src/machine/mod.rs b/substrate/utils/frame/benchmarking-cli/src/machine/mod.rs index fb9f14c9a4a..0186ca58762 100644 --- a/substrate/utils/frame/benchmarking-cli/src/machine/mod.rs +++ b/substrate/utils/frame/benchmarking-cli/src/machine/mod.rs @@ -29,9 +29,9 @@ use log::{error, info, warn}; use sc_cli::{CliConfiguration, Result, SharedParams}; use sc_service::Configuration; use sc_sysinfo::{ - benchmark_cpu, benchmark_disk_random_writes, benchmark_disk_sequential_writes, - benchmark_memory, benchmark_sr25519_verify, ExecutionLimit, Metric, Requirement, Requirements, - Throughput, + benchmark_cpu, benchmark_cpu_parallelism, benchmark_disk_random_writes, + benchmark_disk_sequential_writes, benchmark_memory, benchmark_sr25519_verify, ExecutionLimit, + Metric, Requirement, Requirements, Throughput, }; use crate::shared::check_build_profile; @@ -150,6 +150,8 @@ impl MachineCmd { let score = match metric { Metric::Blake2256 => benchmark_cpu(hash_limit), + Metric::Blake2256Parallel { num_cores } => + benchmark_cpu_parallelism(hash_limit, *num_cores), Metric::Sr25519Verify => benchmark_sr25519_verify(verify_limit), Metric::MemCopy => benchmark_memory(memory_limit), Metric::DiskSeqWrite => benchmark_disk_sequential_writes(disk_limit, dir)?, diff --git a/substrate/utils/frame/benchmarking-cli/src/machine/reference_hardware.json b/substrate/utils/frame/benchmarking-cli/src/machine/reference_hardware.json index cec42b8f245..654eaa6ff13 100644 --- a/substrate/utils/frame/benchmarking-cli/src/machine/reference_hardware.json +++ b/substrate/utils/frame/benchmarking-cli/src/machine/reference_hardware.json @@ -3,6 +3,11 @@ "metric": "Blake2256", "minimum": 1000.00 }, + { + "metric": {"Blake2256Parallel":{"num_cores":8}}, + "minimum": 1000.00, + "validator_only": true + }, { "metric": "Sr25519Verify", "minimum": 0.622675781 diff --git a/templates/parachain/node/src/command.rs b/templates/parachain/node/src/command.rs index fa94d50a8be..610dbd7a686 100644 --- a/templates/parachain/node/src/command.rs +++ b/templates/parachain/node/src/command.rs @@ -220,7 +220,10 @@ pub fn run() -> Result<()> { let hwbench = (!cli.no_hardware_benchmarks) .then_some(config.database.path().map(|database_path| { let _ = std::fs::create_dir_all(database_path); - sc_sysinfo::gather_hwbench(Some(database_path)) + sc_sysinfo::gather_hwbench( + Some(database_path), + &SUBSTRATE_REFERENCE_HARDWARE, + ) })) .flatten(); diff --git a/templates/parachain/node/src/service.rs b/templates/parachain/node/src/service.rs index fc0bcba4c0d..04e20be2bd4 100644 --- a/templates/parachain/node/src/service.rs +++ b/templates/parachain/node/src/service.rs @@ -334,7 +334,7 @@ pub async fn start_parachain_node( // Here you can check whether the hardware meets your chains' requirements. Putting a link // in there and swapping out the requirements for your own are probably a good idea. The // requirements for a para-chain are dictated by its relay-chain. - match SUBSTRATE_REFERENCE_HARDWARE.check_hardware(&hwbench) { + match SUBSTRATE_REFERENCE_HARDWARE.check_hardware(&hwbench, false) { Err(err) if validator => { log::warn!( "⚠️ The hardware does not meet the minimal requirements {} for role 'Authority'.", -- GitLab From 49a68132882e58872411c5c0278b13a008b3682b Mon Sep 17 00:00:00 2001 From: Maksym H <1177472+mordamax@users.noreply.github.com> Date: Thu, 5 Sep 2024 14:57:58 +0100 Subject: [PATCH 009/125] GHA Migration - test-misc (#5385) Closes: https://github.com/paritytech/ci_cd/issues/1018 ![image](https://github.com/user-attachments/assets/b434d809-2c38-47e9-8a62-448f32cf24cb) Added DAG similar to how it was on Gitlab --------- Co-authored-by: Alexander Samusev <41779041+alvicsam@users.noreply.github.com> --- .../scripts}/check-each-crate.py | 10 +- .github/workflows/tests-misc.yml | 413 ++++++++++++++++++ .github/workflows/tests.yml | 9 +- .gitlab-ci.yml | 87 +--- .gitlab/pipeline/test.yml | 294 ------------- 5 files changed, 438 insertions(+), 375 deletions(-) rename {.gitlab => .github/scripts}/check-each-crate.py (81%) create mode 100644 .github/workflows/tests-misc.yml diff --git a/.gitlab/check-each-crate.py b/.github/scripts/check-each-crate.py similarity index 81% rename from .gitlab/check-each-crate.py rename to .github/scripts/check-each-crate.py index 9b654f8071a..7a53e812ddf 100755 --- a/.gitlab/check-each-crate.py +++ b/.github/scripts/check-each-crate.py @@ -9,6 +9,7 @@ # # - `target_group`: Integer starting from 1, the group this script should execute. # - `groups_total`: Integer starting from 1, total number of groups. +# - `disable_forklift`: Boolean, whether to disable forklift or not. import subprocess, sys @@ -31,6 +32,9 @@ crates.sort() target_group = int(sys.argv[1]) - 1 groups_total = int(sys.argv[2]) +disable_forklift = bool(sys.argv[3] if len(sys.argv) > 3 else False) + +print(f"Target group: {target_group}, Total groups: {groups_total}, Disable forklift: {disable_forklift}", file=sys.stderr) if len(crates) == 0: print("No crates detected!", file=sys.stderr) @@ -55,7 +59,11 @@ for i in range(0, crates_per_group + overflow_crates): print(f"Checking {crates[crate][0]}", file=sys.stderr) - res = subprocess.run(["forklift", "cargo", "check", "--locked"], cwd = crates[crate][1]) + cmd = ["cargo", "check", "--locked"] + + cmd.insert(0, 'forklift') if not disable_forklift else None + + res = subprocess.run(cmd, cwd = crates[crate][1]) if res.returncode != 0: sys.exit(1) diff --git a/.github/workflows/tests-misc.yml b/.github/workflows/tests-misc.yml new file mode 100644 index 00000000000..824e8c11c2a --- /dev/null +++ b/.github/workflows/tests-misc.yml @@ -0,0 +1,413 @@ +name: tests misc + +on: + push: + branches: + - master + pull_request: + types: [ opened, synchronize, reopened, ready_for_review ] + merge_group: +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +# Jobs in this workflow depend on each other, only for limiting peak amount of spawned workers + +jobs: + #changes: + # permissions: + # pull-requests: read + # uses: ./.github/workflows/reusable-check-changed-files.yml + + set-image: + # needs: [ changes ] + # if: needs.changes.outputs.rust || needs.changes.outputs.current-workflow + # GitHub Actions allows using 'env' in a container context. + # However, env variables don't work for forks: https://github.com/orgs/community/discussions/44322 + # This workaround sets the container image for each job using 'set-image' job output. + runs-on: ubuntu-latest + outputs: + IMAGE: ${{ steps.set_image.outputs.IMAGE }} + RUNNER: ${{ steps.set_runner.outputs.RUNNER }} + steps: + - name: Checkout + uses: actions/checkout@v4 + - id: set_image + run: cat .github/env >> $GITHUB_OUTPUT + # By default, we use spot machines that can be terminated at any time. + # Merge queues use persistent runners to avoid kicking off from queue when the runner is terminated. + - id: set_runner + run: | + # Run merge queues on persistent runners + if [[ $GITHUB_REF_NAME == *"gh-readonly-queue"* ]]; then + echo "RUNNER=arc-runners-polkadot-sdk-beefy-persistent" >> $GITHUB_OUTPUT + else + echo "RUNNER=arc-runners-polkadot-sdk-beefy" >> $GITHUB_OUTPUT + fi + + # more information about this job can be found here: + # https://github.com/paritytech/substrate/pull/3778 + test-full-crypto-feature: + needs: [ set-image ] + runs-on: ${{ needs.set-image.outputs.RUNNER }} + timeout-minutes: 60 + container: + image: ${{ needs.set-image.outputs.IMAGE }} + env: + # Enable debug assertions since we are running optimized builds for testing + # but still want to have debug assertions. + RUSTFLAGS: "-C debug-assertions" + RUST_BACKTRACE: 1 + steps: + - name: Checkout + uses: actions/checkout@v4.1.7 + + - name: script + run: | + cd substrate/primitives/core/ + forklift cargo build --locked --no-default-features --features full_crypto + cd ../application-crypto + forklift cargo build --locked --no-default-features --features full_crypto + + test-frame-examples-compile-to-wasm: + timeout-minutes: 20 + # into one job + needs: [ set-image, test-full-crypto-feature ] + runs-on: ${{ needs.set-image.outputs.RUNNER }} + container: + image: ${{ needs.set-image.outputs.IMAGE }} + env: + # Enable debug assertions since we are running optimized builds for testing + # but still want to have debug assertions. + RUSTFLAGS: "-C debug-assertions" + RUST_BACKTRACE: 1 + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: script + run: | + cd substrate/frame/examples/offchain-worker/ + forklift cargo build --locked --target=wasm32-unknown-unknown --no-default-features + cd ../basic + forklift cargo build --locked --target=wasm32-unknown-unknown --no-default-features + + test-frame-ui: + timeout-minutes: 60 + needs: [ set-image, test-frame-examples-compile-to-wasm ] + runs-on: ${{ needs.set-image.outputs.RUNNER }} + container: + image: ${{ needs.set-image.outputs.IMAGE }} + env: + # Enable debug assertions since we are running optimized builds for testing + # but still want to have debug assertions. + RUSTFLAGS: "-C debug-assertions -D warnings" + RUST_BACKTRACE: 1 + WASM_BUILD_NO_COLOR: 1 + WASM_BUILD_RUSTFLAGS: "-C debug-assertions -D warnings" + # Ensure we run the UI tests. + RUN_UI_TESTS: 1 + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: script + run: | + forklift cargo test --locked -q --profile testnet -p frame-support-test --features=frame-feature-testing,no-metadata-docs,try-runtime,experimental + forklift cargo test --locked -q --profile testnet -p frame-support-test --features=frame-feature-testing,frame-feature-testing-2,no-metadata-docs,try-runtime,experimental + forklift cargo test --locked -q --profile testnet -p xcm-procedural + forklift cargo test --locked -q --profile testnet -p frame-election-provider-solution-type + forklift cargo test --locked -q --profile testnet -p sp-api-test + # There is multiple version of sp-runtime-interface in the repo. So we point to the manifest. + forklift cargo test --locked -q --profile testnet --manifest-path substrate/primitives/runtime-interface/Cargo.toml + + test-deterministic-wasm: + timeout-minutes: 20 + needs: [ set-image, test-frame-ui ] + runs-on: ${{ needs.set-image.outputs.RUNNER }} + container: + image: ${{ needs.set-image.outputs.IMAGE }} + env: + WASM_BUILD_NO_COLOR: 1 + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: script + run: | + # build runtime + forklift cargo build -q --locked --release -p westend-runtime -p rococo-runtime + # make checksum + sha256sum target/release/wbuild/*-runtime/target/wasm32-unknown-unknown/release/*.wasm > checksum.sha256 + cargo clean + # build again + forklift cargo build -q --locked --release -p westend-runtime -p rococo-runtime + # confirm checksum + sha256sum -c checksum.sha256 + + cargo-check-benches-branches: + needs: [ set-image ] + if: ${{ github.event_name == 'pull_request' }} + timeout-minutes: 60 + outputs: + branch: ${{ steps.branch.outputs.branch }} + runs-on: ubuntu-latest + steps: + - name: Branch + id: branch + run: | + echo "branch=['${{ github.base_ref }}', '${{ github.head_ref }}']" >> $GITHUB_OUTPUT + + cargo-check-benches: + needs: [ set-image, cargo-check-benches-branches ] + timeout-minutes: 60 + strategy: + matrix: + branch: ${{ fromJSON(needs.cargo-check-benches-branches.outputs.branch) }} + runs-on: ${{ needs.set-image.outputs.RUNNER }} + container: + image: ${{ needs.set-image.outputs.IMAGE }} + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ matrix.branch }} + + - name: script + run: | + ARTIFACTS_DIR=./artifacts + BENCH_TRIE_READ=::trie::read::small + BENCH_NODE_IMPORT=::node::import::sr25519::transfer_keep_alive::paritydb::small + mkdir -p $ARTIFACTS_DIR + + SKIP_WASM_BUILD=1 forklift cargo check --locked --benches --all; + forklift cargo run --locked --release -p node-bench -- $BENCH_TRIE_READ --json | tee $ARTIFACTS_DIR/bench_trie_read_small.json; + forklift cargo run --locked --release -p node-bench -- $BENCH_NODE_IMPORT --json | tee $ARTIFACTS_DIR/bench_transfer_keep_alive.json + + - name: Upload artifacts + uses: actions/upload-artifact@v4.3.6 + with: + path: ./artifacts + name: cargo-check-benches-${{ matrix.branch }}-${{ github.sha }} + retention-days: 1 + + node-bench-regression-guard: + timeout-minutes: 20 + runs-on: arc-runners-polkadot-sdk + needs: [ set-image, cargo-check-benches ] + steps: + - name: Checkout + uses: actions/checkout@v4.1.7 + + - name: Download artifact (master run) + uses: actions/download-artifact@v4.1.8 + with: + name: cargo-check-benches-${{ github.base_ref }}-${{ github.sha }} + path: ./artifacts/master + + - name: Download artifact (current run) + uses: actions/download-artifact@v4.1.8 + with: + name: cargo-check-benches-${{ github.head_ref }}-${{ github.sha }} + path: ./artifacts/current + + - name: script + id: compare + run: | + docker run --rm \ + -v $PWD/artifacts/master:/artifacts/master \ + -v $PWD/artifacts/current:/artifacts/current \ + paritytech/node-bench-regression-guard:latest \ + node-bench-regression-guard --reference /artifacts/master --compare-with /artifacts/current + + if [ $? -ne 0 ]; then + FAILED_MSG='### node-bench-regression-guard failed ❌, check the regression in *cargo-check-benches* job' + echo $FAILED_MSG + echo $FAILED_MSG >> $GITHUB_STEP_SUMMARY + exit 1 + else + echo "### node-bench-regression-guard passed ✅" >> $GITHUB_STEP_SUMMARY + fi + + test-node-metrics: + needs: [ set-image ] + timeout-minutes: 30 + runs-on: ${{ needs.set-image.outputs.RUNNER }} + container: + image: ${{ needs.set-image.outputs.IMAGE }} + steps: + - name: Checkout + uses: actions/checkout@v4.1.7 + + - name: Run tests + id: tests + env: + RUST_TOOLCHAIN: stable + # Enable debug assertions since we are running optimized builds for testing + # but still want to have debug assertions. + RUSTFLAGS: "-Cdebug-assertions=y -Dwarnings" + run: | + forklift cargo build --bin polkadot-execute-worker --bin polkadot-prepare-worker --profile testnet --verbose --locked + mkdir -p ./artifacts + forklift cargo test --profile testnet --locked --features=runtime-metrics -p polkadot-node-metrics > ./artifacts/log.txt + echo "Metrics test passed" + + - name: Upload artifacts if failed + if: ${{ steps.tests.outcome != 'success' }} + uses: actions/upload-artifact@v4.3.6 + with: + name: node-metrics-failed + path: ./artifacts + + # more information about this job can be found here: + # https://github.com/paritytech/substrate/pull/6916 + check-tracing: + timeout-minutes: 20 + needs: [ set-image, test-node-metrics ] + runs-on: ${{ needs.set-image.outputs.RUNNER }} + container: + image: ${{ needs.set-image.outputs.IMAGE }} + steps: + - name: Checkout + uses: actions/checkout@v4.1.7 + + - name: script + run: | + forklift cargo test --locked --manifest-path ./substrate/primitives/tracing/Cargo.toml --no-default-features + forklift cargo test --locked --manifest-path ./substrate/primitives/tracing/Cargo.toml --no-default-features --features=with-tracing + + check-metadata-hash: + timeout-minutes: 20 + needs: [ set-image, check-tracing ] + runs-on: ${{ needs.set-image.outputs.RUNNER }} + container: + image: ${{ needs.set-image.outputs.IMAGE }} + steps: + - name: Checkout + uses: actions/checkout@v4.1.7 + + - name: script + run: | + forklift cargo build --locked -p westend-runtime --features metadata-hash + + cargo-hfuzz: + timeout-minutes: 20 + needs: [ set-image, check-metadata-hash ] + runs-on: ${{ needs.set-image.outputs.RUNNER }} + container: + image: ${{ needs.set-image.outputs.IMAGE }} + env: + # max 10s per iteration, 60s per file + HFUZZ_RUN_ARGS: | + --exit_upon_crash + --exit_code_upon_crash 1 + --timeout 10 + --run_time 60 + + # use git version of honggfuzz-rs until v0.5.56 is out, we need a few recent changes: + # https://github.com/rust-fuzz/honggfuzz-rs/pull/75 to avoid breakage on debian + # https://github.com/rust-fuzz/honggfuzz-rs/pull/81 fix to the above pr + # https://github.com/rust-fuzz/honggfuzz-rs/pull/82 fix for handling absolute CARGO_TARGET_DIR + HFUZZ_BUILD_ARGS: | + --config=patch.crates-io.honggfuzz.git="https://github.com/altaua/honggfuzz-rs" + --config=patch.crates-io.honggfuzz.rev="205f7c8c059a0d98fe1cb912cdac84f324cb6981" + steps: + - name: Checkout + uses: actions/checkout@v4.1.7 + + - name: Run honggfuzz + run: | + cd substrate/primitives/arithmetic/fuzzer + forklift cargo hfuzz build + for target in $(cargo read-manifest | jq -r '.targets | .[] | .name'); + do + forklift cargo hfuzz run "$target" || { printf "fuzzing failure for %s\n" "$target"; exit 1; }; + done + + - name: Upload artifacts + uses: actions/upload-artifact@v4.3.6 + with: + path: substrate/primitives/arithmetic/fuzzer/hfuzz_workspace/ + name: hfuzz-${{ github.sha }} + + cargo-check-each-crate: + timeout-minutes: 140 + needs: [ set-image ] + runs-on: ${{ needs.set-image.outputs.RUNNER }} + container: + image: ${{ needs.set-image.outputs.IMAGE }} + env: + RUSTFLAGS: "-D warnings" + CI_JOB_NAME: cargo-check-each-crate + strategy: + matrix: + index: [ 1,2,3,4,5,6,7 ] # 7 parallel jobs + steps: + - name: Checkout + uses: actions/checkout@v4.1.7 + + - name: Check Rust + run: | + rustup show + rustup +nightly show + + - name: script + run: | + mkdir -p /github/home/.forklift + cp .forklift/config.toml /github/home/.forklift/config.toml + PYTHONUNBUFFERED=x .github/scripts/check-each-crate.py ${{ matrix.index }} ${{ strategy.job-total }} + + # TODO: enable when we have a macos Self-Hosted runners + # cargo-check-each-crate-macos: + # timeout-minutes: 120 + # needs: [ set-image ] + # runs-on: macos-latest + # env: + # RUSTFLAGS: "-D warnings" + # CI_JOB_NAME: cargo-check-each-crate + # IMAGE: ${{ needs.set-image.outputs.IMAGE }} + # strategy: + # fail-fast: false + # matrix: + # index: [ 1,2,3,4,5,6,7,8,9,10 ] # 10 parallel jobs + # steps: + # - name: Checkout + # uses: actions/checkout@v4.1.7 + # + # - run: | + # VERSION=$(echo $IMAGE | sed -E 's/.*:bullseye-([^-]+)-.*/\1/') + # echo $VERSION + # echo "VERSION=$VERSION" >> $GITHUB_ENV + # + # - run: | + # rustup install $VERSION + # rustup default $VERSION + # + # - name: Check Rust + # run: | + # rustup show + # rustup +nightly show + # + # - name: MacOS Deps + # run: | + # brew install protobuf openssl pkg-config zlib xz zstd llvm jq curl gcc make cmake + # rustup target add wasm32-unknown-unknown --toolchain $VERSION + # rustup component add rust-src rustfmt clippy --toolchain $VERSION + # + # - name: script + # run: | + # PYTHONUNBUFFERED=x .github/scripts/check-each-crate.py ${{ matrix.index }} ${{ strategy.job-total }} True + + confirm-required-test-misc-jobs-passed: + runs-on: ubuntu-latest + name: All test misc tests passed + # If any new job gets added, be sure to add it to this array + needs: + - test-full-crypto-feature + - test-frame-examples-compile-to-wasm + - test-frame-ui + - cargo-check-benches + - node-bench-regression-guard + - test-node-metrics + - check-tracing + - cargo-check-each-crate + # - cargo-hfuzz remove from required for now, as it's flaky + steps: + - run: echo '### Good job! All the required tests passed 🚀' >> $GITHUB_STEP_SUMMARY \ No newline at end of file diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 25761fb94fd..ed2ef07736b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -5,7 +5,7 @@ on: branches: - master pull_request: - types: [opened, synchronize, reopened, ready_for_review] + types: [ opened, synchronize, reopened, ready_for_review ] merge_group: concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} @@ -44,7 +44,7 @@ jobs: # This job runs all benchmarks defined in the `/bin/node/runtime` once to check that there are no errors. quick-benchmarks: - needs: [set-image] + needs: [ set-image ] # if: ${{ needs.changes.outputs.rust }} runs-on: ${{ needs.set-image.outputs.RUNNER }} timeout-minutes: 60 @@ -63,7 +63,7 @@ jobs: # cf https://github.com/paritytech/polkadot-sdk/issues/1652 test-syscalls: - needs: [set-image] + needs: [ set-image ] # if: ${{ needs.changes.outputs.rust }} runs-on: ${{ needs.set-image.outputs.RUNNER }} timeout-minutes: 60 @@ -87,8 +87,9 @@ jobs: run: | echo "The x86_64 syscalls used by the worker binaries have changed. Please review if this is expected and update polkadot/scripts/list-syscalls/*-worker-syscalls as needed." >> $GITHUB_STEP_SUMMARY + cargo-check-all-benches: - needs: [set-image] + needs: [ set-image ] # if: ${{ needs.changes.outputs.rust }} runs-on: ${{ needs.set-image.outputs.RUNNER }} timeout-minutes: 60 diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5b581c45fb8..8b4ca48150b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -21,7 +21,7 @@ workflow: - if: $CI_COMMIT_BRANCH variables: - CI_IMAGE: !reference [.ci-unified, variables, CI_IMAGE] + CI_IMAGE: !reference [ .ci-unified, variables, CI_IMAGE ] # BUILDAH_IMAGE is defined in group variables BUILDAH_COMMAND: "buildah --storage-driver overlay2" RELENG_SCRIPTS_BRANCH: "master" @@ -39,7 +39,7 @@ default: - runner_system_failure - unknown_failure - api_failure - cache: {} + cache: { } interruptible: true .collect-artifacts: @@ -68,8 +68,8 @@ default: .common-before-script: before_script: - - !reference [.job-switcher, before_script] - - !reference [.pipeline-stopper-vars, script] + - !reference [ .job-switcher, before_script ] + - !reference [ .pipeline-stopper-vars, script ] .job-switcher: before_script: @@ -78,8 +78,8 @@ default: .kubernetes-env: image: "${CI_IMAGE}" before_script: - - !reference [.common-before-script, before_script] - - !reference [.prepare-env, before_script] + - !reference [ .common-before-script, before_script ] + - !reference [ .prepare-env, before_script ] tags: - kubernetes-parity-build @@ -107,12 +107,12 @@ default: .docker-env: image: "${CI_IMAGE}" variables: - FL_FORKLIFT_VERSION: !reference [.forklift, variables, FL_FORKLIFT_VERSION] + FL_FORKLIFT_VERSION: !reference [ .forklift, variables, FL_FORKLIFT_VERSION ] before_script: - - !reference [.common-before-script, before_script] - - !reference [.prepare-env, before_script] - - !reference [.rust-info-script, script] - - !reference [.forklift-cache, before_script] + - !reference [ .common-before-script, before_script ] + - !reference [ .prepare-env, before_script ] + - !reference [ .rust-info-script, script ] + - !reference [ .forklift-cache, before_script ] tags: - linux-docker @@ -269,56 +269,6 @@ remove-cancel-pipeline-message: trigger: project: "parity/infrastructure/ci_cd/pipeline-stopper" -cancel-pipeline-cargo-check-benches1: - extends: .cancel-pipeline-template - needs: - - job: "cargo-check-benches 1/2" - -cancel-pipeline-cargo-check-benches2: - extends: .cancel-pipeline-template - needs: - - job: "cargo-check-benches 2/2" - -cancel-pipeline-cargo-check-each-crate-1: - extends: .cancel-pipeline-template - needs: - - job: "cargo-check-each-crate 1/6" - -cancel-pipeline-cargo-check-each-crate-2: - extends: .cancel-pipeline-template - needs: - - job: "cargo-check-each-crate 2/6" - -cancel-pipeline-cargo-check-each-crate-3: - extends: .cancel-pipeline-template - needs: - - job: "cargo-check-each-crate 3/6" - -cancel-pipeline-cargo-check-each-crate-4: - extends: .cancel-pipeline-template - needs: - - job: "cargo-check-each-crate 4/6" - -cancel-pipeline-cargo-check-each-crate-5: - extends: .cancel-pipeline-template - needs: - - job: "cargo-check-each-crate 5/6" - -cancel-pipeline-cargo-check-each-crate-6: - extends: .cancel-pipeline-template - needs: - - job: "cargo-check-each-crate 6/6" - -cancel-pipeline-cargo-check-each-crate-macos: - extends: .cancel-pipeline-template - needs: - - job: cargo-check-each-crate-macos - -cancel-pipeline-check-tracing: - extends: .cancel-pipeline-template - needs: - - job: check-tracing - cancel-pipeline-build-linux-stable: extends: .cancel-pipeline-template needs: @@ -334,21 +284,6 @@ cancel-pipeline-build-linux-substrate: needs: - job: build-linux-substrate -cancel-pipeline-test-node-metrics: - extends: .cancel-pipeline-template - needs: - - job: test-node-metrics - -cancel-pipeline-test-frame-ui: - extends: .cancel-pipeline-template - needs: - - job: test-frame-ui - -cancel-pipeline-test-frame-examples-compile-to-wasm: - extends: .cancel-pipeline-template - needs: - - job: test-frame-examples-compile-to-wasm - cancel-pipeline-build-short-benchmark: extends: .cancel-pipeline-template needs: diff --git a/.gitlab/pipeline/test.yml b/.gitlab/pipeline/test.yml index ca3a2394fb3..00a0aa2c977 100644 --- a/.gitlab/pipeline/test.yml +++ b/.gitlab/pipeline/test.yml @@ -137,159 +137,6 @@ test-rustdoc: script: - time cargo doc --workspace --all-features --no-deps -test-node-metrics: - stage: test - extends: - - .docker-env - - .common-refs - - .run-immediately - - .collect-artifacts-short - variables: - RUST_TOOLCHAIN: stable - # Enable debug assertions since we are running optimized builds for testing - # but still want to have debug assertions. - RUSTFLAGS: "-Cdebug-assertions=y -Dwarnings" - script: - # Build the required workers. - - cargo build --bin polkadot-execute-worker --bin polkadot-prepare-worker --profile testnet --verbose --locked - - mkdir -p artifacts - - time cargo test --profile testnet - --locked - --features=runtime-metrics -p polkadot-node-metrics > artifacts/log.txt - -test-deterministic-wasm: - stage: test - extends: - - .docker-env - - .common-refs - # DAG - needs: - - job: test-frame-ui - artifacts: false - script: - # build runtime - - WASM_BUILD_NO_COLOR=1 cargo build -q --locked --release -p westend-runtime -p rococo-runtime - # make checksum - - sha256sum target/release/wbuild/*-runtime/target/wasm32-unknown-unknown/release/*.wasm > checksum.sha256 - - cargo clean - # build again - - WASM_BUILD_NO_COLOR=1 cargo build -q --locked --release -p westend-runtime -p rococo-runtime - # confirm checksum - - sha256sum -c checksum.sha256 - -cargo-check-benches: - stage: test - artifacts: - expire_in: 10 days - variables: - CI_JOB_NAME: "cargo-check-benches" - extends: - - .docker-env - - .common-refs - - .run-immediately - - .collect-artifacts - - .pipeline-stopper-artifacts - before_script: - # TODO: DON'T FORGET TO CHANGE FOR PROD VALUES!!! - # merges in the master branch on PRs. skip if base is not master - - 'if [ $CI_COMMIT_REF_NAME != "master" ]; then - BASE=$(curl -s -H "Authorization: Bearer ${GITHUB_PR_TOKEN}" https://api.github.com/repos/paritytech-stg/polkadot-sdk/pulls/${CI_COMMIT_REF_NAME} | jq -r .base.ref); - printf "Merging base branch %s\n" "${BASE:=master}"; - if [ $BASE != "master" ]; then - echo "$BASE is not master, skipping merge"; - else - git config user.email "ci@gitlab.parity.io"; - git fetch origin "refs/heads/${BASE}"; - git merge --verbose --no-edit FETCH_HEAD; - fi - fi' - - !reference [.forklift-cache, before_script] - parallel: 2 - script: - - mkdir -p ./artifacts/benches/$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA - # this job is executed in parallel on two runners - - echo "___Running benchmarks___"; - - case ${CI_NODE_INDEX} in - 1) - SKIP_WASM_BUILD=1 time cargo check --locked --benches --all; - cargo run --locked --release -p node-bench -- ::trie::read::small --json - | tee ./artifacts/benches/$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA/::trie::read::small.json; - echo "___Cache could be uploaded___"; - ;; - 2) - cargo run --locked --release -p node-bench -- ::node::import::sr25519::transfer_keep_alive::paritydb::small --json - | tee ./artifacts/benches/$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA/::node::import::sr25519::transfer_keep_alive::paritydb::small.json - ;; - esac - -node-bench-regression-guard: - # it's not belong to `build` semantically, but dag jobs can't depend on each other - # within the single stage - https://gitlab.com/gitlab-org/gitlab/-/issues/30632 - # more: https://github.com/paritytech/substrate/pull/8519#discussion_r608012402 - stage: build - extends: - - .docker-env - - .common-refs - needs: - # this is a DAG - - job: cargo-check-benches - artifacts: true - # polls artifact from master to compare with current result - # need to specify both parallel jobs from master because of the bug - # https://gitlab.com/gitlab-org/gitlab/-/issues/39063 - - project: $CI_PROJECT_PATH - job: "cargo-check-benches 1/2" - ref: master - artifacts: true - - project: $CI_PROJECT_PATH - job: "cargo-check-benches 2/2" - ref: master - artifacts: true - variables: - CI_IMAGE: "paritytech/node-bench-regression-guard:latest" - before_script: [""] - script: - - if [ $(ls -la artifacts/benches/ | grep master | wc -l) == 0 ]; then - echo "Couldn't find master artifacts"; - exit 1; - fi - - echo "------- IMPORTANT -------" - - echo "node-bench-regression-guard depends on the results of a cargo-check-benches job" - - echo "In case of this job failure, check your pipeline's cargo-check-benches" - - "node-bench-regression-guard --reference artifacts/benches/master-* - --compare-with artifacts/benches/$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA" - after_script: [""] - -# if this fails run `bot update-ui` in the Pull Request or "./scripts/update-ui-tests.sh" locally -# see ./docs/contributor/CONTRIBUTING.md#ui-tests -test-frame-ui: - stage: test - extends: - - .docker-env - - .common-refs - # DAG - needs: - - job: test-frame-examples-compile-to-wasm - artifacts: false - variables: - # Enable debug assertions since we are running optimized builds for testing - # but still want to have debug assertions. - RUSTFLAGS: "-C debug-assertions -D warnings" - RUST_BACKTRACE: 1 - WASM_BUILD_NO_COLOR: 1 - WASM_BUILD_RUSTFLAGS: "-C debug-assertions -D warnings" - # Ensure we run the UI tests. - RUN_UI_TESTS: 1 - script: - - time cargo test --locked -q --profile testnet -p frame-support-test --features=frame-feature-testing,no-metadata-docs,try-runtime,experimental - - time cargo test --locked -q --profile testnet -p frame-support-test --features=frame-feature-testing,frame-feature-testing-2,no-metadata-docs,try-runtime,experimental - - time cargo test --locked -q --profile testnet -p xcm-procedural - - time cargo test --locked -q --profile testnet -p frame-election-provider-solution-type - - time cargo test --locked -q --profile testnet -p sp-api-test - # There is multiple version of sp-runtime-interface in the repo. So we point to the manifest. - - time cargo test --locked -q --profile testnet --manifest-path substrate/primitives/runtime-interface/Cargo.toml - - cat /cargo_target_dir/debug/.fingerprint/memory_units-759eddf317490d2b/lib-memory_units.json || true - quick-benchmarks-omni: stage: test extends: @@ -306,144 +153,3 @@ quick-benchmarks-omni: script: - time cargo build --locked --quiet --release -p asset-hub-westend-runtime --features runtime-benchmarks - time cargo run --locked --release -p frame-omni-bencher --quiet -- v1 benchmark pallet --runtime target/release/wbuild/asset-hub-westend-runtime/asset_hub_westend_runtime.compact.compressed.wasm --all --steps 2 --repeat 1 --quiet - -test-frame-examples-compile-to-wasm: - # into one job - stage: test - extends: - - .docker-env - - .common-refs - # DAG - needs: - - job: test-full-crypto-feature - artifacts: false - variables: - # Enable debug assertions since we are running optimized builds for testing - # but still want to have debug assertions. - RUSTFLAGS: "-C debug-assertions" - RUST_BACKTRACE: 1 - script: - - cd ./substrate/frame/examples/offchain-worker/ - - cargo build --locked --target=wasm32-unknown-unknown --no-default-features - - cd ../basic - - cargo build --locked --target=wasm32-unknown-unknown --no-default-features - # FIXME - allow_failure: true - -# more information about this job can be found here: -# https://github.com/paritytech/substrate/pull/6916 -check-tracing: - stage: test - extends: - - .docker-env - - .common-refs - - .run-immediately - - .pipeline-stopper-artifacts - script: - # with-tracing must be explicitly activated, we run a test to ensure this works as expected in both cases - - time cargo test --locked --manifest-path ./substrate/primitives/tracing/Cargo.toml --no-default-features - - time cargo test --locked --manifest-path ./substrate/primitives/tracing/Cargo.toml --no-default-features --features=with-tracing - -# Check that `westend-runtime` compiles with the `metadata-hash` feature enabled. -check-metadata-hash: - stage: test - extends: - - .docker-env - - .common-refs - - .run-immediately - - .pipeline-stopper-artifacts - script: - - time cargo build --locked -p westend-runtime --features metadata-hash - -# more information about this job can be found here: -# https://github.com/paritytech/substrate/pull/3778 -test-full-crypto-feature: - stage: test - extends: - - .docker-env - - .common-refs - - .run-immediately - variables: - # Enable debug assertions since we are running optimized builds for testing - # but still want to have debug assertions. - RUSTFLAGS: "-C debug-assertions" - RUST_BACKTRACE: 1 - script: - - cd substrate/primitives/core/ - - time cargo build --locked --no-default-features --features full_crypto - - cd ../application-crypto - - time cargo build --locked --no-default-features --features full_crypto - -cargo-check-each-crate: - stage: test - extends: - - .docker-env - - .common-refs - - .run-immediately - # - .collect-artifacts - variables: - RUSTFLAGS: "-D warnings" - # $CI_JOB_NAME is set manually so that cache could be shared for all jobs - # "cargo-check-each-crate I/N" jobs - CI_JOB_NAME: cargo-check-each-crate - timeout: 2h - script: - - PYTHONUNBUFFERED=x time .gitlab/check-each-crate.py "$CI_NODE_INDEX" "$CI_NODE_TOTAL" - parallel: 6 - -cargo-check-each-crate-macos: - stage: test - extends: - - .docker-env - - .common-refs - - .run-immediately - # - .collect-artifacts - before_script: - # skip timestamp script, the osx bash doesn't support printf %()T - - !reference [.job-switcher, before_script] - - !reference [.rust-info-script, script] - - !reference [.pipeline-stopper-vars, script] - variables: - SKIP_WASM_BUILD: 1 - script: - # TODO: use parallel jobs, as per cargo-check-each-crate, once more Mac runners are available - # - time ./scripts/ci/gitlab/check-each-crate.py 1 1 - - time cargo check --workspace --locked - timeout: 2h - tags: - - osx - -cargo-hfuzz: - stage: test - extends: - - .docker-env - - .common-refs - # DAG - needs: - - job: check-tracing - artifacts: false - variables: - # max 10s per iteration, 60s per file - HFUZZ_RUN_ARGS: > - --exit_upon_crash - --exit_code_upon_crash 1 - --timeout 10 - --run_time 60 - # use git version of honggfuzz-rs until v0.5.56 is out, we need a few recent changes: - # https://github.com/rust-fuzz/honggfuzz-rs/pull/75 to avoid breakage on debian - # https://github.com/rust-fuzz/honggfuzz-rs/pull/81 fix to the above pr - # https://github.com/rust-fuzz/honggfuzz-rs/pull/82 fix for handling absolute CARGO_TARGET_DIR - HFUZZ_BUILD_ARGS: > - --config=patch.crates-io.honggfuzz.git="https://github.com/altaua/honggfuzz-rs" - --config=patch.crates-io.honggfuzz.rev="205f7c8c059a0d98fe1cb912cdac84f324cb6981" - artifacts: - name: "hfuzz-$CI_COMMIT_SHORT_SHA" - expire_in: 7 days - when: on_failure - paths: - - substrate/primitives/arithmetic/fuzzer/hfuzz_workspace/ - script: - - cd ./substrate/primitives/arithmetic/fuzzer - - cargo hfuzz build - - for target in $(cargo read-manifest | jq -r '.targets | .[] | .name'); do - cargo hfuzz run "$target" || { printf "fuzzing failure for %s\n" "$target"; exit 1; }; done -- GitLab From b9b34fb983dac58ae05b0e1379e20363f6f7c88e Mon Sep 17 00:00:00 2001 From: Evgeny Snitko Date: Thu, 5 Sep 2024 19:27:18 +0400 Subject: [PATCH 010/125] Github actions coverage (#5148) Jobs for coverage collections and upload to codecov for github PR's Uses same test suit as test-linux-stable, splits tests into 5 parallel jobs, uploads to codecov, generates report comment and status checks (can be made required) | | | | --- | --- | | image | image | Codecov behavior (required coverage, thresholds, comment info etc.) is highly customizable via `.github/codecov.yaml` ([reference](https://docs.codecov.com/docs/codecovyml-reference)) Unfortunately, some tests are excluded because with `-C instrument-coverage` they run very slowly, flaky, or fail (see [nextest filter expression](https://github.com/paritytech/polkadot-sdk/pull/5148/files#diff-b19504a9520a2498d03020108344d8e6d93d254d812bfa26247faaa7f55263d6R80) of test-linux-stable-coverage). So for now, this workflow is optional, and will only run for pr's with the `GHA-coverage` label --- .github/codecov.yml | 8 +- .../workflows/tests-linux-stable-coverage.yml | 143 ++++++++++++++++++ substrate/bin/node/runtime/src/lib.rs | 7 +- substrate/frame/babe/src/mock.rs | 6 +- substrate/frame/grandpa/src/mock.rs | 6 +- 5 files changed, 159 insertions(+), 11 deletions(-) create mode 100644 .github/workflows/tests-linux-stable-coverage.yml diff --git a/.github/codecov.yml b/.github/codecov.yml index ceceb9e6365..b237c9fe6b0 100644 --- a/.github/codecov.yml +++ b/.github/codecov.yml @@ -6,4 +6,10 @@ coverage: project: default: target: 1.0 - threshold: 2.0 \ No newline at end of file + threshold: 2.0 + +comment: + behavior: new + +fixes: + - "/__w/polkadot-sdk/polkadot-sdk/::" \ No newline at end of file diff --git a/.github/workflows/tests-linux-stable-coverage.yml b/.github/workflows/tests-linux-stable-coverage.yml new file mode 100644 index 00000000000..ddf0642a404 --- /dev/null +++ b/.github/workflows/tests-linux-stable-coverage.yml @@ -0,0 +1,143 @@ +# GHA for test-linux-stable-int, test-linux-stable, test-linux-stable-oldkernel +name: tests linux stable coverage + +on: + push: + branches: + - master + pull_request: + types: [opened, synchronize, reopened, ready_for_review, labeled] + merge_group: +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + + set-image: + # GitHub Actions allows using 'env' in a container context. + # However, env variables don't work for forks: https://github.com/orgs/community/discussions/44322 + # This workaround sets the container image for each job using 'set-image' job output. + if: contains(github.event.label.name, 'GHA-coverage') || contains(github.event.pull_request.labels.*.name, 'GHA-coverage') + runs-on: ubuntu-latest + outputs: + IMAGE: ${{ steps.set_image.outputs.IMAGE }} + RUNNER: ${{ steps.set_runner.outputs.RUNNER }} + steps: + - name: Checkout + uses: actions/checkout@v4 + - id: set_image + run: cat .github/env >> $GITHUB_OUTPUT + - id: set_runner + run: | + # Run merge queues on persistent runners + if [[ $GITHUB_REF_NAME == *"gh-readonly-queue"* ]]; then + echo "RUNNER=arc-runners-polkadot-sdk-beefy-persistent" >> $GITHUB_OUTPUT + else + echo "RUNNER=arc-runners-polkadot-sdk-beefy" >> $GITHUB_OUTPUT + fi + + # + # + # + test-linux-stable-coverage: + needs: [set-image] + runs-on: ${{ needs.set-image.outputs.RUNNER }} + timeout-minutes: 120 + container: + image: ${{ needs.set-image.outputs.IMAGE }} + env: + RUST_TOOLCHAIN: stable + # Enable debug assertions since we are running optimized builds for testing + # but still want to have debug assertions. + # + # -Cinstrument-coverage slows everything down but it is necessary for code coverage + # https://doc.rust-lang.org/rustc/instrument-coverage.html + RUSTFLAGS: "-Cdebug-assertions=y -Dwarnings -Cinstrument-coverage" + LLVM_PROFILE_FILE: "/__w/polkadot-sdk/polkadot-sdk/target/coverage/cargo-test-${{ matrix.ci_node_index }}-%p-%m.profraw" + strategy: + fail-fast: false + matrix: + ci_node_index: [1, 2, 3, 4, 5] + ci_node_total: [5] + steps: + - name: Checkout + uses: actions/checkout@v4 + + - run: rustup component add llvm-tools-preview + - run: cargo install cargo-llvm-cov + + - run: mkdir -p target/coverage + + # Some tests are excluded because they run very slowly or fail with -Cinstrument-coverage + - name: run tests + run: > + time cargo llvm-cov nextest + --no-report --release + --workspace + --locked --no-fail-fast + --features try-runtime,ci-only-tests,experimental,riscv + --filter-expr " + !test(/.*benchmark.*/) + - test(/recovers_from_only_chunks_if_pov_large::case_1/) + - test(/participation_requests_reprioritized_for_newly_included/) + - test(/availability_is_recovered_from_chunks_if_no_group_provided::case_1/) + - test(/rejects_missing_inherent_digest/) + - test(/availability_is_recovered_from_chunks_even_if_backing_group_supplied_if_chunks_only::case_1/) + - test(/availability_is_recovered_from_chunks_if_no_group_provided::case_2/) + - test(/all_security_features_work/) + - test(/nonexistent_cache_dir/) + - test(/recovers_from_only_chunks_if_pov_large::case_3/) + - test(/recovers_from_only_chunks_if_pov_large::case_2/) + - test(/authoring_blocks/) + - test(/rejects_missing_seals/) + - test(/generate_chain_spec/) + - test(/get_preset/) + - test(/list_presets/) + - test(/tests::receive_rate_limit_is_enforced/) + - test(/polkadot-availability-recovery/) + " + --partition count:${{ matrix.ci_node_index }}/${{ matrix.ci_node_total }} + + - name: generate report + run: cargo llvm-cov report --release --codecov --output-path coverage-${{ matrix.ci_node_index }}.lcov + - name: upload report + uses: actions/upload-artifact@v4 + with: + name: coverage-report-${{ matrix.ci_node_index }}.lcov + path: coverage-${{ matrix.ci_node_index }}.lcov + + # + # + # Upload to codecov + upload-reports: + needs: [test-linux-stable-coverage] + runs-on: ubuntu-latest + steps: + - name: Download artifacts + uses: actions/download-artifact@v4 + with: + path: reports + pattern: coverage-report-* + merge-multiple: true + - run: ls -al reports/ + - name: Upload to Codecov + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} + verbose: true + directory: reports + root_dir: /__w/polkadot-sdk/polkadot-sdk/ + + # + # + # + remove-label: + runs-on: ubuntu-latest + needs: [upload-reports] + if: github.event_name == 'pull_request' + steps: + - uses: actions/checkout@v2 + - uses: actions-ecosystem/action-remove-labels@v1 + with: + labels: GHA-coverage \ No newline at end of file diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index 001b2273c9b..6ae04902aa8 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -507,8 +507,7 @@ impl pallet_babe::Config for Runtime { type WeightInfo = (); type MaxAuthorities = MaxAuthorities; type MaxNominators = MaxNominators; - type KeyOwnerProof = - >::Proof; + type KeyOwnerProof = sp_session::MembershipProof; type EquivocationReportSystem = pallet_babe::EquivocationReportSystem; } @@ -1534,7 +1533,7 @@ impl pallet_grandpa::Config for Runtime { type MaxAuthorities = MaxAuthorities; type MaxNominators = MaxNominators; type MaxSetIdSessionEntries = MaxSetIdSessionEntries; - type KeyOwnerProof = >::Proof; + type KeyOwnerProof = sp_session::MembershipProof; type EquivocationReportSystem = pallet_grandpa::EquivocationReportSystem; } @@ -2614,7 +2613,7 @@ impl pallet_beefy::Config for Runtime { type OnNewValidatorSet = MmrLeaf; type AncestryHelper = MmrLeaf; type WeightInfo = (); - type KeyOwnerProof = >::Proof; + type KeyOwnerProof = sp_session::MembershipProof; type EquivocationReportSystem = pallet_beefy::EquivocationReportSystem; } diff --git a/substrate/frame/babe/src/mock.rs b/substrate/frame/babe/src/mock.rs index 912cb3e27cd..4e4052b2b56 100644 --- a/substrate/frame/babe/src/mock.rs +++ b/substrate/frame/babe/src/mock.rs @@ -25,12 +25,12 @@ use frame_election_provider_support::{ }; use frame_support::{ derive_impl, parameter_types, - traits::{ConstU128, ConstU32, ConstU64, KeyOwnerProofSystem, OnInitialize}, + traits::{ConstU128, ConstU32, ConstU64, OnInitialize}, }; use pallet_session::historical as pallet_session_historical; use sp_consensus_babe::{AuthorityId, AuthorityPair, Randomness, Slot, VrfSignature}; use sp_core::{ - crypto::{KeyTypeId, Pair, VrfSecret}, + crypto::{Pair, VrfSecret}, U256, }; use sp_io; @@ -182,7 +182,7 @@ impl Config for Test { type WeightInfo = (); type MaxAuthorities = ConstU32<10>; type MaxNominators = ConstU32<100>; - type KeyOwnerProof = >::Proof; + type KeyOwnerProof = sp_session::MembershipProof; type EquivocationReportSystem = super::EquivocationReportSystem; } diff --git a/substrate/frame/grandpa/src/mock.rs b/substrate/frame/grandpa/src/mock.rs index ae230a0209a..caac4107cfb 100644 --- a/substrate/frame/grandpa/src/mock.rs +++ b/substrate/frame/grandpa/src/mock.rs @@ -28,11 +28,11 @@ use frame_election_provider_support::{ }; use frame_support::{ derive_impl, parameter_types, - traits::{ConstU128, ConstU32, ConstU64, KeyOwnerProofSystem, OnFinalize, OnInitialize}, + traits::{ConstU128, ConstU32, ConstU64, OnFinalize, OnInitialize}, }; use pallet_session::historical as pallet_session_historical; use sp_consensus_grandpa::{RoundNumber, SetId, GRANDPA_ENGINE_ID}; -use sp_core::{crypto::KeyTypeId, H256}; +use sp_core::H256; use sp_keyring::Ed25519Keyring; use sp_runtime::{ curve::PiecewiseLinear, @@ -186,7 +186,7 @@ impl Config for Test { type MaxAuthorities = ConstU32<100>; type MaxNominators = ConstU32<1000>; type MaxSetIdSessionEntries = MaxSetIdSessionEntries; - type KeyOwnerProof = >::Proof; + type KeyOwnerProof = sp_session::MembershipProof; type EquivocationReportSystem = super::EquivocationReportSystem; } -- GitLab From 702a15cbaa032899f2321fda892faf723d32efca Mon Sep 17 00:00:00 2001 From: Maksym H <1177472+mordamax@users.noreply.github.com> Date: Thu, 5 Sep 2024 17:02:24 +0100 Subject: [PATCH 011/125] minor fixes pipeline (#5607) - [return macos jobs to gitlab](https://github.com/paritytech/polkadot-sdk/commit/dcd44b1d8bb681b66cbc0a063a6a999bd8253cdc) - [add benches to merge queue](https://github.com/paritytech/polkadot-sdk/commit/494eb21bb9ac4633f3217e6b58ba7256aea6e38a) - [require test-deterministic-wasm and run it earlier](https://github.com/paritytech/polkadot-sdk/commit/ab9ae5ca6c5128e002cc745d608e542138633250) --- .github/workflows/tests-misc.yml | 69 +++++++++++++------------------- .gitlab-ci.yml | 5 +++ .gitlab/pipeline/test.yml | 22 ++++++++++ 3 files changed, 54 insertions(+), 42 deletions(-) diff --git a/.github/workflows/tests-misc.yml b/.github/workflows/tests-misc.yml index 824e8c11c2a..2e78f4a34ed 100644 --- a/.github/workflows/tests-misc.yml +++ b/.github/workflows/tests-misc.yml @@ -121,7 +121,7 @@ jobs: test-deterministic-wasm: timeout-minutes: 20 - needs: [ set-image, test-frame-ui ] + needs: [ set-image ] runs-on: ${{ needs.set-image.outputs.RUNNER }} container: image: ${{ needs.set-image.outputs.IMAGE }} @@ -144,7 +144,7 @@ jobs: cargo-check-benches-branches: needs: [ set-image ] - if: ${{ github.event_name == 'pull_request' }} + if: ${{ github.event_name == 'pull_request' || github.event_name == 'merge_group' }} timeout-minutes: 60 outputs: branch: ${{ steps.branch.outputs.branch }} @@ -354,46 +354,30 @@ jobs: cp .forklift/config.toml /github/home/.forklift/config.toml PYTHONUNBUFFERED=x .github/scripts/check-each-crate.py ${{ matrix.index }} ${{ strategy.job-total }} - # TODO: enable when we have a macos Self-Hosted runners - # cargo-check-each-crate-macos: - # timeout-minutes: 120 - # needs: [ set-image ] - # runs-on: macos-latest - # env: - # RUSTFLAGS: "-D warnings" - # CI_JOB_NAME: cargo-check-each-crate - # IMAGE: ${{ needs.set-image.outputs.IMAGE }} - # strategy: - # fail-fast: false - # matrix: - # index: [ 1,2,3,4,5,6,7,8,9,10 ] # 10 parallel jobs - # steps: - # - name: Checkout - # uses: actions/checkout@v4.1.7 - # - # - run: | - # VERSION=$(echo $IMAGE | sed -E 's/.*:bullseye-([^-]+)-.*/\1/') - # echo $VERSION - # echo "VERSION=$VERSION" >> $GITHUB_ENV - # - # - run: | - # rustup install $VERSION - # rustup default $VERSION - # - # - name: Check Rust - # run: | - # rustup show - # rustup +nightly show - # - # - name: MacOS Deps - # run: | - # brew install protobuf openssl pkg-config zlib xz zstd llvm jq curl gcc make cmake - # rustup target add wasm32-unknown-unknown --toolchain $VERSION - # rustup component add rust-src rustfmt clippy --toolchain $VERSION - # - # - name: script - # run: | - # PYTHONUNBUFFERED=x .github/scripts/check-each-crate.py ${{ matrix.index }} ${{ strategy.job-total }} True + # cargo-check-each-crate-macos: + # timeout-minutes: 120 + # needs: [ set-image ] + # runs-on: macOS + # env: + # RUSTFLAGS: "-D warnings" + # CI_JOB_NAME: cargo-check-each-crate + # IMAGE: ${{ needs.set-image.outputs.IMAGE }} + # strategy: + # fail-fast: false + # matrix: + # index: [ 1,2,3,4,5,6,7,8,9,10 ] # 10 parallel jobs + # steps: + # - name: Checkout + # uses: actions/checkout@v4.1.7 + + # - name: Install dependencies + # uses: ./.github/actions/set-up-mac + # with: + # IMAGE: ${{ needs.set-image.outputs.IMAGE }} + + # - name: script + # run: | + # PYTHONUNBUFFERED=x .github/scripts/check-each-crate.py ${{ matrix.index }} ${{ strategy.job-total }} True confirm-required-test-misc-jobs-passed: runs-on: ubuntu-latest @@ -408,6 +392,7 @@ jobs: - test-node-metrics - check-tracing - cargo-check-each-crate + - test-deterministic-wasm # - cargo-hfuzz remove from required for now, as it's flaky steps: - run: echo '### Good job! All the required tests passed 🚀' >> $GITHUB_STEP_SUMMARY \ No newline at end of file diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8b4ca48150b..43123cdbfc4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -288,3 +288,8 @@ cancel-pipeline-build-short-benchmark: extends: .cancel-pipeline-template needs: - job: build-short-benchmark + +cancel-pipeline-cargo-check-each-crate-macos: + extends: .cancel-pipeline-template + needs: + - job: cargo-check-each-crate-macos \ No newline at end of file diff --git a/.gitlab/pipeline/test.yml b/.gitlab/pipeline/test.yml index 00a0aa2c977..0879870ae13 100644 --- a/.gitlab/pipeline/test.yml +++ b/.gitlab/pipeline/test.yml @@ -153,3 +153,25 @@ quick-benchmarks-omni: script: - time cargo build --locked --quiet --release -p asset-hub-westend-runtime --features runtime-benchmarks - time cargo run --locked --release -p frame-omni-bencher --quiet -- v1 benchmark pallet --runtime target/release/wbuild/asset-hub-westend-runtime/asset_hub_westend_runtime.compact.compressed.wasm --all --steps 2 --repeat 1 --quiet + +cargo-check-each-crate-macos: + stage: test + extends: + - .docker-env + - .common-refs + - .run-immediately + # - .collect-artifacts + before_script: + # skip timestamp script, the osx bash doesn't support printf %()T + - !reference [.job-switcher, before_script] + - !reference [.rust-info-script, script] + - !reference [.pipeline-stopper-vars, script] + variables: + SKIP_WASM_BUILD: 1 + script: + # TODO: use parallel jobs, as per cargo-check-each-crate, once more Mac runners are available + # - time ./scripts/ci/gitlab/check-each-crate.py 1 1 + - time cargo check --workspace --locked + timeout: 2h + tags: + - osx -- GitLab From 8d81f1e648a21d7d14f94bc86503d3c77ead5807 Mon Sep 17 00:00:00 2001 From: Maksym H <1177472+mordamax@users.noreply.github.com> Date: Thu, 5 Sep 2024 17:07:14 +0100 Subject: [PATCH 012/125] /cmd followups (#5533) Closes: https://github.com/paritytech/polkadot-sdk/issues/5545 - add missing template for frame & xcm benchmarks - fix `git pull` -> https://github.com/paritytech/polkadot-sdk/actions/runs/10644887539/job/29510118915 - respect runtimes headers - use GNU instead of apache for runtimes - adds tests for cmd.py Tip: review this one with Whitespace hidden ![image](https://github.com/user-attachments/assets/3bcdc6c2-7371-428f-9962-556ca81c1467) --------- Co-authored-by: GitHub Action --- .github/scripts/cmd/cmd.py | 275 +++++++++++--------- .github/scripts/cmd/test_cmd.py | 321 ++++++++++++++++++++++++ .github/workflows/cmd-tests.yml | 14 ++ .github/workflows/cmd.yml | 2 +- .github/workflows/runtimes-matrix.json | 29 +++ substrate/frame/balances/src/weights.rs | 138 +++++----- 6 files changed, 584 insertions(+), 195 deletions(-) create mode 100644 .github/scripts/cmd/test_cmd.py create mode 100644 .github/workflows/cmd-tests.yml diff --git a/.github/scripts/cmd/cmd.py b/.github/scripts/cmd/cmd.py index 63bd6a2795a..1c08b621467 100755 --- a/.github/scripts/cmd/cmd.py +++ b/.github/scripts/cmd/cmd.py @@ -11,6 +11,8 @@ _HelpAction = _help._HelpAction f = open('.github/workflows/runtimes-matrix.json', 'r') runtimesMatrix = json.load(f) +print(f'runtimesMatrix: {runtimesMatrix}\n') + runtimeNames = list(map(lambda x: x['name'], runtimesMatrix)) common_args = { @@ -67,130 +69,153 @@ parser_ui = subparsers.add_parser('update-ui', help='Updates UI tests') for arg, config in common_args.items(): parser_ui.add_argument(arg, **config) +def main(): + global args, unknown, runtimesMatrix + args, unknown = parser.parse_known_args() + + print(f'args: {args}') + + if args.command == 'bench': + runtime_pallets_map = {} + failed_benchmarks = {} + successful_benchmarks = {} + + profile = "release" + + print(f'Provided runtimes: {args.runtime}') + # convert to mapped dict + runtimesMatrix = list(filter(lambda x: x['name'] in args.runtime, runtimesMatrix)) + runtimesMatrix = {x['name']: x for x in runtimesMatrix} + print(f'Filtered out runtimes: {runtimesMatrix}') + + # loop over remaining runtimes to collect available pallets + for runtime in runtimesMatrix.values(): + os.system(f"forklift cargo build -p {runtime['package']} --profile {profile} --features runtime-benchmarks") + print(f'-- listing pallets for benchmark for {runtime["name"]}') + wasm_file = f"target/{profile}/wbuild/{runtime['package']}/{runtime['package'].replace('-', '_')}.wasm" + output = os.popen( + f"frame-omni-bencher v1 benchmark pallet --no-csv-header --no-storage-info --no-min-squares --no-median-slopes --all --list --runtime={wasm_file}").read() + raw_pallets = output.strip().split('\n') + + all_pallets = set() + for pallet in raw_pallets: + if pallet: + all_pallets.add(pallet.split(',')[0].strip()) + + pallets = list(all_pallets) + print(f'Pallets in {runtime["name"]}: {pallets}') + runtime_pallets_map[runtime['name']] = pallets + + print(f'\n') + + # filter out only the specified pallets from collected runtimes/pallets + if args.pallet: + print(f'Pallets: {args.pallet}') + new_pallets_map = {} + # keep only specified pallets if they exist in the runtime + for runtime in runtime_pallets_map: + if set(args.pallet).issubset(set(runtime_pallets_map[runtime])): + new_pallets_map[runtime] = args.pallet + + runtime_pallets_map = new_pallets_map + + print(f'Filtered out runtimes & pallets: {runtime_pallets_map}\n') + + if not runtime_pallets_map: + if args.pallet and not args.runtime: + print(f"No pallets {args.pallet} found in any runtime") + elif args.runtime and not args.pallet: + print(f"{args.runtime} runtime does not have any pallets") + elif args.runtime and args.pallet: + print(f"No pallets {args.pallet} found in {args.runtime}") + else: + print('No runtimes found') + sys.exit(1) -args, unknown = parser.parse_known_args() - -print(f'args: {args}') - -if args.command == 'bench': - runtime_pallets_map = {} - failed_benchmarks = {} - successful_benchmarks = {} - - profile = "release" - - print(f'Provided runtimes: {args.runtime}') - # convert to mapped dict - runtimesMatrix = list(filter(lambda x: x['name'] in args.runtime, runtimesMatrix)) - runtimesMatrix = {x['name']: x for x in runtimesMatrix} - print(f'Filtered out runtimes: {runtimesMatrix}') - - # loop over remaining runtimes to collect available pallets - for runtime in runtimesMatrix.values(): - os.system(f"forklift cargo build -p {runtime['package']} --profile {profile} --features runtime-benchmarks") - print(f'-- listing pallets for benchmark for {runtime["name"]}') - wasm_file = f"target/{profile}/wbuild/{runtime['package']}/{runtime['package'].replace('-', '_')}.wasm" - output = os.popen( - f"frame-omni-bencher v1 benchmark pallet --no-csv-header --no-storage-info --no-min-squares --no-median-slopes --all --list --runtime={wasm_file}").read() - raw_pallets = output.strip().split('\n') - - all_pallets = set() - for pallet in raw_pallets: - if pallet: - all_pallets.add(pallet.split(',')[0].strip()) - - pallets = list(all_pallets) - print(f'Pallets in {runtime}: {pallets}') - runtime_pallets_map[runtime['name']] = pallets - - # filter out only the specified pallets from collected runtimes/pallets - if args.pallet: - print(f'Pallet: {args.pallet}') - new_pallets_map = {} - # keep only specified pallets if they exist in the runtime for runtime in runtime_pallets_map: - if set(args.pallet).issubset(set(runtime_pallets_map[runtime])): - new_pallets_map[runtime] = args.pallet - - runtime_pallets_map = new_pallets_map - - print(f'Filtered out runtimes & pallets: {runtime_pallets_map}') - - if not runtime_pallets_map: - if args.pallet and not args.runtime: - print(f"No pallets {args.pallet} found in any runtime") - elif args.runtime and not args.pallet: - print(f"{args.runtime} runtime does not have any pallets") - elif args.runtime and args.pallet: - print(f"No pallets {args.pallet} found in {args.runtime}") - else: - print('No runtimes found') - sys.exit(1) - - header_path = os.path.abspath('./substrate/HEADER-APACHE2') - - for runtime in runtime_pallets_map: - for pallet in runtime_pallets_map[runtime]: - config = runtimesMatrix[runtime] - print(f'-- config: {config}') - if runtime == 'dev': - # to support sub-modules (https://github.com/paritytech/command-bot/issues/275) - search_manifest_path = f"cargo metadata --locked --format-version 1 --no-deps | jq -r '.packages[] | select(.name == \"{pallet.replace('_', '-')}\") | .manifest_path'" - print(f'-- running: {search_manifest_path}') - manifest_path = os.popen(search_manifest_path).read() - if not manifest_path: - print(f'-- pallet {pallet} not found in dev runtime') - exit(1) - package_dir = os.path.dirname(manifest_path) - print(f'-- package_dir: {package_dir}') - print(f'-- manifest_path: {manifest_path}') - output_path = os.path.join(package_dir, "src", "weights.rs") - else: - default_path = f"./{config['path']}/src/weights" - xcm_path = f"./{config['path']}/src/weights/xcm" - output_path = default_path if not pallet.startswith("pallet_xcm_benchmarks") else xcm_path - print(f'-- benchmarking {pallet} in {runtime} into {output_path}') - cmd = f"frame-omni-bencher v1 benchmark pallet --extrinsic=* --runtime=target/{profile}/wbuild/{config['package']}/{config['package'].replace('-', '_')}.wasm --pallet={pallet} --header={header_path} --output={output_path} --wasm-execution=compiled --steps=50 --repeat=20 --heap-pages=4096 --no-storage-info --no-min-squares --no-median-slopes" - print(f'-- Running: {cmd}') - status = os.system(cmd) - if status != 0 and not args.continue_on_fail: - print(f'Failed to benchmark {pallet} in {runtime}') - sys.exit(1) - - # Otherwise collect failed benchmarks and print them at the end - # push failed pallets to failed_benchmarks - if status != 0: - failed_benchmarks[f'{runtime}'] = failed_benchmarks.get(f'{runtime}', []) + [pallet] - else: - successful_benchmarks[f'{runtime}'] = successful_benchmarks.get(f'{runtime}', []) + [pallet] - - if failed_benchmarks: - print('❌ Failed benchmarks of runtimes/pallets:') - for runtime, pallets in failed_benchmarks.items(): - print(f'-- {runtime}: {pallets}') - - if successful_benchmarks: - print('✅ Successful benchmarks of runtimes/pallets:') - for runtime, pallets in successful_benchmarks.items(): - print(f'-- {runtime}: {pallets}') - -elif args.command == 'fmt': - command = f"cargo +nightly fmt" - print(f'Formatting with `{command}`') - nightly_status = os.system(f'{command}') - taplo_status = os.system('taplo format --config .config/taplo.toml') - - if (nightly_status != 0 or taplo_status != 0) and not args.continue_on_fail: - print('❌ Failed to format code') - sys.exit(1) - -elif args.command == 'update-ui': - command = 'sh ./scripts/update-ui-tests.sh' - print(f'Updating ui with `{command}`') - status = os.system(f'{command}') - - if status != 0 and not args.continue_on_fail: - print('❌ Failed to format code') - sys.exit(1) - -print('🚀 Done') + for pallet in runtime_pallets_map[runtime]: + config = runtimesMatrix[runtime] + header_path = os.path.abspath(config['header']) + template = None + + print(f'-- config: {config}') + if runtime == 'dev': + # to support sub-modules (https://github.com/paritytech/command-bot/issues/275) + search_manifest_path = f"cargo metadata --locked --format-version 1 --no-deps | jq -r '.packages[] | select(.name == \"{pallet.replace('_', '-')}\") | .manifest_path'" + print(f'-- running: {search_manifest_path}') + manifest_path = os.popen(search_manifest_path).read() + if not manifest_path: + print(f'-- pallet {pallet} not found in dev runtime') + exit(1) + package_dir = os.path.dirname(manifest_path) + print(f'-- package_dir: {package_dir}') + print(f'-- manifest_path: {manifest_path}') + output_path = os.path.join(package_dir, "src", "weights.rs") + template = config['template'] + else: + default_path = f"./{config['path']}/src/weights" + xcm_path = f"./{config['path']}/src/weights/xcm" + output_path = default_path + if pallet.startswith("pallet_xcm_benchmarks"): + template = config['template'] + output_path = xcm_path + + print(f'-- benchmarking {pallet} in {runtime} into {output_path}') + cmd = f"frame-omni-bencher v1 benchmark pallet " \ + f"--extrinsic=* " \ + f"--runtime=target/{profile}/wbuild/{config['package']}/{config['package'].replace('-', '_')}.wasm " \ + f"--pallet={pallet} " \ + f"--header={header_path} " \ + f"--output={output_path} " \ + f"--wasm-execution=compiled " \ + f"--steps=50 " \ + f"--repeat=20 " \ + f"--heap-pages=4096 " \ + f"{f'--template={template} ' if template else ''}" \ + f"--no-storage-info --no-min-squares --no-median-slopes" + print(f'-- Running: {cmd} \n') + status = os.system(cmd) + if status != 0 and not args.continue_on_fail: + print(f'Failed to benchmark {pallet} in {runtime}') + sys.exit(1) + + # Otherwise collect failed benchmarks and print them at the end + # push failed pallets to failed_benchmarks + if status != 0: + failed_benchmarks[f'{runtime}'] = failed_benchmarks.get(f'{runtime}', []) + [pallet] + else: + successful_benchmarks[f'{runtime}'] = successful_benchmarks.get(f'{runtime}', []) + [pallet] + + if failed_benchmarks: + print('❌ Failed benchmarks of runtimes/pallets:') + for runtime, pallets in failed_benchmarks.items(): + print(f'-- {runtime}: {pallets}') + + if successful_benchmarks: + print('✅ Successful benchmarks of runtimes/pallets:') + for runtime, pallets in successful_benchmarks.items(): + print(f'-- {runtime}: {pallets}') + + elif args.command == 'fmt': + command = f"cargo +nightly fmt" + print(f'Formatting with `{command}`') + nightly_status = os.system(f'{command}') + taplo_status = os.system('taplo format --config .config/taplo.toml') + + if (nightly_status != 0 or taplo_status != 0) and not args.continue_on_fail: + print('❌ Failed to format code') + sys.exit(1) + + elif args.command == 'update-ui': + command = 'sh ./scripts/update-ui-tests.sh' + print(f'Updating ui with `{command}`') + status = os.system(f'{command}') + + if status != 0 and not args.continue_on_fail: + print('❌ Failed to format code') + sys.exit(1) + + print('🚀 Done') + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/.github/scripts/cmd/test_cmd.py b/.github/scripts/cmd/test_cmd.py new file mode 100644 index 00000000000..4cf1b290915 --- /dev/null +++ b/.github/scripts/cmd/test_cmd.py @@ -0,0 +1,321 @@ +import unittest +from unittest.mock import patch, mock_open, MagicMock, call +import json +import sys +import os +import argparse + +# Mock data for runtimes-matrix.json +mock_runtimes_matrix = [ + {"name": "dev", "package": "kitchensink-runtime", "path": "substrate/frame", "header": "substrate/HEADER-APACHE2", "template": "substrate/.maintain/frame-weight-template.hbs"}, + {"name": "westend", "package": "westend-runtime", "path": "polkadot/runtime/westend", "header": "polkadot/file_header.txt", "template": "polkadot/xcm/pallet-xcm-benchmarks/template.hbs"}, + {"name": "rococo", "package": "rococo-runtime", "path": "polkadot/runtime/rococo", "header": "polkadot/file_header.txt", "template": "polkadot/xcm/pallet-xcm-benchmarks/template.hbs"}, + {"name": "asset-hub-westend", "package": "asset-hub-westend-runtime", "path": "cumulus/parachains/runtimes/assets/asset-hub-westend", "header": "cumulus/file_header.txt", "template": "cumulus/templates/xcm-bench-template.hbs"}, +] + +def get_mock_bench_output(runtime, pallets, output_path, header, template = None): + return f"frame-omni-bencher v1 benchmark pallet --extrinsic=* " \ + f"--runtime=target/release/wbuild/{runtime}-runtime/{runtime.replace('-', '_')}_runtime.wasm " \ + f"--pallet={pallets} --header={header} " \ + f"--output={output_path} " \ + f"--wasm-execution=compiled " \ + f"--steps=50 --repeat=20 --heap-pages=4096 " \ + f"{f'--template={template} ' if template else ''}" \ + f"--no-storage-info --no-min-squares --no-median-slopes" + +class TestCmd(unittest.TestCase): + + def setUp(self): + self.patcher1 = patch('builtins.open', new_callable=mock_open, read_data=json.dumps(mock_runtimes_matrix)) + self.patcher2 = patch('json.load', return_value=mock_runtimes_matrix) + self.patcher3 = patch('argparse.ArgumentParser.parse_known_args') + self.patcher4 = patch('os.system', return_value=0) + self.patcher5 = patch('os.popen') + + self.mock_open = self.patcher1.start() + self.mock_json_load = self.patcher2.start() + self.mock_parse_args = self.patcher3.start() + self.mock_system = self.patcher4.start() + self.mock_popen = self.patcher5.start() + + # Ensure that cmd.py uses the mock_runtimes_matrix + import cmd + cmd.runtimesMatrix = mock_runtimes_matrix + + def tearDown(self): + self.patcher1.stop() + self.patcher2.stop() + self.patcher3.stop() + self.patcher4.stop() + self.patcher5.stop() + + def test_bench_command_normal_execution_all_runtimes(self): + self.mock_parse_args.return_value = (argparse.Namespace( + command='bench', + runtime=list(map(lambda x: x['name'], mock_runtimes_matrix)), + pallet=['pallet_balances'], + continue_on_fail=False, + quiet=False, + clean=False, + image=None + ), []) + + self.mock_popen.return_value.read.side_effect = [ + "pallet_balances\npallet_staking\npallet_something\n", # Output for dev runtime + "pallet_balances\npallet_staking\npallet_something\n", # Output for westend runtime + "pallet_staking\npallet_something\n", # Output for rococo runtime - no pallet here + "pallet_balances\npallet_staking\npallet_something\n", # Output for asset-hub-westend runtime + "./substrate/frame/balances/Cargo.toml\n", # Mock manifest path for dev -> pallet_balances + ] + + with patch('sys.exit') as mock_exit: + import cmd + cmd.main() + mock_exit.assert_not_called() + + expected_calls = [ + # Build calls + call("forklift cargo build -p kitchensink-runtime --profile release --features runtime-benchmarks"), + call("forklift cargo build -p westend-runtime --profile release --features runtime-benchmarks"), + call("forklift cargo build -p rococo-runtime --profile release --features runtime-benchmarks"), + call("forklift cargo build -p asset-hub-westend-runtime --profile release --features runtime-benchmarks"), + + call(get_mock_bench_output('kitchensink', 'pallet_balances', './substrate/frame/balances/src/weights.rs', os.path.abspath('substrate/HEADER-APACHE2'), "substrate/.maintain/frame-weight-template.hbs")), + call(get_mock_bench_output('westend', 'pallet_balances', './polkadot/runtime/westend/src/weights', os.path.abspath('polkadot/file_header.txt'))), + # skips rococo benchmark + call(get_mock_bench_output('asset-hub-westend', 'pallet_balances', './cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights', os.path.abspath('cumulus/file_header.txt'))), + ] + self.mock_system.assert_has_calls(expected_calls, any_order=True) + + def test_bench_command_normal_execution(self): + self.mock_parse_args.return_value = (argparse.Namespace( + command='bench', + runtime=['westend'], + pallet=['pallet_balances', 'pallet_staking'], + continue_on_fail=False, + quiet=False, + clean=False, + image=None + ), []) + header_path = os.path.abspath('polkadot/file_header.txt') + self.mock_popen.return_value.read.side_effect = [ + "pallet_balances\npallet_staking\npallet_something\n", # Output for westend runtime + ] + + with patch('sys.exit') as mock_exit: + import cmd + cmd.main() + mock_exit.assert_not_called() + + expected_calls = [ + # Build calls + call("forklift cargo build -p westend-runtime --profile release --features runtime-benchmarks"), + + # Westend runtime calls + call(get_mock_bench_output('westend', 'pallet_balances', './polkadot/runtime/westend/src/weights', header_path)), + call(get_mock_bench_output('westend', 'pallet_staking', './polkadot/runtime/westend/src/weights', header_path)), + ] + self.mock_system.assert_has_calls(expected_calls, any_order=True) + + + def test_bench_command_normal_execution_xcm(self): + self.mock_parse_args.return_value = (argparse.Namespace( + command='bench', + runtime=['westend'], + pallet=['pallet_xcm_benchmarks::generic'], + continue_on_fail=False, + quiet=False, + clean=False, + image=None + ), []) + header_path = os.path.abspath('polkadot/file_header.txt') + self.mock_popen.return_value.read.side_effect = [ + "pallet_balances\npallet_staking\npallet_something\npallet_xcm_benchmarks::generic\n", # Output for westend runtime + ] + + with patch('sys.exit') as mock_exit: + import cmd + cmd.main() + mock_exit.assert_not_called() + + expected_calls = [ + # Build calls + call("forklift cargo build -p westend-runtime --profile release --features runtime-benchmarks"), + + # Westend runtime calls + call(get_mock_bench_output( + 'westend', + 'pallet_xcm_benchmarks::generic', + './polkadot/runtime/westend/src/weights/xcm', + header_path, + "polkadot/xcm/pallet-xcm-benchmarks/template.hbs" + )), + ] + self.mock_system.assert_has_calls(expected_calls, any_order=True) + + def test_bench_command_two_runtimes_two_pallets(self): + self.mock_parse_args.return_value = (argparse.Namespace( + command='bench', + runtime=['westend', 'rococo'], + pallet=['pallet_balances', 'pallet_staking'], + continue_on_fail=False, + quiet=False, + clean=False, + image=None + ), []) + self.mock_popen.return_value.read.side_effect = [ + "pallet_staking\npallet_balances\n", # Output for westend runtime + "pallet_staking\npallet_balances\n", # Output for rococo runtime + ] + + with patch('sys.exit') as mock_exit: + import cmd + cmd.main() + mock_exit.assert_not_called() + header_path = os.path.abspath('polkadot/file_header.txt') + + expected_calls = [ + # Build calls + call("forklift cargo build -p westend-runtime --profile release --features runtime-benchmarks"), + call("forklift cargo build -p rococo-runtime --profile release --features runtime-benchmarks"), + # Westend runtime calls + call(get_mock_bench_output('westend', 'pallet_staking', './polkadot/runtime/westend/src/weights', header_path)), + call(get_mock_bench_output('westend', 'pallet_balances', './polkadot/runtime/westend/src/weights', header_path)), + # Rococo runtime calls + call(get_mock_bench_output('rococo', 'pallet_staking', './polkadot/runtime/rococo/src/weights', header_path)), + call(get_mock_bench_output('rococo', 'pallet_balances', './polkadot/runtime/rococo/src/weights', header_path)), + ] + self.mock_system.assert_has_calls(expected_calls, any_order=True) + + def test_bench_command_one_dev_runtime(self): + self.mock_parse_args.return_value = (argparse.Namespace( + command='bench', + runtime=['dev'], + pallet=['pallet_balances'], + continue_on_fail=False, + quiet=False, + clean=False, + image=None + ), []) + manifest_dir = "substrate/frame/kitchensink" + self.mock_popen.return_value.read.side_effect = [ + "pallet_balances\npallet_something", # Output for dev runtime + manifest_dir + "/Cargo.toml" # Output for manifest path in dev runtime + ] + header_path = os.path.abspath('substrate/HEADER-APACHE2') + + with patch('sys.exit') as mock_exit: + import cmd + cmd.main() + mock_exit.assert_not_called() + + expected_calls = [ + # Build calls + call("forklift cargo build -p kitchensink-runtime --profile release --features runtime-benchmarks"), + # Westend runtime calls + call(get_mock_bench_output( + 'kitchensink', + 'pallet_balances', + manifest_dir + "/src/weights.rs", + header_path, + "substrate/.maintain/frame-weight-template.hbs" + )), + ] + self.mock_system.assert_has_calls(expected_calls, any_order=True) + + def test_bench_command_one_cumulus_runtime(self): + self.mock_parse_args.return_value = (argparse.Namespace( + command='bench', + runtime=['asset-hub-westend'], + pallet=['pallet_assets'], + continue_on_fail=False, + quiet=False, + clean=False, + image=None + ), []) + self.mock_popen.return_value.read.side_effect = [ + "pallet_assets\n", # Output for asset-hub-westend runtime + ] + header_path = os.path.abspath('cumulus/file_header.txt') + + with patch('sys.exit') as mock_exit: + import cmd + cmd.main() + mock_exit.assert_not_called() + + expected_calls = [ + # Build calls + call("forklift cargo build -p asset-hub-westend-runtime --profile release --features runtime-benchmarks"), + # Asset-hub-westend runtime calls + call(get_mock_bench_output( + 'asset-hub-westend', + 'pallet_assets', + './cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights', + header_path + )), + ] + + self.mock_system.assert_has_calls(expected_calls, any_order=True) + + def test_bench_command_one_cumulus_runtime_xcm(self): + self.mock_parse_args.return_value = (argparse.Namespace( + command='bench', + runtime=['asset-hub-westend'], + pallet=['pallet_xcm_benchmarks::generic', 'pallet_assets'], + continue_on_fail=False, + quiet=False, + clean=False, + image=None + ), []) + self.mock_popen.return_value.read.side_effect = [ + "pallet_assets\npallet_xcm_benchmarks::generic\n", # Output for asset-hub-westend runtime + ] + header_path = os.path.abspath('cumulus/file_header.txt') + + with patch('sys.exit') as mock_exit: + import cmd + cmd.main() + mock_exit.assert_not_called() + + expected_calls = [ + # Build calls + call("forklift cargo build -p asset-hub-westend-runtime --profile release --features runtime-benchmarks"), + # Asset-hub-westend runtime calls + call(get_mock_bench_output( + 'asset-hub-westend', + 'pallet_xcm_benchmarks::generic', + './cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/xcm', + header_path, + "cumulus/templates/xcm-bench-template.hbs" + )), + call(get_mock_bench_output( + 'asset-hub-westend', + 'pallet_assets', + './cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights', + header_path + )), + ] + + self.mock_system.assert_has_calls(expected_calls, any_order=True) + + @patch('argparse.ArgumentParser.parse_known_args', return_value=(argparse.Namespace(command='fmt', continue_on_fail=False), [])) + @patch('os.system', return_value=0) + def test_fmt_command(self, mock_system, mock_parse_args): + with patch('sys.exit') as mock_exit: + import cmd + cmd.main() + mock_exit.assert_not_called() + mock_system.assert_any_call('cargo +nightly fmt') + mock_system.assert_any_call('taplo format --config .config/taplo.toml') + + @patch('argparse.ArgumentParser.parse_known_args', return_value=(argparse.Namespace(command='update-ui', continue_on_fail=False), [])) + @patch('os.system', return_value=0) + def test_update_ui_command(self, mock_system, mock_parse_args): + with patch('sys.exit') as mock_exit: + import cmd + cmd.main() + mock_exit.assert_not_called() + mock_system.assert_called_with('sh ./scripts/update-ui-tests.sh') + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/.github/workflows/cmd-tests.yml b/.github/workflows/cmd-tests.yml new file mode 100644 index 00000000000..87d7ee1dcc2 --- /dev/null +++ b/.github/workflows/cmd-tests.yml @@ -0,0 +1,14 @@ +name: Command Bot Tests + +on: + pull_request: + +permissions: + contents: read + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - run: python3 .github/scripts/cmd/test_cmd.py \ No newline at end of file diff --git a/.github/workflows/cmd.yml b/.github/workflows/cmd.yml index dfdf771a610..79a4f6c3b19 100644 --- a/.github/workflows/cmd.yml +++ b/.github/workflows/cmd.yml @@ -358,7 +358,7 @@ jobs: git config --local user.email "action@github.com" git config --local user.name "GitHub Action" - git pull origin ${{ needs.get-pr-branch.outputs.pr-branch }} + git pull --rebase origin ${{ needs.get-pr-branch.outputs.pr-branch }} git add . git restore --staged Cargo.lock # ignore changes in Cargo.lock git commit -m "Update from ${{ github.actor }} running command '${{ steps.get-pr-comment.outputs.group2 }}'" || true diff --git a/.github/workflows/runtimes-matrix.json b/.github/workflows/runtimes-matrix.json index 45a3acd3f16..102437876da 100644 --- a/.github/workflows/runtimes-matrix.json +++ b/.github/workflows/runtimes-matrix.json @@ -3,6 +3,8 @@ "name": "dev", "package": "kitchensink-runtime", "path": "substrate/frame", + "header": "substrate/HEADER-APACHE2", + "template": "substrate/.maintain/frame-weight-template.hbs", "uri": null, "is_relay": false }, @@ -10,6 +12,8 @@ "name": "westend", "package": "westend-runtime", "path": "polkadot/runtime/westend", + "header": "polkadot/file_header.txt", + "template": "polkadot/xcm/pallet-xcm-benchmarks/template.hbs", "uri": "wss://try-runtime-westend.polkadot.io:443", "is_relay": true }, @@ -17,6 +21,8 @@ "name": "rococo", "package": "rococo-runtime", "path": "polkadot/runtime/rococo", + "header": "polkadot/file_header.txt", + "template": "polkadot/xcm/pallet-xcm-benchmarks/template.hbs", "uri": "wss://try-runtime-rococo.polkadot.io:443", "is_relay": true }, @@ -24,6 +30,8 @@ "name": "asset-hub-westend", "package": "asset-hub-westend-runtime", "path": "cumulus/parachains/runtimes/assets/asset-hub-westend", + "header": "cumulus/file_header.txt", + "template": "cumulus/templates/xcm-bench-template.hbs", "uri": "wss://westend-asset-hub-rpc.polkadot.io:443", "is_relay": false }, @@ -31,6 +39,8 @@ "name": "asset-hub-rococo", "package": "asset-hub-rococo-runtime", "path": "cumulus/parachains/runtimes/assets/asset-hub-rococo", + "header": "cumulus/file_header.txt", + "template": "cumulus/templates/xcm-bench-template.hbs", "uri": "wss://rococo-asset-hub-rpc.polkadot.io:443", "is_relay": false }, @@ -38,6 +48,8 @@ "name": "bridge-hub-rococo", "package": "bridge-hub-rococo-runtime", "path": "cumulus/parachains/runtimes/bridges/bridge-hub-rococo", + "header": "cumulus/file_header.txt", + "template": "cumulus/templates/xcm-bench-template.hbs", "uri": "wss://rococo-bridge-hub-rpc.polkadot.io:443", "is_relay": false }, @@ -45,6 +57,8 @@ "name": "bridge-hub-westend", "package": "bridge-hub-rococo-runtime", "path": "cumulus/parachains/runtimes/bridges/bridge-hub-westend", + "header": "cumulus/file_header.txt", + "template": "cumulus/templates/xcm-bench-template.hbs", "uri": "wss://westend-bridge-hub-rpc.polkadot.io:443", "is_relay": false }, @@ -52,12 +66,16 @@ "name": "collectives-westend", "package": "collectives-westend-runtime", "path": "cumulus/parachains/runtimes/collectives/collectives-westend", + "header": "cumulus/file_header.txt", + "template": "cumulus/templates/xcm-bench-template.hbs", "uri": "wss://westend-collectives-rpc.polkadot.io:443" }, { "name": "contracts-rococo", "package": "contracts-rococo-runtime", "path": "cumulus/parachains/runtimes/contracts/contracts-rococo", + "header": "cumulus/file_header.txt", + "template": "cumulus/templates/xcm-bench-template.hbs", "uri": "wss://rococo-contracts-rpc.polkadot.io:443", "is_relay": false }, @@ -65,6 +83,8 @@ "name": "coretime-rococo", "package": "coretime-rococo-runtime", "path": "cumulus/parachains/runtimes/coretime/coretime-rococo", + "header": "cumulus/file_header.txt", + "template": "cumulus/templates/xcm-bench-template.hbs", "uri": "wss://rococo-coretime-rpc.polkadot.io:443", "is_relay": false }, @@ -72,6 +92,8 @@ "name": "coretime-westend", "package": "coretime-westend-runtime", "path": "cumulus/parachains/runtimes/coretime/coretime-westend", + "header": "cumulus/file_header.txt", + "template": "cumulus/templates/xcm-bench-template.hbs", "uri": "wss://westend-coretime-rpc.polkadot.io:443", "is_relay": false }, @@ -79,12 +101,17 @@ "name": "glutton-westend", "package": "glutton-westend-runtime", "path": "cumulus/parachains/runtimes/gluttons/glutton-westend", + "header": "cumulus/file_header.txt", + "template": "cumulus/templates/xcm-bench-template.hbs", + "uri": null, "is_relay": false }, { "name": "people-rococo", "package": "people-rococo-runtime", "path": "cumulus/parachains/runtimes/people/people-rococo", + "header": "cumulus/file_header.txt", + "template": "cumulus/templates/xcm-bench-template.hbs", "uri": "wss://rococo-people-rpc.polkadot.io:443", "is_relay": false }, @@ -92,6 +119,8 @@ "name": "people-westend", "package": "people-westend-runtime", "path": "cumulus/parachains/runtimes/people/people-westend", + "header": "cumulus/file_header.txt", + "template": "cumulus/templates/xcm-bench-template.hbs", "uri": "wss://westend-people-rpc.polkadot.io:443", "is_relay": false } diff --git a/substrate/frame/balances/src/weights.rs b/substrate/frame/balances/src/weights.rs index e82c97160ef..55decef273f 100644 --- a/substrate/frame/balances/src/weights.rs +++ b/substrate/frame/balances/src/weights.rs @@ -17,27 +17,27 @@ //! Autogenerated weights for `pallet_balances` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-05-06, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 42.0.0 +//! DATE: 2024-09-04, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-unxyhko3-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` +//! HOSTNAME: `8f4ffe8f7785`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: `1024` // Executed Command: -// target/production/substrate-node +// frame-omni-bencher +// v1 // benchmark // pallet -// --steps=50 -// --repeat=20 // --extrinsic=* +// --runtime=target/release/wbuild/kitchensink-runtime/kitchensink_runtime.wasm +// --pallet=pallet_balances +// --header=/__w/polkadot-sdk/polkadot-sdk/substrate/HEADER-APACHE2 +// --output=/__w/polkadot-sdk/polkadot-sdk/substrate/frame/balances/src/weights.rs // --wasm-execution=compiled +// --steps=50 +// --repeat=20 // --heap-pages=4096 -// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json -// --pallet=pallet_balances -// --chain=dev -// --header=./substrate/HEADER-APACHE2 -// --output=./substrate/frame/balances/src/weights.rs -// --template=./substrate/.maintain/frame-weight-template.hbs +// --template=substrate/.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -71,8 +71,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `3593` - // Minimum execution time: 47_552_000 picoseconds. - Weight::from_parts(48_363_000, 3593) + // Minimum execution time: 75_624_000 picoseconds. + Weight::from_parts(77_290_000, 3593) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -82,8 +82,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `3593` - // Minimum execution time: 37_565_000 picoseconds. - Weight::from_parts(38_159_000, 3593) + // Minimum execution time: 60_398_000 picoseconds. + Weight::from_parts(61_290_000, 3593) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -91,10 +91,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) fn force_set_balance_creating() -> Weight { // Proof Size summary in bytes: - // Measured: `174` + // Measured: `52` // Estimated: `3593` - // Minimum execution time: 14_147_000 picoseconds. - Weight::from_parts(14_687_000, 3593) + // Minimum execution time: 18_963_000 picoseconds. + Weight::from_parts(19_802_000, 3593) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -102,10 +102,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) fn force_set_balance_killing() -> Weight { // Proof Size summary in bytes: - // Measured: `174` + // Measured: `52` // Estimated: `3593` - // Minimum execution time: 19_188_000 picoseconds. - Weight::from_parts(19_929_000, 3593) + // Minimum execution time: 30_517_000 picoseconds. + Weight::from_parts(31_293_000, 3593) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -113,10 +113,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) fn force_transfer() -> Weight { // Proof Size summary in bytes: - // Measured: `103` + // Measured: `52` // Estimated: `6196` - // Minimum execution time: 48_903_000 picoseconds. - Weight::from_parts(49_944_000, 6196) + // Minimum execution time: 77_017_000 picoseconds. + Weight::from_parts(78_184_000, 6196) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -126,8 +126,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `3593` - // Minimum execution time: 46_573_000 picoseconds. - Weight::from_parts(47_385_000, 3593) + // Minimum execution time: 75_600_000 picoseconds. + Weight::from_parts(76_817_000, 3593) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -135,10 +135,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) fn force_unreserve() -> Weight { // Proof Size summary in bytes: - // Measured: `174` + // Measured: `52` // Estimated: `3593` - // Minimum execution time: 16_750_000 picoseconds. - Weight::from_parts(17_233_000, 3593) + // Minimum execution time: 24_503_000 picoseconds. + Weight::from_parts(25_026_000, 3593) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -149,10 +149,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0 + u * (135 ±0)` // Estimated: `990 + u * (2603 ±0)` - // Minimum execution time: 16_333_000 picoseconds. - Weight::from_parts(16_588_000, 990) - // Standard Error: 12_254 - .saturating_add(Weight::from_parts(13_973_659, 0).saturating_mul(u.into())) + // Minimum execution time: 24_077_000 picoseconds. + Weight::from_parts(24_339_000, 990) + // Standard Error: 18_669 + .saturating_add(Weight::from_parts(21_570_294, 0).saturating_mul(u.into())) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(u.into()))) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(u.into()))) .saturating_add(Weight::from_parts(0, 2603).saturating_mul(u.into())) @@ -161,22 +161,22 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_265_000 picoseconds. - Weight::from_parts(6_594_000, 0) + // Minimum execution time: 8_070_000 picoseconds. + Weight::from_parts(8_727_000, 0) } fn burn_allow_death() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 30_151_000 picoseconds. - Weight::from_parts(30_968_000, 0) + // Minimum execution time: 46_978_000 picoseconds. + Weight::from_parts(47_917_000, 0) } fn burn_keep_alive() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 20_055_000 picoseconds. - Weight::from_parts(20_711_000, 0) + // Minimum execution time: 31_141_000 picoseconds. + Weight::from_parts(31_917_000, 0) } } @@ -188,8 +188,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `3593` - // Minimum execution time: 47_552_000 picoseconds. - Weight::from_parts(48_363_000, 3593) + // Minimum execution time: 75_624_000 picoseconds. + Weight::from_parts(77_290_000, 3593) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -199,8 +199,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `3593` - // Minimum execution time: 37_565_000 picoseconds. - Weight::from_parts(38_159_000, 3593) + // Minimum execution time: 60_398_000 picoseconds. + Weight::from_parts(61_290_000, 3593) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -208,10 +208,10 @@ impl WeightInfo for () { /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) fn force_set_balance_creating() -> Weight { // Proof Size summary in bytes: - // Measured: `174` + // Measured: `52` // Estimated: `3593` - // Minimum execution time: 14_147_000 picoseconds. - Weight::from_parts(14_687_000, 3593) + // Minimum execution time: 18_963_000 picoseconds. + Weight::from_parts(19_802_000, 3593) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -219,10 +219,10 @@ impl WeightInfo for () { /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) fn force_set_balance_killing() -> Weight { // Proof Size summary in bytes: - // Measured: `174` + // Measured: `52` // Estimated: `3593` - // Minimum execution time: 19_188_000 picoseconds. - Weight::from_parts(19_929_000, 3593) + // Minimum execution time: 30_517_000 picoseconds. + Weight::from_parts(31_293_000, 3593) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -230,10 +230,10 @@ impl WeightInfo for () { /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) fn force_transfer() -> Weight { // Proof Size summary in bytes: - // Measured: `103` + // Measured: `52` // Estimated: `6196` - // Minimum execution time: 48_903_000 picoseconds. - Weight::from_parts(49_944_000, 6196) + // Minimum execution time: 77_017_000 picoseconds. + Weight::from_parts(78_184_000, 6196) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -243,8 +243,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `3593` - // Minimum execution time: 46_573_000 picoseconds. - Weight::from_parts(47_385_000, 3593) + // Minimum execution time: 75_600_000 picoseconds. + Weight::from_parts(76_817_000, 3593) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -252,10 +252,10 @@ impl WeightInfo for () { /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) fn force_unreserve() -> Weight { // Proof Size summary in bytes: - // Measured: `174` + // Measured: `52` // Estimated: `3593` - // Minimum execution time: 16_750_000 picoseconds. - Weight::from_parts(17_233_000, 3593) + // Minimum execution time: 24_503_000 picoseconds. + Weight::from_parts(25_026_000, 3593) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -266,10 +266,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0 + u * (135 ±0)` // Estimated: `990 + u * (2603 ±0)` - // Minimum execution time: 16_333_000 picoseconds. - Weight::from_parts(16_588_000, 990) - // Standard Error: 12_254 - .saturating_add(Weight::from_parts(13_973_659, 0).saturating_mul(u.into())) + // Minimum execution time: 24_077_000 picoseconds. + Weight::from_parts(24_339_000, 990) + // Standard Error: 18_669 + .saturating_add(Weight::from_parts(21_570_294, 0).saturating_mul(u.into())) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(u.into()))) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(u.into()))) .saturating_add(Weight::from_parts(0, 2603).saturating_mul(u.into())) @@ -278,21 +278,21 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_265_000 picoseconds. - Weight::from_parts(6_594_000, 0) + // Minimum execution time: 8_070_000 picoseconds. + Weight::from_parts(8_727_000, 0) } fn burn_allow_death() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 30_151_000 picoseconds. - Weight::from_parts(30_968_000, 0) + // Minimum execution time: 46_978_000 picoseconds. + Weight::from_parts(47_917_000, 0) } fn burn_keep_alive() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 20_055_000 picoseconds. - Weight::from_parts(20_711_000, 0) + // Minimum execution time: 31_141_000 picoseconds. + Weight::from_parts(31_917_000, 0) } } -- GitLab From fdb4554e26ebdd4d729158501a3ddb3c6ebdfb6f Mon Sep 17 00:00:00 2001 From: Liu-Cheng Xu Date: Fri, 6 Sep 2024 16:21:09 +0800 Subject: [PATCH 013/125] Introduce `BlockGap` (#5592) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, block gaps could only be created by warp sync, but block gaps will also be generated by fast sync once #5406 is fixed. This PR is part 1 of the detailed implementation plan in https://github.com/paritytech/polkadot-sdk/issues/5406#issuecomment-2325064863: refactor `BlockGap`. This refactor converts the existing `(NumberFor, NumberFor)` into a dedicated `BlockGap>` struct. This change is purely structural and does not alter existing logic, but lays the groundwork for the follow-up PR. The compatibility concern caused by the new structure is addressed in the second commit. cc @dmitry-markin --------- Co-authored-by: Bastian Köcher --- prdoc/pr_5592.prdoc | 26 +++++++ substrate/client/consensus/babe/src/lib.rs | 6 +- substrate/client/db/src/lib.rs | 73 ++++++++++++------- substrate/client/db/src/utils.rs | 47 ++++++++++-- .../network/sync/src/strategy/chain_sync.rs | 4 +- substrate/client/service/src/client/client.rs | 5 +- .../primitives/blockchain/src/backend.rs | 40 ++++++++-- 7 files changed, 153 insertions(+), 48 deletions(-) create mode 100644 prdoc/pr_5592.prdoc diff --git a/prdoc/pr_5592.prdoc b/prdoc/pr_5592.prdoc new file mode 100644 index 00000000000..9d51917db7b --- /dev/null +++ b/prdoc/pr_5592.prdoc @@ -0,0 +1,26 @@ +title: Introduce `BlockGap` + +doc: + - audience: Node Dev + description: | + This is the first step towards https://github.com/paritytech/polkadot-sdk/issues/5406, + refactoring the representation of block gap. This refactor converts the existing + `(NumberFor, NumberFor)` into a dedicated `BlockGap>` + struct. This change is purely structural and does not alter existing logic, but lays + the groundwork for the follow-up PR. The compatibility concern in the database caused + by the new structure transition is addressed as well. + + The `BlockGap` refactoring results in breaking changes in the `Info` structure returned + in `client.info()`. + +crates: + - name: sc-consensus-babe + bump: none + - name: sc-client-db + bump: none + - name: sc-network-sync + bump: none + - name: sc-service + bump: none + - name: sp-blockchain + bump: major diff --git a/substrate/client/consensus/babe/src/lib.rs b/substrate/client/consensus/babe/src/lib.rs index 9770b16871e..4cf66302ec8 100644 --- a/substrate/client/consensus/babe/src/lib.rs +++ b/substrate/client/consensus/babe/src/lib.rs @@ -1146,7 +1146,9 @@ where let info = self.client.info(); let number = *block.header.number(); - if info.block_gap.map_or(false, |(s, e)| s <= number && number <= e) || block.with_state() { + if info.block_gap.map_or(false, |gap| gap.start <= number && number <= gap.end) || + block.with_state() + { // Verification for imported blocks is skipped in two cases: // 1. When importing blocks below the last finalized block during network initial // synchronization. @@ -1420,7 +1422,7 @@ where // Skip babe logic if block already in chain or importing blocks during initial sync, // otherwise the check for epoch changes will error because trying to re-import an // epoch change or because of missing epoch data in the tree, respectively. - if info.block_gap.map_or(false, |(s, e)| s <= number && number <= e) || + if info.block_gap.map_or(false, |gap| gap.start <= number && number <= gap.end) || block_status == BlockStatus::InChain { // When re-importing existing block strip away intermediates. diff --git a/substrate/client/db/src/lib.rs b/substrate/client/db/src/lib.rs index eadb26254a1..4559a01e57e 100644 --- a/substrate/client/db/src/lib.rs +++ b/substrate/client/db/src/lib.rs @@ -61,6 +61,7 @@ use codec::{Decode, Encode}; use hash_db::Prefix; use sc_client_api::{ backend::NewBlockState, + blockchain::{BlockGap, BlockGapType}, leaves::{FinalizationOutcome, LeafSet}, utils::is_descendent_of, IoInfo, MemoryInfo, MemorySize, UsageInfo, @@ -91,6 +92,7 @@ use sp_state_machine::{ StorageValue, UsageInfo as StateUsageInfo, }; use sp_trie::{cache::SharedTrieCache, prefixed_key, MemoryDB, MerkleValue, PrefixedMemoryDB}; +use utils::BLOCK_GAP_CURRENT_VERSION; // Re-export the Database trait so that one can pass an implementation of it. pub use sc_state_db::PruningMode; @@ -522,7 +524,7 @@ impl BlockchainDb { } } - fn update_block_gap(&self, gap: Option<(NumberFor, NumberFor)>) { + fn update_block_gap(&self, gap: Option>>) { let mut meta = self.meta.write(); meta.block_gap = gap; } @@ -1671,35 +1673,56 @@ impl Backend { ); } - if let Some((mut start, end)) = block_gap { - if number == start { - start += One::one(); - utils::insert_number_to_key_mapping( - &mut transaction, - columns::KEY_LOOKUP, - number, - hash, - )?; - if start > end { - transaction.remove(columns::META, meta_keys::BLOCK_GAP); - block_gap = None; - debug!(target: "db", "Removed block gap."); - } else { - block_gap = Some((start, end)); - debug!(target: "db", "Update block gap. {block_gap:?}"); - transaction.set( - columns::META, - meta_keys::BLOCK_GAP, - &(start, end).encode(), - ); - } - block_gap_updated = true; + if let Some(mut gap) = block_gap { + match gap.gap_type { + BlockGapType::MissingHeaderAndBody => + if number == gap.start { + gap.start += One::one(); + utils::insert_number_to_key_mapping( + &mut transaction, + columns::KEY_LOOKUP, + number, + hash, + )?; + if gap.start > gap.end { + transaction.remove(columns::META, meta_keys::BLOCK_GAP); + transaction.remove(columns::META, meta_keys::BLOCK_GAP_VERSION); + block_gap = None; + debug!(target: "db", "Removed block gap."); + } else { + block_gap = Some(gap); + debug!(target: "db", "Update block gap. {block_gap:?}"); + transaction.set( + columns::META, + meta_keys::BLOCK_GAP, + &gap.encode(), + ); + transaction.set( + columns::META, + meta_keys::BLOCK_GAP_VERSION, + &BLOCK_GAP_CURRENT_VERSION.encode(), + ); + } + block_gap_updated = true; + }, + BlockGapType::MissingBody => { + unreachable!("Unsupported block gap. TODO: https://github.com/paritytech/polkadot-sdk/issues/5406") + }, } } else if number > best_num + One::one() && number > One::one() && self.blockchain.header(parent_hash)?.is_none() { - let gap = (best_num + One::one(), number - One::one()); + let gap = BlockGap { + start: best_num + One::one(), + end: number - One::one(), + gap_type: BlockGapType::MissingHeaderAndBody, + }; transaction.set(columns::META, meta_keys::BLOCK_GAP, &gap.encode()); + transaction.set( + columns::META, + meta_keys::BLOCK_GAP_VERSION, + &BLOCK_GAP_CURRENT_VERSION.encode(), + ); block_gap = Some(gap); block_gap_updated = true; debug!(target: "db", "Detected block gap {block_gap:?}"); diff --git a/substrate/client/db/src/utils.rs b/substrate/client/db/src/utils.rs index b532e0d4666..0b591c967e6 100644 --- a/substrate/client/db/src/utils.rs +++ b/substrate/client/db/src/utils.rs @@ -25,10 +25,14 @@ use log::{debug, info}; use crate::{Database, DatabaseSource, DbHash}; use codec::Decode; +use sc_client_api::blockchain::{BlockGap, BlockGapType}; use sp_database::Transaction; use sp_runtime::{ generic::BlockId, - traits::{Block as BlockT, Header as HeaderT, UniqueSaturatedFrom, UniqueSaturatedInto, Zero}, + traits::{ + Block as BlockT, Header as HeaderT, NumberFor, UniqueSaturatedFrom, UniqueSaturatedInto, + Zero, + }, }; use sp_trie::DBValue; @@ -38,6 +42,9 @@ pub const NUM_COLUMNS: u32 = 13; /// Meta column. The set of keys in the column is shared by full && light storages. pub const COLUMN_META: u32 = 0; +/// Current block gap version. +pub const BLOCK_GAP_CURRENT_VERSION: u32 = 1; + /// Keys of entries in COLUMN_META. pub mod meta_keys { /// Type of storage (full or light). @@ -50,6 +57,8 @@ pub mod meta_keys { pub const FINALIZED_STATE: &[u8; 6] = b"fstate"; /// Block gap. pub const BLOCK_GAP: &[u8; 3] = b"gap"; + /// Block gap version. + pub const BLOCK_GAP_VERSION: &[u8; 7] = b"gap_ver"; /// Genesis block hash. pub const GENESIS_HASH: &[u8; 3] = b"gen"; /// Leaves prefix list key. @@ -73,8 +82,8 @@ pub struct Meta { pub genesis_hash: H, /// Finalized state, if any pub finalized_state: Option<(H, N)>, - /// Block gap, start and end inclusive, if any. - pub block_gap: Option<(N, N)>, + /// Block gap, if any. + pub block_gap: Option>, } /// A block lookup key: used for canonical lookup from block number to hash @@ -197,7 +206,7 @@ fn open_database_at( open_kvdb_rocksdb::(path, db_type, create, *cache_size)?, DatabaseSource::Custom { db, require_create_flag } => { if *require_create_flag && !create { - return Err(OpenDbError::DoesNotExist) + return Err(OpenDbError::DoesNotExist); } db.clone() }, @@ -364,7 +373,7 @@ pub fn check_database_type( return Err(OpenDbError::UnexpectedDbType { expected: db_type, found: stored_type.to_owned(), - }) + }); }, None => { let mut transaction = Transaction::new(); @@ -515,9 +524,31 @@ where } else { None }; - let block_gap = db - .get(COLUMN_META, meta_keys::BLOCK_GAP) - .and_then(|d| Decode::decode(&mut d.as_slice()).ok()); + let block_gap = match db + .get(COLUMN_META, meta_keys::BLOCK_GAP_VERSION) + .and_then(|d| u32::decode(&mut d.as_slice()).ok()) + { + None => { + let old_block_gap: Option<(NumberFor, NumberFor)> = db + .get(COLUMN_META, meta_keys::BLOCK_GAP) + .and_then(|d| Decode::decode(&mut d.as_slice()).ok()); + + old_block_gap.map(|(start, end)| BlockGap { + start, + end, + gap_type: BlockGapType::MissingHeaderAndBody, + }) + }, + Some(version) => match version { + BLOCK_GAP_CURRENT_VERSION => db + .get(COLUMN_META, meta_keys::BLOCK_GAP) + .and_then(|d| Decode::decode(&mut d.as_slice()).ok()), + v => + return Err(sp_blockchain::Error::Backend(format!( + "Unsupported block gap DB version: {v}" + ))), + }, + }; debug!(target: "db", "block_gap={:?}", block_gap); Ok(Meta { diff --git a/substrate/client/network/sync/src/strategy/chain_sync.rs b/substrate/client/network/sync/src/strategy/chain_sync.rs index 21e47404862..f29ed1b083e 100644 --- a/substrate/client/network/sync/src/strategy/chain_sync.rs +++ b/substrate/client/network/sync/src/strategy/chain_sync.rs @@ -44,7 +44,7 @@ use crate::{ use codec::Encode; use log::{debug, error, info, trace, warn}; use prometheus_endpoint::{register, Gauge, PrometheusError, Registry, U64}; -use sc_client_api::{BlockBackend, ProofProvider}; +use sc_client_api::{blockchain::BlockGap, BlockBackend, ProofProvider}; use sc_consensus::{BlockImportError, BlockImportStatus, IncomingBlock}; use sc_network_common::sync::message::{ BlockAnnounce, BlockAttributes, BlockData, BlockRequest, BlockResponse, Direction, FromBlock, @@ -1381,7 +1381,7 @@ where } } - if let Some((start, end)) = info.block_gap { + if let Some(BlockGap { start, end, .. }) = info.block_gap { debug!(target: LOG_TARGET, "Starting gap sync #{start} - #{end}"); self.gap_sync = Some(GapSync { best_queued_number: start - One::one(), diff --git a/substrate/client/service/src/client/client.rs b/substrate/client/service/src/client/client.rs index 22defd7c551..8b699c7faff 100644 --- a/substrate/client/service/src/client/client.rs +++ b/substrate/client/service/src/client/client.rs @@ -604,9 +604,8 @@ where } let info = self.backend.blockchain().info(); - let gap_block = info - .block_gap - .map_or(false, |(start, _)| *import_headers.post().number() == start); + let gap_block = + info.block_gap.map_or(false, |gap| *import_headers.post().number() == gap.start); // the block is lower than our last finalized block so it must revert // finality, refusing import. diff --git a/substrate/primitives/blockchain/src/backend.rs b/substrate/primitives/blockchain/src/backend.rs index fd0c5795cbf..d7386a71a0d 100644 --- a/substrate/primitives/blockchain/src/backend.rs +++ b/substrate/primitives/blockchain/src/backend.rs @@ -17,6 +17,7 @@ //! Substrate blockchain trait +use codec::{Decode, Encode}; use parking_lot::RwLock; use sp_runtime::{ generic::BlockId, @@ -109,7 +110,7 @@ pub trait ForkBackend: for block in tree_route.retracted() { expanded_forks.insert(block.hash); } - continue + continue; }, Err(_) => { // There are cases when blocks are missing (e.g. warp-sync). @@ -196,7 +197,7 @@ pub trait Backend: let info = self.info(); if info.finalized_number > *base_header.number() { // `base_header` is on a dead fork. - return Ok(None) + return Ok(None); } self.leaves()? }; @@ -207,7 +208,7 @@ pub trait Backend: // go backwards through the chain (via parent links) loop { if current_hash == base_hash { - return Ok(Some(leaf_hash)) + return Ok(Some(leaf_hash)); } let current_header = self @@ -216,7 +217,7 @@ pub trait Backend: // stop search in this chain once we go below the target's block number if current_header.number() < base_header.number() { - break + break; } current_hash = *current_header.parent_hash(); @@ -266,7 +267,7 @@ pub trait Backend: // If we have only one leaf there are no forks, and we can return early. if finalized_block_number == Zero::zero() || leaves.len() == 1 { - return Ok(DisplacedLeavesAfterFinalization::default()) + return Ok(DisplacedLeavesAfterFinalization::default()); } // Store hashes of finalized blocks for quick checking later, the last block is the @@ -332,7 +333,7 @@ pub trait Backend: elapsed = ?now.elapsed(), "Added genesis leaf to displaced leaves." ); - continue + continue; } debug!( @@ -539,6 +540,29 @@ impl DisplacedLeavesAfterFinalization { } } +/// Represents the type of block gaps that may result from either warp sync or fast sync. +#[derive(Debug, Clone, Copy, Eq, PartialEq, Encode, Decode)] +pub enum BlockGapType { + /// Both the header and body are missing, as a result of warp sync. + MissingHeaderAndBody, + /// The block body is missing, as a result of fast sync. + MissingBody, +} + +/// Represents the block gap resulted by warp sync or fast sync. +/// +/// A block gap is a range of blocks where either the bodies, or both headers and bodies are +/// missing. +#[derive(Debug, Clone, Copy, Eq, PartialEq, Encode, Decode)] +pub struct BlockGap { + /// The starting block number of the gap (inclusive). + pub start: N, + /// The ending block number of the gap (inclusive). + pub end: N, + /// The type of gap. + pub gap_type: BlockGapType, +} + /// Blockchain info #[derive(Debug, Eq, PartialEq, Clone)] pub struct Info { @@ -556,8 +580,8 @@ pub struct Info { pub finalized_state: Option<(Block::Hash, <::Header as HeaderT>::Number)>, /// Number of concurrent leave forks. pub number_leaves: usize, - /// Missing blocks after warp sync. (start, end). - pub block_gap: Option<(NumberFor, NumberFor)>, + /// Missing blocks after warp sync or fast sync. + pub block_gap: Option>>, } /// Block status. -- GitLab From 76df1ae460fb2f9910051e0dac2211ab8d156ced Mon Sep 17 00:00:00 2001 From: Egor_P Date: Fri, 6 Sep 2024 10:29:26 +0200 Subject: [PATCH 014/125] [CI/Release] Pipeline to create a stable release branch (#5598) This PR contains a pipeline which is going to branch off the new stable release branch (e.g. `stab2412`, `stable2503`) and bump `polkadot` `NODE_VERSION`, `spec_version` of the runtimes and reorganisation of the `prdocs` related to the new stable release. This is a first step in the automated `polkadot-sdk` release flow as part of the task: https://github.com/paritytech/polkadot-sdk/issues/3291 The pipeline is not supposed to be triggered in the main` polkadot-sdk` repo, but in the fork in the [`paritytech-release`](https://github.com/paritytech-release/polkadot-sdk) org, where the whole release flow is going to land. Closes: https://github.com/paritytech/release-engineering/issues/222 --- .github/scripts/common/lib.sh | 12 +- .github/scripts/release/release_lib.sh | 118 ++++++++++++++++++ .../workflows/release-branchoff-stable.yml | 105 ++++++++++++++++ 3 files changed, 229 insertions(+), 6 deletions(-) create mode 100644 .github/scripts/release/release_lib.sh create mode 100644 .github/workflows/release-branchoff-stable.yml diff --git a/.github/scripts/common/lib.sh b/.github/scripts/common/lib.sh index bfb3120ad9b..5361db398ae 100755 --- a/.github/scripts/common/lib.sh +++ b/.github/scripts/common/lib.sh @@ -299,23 +299,23 @@ function check_sha256() { } # Import GPG keys of the release team members -# This is done in parallel as it can take a while sometimes function import_gpg_keys() { - GPG_KEYSERVER=${GPG_KEYSERVER:-"keyserver.ubuntu.com"} + GPG_KEYSERVER=${GPG_KEYSERVER:-"hkps://keyserver.ubuntu.com"} SEC="9D4B2B6EB8F97156D19669A9FF0812D491B96798" EGOR="E6FC4D4782EB0FA64A4903CCDB7D3555DD3932D3" MORGAN="2E92A9D8B15D7891363D1AE8AF9E6C43F7F8C4CF" + PARITY_RELEASES="90BD75EBBB8E95CB3DA6078F94A4029AB4B35DAE" - echo "Importing GPG keys from $GPG_KEYSERVER in parallel" - for key in $SEC $EGOR $MORGAN; do + echo "Importing GPG keys from $GPG_KEYSERVER" + for key in $SEC $EGOR $MORGAN $PARITY_RELEASES; do ( echo "Importing GPG key $key" gpg --no-tty --quiet --keyserver $GPG_KEYSERVER --recv-keys $key echo -e "5\ny\n" | gpg --no-tty --command-fd 0 --expert --edit-key $key trust; - ) & + ) done wait - gpg -k $SEC + gpg -k } # Check the GPG signature for a given binary diff --git a/.github/scripts/release/release_lib.sh b/.github/scripts/release/release_lib.sh new file mode 100644 index 00000000000..81a3c14edec --- /dev/null +++ b/.github/scripts/release/release_lib.sh @@ -0,0 +1,118 @@ +#!/usr/bin/env bash + +# Set the new version by replacing the value of the constant given as patetrn +# in the file. +# +# input: pattern, version, file +#output: none +set_version() { + pattern=$1 + version=$2 + file=$3 + + sed -i "s/$pattern/\1\"${version}\"/g" $file + return 0 +} + +# Commit changes to git with specific message. +# "|| true" does not let script to fail with exit code 1, +# in case there is nothing to commit. +# +# input: MESSAGE (any message which should be used for the commit) +# output: none +commit_with_message() { + MESSAGE=$1 + git commit -a -m "$MESSAGE" || true +} + +# Retun list of the runtimes filterd +# input: none +# output: list of filtered runtimes +get_filtered_runtimes_list() { + grep_filters=("runtime.*" "test|template|starters|substrate") + + git grep spec_version: | grep .rs: | grep -e "${grep_filters[0]}" | grep "lib.rs" | grep -vE "${grep_filters[1]}" | cut -d: -f1 +} + +# Sets provided spec version +# input: version +set_spec_versions() { + NEW_VERSION=$1 + runtimes_list=(${@:2}) + + printf "Setting spec_version to $NEW_VERSION\n" + + for f in ${runtimes_list[@]}; do + printf " processing $f" + sed -ri "s/spec_version: [0-9]+_[0-9]+_[0-9]+,/spec_version: $NEW_VERSION,/" $f + done + + commit_with_message "Bump spec_version to $NEW_VERSION" + + git_show_log 'spec_version' +} + +# Displays formated results of the git log command +# for the given pattern which needs to be found in logs +# input: pattern, count (optional, default is 10) +git_show_log() { + PATTERN="$1" + COUNT=${2:-10} + git log --pretty=format:"%h %ad | %s%d [%an]" --graph --date=iso-strict | \ + head -n $COUNT | grep -iE "$PATTERN" --color=always -z +} + +# Get a spec_version number from the crate version +# +# ## inputs +# - v1.12.0 or 1.12.0 +# +# ## output: +# 1_012_000 or 1_012_001 if SUFFIX is set +function get_spec_version() { + INPUT=$1 + SUFFIX=${SUFFIX:-000} #this variable makes it possible to set a specific ruuntime version like 93826 it can be intialised as sestem variable + [[ $INPUT =~ .*([0-9]+\.[0-9]+\.[0-9]{1,2}).* ]] + VERSION="${BASH_REMATCH[1]}" + MATCH="${BASH_REMATCH[0]}" + if [ -z $MATCH ]; then + return 1 + else + SPEC_VERSION="$(sed -e "s/\./_0/g" -e "s/_[^_]*\$/_$SUFFIX/" <<< $VERSION)" + echo "$SPEC_VERSION" + return 0 + fi +} + +# Reorganize the prdoc files for the release +# +# input: VERSION (e.g. v1.0.0) +# output: none +reorder_prdocs() { + VERSION="$1" + + printf "[+] ℹ️ Reordering prdocs:" + + VERSION=$(sed -E 's/^v([0-9]+\.[0-9]+\.[0-9]+).*$/\1/' <<< "$VERSION") #getting reed of the 'v' prefix + mkdir -p "prdoc/$VERSION" + mv prdoc/pr_*.prdoc prdoc/$VERSION + git add -A + commit_with_message "Reordering prdocs for the release $VERSION" +} + +# Bump the binary version of the polkadot-parachain binary with the +# new bumped version and commit changes. +# +# input: version e.g. 1.16.0 +set_polkadot_parachain_binary_version() { + bumped_version="$1" + cargo_toml_file="$2" + + set_version "\(^version = \)\".*\"" $bumped_version $cargo_toml_file + + cargo update --workspace --offline # we need this to update Cargo.loc with the new versions as well + + MESSAGE="Bump versions in: ${cargo_toml_file}" + commit_with_message "$MESSAGE" + git_show_log "$MESSAGE" +} diff --git a/.github/workflows/release-branchoff-stable.yml b/.github/workflows/release-branchoff-stable.yml new file mode 100644 index 00000000000..c236a66a9fa --- /dev/null +++ b/.github/workflows/release-branchoff-stable.yml @@ -0,0 +1,105 @@ +name: Release - Branch off stable branch + +on: + workflow_dispatch: + inputs: + stable_version: + description: New stable version in the format stableYYMM + required: true + type: string + + node_version: + description: Version of the polkadot node in the format vX.XX.X (e.g. 1.15.0) + required: true + +jobs: + # TODO: Activate this job when the pipeline is moved to the fork in the `paritytech-release` org + # check-workflow-can-run: + # uses: paritytech-release/sync-workflows/.github/workflows/check-syncronization.yml@latest + + + prepare-tooling: + runs-on: ubuntu-latest + outputs: + node_version: ${{ steps.validate_inputs.outputs.node_version }} + stable_version: ${{ steps.validate_inputs.outputs.stable_version }} + + steps: + - name: Checkout sources + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + + - name: Validate inputs + id: validate_inputs + run: | + . ./.github/scripts/common/lib.sh + + node_version=$(filter_version_from_input "${{ inputs.node_version }}") + echo "node_version=${node_version}" >> $GITHUB_OUTPUT + + stable_version=$(validate_stable_tag ${{ inputs.stable_version }}) + echo "stable_version=${stable_version}" >> $GITHUB_OUTPUT + + create-stable-branch: + # needs: [check-workflow-can-run, prepare-tooling] + needs: [prepare-tooling] + # if: needs. check-workflow-can-run.outputs.checks_passed == 'true' + runs-on: ubuntu-latest + + env: + PGP_KMS_KEY: ${{ secrets.PGP_KMS_KEY }} + PGP_KMS_HASH: ${{ secrets.PGP_KMS_HASH }} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }} + STABLE_BRANCH_NAME: ${{ needs.prepare-tooling.outputs.stable_version }} + + steps: + - name: Install pgpkkms + run: | + # Install pgpkms that is used to sign commits + pip install git+https://github.com/paritytech-release/pgpkms.git@5a8f82fbb607ea102d8c178e761659de54c7af69 + + - name: Checkout sources + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + with: + ref: master + + - name: Import gpg keys + run: | + . ./.github/scripts/common/lib.sh + + import_gpg_keys + + + - name: Config git + run: | + git config --global commit.gpgsign true + git config --global gpg.program /home/runner/.local/bin/pgpkms-git + git config --global user.name "ParityReleases" + git config --global user.email "release-team@parity.io" + git config --global user.signingKey "90BD75EBBB8E95CB3DA6078F94A4029AB4B35DAE" + + - name: Create stable branch + run: | + git checkout -b "$STABLE_BRANCH_NAME" + git show-ref "$STABLE_BRANCH_NAME" + + - name: Bump versions, reorder prdocs and push stable branch + run: | + . ./.github/scripts/release/release_lib.sh + + NODE_VERSION="${{ needs.prepare-tooling.outputs.node_version }}" + set_version "\(NODE_VERSION[^=]*= \)\".*\"" $NODE_VERSION "polkadot/node/primitives/src/lib.rs" + commit_with_message "Bump node version to $NODE_VERSION in polkadot-cli" + + SPEC_VERSION=$(get_spec_version $NODE_VERSION) + runtimes_list=$(get_filtered_runtimes_list) + set_spec_versions $SPEC_VERSION "${runtimes_list[@]}" + + # TODO: clarify what to do with the polkadot-parachain binary + # Set new version for polkadot-parachain binary to match the polkadot node binary + # set_polkadot_parachain_binary_version $NODE_VERSION "cumulus/polkadot-parachain/Cargo.toml" + + reorder_prdocs $NODE_VERSION + + git push origin "$STABLE_BRANCH_NAME" -- GitLab From 986e7ae4f29f804ee4dc89aaf52984d6eda5bd0b Mon Sep 17 00:00:00 2001 From: Radha <86818441+DrW3RK@users.noreply.github.com> Date: Fri, 6 Sep 2024 10:30:49 +0200 Subject: [PATCH 015/125] Update Templates Readme - Github Repo links (#5381) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When someone downloads the Polkadot SDK repo and navigates to the templates folder, the Readme instructions do not work. There is a getting started script of the Polkadot SDK readme which can be overlooked (and also it covers only minimal template and not the parachain/solochain templates). The instructions of the Readme files are updated such that they work for anyone on https://github.com/paritytech/polkadot-sdk https://github.com/paritytech/polkadot-sdk-minimal-template https://github.com/paritytech/polkadot-sdk-parachain-template https://github.com/paritytech/polkadot-sdk-solochain-template --------- Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Co-authored-by: Bastian Köcher --- templates/minimal/README.md | 8 ++++++++ templates/parachain/README.md | 8 ++++++++ templates/solochain/README.md | 10 +++++++++- 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/templates/minimal/README.md b/templates/minimal/README.md index 180c229e744..fe1317a033c 100644 --- a/templates/minimal/README.md +++ b/templates/minimal/README.md @@ -37,6 +37,14 @@ A Polkadot SDK based project such as this one consists of: * 🛠️ Depending on your operating system and Rust version, there might be additional packages required to compile this template - please take note of the Rust compiler output. +Fetch minimal template code: + +```sh +git clone https://github.com/paritytech/polkadot-sdk-minimal-template.git minimal-template + +cd minimal-template +``` + ### Build 🔨 Use the following command to build the node without launching it: diff --git a/templates/parachain/README.md b/templates/parachain/README.md index b912d8e005c..3de85cbeb4d 100644 --- a/templates/parachain/README.md +++ b/templates/parachain/README.md @@ -39,6 +39,14 @@ A Polkadot SDK based project such as this one consists of: * 🛠️ Depending on your operating system and Rust version, there might be additional packages required to compile this template - please take note of the Rust compiler output. +Fetch parachain template code: + +```sh +git clone https://github.com/paritytech/polkadot-sdk-parachain-template.git parachain-template + +cd parachain-template +``` + ### Build 🔨 Use the following command to build the node without launching it: diff --git a/templates/solochain/README.md b/templates/solochain/README.md index 6a5a7853f9c..c4ce5c7f3fb 100644 --- a/templates/solochain/README.md +++ b/templates/solochain/README.md @@ -23,9 +23,17 @@ packages required to compile this template. Check the the most common dependencies. Alternatively, you can use one of the [alternative installation](#alternatives-installations) options. +Fetch solochain template code: + +```sh +git clone https://github.com/paritytech/polkadot-sdk-solochain-template.git solochain-template + +cd solochain-template +``` + ### Build -Use the following command to build the node without launching it: +🔨 Use the following command to build the node without launching it: ```sh cargo build --release -- GitLab From 5040b3c2186308a06bad408643a5e475df4cfeeb Mon Sep 17 00:00:00 2001 From: Andrei Eres Date: Fri, 6 Sep 2024 13:29:16 +0200 Subject: [PATCH 016/125] Fix PVF precompilation for Kusama (#5606) ![image](https://github.com/user-attachments/assets/2deaee85-67c3-4119-b0c0-d2e7f818b4ea) Because on Kusama validators.len() < discovery_keys.len() we can tweak the PVF precompilation to allow prepare PVFs when the node is an authority but not a validator. --- .../node/core/candidate-validation/src/lib.rs | 11 +++++----- .../core/candidate-validation/src/tests.rs | 21 +++++++++---------- prdoc/pr_5606.prdoc | 13 ++++++++++++ 3 files changed, 29 insertions(+), 16 deletions(-) create mode 100644 prdoc/pr_5606.prdoc diff --git a/polkadot/node/core/candidate-validation/src/lib.rs b/polkadot/node/core/candidate-validation/src/lib.rs index 103d29e8d26..a9732e93441 100644 --- a/polkadot/node/core/candidate-validation/src/lib.rs +++ b/polkadot/node/core/candidate-validation/src/lib.rs @@ -47,7 +47,7 @@ use polkadot_primitives::{ }, AuthorityDiscoveryId, CandidateCommitments, CandidateDescriptor, CandidateEvent, CandidateReceipt, ExecutorParams, Hash, OccupiedCoreAssumption, PersistedValidationData, - PvfExecKind, PvfPrepKind, SessionIndex, ValidationCode, ValidationCodeHash, + PvfExecKind, PvfPrepKind, SessionIndex, ValidationCode, ValidationCodeHash, ValidatorId, }; use sp_application_crypto::{AppCrypto, ByteArray}; use sp_keystore::KeystorePtr; @@ -427,14 +427,15 @@ where .iter() .any(|v| keystore.has_keys(&[(v.to_raw_vec(), AuthorityDiscoveryId::ID)])); - let is_present_authority = session_info - .discovery_keys + // We could've checked discovery_keys but on Kusama validators.len() < discovery_keys.len(). + let is_present_validator = session_info + .validators .iter() - .any(|v| keystore.has_keys(&[(v.to_raw_vec(), AuthorityDiscoveryId::ID)])); + .any(|v| keystore.has_keys(&[(v.to_raw_vec(), ValidatorId::ID)])); // There is still a chance to be a previous session authority, but this extra work does not // affect the finalization. - is_past_present_or_future_authority && !is_present_authority + is_past_present_or_future_authority && !is_present_validator } // Sends PVF with unknown code hashes to the validation host returning the list of code hashes sent. diff --git a/polkadot/node/core/candidate-validation/src/tests.rs b/polkadot/node/core/candidate-validation/src/tests.rs index 55282fdf4ee..0dcd84bab6c 100644 --- a/polkadot/node/core/candidate-validation/src/tests.rs +++ b/polkadot/node/core/candidate-validation/src/tests.rs @@ -25,13 +25,12 @@ use polkadot_node_subsystem::messages::AllMessages; use polkadot_node_subsystem_util::reexports::SubsystemContext; use polkadot_overseer::ActivatedLeaf; use polkadot_primitives::{ - CoreIndex, GroupIndex, HeadData, Id as ParaId, IndexedVec, SessionInfo, UpwardMessage, - ValidatorId, ValidatorIndex, + CoreIndex, GroupIndex, HeadData, Id as ParaId, SessionInfo, UpwardMessage, ValidatorId, }; use polkadot_primitives_test_helpers::{ dummy_collator, dummy_collator_signature, dummy_hash, make_valid_candidate_descriptor, }; -use sp_core::testing::TaskExecutor; +use sp_core::{sr25519::Public, testing::TaskExecutor}; use sp_keyring::Sr25519Keyring; use sp_keystore::{testing::MemoryKeystore, Keystore}; @@ -1194,10 +1193,10 @@ fn dummy_candidate_backed( ) } -fn dummy_session_info(discovery_keys: Vec) -> SessionInfo { +fn dummy_session_info(keys: Vec) -> SessionInfo { SessionInfo { - validators: IndexedVec::::from(vec![]), - discovery_keys, + validators: keys.iter().cloned().map(Into::into).collect(), + discovery_keys: keys.iter().cloned().map(Into::into).collect(), assignment_keys: vec![], validator_groups: Default::default(), n_cores: 4u32, @@ -1246,7 +1245,7 @@ fn maybe_prepare_validation_golden_path() { ctx_handle.recv().await, AllMessages::RuntimeApi(RuntimeApiMessage::Request(_, RuntimeApiRequest::SessionInfo(index, tx))) => { assert_eq!(index, 1); - let _ = tx.send(Ok(Some(dummy_session_info(vec![Sr25519Keyring::Bob.public().into()])))); + let _ = tx.send(Ok(Some(dummy_session_info(vec![Sr25519Keyring::Bob.public()])))); } ); @@ -1364,7 +1363,7 @@ fn maybe_prepare_validation_resets_state_on_a_new_session() { ctx_handle.recv().await, AllMessages::RuntimeApi(RuntimeApiMessage::Request(_, RuntimeApiRequest::SessionInfo(index, tx))) => { assert_eq!(index, 2); - let _ = tx.send(Ok(Some(dummy_session_info(vec![Sr25519Keyring::Bob.public().into()])))); + let _ = tx.send(Ok(Some(dummy_session_info(vec![Sr25519Keyring::Bob.public()])))); } ); }; @@ -1510,7 +1509,7 @@ fn maybe_prepare_validation_does_not_prepare_pvfs_if_not_a_validator_in_the_next ctx_handle.recv().await, AllMessages::RuntimeApi(RuntimeApiMessage::Request(_, RuntimeApiRequest::SessionInfo(index, tx))) => { assert_eq!(index, 1); - let _ = tx.send(Ok(Some(dummy_session_info(vec![Sr25519Keyring::Bob.public().into()])))); + let _ = tx.send(Ok(Some(dummy_session_info(vec![Sr25519Keyring::Bob.public()])))); } ); }; @@ -1557,7 +1556,7 @@ fn maybe_prepare_validation_does_not_prepare_pvfs_if_a_validator_in_the_current_ ctx_handle.recv().await, AllMessages::RuntimeApi(RuntimeApiMessage::Request(_, RuntimeApiRequest::SessionInfo(index, tx))) => { assert_eq!(index, 1); - let _ = tx.send(Ok(Some(dummy_session_info(vec![Sr25519Keyring::Alice.public().into()])))); + let _ = tx.send(Ok(Some(dummy_session_info(vec![Sr25519Keyring::Alice.public()])))); } ); }; @@ -1604,7 +1603,7 @@ fn maybe_prepare_validation_prepares_a_limited_number_of_pvfs() { ctx_handle.recv().await, AllMessages::RuntimeApi(RuntimeApiMessage::Request(_, RuntimeApiRequest::SessionInfo(index, tx))) => { assert_eq!(index, 1); - let _ = tx.send(Ok(Some(dummy_session_info(vec![Sr25519Keyring::Bob.public().into()])))); + let _ = tx.send(Ok(Some(dummy_session_info(vec![Sr25519Keyring::Bob.public()])))); } ); diff --git a/prdoc/pr_5606.prdoc b/prdoc/pr_5606.prdoc new file mode 100644 index 00000000000..46883c5722c --- /dev/null +++ b/prdoc/pr_5606.prdoc @@ -0,0 +1,13 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Fix PVF precompilation for Kusama + +doc: + - audience: Node Operator + description: | + Tweaks the PVF precompilation on Kusama to allow prepare PVFs when the node is an authority but not a validator. + +crates: + - name: polkadot-node-core-candidate-validation + bump: patch -- GitLab From b2089d88bd6ef40ca17ddfa097b05b257cdcdf13 Mon Sep 17 00:00:00 2001 From: Alexander Samusev <41779041+alvicsam@users.noreply.github.com> Date: Fri, 6 Sep 2024 15:56:29 +0200 Subject: [PATCH 017/125] [ci] Fix final job for required workflows (#5619) Currently if a required job fails the final jobs is skipped which breaks the logic of required jobs. PR fixes it. Closes https://github.com/paritytech/ci_cd/issues/1033 --- .github/workflows/build-misc.yml | 24 ++++++----- .../workflows/check-cargo-check-runtimes.yml | 14 ++++-- .github/workflows/check-runtime-migration.yml | 13 +++++- .github/workflows/checks.yml | 13 +++++- .github/workflows/tests-linux-stable.yml | 11 ++++- .github/workflows/tests-misc.yml | 43 +++++++++++-------- 6 files changed, 83 insertions(+), 35 deletions(-) diff --git a/.github/workflows/build-misc.yml b/.github/workflows/build-misc.yml index c85549b3799..a01384dc002 100644 --- a/.github/workflows/build-misc.yml +++ b/.github/workflows/build-misc.yml @@ -5,10 +5,9 @@ on: branches: - master pull_request: - types: [ opened, synchronize, reopened, ready_for_review ] + types: [opened, synchronize, reopened, ready_for_review] merge_group: - concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true @@ -41,7 +40,7 @@ jobs: build-runtimes-polkavm: timeout-minutes: 20 - needs: [ set-image ] + needs: [set-image] runs-on: ${{ needs.set-image.outputs.RUNNER }} container: image: ${{ needs.set-image.outputs.IMAGE }} @@ -65,7 +64,7 @@ jobs: build-subkey: timeout-minutes: 20 - needs: [ set-image ] + needs: [set-image] runs-on: ${{ needs.set-image.outputs.RUNNER }} container: image: ${{ needs.set-image.outputs.IMAGE }} @@ -89,10 +88,15 @@ jobs: runs-on: ubuntu-latest name: All build misc jobs passed # If any new job gets added, be sure to add it to this array - needs: - [ - build-runtimes-polkavm, - build-subkey - ] + needs: [build-runtimes-polkavm, build-subkey] + if: always() && !cancelled() steps: - - run: echo '### Good job! All the build misc tests passed 🚀' >> $GITHUB_STEP_SUMMARY \ No newline at end of file + - run: | + tee resultfile <<< '${{ toJSON(needs) }}' + FAILURES=$(cat resultfile | grep '"result": "failure"' | wc -l) + if [ $FAILURES -gt 0 ]; then + echo "### At least one required job failed ❌" >> $GITHUB_STEP_SUMMARY + exit 1 + else + echo '### Good job! All the required jobs passed 🚀' >> $GITHUB_STEP_SUMMARY + fi diff --git a/.github/workflows/check-cargo-check-runtimes.yml b/.github/workflows/check-cargo-check-runtimes.yml index ebcf6c5fc9b..6325033d214 100644 --- a/.github/workflows/check-cargo-check-runtimes.yml +++ b/.github/workflows/check-cargo-check-runtimes.yml @@ -2,8 +2,7 @@ name: Check Cargo Check Runtimes on: pull_request: - types: [ opened, synchronize, reopened, ready_for_review, labeled ] - + types: [opened, synchronize, reopened, ready_for_review, labeled] # Jobs in this workflow depend on each other, only for limiting peak amount of spawned workers @@ -132,5 +131,14 @@ jobs: - check-runtime-contracts - check-runtime-starters - check-runtime-testing + if: always() && !cancelled() steps: - - run: echo '### Good job! All the tests passed 🚀' >> $GITHUB_STEP_SUMMARY + - run: | + tee resultfile <<< '${{ toJSON(needs) }}' + FAILURES=$(cat resultfile | grep '"result": "failure"' | wc -l) + if [ $FAILURES -gt 0 ]; then + echo "### At least one required job failed ❌" >> $GITHUB_STEP_SUMMARY + exit 1 + else + echo '### Good job! All the required jobs passed 🚀' >> $GITHUB_STEP_SUMMARY + fi diff --git a/.github/workflows/check-runtime-migration.yml b/.github/workflows/check-runtime-migration.yml index 5fb9dca38d1..0a1dbc4790c 100644 --- a/.github/workflows/check-runtime-migration.yml +++ b/.github/workflows/check-runtime-migration.yml @@ -46,7 +46,7 @@ jobs: # We need to set this to rather long to allow the snapshot to be created, but the average time # should be much lower. timeout-minutes: 60 - needs: [ set-image ] + needs: [set-image] container: image: ${{ needs.set-image.outputs.IMAGE }} strategy: @@ -162,5 +162,14 @@ jobs: name: All runtime migrations passed # If any new job gets added, be sure to add it to this array needs: [check-runtime-migration] + if: always() && !cancelled() steps: - - run: echo '### Good job! All the checks passed 🚀' >> $GITHUB_STEP_SUMMARY + - run: | + tee resultfile <<< '${{ toJSON(needs) }}' + FAILURES=$(cat resultfile | grep '"result": "failure"' | wc -l) + if [ $FAILURES -gt 0 ]; then + echo "### At least one required job failed ❌" >> $GITHUB_STEP_SUMMARY + exit 1 + else + echo '### Good job! All the required jobs passed 🚀' >> $GITHUB_STEP_SUMMARY + fi diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 9aebd83282e..9de879d8367 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -12,7 +12,7 @@ concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true -permissions: { } +permissions: {} jobs: # temporary disabled because currently doesn't work in merge queue @@ -105,5 +105,14 @@ jobs: name: All checks passed # If any new job gets added, be sure to add it to this array needs: [cargo-clippy, check-try-runtime, check-core-crypto-features] + if: always() && !cancelled() steps: - - run: echo '### Good job! All the checks passed 🚀' >> $GITHUB_STEP_SUMMARY + - run: | + tee resultfile <<< '${{ toJSON(needs) }}' + FAILURES=$(cat resultfile | grep '"result": "failure"' | wc -l) + if [ $FAILURES -gt 0 ]; then + echo "### At least one required job failed ❌" >> $GITHUB_STEP_SUMMARY + exit 1 + else + echo '### Good job! All the required jobs passed 🚀' >> $GITHUB_STEP_SUMMARY + fi diff --git a/.github/workflows/tests-linux-stable.yml b/.github/workflows/tests-linux-stable.yml index 997d7622f0c..7ed67703395 100644 --- a/.github/workflows/tests-linux-stable.yml +++ b/.github/workflows/tests-linux-stable.yml @@ -138,5 +138,14 @@ jobs: test-linux-stable-runtime-benchmarks, test-linux-stable, ] + if: always() && !cancelled() steps: - - run: echo '### Good job! All the tests passed 🚀' >> $GITHUB_STEP_SUMMARY + - run: | + tee resultfile <<< '${{ toJSON(needs) }}' + FAILURES=$(cat resultfile | grep '"result": "failure"' | wc -l) + if [ $FAILURES -gt 0 ]; then + echo "### At least one required job failed ❌" >> $GITHUB_STEP_SUMMARY + exit 1 + else + echo '### Good job! All the required jobs passed 🚀' >> $GITHUB_STEP_SUMMARY + fi diff --git a/.github/workflows/tests-misc.yml b/.github/workflows/tests-misc.yml index 2e78f4a34ed..9aa6bf23727 100644 --- a/.github/workflows/tests-misc.yml +++ b/.github/workflows/tests-misc.yml @@ -5,7 +5,7 @@ on: branches: - master pull_request: - types: [ opened, synchronize, reopened, ready_for_review ] + types: [opened, synchronize, reopened, ready_for_review] merge_group: concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} @@ -43,12 +43,12 @@ jobs: echo "RUNNER=arc-runners-polkadot-sdk-beefy-persistent" >> $GITHUB_OUTPUT else echo "RUNNER=arc-runners-polkadot-sdk-beefy" >> $GITHUB_OUTPUT - fi - + fi + # more information about this job can be found here: # https://github.com/paritytech/substrate/pull/3778 test-full-crypto-feature: - needs: [ set-image ] + needs: [set-image] runs-on: ${{ needs.set-image.outputs.RUNNER }} timeout-minutes: 60 container: @@ -72,7 +72,7 @@ jobs: test-frame-examples-compile-to-wasm: timeout-minutes: 20 # into one job - needs: [ set-image, test-full-crypto-feature ] + needs: [set-image, test-full-crypto-feature] runs-on: ${{ needs.set-image.outputs.RUNNER }} container: image: ${{ needs.set-image.outputs.IMAGE }} @@ -93,7 +93,7 @@ jobs: test-frame-ui: timeout-minutes: 60 - needs: [ set-image, test-frame-examples-compile-to-wasm ] + needs: [set-image, test-frame-examples-compile-to-wasm] runs-on: ${{ needs.set-image.outputs.RUNNER }} container: image: ${{ needs.set-image.outputs.IMAGE }} @@ -121,7 +121,7 @@ jobs: test-deterministic-wasm: timeout-minutes: 20 - needs: [ set-image ] + needs: [set-image] runs-on: ${{ needs.set-image.outputs.RUNNER }} container: image: ${{ needs.set-image.outputs.IMAGE }} @@ -143,7 +143,7 @@ jobs: sha256sum -c checksum.sha256 cargo-check-benches-branches: - needs: [ set-image ] + needs: [set-image] if: ${{ github.event_name == 'pull_request' || github.event_name == 'merge_group' }} timeout-minutes: 60 outputs: @@ -156,7 +156,7 @@ jobs: echo "branch=['${{ github.base_ref }}', '${{ github.head_ref }}']" >> $GITHUB_OUTPUT cargo-check-benches: - needs: [ set-image, cargo-check-benches-branches ] + needs: [set-image, cargo-check-benches-branches] timeout-minutes: 60 strategy: matrix: @@ -191,7 +191,7 @@ jobs: node-bench-regression-guard: timeout-minutes: 20 runs-on: arc-runners-polkadot-sdk - needs: [ set-image, cargo-check-benches ] + needs: [set-image, cargo-check-benches] steps: - name: Checkout uses: actions/checkout@v4.1.7 @@ -227,7 +227,7 @@ jobs: fi test-node-metrics: - needs: [ set-image ] + needs: [set-image] timeout-minutes: 30 runs-on: ${{ needs.set-image.outputs.RUNNER }} container: @@ -260,7 +260,7 @@ jobs: # https://github.com/paritytech/substrate/pull/6916 check-tracing: timeout-minutes: 20 - needs: [ set-image, test-node-metrics ] + needs: [set-image, test-node-metrics] runs-on: ${{ needs.set-image.outputs.RUNNER }} container: image: ${{ needs.set-image.outputs.IMAGE }} @@ -275,7 +275,7 @@ jobs: check-metadata-hash: timeout-minutes: 20 - needs: [ set-image, check-tracing ] + needs: [set-image, check-tracing] runs-on: ${{ needs.set-image.outputs.RUNNER }} container: image: ${{ needs.set-image.outputs.IMAGE }} @@ -289,7 +289,7 @@ jobs: cargo-hfuzz: timeout-minutes: 20 - needs: [ set-image, check-metadata-hash ] + needs: [set-image, check-metadata-hash] runs-on: ${{ needs.set-image.outputs.RUNNER }} container: image: ${{ needs.set-image.outputs.IMAGE }} @@ -329,7 +329,7 @@ jobs: cargo-check-each-crate: timeout-minutes: 140 - needs: [ set-image ] + needs: [set-image] runs-on: ${{ needs.set-image.outputs.RUNNER }} container: image: ${{ needs.set-image.outputs.IMAGE }} @@ -338,7 +338,7 @@ jobs: CI_JOB_NAME: cargo-check-each-crate strategy: matrix: - index: [ 1,2,3,4,5,6,7 ] # 7 parallel jobs + index: [1, 2, 3, 4, 5, 6, 7] # 7 parallel jobs steps: - name: Checkout uses: actions/checkout@v4.1.7 @@ -394,5 +394,14 @@ jobs: - cargo-check-each-crate - test-deterministic-wasm # - cargo-hfuzz remove from required for now, as it's flaky + if: always() && !cancelled() steps: - - run: echo '### Good job! All the required tests passed 🚀' >> $GITHUB_STEP_SUMMARY \ No newline at end of file + - run: | + tee resultfile <<< '${{ toJSON(needs) }}' + FAILURES=$(cat resultfile | grep '"result": "failure"' | wc -l) + if [ $FAILURES -gt 0 ]; then + echo "### At least one required job failed ❌" >> $GITHUB_STEP_SUMMARY + exit 1 + else + echo '### Good job! All the required jobs passed 🚀' >> $GITHUB_STEP_SUMMARY + fi -- GitLab From 365d9928a8e71bf3467f06f89a1786f8d3d349c4 Mon Sep 17 00:00:00 2001 From: Maksym H <1177472+mordamax@users.noreply.github.com> Date: Fri, 6 Sep 2024 16:25:48 +0100 Subject: [PATCH 018/125] Update tests-misc.yml (#5615) Fixes https://github.com/paritytech/ci_cd/issues/1032 --------- Co-authored-by: Alexander Samusev <41779041+alvicsam@users.noreply.github.com> --- .github/workflows/tests-misc.yml | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/.github/workflows/tests-misc.yml b/.github/workflows/tests-misc.yml index 9aa6bf23727..8e8f5770e92 100644 --- a/.github/workflows/tests-misc.yml +++ b/.github/workflows/tests-misc.yml @@ -142,25 +142,13 @@ jobs: # confirm checksum sha256sum -c checksum.sha256 - cargo-check-benches-branches: + cargo-check-benches: needs: [set-image] if: ${{ github.event_name == 'pull_request' || github.event_name == 'merge_group' }} timeout-minutes: 60 - outputs: - branch: ${{ steps.branch.outputs.branch }} - runs-on: ubuntu-latest - steps: - - name: Branch - id: branch - run: | - echo "branch=['${{ github.base_ref }}', '${{ github.head_ref }}']" >> $GITHUB_OUTPUT - - cargo-check-benches: - needs: [set-image, cargo-check-benches-branches] - timeout-minutes: 60 strategy: matrix: - branch: ${{ fromJSON(needs.cargo-check-benches-branches.outputs.branch) }} + branch: [ master, current ] runs-on: ${{ needs.set-image.outputs.RUNNER }} container: image: ${{ needs.set-image.outputs.IMAGE }} @@ -168,7 +156,9 @@ jobs: - name: Checkout uses: actions/checkout@v4 with: - ref: ${{ matrix.branch }} + # if branch is master, use the branch, otherwise set empty string, so it uses the current context + # either PR (including forks) or merge group (main repo) + ref: ${{ matrix.branch == 'master' && matrix.branch || '' }} - name: script run: | @@ -190,6 +180,7 @@ jobs: node-bench-regression-guard: timeout-minutes: 20 + if: always() && !cancelled() runs-on: arc-runners-polkadot-sdk needs: [set-image, cargo-check-benches] steps: @@ -199,13 +190,13 @@ jobs: - name: Download artifact (master run) uses: actions/download-artifact@v4.1.8 with: - name: cargo-check-benches-${{ github.base_ref }}-${{ github.sha }} + name: cargo-check-benches-master-${{ github.sha }} path: ./artifacts/master - name: Download artifact (current run) uses: actions/download-artifact@v4.1.8 with: - name: cargo-check-benches-${{ github.head_ref }}-${{ github.sha }} + name: cargo-check-benches-current-${{ github.sha }} path: ./artifacts/current - name: script -- GitLab From 96fecc3cfcfa91dc797a94f225027a266215d6e5 Mon Sep 17 00:00:00 2001 From: clangenb <37865735+clangenb@users.noreply.github.com> Date: Sat, 7 Sep 2024 12:15:47 +0200 Subject: [PATCH 019/125] Fix occasional `alloc` not found error in `format_runtime_string!` (#5632) The macro hygiene for the `format_runtime_string!` macro was broken since https://github.com/paritytech/polkadot-sdk/pull/5010, which resulted in the following build error under certain circumstances: ```console error[E0433]: failed to resolve: use of undeclared crate or module `alloc` --> /home/clang/.cargo/registry/src/index.crates.io-6f17d22bba15001f/frame-benchmarking-36.0.0/src/v1.rs:1738:2 | 1738 | / sp_runtime::format_runtime_string!( 1739 | | "\n* Pallet: {}\n\ 1740 | | * Benchmark: {}\n\ 1741 | | * Components: {:?}\n\ ... | 1750 | | error_message, 1751 | | ) | |_____^ use of undeclared crate or module `alloc` | = note: this error originates in the macro `sp_runtime::format_runtime_string` (in Nightly builds, run with -Z macro-backtrace for more info) For more information about this error, try `rustc --explain E0433`. ``` This bug has been known already, but hasn't been fixed so far, see https://github.com/paritytech/polkadot-sdk/issues/5213 and https://substrate.stackexchange.com/questions/11786/use-of-undeclared-crate-or-module-alloc-when-upgrade-to-v1-13-0. I have made a mini rust crate that can reproduce the bug, and it also shows that this PR will fix the issue: https://github.com/clangenb/sp-runtime-string-test. --- prdoc/pr_5632.prdoc | 13 +++++++++++++ substrate/primitives/runtime/src/lib.rs | 4 +--- substrate/primitives/runtime/src/runtime_string.rs | 2 +- 3 files changed, 15 insertions(+), 4 deletions(-) create mode 100644 prdoc/pr_5632.prdoc diff --git a/prdoc/pr_5632.prdoc b/prdoc/pr_5632.prdoc new file mode 100644 index 00000000000..f76428bbc8f --- /dev/null +++ b/prdoc/pr_5632.prdoc @@ -0,0 +1,13 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Fix `alloc` not found error in `format_runtime_string!` + +doc: + - audience: Runtime Dev + description: | + Fixes the macro hygiene in the `format_runtime_string!` macro to fix the `alloc` not found build error. + +crates: + - name: sp-runtime + bump: patch diff --git a/substrate/primitives/runtime/src/lib.rs b/substrate/primitives/runtime/src/lib.rs index ba1ea376972..260c9a91855 100644 --- a/substrate/primitives/runtime/src/lib.rs +++ b/substrate/primitives/runtime/src/lib.rs @@ -49,7 +49,7 @@ extern crate alloc; #[doc(hidden)] -pub use alloc::vec::Vec; +pub use alloc::{format, vec::Vec}; #[doc(hidden)] pub use codec; #[doc(hidden)] @@ -79,8 +79,6 @@ use sp_core::{ sr25519, }; -#[cfg(all(not(feature = "std"), feature = "serde"))] -use alloc::format; use alloc::vec; use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; diff --git a/substrate/primitives/runtime/src/runtime_string.rs b/substrate/primitives/runtime/src/runtime_string.rs index 71aacf07a76..bb0347badcb 100644 --- a/substrate/primitives/runtime/src/runtime_string.rs +++ b/substrate/primitives/runtime/src/runtime_string.rs @@ -50,7 +50,7 @@ macro_rules! format_runtime_string { } #[cfg(not(feature = "std"))] { - sp_runtime::RuntimeString::Owned(alloc::format!($($args)*).as_bytes().to_vec()) + sp_runtime::RuntimeString::Owned($crate::format!($($args)*).as_bytes().to_vec()) } }}; } -- GitLab From 016421ac71574333da92a56ef7bcbef8621ccc14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Molina=20Colmenero?= Date: Sat, 7 Sep 2024 23:42:02 +0200 Subject: [PATCH 020/125] Add debugging info for `StorageWeightReclaim` (#5594) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When inspecting the logs we often encounter the following message: `Benchmarked storage weight smaller than consumed storage weight. benchmarked: {benchmarked_weight} consumed: {consumed_weight} unspent: {unspent}` However, it is very hard to guess which call is causing the issue. With the changes proposed in this PR, information about the call is provided so that we can easily identify the source of the problem without further delay, and this way work more efficiently in solving the issue. --------- Co-authored-by: Bastian Köcher --- .../primitives/storage-weight-reclaim/src/lib.rs | 6 ++++-- prdoc/pr_5594.prdoc | 13 +++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 prdoc/pr_5594.prdoc diff --git a/cumulus/primitives/storage-weight-reclaim/src/lib.rs b/cumulus/primitives/storage-weight-reclaim/src/lib.rs index a557e881e26..2529297691e 100644 --- a/cumulus/primitives/storage-weight-reclaim/src/lib.rs +++ b/cumulus/primitives/storage-weight-reclaim/src/lib.rs @@ -183,13 +183,15 @@ where if consumed_weight > benchmarked_weight { log::error!( target: LOG_TARGET, - "Benchmarked storage weight smaller than consumed storage weight. benchmarked: {benchmarked_weight} consumed: {consumed_weight} unspent: {unspent}" + "Benchmarked storage weight smaller than consumed storage weight. extrinsic: {} benchmarked: {benchmarked_weight} consumed: {consumed_weight} unspent: {unspent}", + frame_system::Pallet::::extrinsic_index().unwrap_or(0) ); current.accrue(Weight::from_parts(0, storage_size_diff), info.class) } else { log::trace!( target: LOG_TARGET, - "Reclaiming storage weight. benchmarked: {benchmarked_weight}, consumed: {consumed_weight} unspent: {unspent}" + "Reclaiming storage weight. extrinsic: {} benchmarked: {benchmarked_weight} consumed: {consumed_weight} unspent: {unspent}", + frame_system::Pallet::::extrinsic_index().unwrap_or(0) ); current.reduce(Weight::from_parts(0, storage_size_diff), info.class) } diff --git a/prdoc/pr_5594.prdoc b/prdoc/pr_5594.prdoc new file mode 100644 index 00000000000..dbdc7937b73 --- /dev/null +++ b/prdoc/pr_5594.prdoc @@ -0,0 +1,13 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "Add debugging info for `StorageWeightReclaim`" + +doc: + - audience: Runtime Dev + description: | + - Includes extrinsic index to be displayed in the logs when the consumed weight is higher than the measured one. + +crates: + - name: cumulus-primitives-storage-weight-reclaim + bump: patch -- GitLab From 868a36bd186f3ef9535ebf7deceac1b2fab19fcb Mon Sep 17 00:00:00 2001 From: PG Herveou Date: Sun, 8 Sep 2024 23:14:51 +0200 Subject: [PATCH 021/125] [pallet-revive] update generic runtime types (#5608) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix #5574 - Use U256 instead of BalanceOf and MomentOf in Ext trait - Enforce H256 for T::Hash The Ext trait still depends on the associated type `T: Config`, we can look into refactoring it even more later but even in the current state it should not influence how the data is encoded / decoded between the contract and the host ``` fn caller(&self) -> Origin; -> only use to extract the address of the caller fn account_id(&self) -> &AccountIdOf; -> only used to expose the address or access the account_id internally fn gas_meter(&self) -> &GasMeter; fn gas_meter_mut(&mut self) -> &mut GasMeter; -> encoding does not depend on T fn call_runtime(&self, call: ::RuntimeCall) -> DispatchResultWithPostInfo; -> Substrate specific, just an opaque blob of bytes from the contract's perspective fn contract_info(&mut self) -> &mut ContractInfo; fn transient_storage(&mut self) -> &mut TransientStorage; -> gated by #[cfg(any(test, feature = "runtime-benchmarks"))] ``` --------- Co-authored-by: Alexander Theißen --- prdoc/pr_5608.prdoc | 16 + substrate/bin/node/runtime/src/lib.rs | 5 - substrate/frame/revive/Cargo.toml | 1 - substrate/frame/revive/build.rs | 78 --- .../revive/fixtures/contracts/balance.rs | 9 +- .../frame/revive/fixtures/contracts/call.rs | 8 +- .../fixtures/contracts/call_return_code.rs | 10 +- .../contracts/call_runtime_and_call.rs | 8 +- .../contracts/call_with_flags_and_value.rs | 10 +- .../fixtures/contracts/call_with_limit.rs | 4 +- .../fixtures/contracts/caller_contract.rs | 4 +- .../contracts/chain_extension_temp_storage.rs | 8 +- .../fixtures/contracts/common/src/lib.rs | 27 + .../contracts/create_storage_and_call.rs | 4 +- .../create_storage_and_instantiate.rs | 6 +- .../create_transient_storage_and_call.rs | 2 +- .../fixtures/contracts/delegate_call_lib.rs | 7 +- .../contracts/destroy_and_transfer.rs | 4 +- .../frame/revive/fixtures/contracts/drain.rs | 11 +- .../contracts/event_and_return_on_deploy.rs | 3 +- .../revive/fixtures/contracts/event_size.rs | 3 +- .../contracts/instantiate_return_code.rs | 6 +- .../fixtures/contracts/read_only_call.rs | 8 +- .../revive/fixtures/contracts/recurse.rs | 8 +- .../fixtures/contracts/self_destruct.rs | 8 +- .../contracts/transfer_return_code.rs | 4 +- .../revive/src/benchmarking/call_builder.rs | 10 +- .../frame/revive/src/benchmarking/mod.rs | 194 ++---- substrate/frame/revive/src/exec.rs | 178 +++-- substrate/frame/revive/src/lib.rs | 164 ++--- substrate/frame/revive/src/migration.rs | 650 ------------------ substrate/frame/revive/src/primitives.rs | 2 - substrate/frame/revive/src/storage.rs | 8 +- substrate/frame/revive/src/storage/meter.rs | 10 +- .../frame/revive/src/test_utils/builder.rs | 3 + substrate/frame/revive/src/tests.rs | 103 +-- substrate/frame/revive/src/wasm/mod.rs | 21 +- substrate/frame/revive/src/wasm/runtime.rs | 170 ++--- substrate/frame/revive/src/weights.rs | 365 ---------- substrate/frame/revive/uapi/src/host.rs | 61 +- .../frame/revive/uapi/src/host/riscv32.rs | 94 +-- 41 files changed, 507 insertions(+), 1788 deletions(-) create mode 100644 prdoc/pr_5608.prdoc delete mode 100644 substrate/frame/revive/build.rs delete mode 100644 substrate/frame/revive/src/migration.rs diff --git a/prdoc/pr_5608.prdoc b/prdoc/pr_5608.prdoc new file mode 100644 index 00000000000..9a0748e46ba --- /dev/null +++ b/prdoc/pr_5608.prdoc @@ -0,0 +1,16 @@ +title: "[pallet-revive] update runtime types" + +doc: + - audience: Runtime Dev + description: | + Refactor the Ext trait to use U256 instead of BalanceOf or MomentOf + +crates: + - name: pallet-revive + bump: major + - name: pallet-revive-uapi + bump: patch + - name: pallet-revive-fixtures + bump: patch + + diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index 6ae04902aa8..caebd63408d 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -1417,10 +1417,6 @@ impl pallet_revive::Config for Runtime { type UploadOrigin = EnsureSigned; type InstantiateOrigin = EnsureSigned; type RuntimeHoldReason = RuntimeHoldReason; - #[cfg(not(feature = "runtime-benchmarks"))] - type Migrations = (); - #[cfg(feature = "runtime-benchmarks")] - type Migrations = pallet_revive::migration::codegen::BenchMigrations; type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent; type Debug = (); type Xcm = (); @@ -2592,7 +2588,6 @@ type Migrations = ( pallet_nomination_pools::migration::versioned::V6ToV7, pallet_alliance::migration::Migration, pallet_contracts::Migration, - pallet_revive::Migration, pallet_identity::migration::versioned::V0ToV1, ); diff --git a/substrate/frame/revive/Cargo.toml b/substrate/frame/revive/Cargo.toml index 6b7542e8920..667328ac2d0 100644 --- a/substrate/frame/revive/Cargo.toml +++ b/substrate/frame/revive/Cargo.toml @@ -3,7 +3,6 @@ name = "pallet-revive" version = "0.1.0" authors.workspace = true edition.workspace = true -build = "build.rs" license = "Apache-2.0" homepage.workspace = true repository.workspace = true diff --git a/substrate/frame/revive/build.rs b/substrate/frame/revive/build.rs deleted file mode 100644 index ca8e62df604..00000000000 --- a/substrate/frame/revive/build.rs +++ /dev/null @@ -1,78 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use std::io::Write; - -/// We start with version 2 instead of 0 when adding the pallet. -/// -/// Because otherwise we can't test any migrations since they require the storage version -/// to be lower than the pallet version in order to be triggerd. With the pallet version -/// at the minimum (0) this would not work. -const LOWEST_STORAGE_VERSION: u16 = 2; - -/// Get the latest migration version. -/// -/// Find the highest version number from the available migration files. -/// Each migration file should follow the naming convention `vXX.rs`, where `XX` is the version -/// number. -fn get_latest_version() -> u16 { - let Ok(dir) = std::fs::read_dir("src/migration") else { return LOWEST_STORAGE_VERSION }; - dir.filter_map(|entry| { - let file_name = entry.as_ref().ok()?.file_name(); - let file_name = file_name.to_str()?; - if file_name.starts_with('v') && file_name.ends_with(".rs") { - let version = &file_name[1..&file_name.len() - 3]; - let version = version.parse::().ok()?; - - // Ensure that the version matches the one defined in the file. - let path = entry.unwrap().path(); - let file_content = std::fs::read_to_string(&path).ok()?; - assert!( - file_content.contains(&format!("const VERSION: u16 = {}", version)), - "Invalid MigrationStep::VERSION in {:?}", - path - ); - - return Some(version) - } - None - }) - .max() - .unwrap_or(LOWEST_STORAGE_VERSION) -} - -/// Generates a module that exposes the latest migration version, and the benchmark migrations type. -fn main() -> Result<(), Box> { - let out_dir = std::env::var("OUT_DIR")?; - let path = std::path::Path::new(&out_dir).join("migration_codegen.rs"); - let mut f = std::fs::File::create(path)?; - let version = get_latest_version(); - write!( - f, - " - pub mod codegen {{ - use crate::NoopMigration; - /// The latest migration version, pulled from the latest migration file. - pub const LATEST_MIGRATION_VERSION: u16 = {version}; - /// The Migration Steps used for benchmarking the migration framework. - pub type BenchMigrations = (NoopMigration<{}>, NoopMigration<{version}>); - }}", - version - 1, - )?; - - Ok(()) -} diff --git a/substrate/frame/revive/fixtures/contracts/balance.rs b/substrate/frame/revive/fixtures/contracts/balance.rs index 4011b8379cb..4606135d980 100644 --- a/substrate/frame/revive/fixtures/contracts/balance.rs +++ b/substrate/frame/revive/fixtures/contracts/balance.rs @@ -18,7 +18,7 @@ #![no_std] #![no_main] -use common::output; +use common::u64_output; use uapi::{HostFn, HostFnImpl as api}; #[no_mangle] @@ -28,9 +28,6 @@ pub extern "C" fn deploy() {} #[no_mangle] #[polkavm_derive::polkavm_export] pub extern "C" fn call() { - // Initialize buffer with 1s so that we can check that it is overwritten. - output!(balance, [1u8; 8], api::balance,); - - // Assert that the balance is 0. - assert_eq!(&[0u8; 8], balance); + let balance = u64_output!(api::balance,); + assert_eq!(balance, 0); } diff --git a/substrate/frame/revive/fixtures/contracts/call.rs b/substrate/frame/revive/fixtures/contracts/call.rs index 93687441fa5..ee51548879d 100644 --- a/substrate/frame/revive/fixtures/contracts/call.rs +++ b/substrate/frame/revive/fixtures/contracts/call.rs @@ -38,10 +38,10 @@ pub extern "C" fn call() { api::call( uapi::CallFlags::empty(), callee_addr, - 0u64, // How much ref_time to devote for the execution. 0 = all. - 0u64, // How much proof_size to devote for the execution. 0 = all. - None, // No deposit limit. - &0u64.to_le_bytes(), // Value transferred to the contract. + 0u64, // How much ref_time to devote for the execution. 0 = all. + 0u64, // How much proof_size to devote for the execution. 0 = all. + None, // No deposit limit. + &[0u8; 32], // Value transferred to the contract. callee_input, None, ) diff --git a/substrate/frame/revive/fixtures/contracts/call_return_code.rs b/substrate/frame/revive/fixtures/contracts/call_return_code.rs index 29b77c343fe..25370459acb 100644 --- a/substrate/frame/revive/fixtures/contracts/call_return_code.rs +++ b/substrate/frame/revive/fixtures/contracts/call_return_code.rs @@ -21,7 +21,7 @@ #![no_std] #![no_main] -use common::input; +use common::{input, u256_bytes}; use uapi::{HostFn, HostFnImpl as api}; #[no_mangle] @@ -41,10 +41,10 @@ pub extern "C" fn call() { let err_code = match api::call( uapi::CallFlags::empty(), callee_addr, - 0u64, // How much ref_time to devote for the execution. 0 = all. - 0u64, // How much proof_size to devote for the execution. 0 = all. - None, // No deposit limit. - &100u64.to_le_bytes(), // Value transferred to the contract. + 0u64, // How much ref_time to devote for the execution. 0 = all. + 0u64, // How much proof_size to devote for the execution. 0 = all. + None, // No deposit limit. + &u256_bytes(100u64), // Value transferred to the contract. input, None, ) { diff --git a/substrate/frame/revive/fixtures/contracts/call_runtime_and_call.rs b/substrate/frame/revive/fixtures/contracts/call_runtime_and_call.rs index 7cd46849655..8c8aee96284 100644 --- a/substrate/frame/revive/fixtures/contracts/call_runtime_and_call.rs +++ b/substrate/frame/revive/fixtures/contracts/call_runtime_and_call.rs @@ -42,10 +42,10 @@ pub extern "C" fn call() { api::call( uapi::CallFlags::empty(), callee_addr, - 0u64, // How much ref_time to devote for the execution. 0 = all. - 0u64, // How much proof_size to devote for the execution. 0 = all. - None, // No deposit limit. - &0u64.to_le_bytes(), // Value transferred to the contract. + 0u64, // How much ref_time to devote for the execution. 0 = all. + 0u64, // How much proof_size to devote for the execution. 0 = all. + None, // No deposit limit. + &[0u8; 32], // Value transferred to the contract. callee_input, None, ) diff --git a/substrate/frame/revive/fixtures/contracts/call_with_flags_and_value.rs b/substrate/frame/revive/fixtures/contracts/call_with_flags_and_value.rs index c3204c29281..330393e706e 100644 --- a/substrate/frame/revive/fixtures/contracts/call_with_flags_and_value.rs +++ b/substrate/frame/revive/fixtures/contracts/call_with_flags_and_value.rs @@ -19,7 +19,7 @@ #![no_std] #![no_main] -use common::input; +use common::{input, u256_bytes}; use uapi::{HostFn, HostFnImpl as api}; #[no_mangle] @@ -40,10 +40,10 @@ pub extern "C" fn call() { api::call( uapi::CallFlags::from_bits(flags).unwrap(), callee_addr, - 0u64, // How much ref_time to devote for the execution. 0 = all. - 0u64, // How much proof_size to devote for the execution. 0 = all. - None, // No deposit limit. - &value.to_le_bytes(), // Value transferred to the contract. + 0u64, // How much ref_time to devote for the execution. 0 = all. + 0u64, // How much proof_size to devote for the execution. 0 = all. + None, // No deposit limit. + &u256_bytes(value), // Value transferred to the contract. forwarded_input, None, ) diff --git a/substrate/frame/revive/fixtures/contracts/call_with_limit.rs b/substrate/frame/revive/fixtures/contracts/call_with_limit.rs index a941aa9a342..6ab892a6b7a 100644 --- a/substrate/frame/revive/fixtures/contracts/call_with_limit.rs +++ b/substrate/frame/revive/fixtures/contracts/call_with_limit.rs @@ -43,8 +43,8 @@ pub extern "C" fn call() { callee_addr, ref_time, proof_size, - None, // No deposit limit. - &0u64.to_le_bytes(), // value transferred to the contract. + None, // No deposit limit. + &[0u8; 32], // value transferred to the contract. forwarded_input, None, ) diff --git a/substrate/frame/revive/fixtures/contracts/caller_contract.rs b/substrate/frame/revive/fixtures/contracts/caller_contract.rs index 3b83f208d62..eb29fca87c1 100644 --- a/substrate/frame/revive/fixtures/contracts/caller_contract.rs +++ b/substrate/frame/revive/fixtures/contracts/caller_contract.rs @@ -18,7 +18,7 @@ #![no_std] #![no_main] -use common::input; +use common::{input, u256_bytes}; use uapi::{HostFn, HostFnImpl as api, ReturnErrorCode}; #[no_mangle] @@ -32,7 +32,7 @@ pub extern "C" fn call() { // The value to transfer on instantiation and calls. Chosen to be greater than existential // deposit. - let value = 32768u64.to_le_bytes(); + let value = u256_bytes(32768u64); let salt = [0u8; 32]; // Callee will use the first 4 bytes of the input to return an exit status. diff --git a/substrate/frame/revive/fixtures/contracts/chain_extension_temp_storage.rs b/substrate/frame/revive/fixtures/contracts/chain_extension_temp_storage.rs index bb5c1ccbc1d..22d6c5b548d 100644 --- a/substrate/frame/revive/fixtures/contracts/chain_extension_temp_storage.rs +++ b/substrate/frame/revive/fixtures/contracts/chain_extension_temp_storage.rs @@ -54,10 +54,10 @@ pub extern "C" fn call() { api::call( uapi::CallFlags::ALLOW_REENTRY, &addr, - 0u64, // How much ref_time to devote for the execution. 0 = all. - 0u64, // How much proof_size to devote for the execution. 0 = all. - None, // No deposit limit. - &0u64.to_le_bytes(), // Value transferred to the contract. + 0u64, // How much ref_time to devote for the execution. 0 = all. + 0u64, // How much proof_size to devote for the execution. 0 = all. + None, // No deposit limit. + &[0u8; 32], // Value transferred to the contract. input, None, ) diff --git a/substrate/frame/revive/fixtures/contracts/common/src/lib.rs b/substrate/frame/revive/fixtures/contracts/common/src/lib.rs index 947247e9cf7..abfba282bec 100644 --- a/substrate/frame/revive/fixtures/contracts/common/src/lib.rs +++ b/substrate/frame/revive/fixtures/contracts/common/src/lib.rs @@ -167,3 +167,30 @@ macro_rules! unwrap_output { $host_fn($($arg,)* $output).unwrap(); }; } + +/// Call the host function and convert the [u8; 32] output to u64. +#[macro_export] +macro_rules! u64_output { + ($host_fn:path, $($arg:expr),*) => {{ + let mut buffer = [1u8; 32]; + $host_fn($($arg,)* &mut buffer); + assert!(buffer[8..].iter().all(|&x| x == 0)); + u64::from_le_bytes(buffer[..8].try_into().unwrap()) + }}; +} + +/// Convert a u64 into a [u8; 32]. +pub const fn u256_bytes(value: u64) -> [u8; 32] { + let mut buffer = [0u8; 32]; + let bytes = value.to_le_bytes(); + + buffer[0] = bytes[0]; + buffer[1] = bytes[1]; + buffer[2] = bytes[2]; + buffer[3] = bytes[3]; + buffer[4] = bytes[4]; + buffer[5] = bytes[5]; + buffer[6] = bytes[6]; + buffer[7] = bytes[7]; + buffer +} diff --git a/substrate/frame/revive/fixtures/contracts/create_storage_and_call.rs b/substrate/frame/revive/fixtures/contracts/create_storage_and_call.rs index 28d161791e5..4fa2db0c8c1 100644 --- a/substrate/frame/revive/fixtures/contracts/create_storage_and_call.rs +++ b/substrate/frame/revive/fixtures/contracts/create_storage_and_call.rs @@ -33,7 +33,7 @@ pub extern "C" fn call() { buffer, input: [u8; 4], callee: &[u8; 20], - deposit_limit: [u8; 8], + deposit_limit: &[u8; 32], ); // create 4 byte of storage before calling @@ -46,7 +46,7 @@ pub extern "C" fn call() { 0u64, // How much ref_time weight to devote for the execution. 0 = all. 0u64, // How much proof_size weight to devote for the execution. 0 = all. Some(deposit_limit), - &0u64.to_le_bytes(), // Value transferred to the contract. + &[0u8; 32], // Value transferred to the contract. input, None, ) diff --git a/substrate/frame/revive/fixtures/contracts/create_storage_and_instantiate.rs b/substrate/frame/revive/fixtures/contracts/create_storage_and_instantiate.rs index d87c2e8cd35..e1372e2eb8b 100644 --- a/substrate/frame/revive/fixtures/contracts/create_storage_and_instantiate.rs +++ b/substrate/frame/revive/fixtures/contracts/create_storage_and_instantiate.rs @@ -19,7 +19,7 @@ #![no_std] #![no_main] -use common::input; +use common::{input, u256_bytes}; use uapi::{HostFn, HostFnImpl as api}; #[no_mangle] @@ -32,10 +32,10 @@ pub extern "C" fn call() { input!( input: [u8; 4], code_hash: &[u8; 32], - deposit_limit: [u8; 8], + deposit_limit: &[u8; 32], ); - let value = 10_000u64.to_le_bytes(); + let value = u256_bytes(10_000u64); let salt = [0u8; 32]; let mut address = [0u8; 20]; diff --git a/substrate/frame/revive/fixtures/contracts/create_transient_storage_and_call.rs b/substrate/frame/revive/fixtures/contracts/create_transient_storage_and_call.rs index 753490cf26b..d2efb26e5ce 100644 --- a/substrate/frame/revive/fixtures/contracts/create_transient_storage_and_call.rs +++ b/substrate/frame/revive/fixtures/contracts/create_transient_storage_and_call.rs @@ -52,7 +52,7 @@ pub extern "C" fn call() { 0u64, // How much ref_time weight to devote for the execution. 0 = all. 0u64, // How much proof_size weight to devote for the execution. 0 = all. None, - &0u64.to_le_bytes(), // Value transferred to the contract. + &[0u8; 32], // Value transferred to the contract. input, None, ) diff --git a/substrate/frame/revive/fixtures/contracts/delegate_call_lib.rs b/substrate/frame/revive/fixtures/contracts/delegate_call_lib.rs index c5525423a9e..95c1bd2aa6c 100644 --- a/substrate/frame/revive/fixtures/contracts/delegate_call_lib.rs +++ b/substrate/frame/revive/fixtures/contracts/delegate_call_lib.rs @@ -18,7 +18,7 @@ #![no_std] #![no_main] -use common::output; +use common::u64_output; use uapi::{HostFn, HostFnImpl as api, StorageFlags}; #[no_mangle] @@ -39,9 +39,8 @@ pub extern "C" fn call() { // Assert that `value_transferred` is equal to the value // passed to the `caller` contract: 1337. - output!(value_transferred, [0u8; 8], api::value_transferred,); - let value_transferred = u64::from_le_bytes(value_transferred[..].try_into().unwrap()); - assert_eq!(value_transferred, 1337); + let value = u64_output!(api::value_transferred,); + assert_eq!(value, 1337); // Assert that ALICE is the caller of the contract. let mut caller = [0u8; 20]; diff --git a/substrate/frame/revive/fixtures/contracts/destroy_and_transfer.rs b/substrate/frame/revive/fixtures/contracts/destroy_and_transfer.rs index 4959a5e2e0c..d381db8e398 100644 --- a/substrate/frame/revive/fixtures/contracts/destroy_and_transfer.rs +++ b/substrate/frame/revive/fixtures/contracts/destroy_and_transfer.rs @@ -18,11 +18,11 @@ #![no_std] #![no_main] -use common::input; +use common::{input, u256_bytes}; use uapi::{HostFn, HostFnImpl as api, StorageFlags}; const ADDRESS_KEY: [u8; 32] = [0u8; 32]; -const VALUE: [u8; 8] = [0, 0, 1u8, 0, 0, 0, 0, 0]; +const VALUE: [u8; 32] = u256_bytes(65536); #[no_mangle] #[polkavm_derive::polkavm_export] diff --git a/substrate/frame/revive/fixtures/contracts/drain.rs b/substrate/frame/revive/fixtures/contracts/drain.rs index b46d4f7c841..0d644a4238c 100644 --- a/substrate/frame/revive/fixtures/contracts/drain.rs +++ b/substrate/frame/revive/fixtures/contracts/drain.rs @@ -18,7 +18,7 @@ #![no_std] #![no_main] -use common::output; +use common::{u256_bytes, u64_output}; use uapi::{HostFn, HostFnImpl as api}; #[no_mangle] @@ -28,17 +28,14 @@ pub extern "C" fn deploy() {} #[no_mangle] #[polkavm_derive::polkavm_export] pub extern "C" fn call() { - output!(balance, [0u8; 8], api::balance,); - let balance = u64::from_le_bytes(balance[..].try_into().unwrap()); - - output!(minimum_balance, [0u8; 8], api::minimum_balance,); - let minimum_balance = u64::from_le_bytes(minimum_balance[..].try_into().unwrap()); + let balance = u64_output!(api::balance,); + let minimum_balance = u64_output!(api::minimum_balance,); // Make the transferred value exceed the balance by adding the minimum balance. let balance = balance + minimum_balance; // Try to self-destruct by sending more balance to the 0 address. // The call will fail because a contract transfer has a keep alive requirement. - let res = api::transfer(&[0u8; 20], &balance.to_le_bytes()); + let res = api::transfer(&[0u8; 20], &u256_bytes(balance)); assert!(matches!(res, Err(uapi::ReturnErrorCode::TransferFailed))); } diff --git a/substrate/frame/revive/fixtures/contracts/event_and_return_on_deploy.rs b/substrate/frame/revive/fixtures/contracts/event_and_return_on_deploy.rs index 9186835d291..5c438c1a75a 100644 --- a/substrate/frame/revive/fixtures/contracts/event_and_return_on_deploy.rs +++ b/substrate/frame/revive/fixtures/contracts/event_and_return_on_deploy.rs @@ -25,7 +25,8 @@ use uapi::{HostFn, HostFnImpl as api}; #[polkavm_derive::polkavm_export] pub extern "C" fn deploy() { let buffer = [1u8, 2, 3, 4]; - api::deposit_event(&[0u8; 0], &buffer); + let topics = [[42u8; 32]; 1]; + api::deposit_event(&topics, &buffer); api::return_value(uapi::ReturnFlags::empty(), &buffer); } diff --git a/substrate/frame/revive/fixtures/contracts/event_size.rs b/substrate/frame/revive/fixtures/contracts/event_size.rs index 2b56de4bd3f..7f04ae42765 100644 --- a/substrate/frame/revive/fixtures/contracts/event_size.rs +++ b/substrate/frame/revive/fixtures/contracts/event_size.rs @@ -33,6 +33,7 @@ pub extern "C" fn call() { input!(len: u32,); let data = &BUFFER[..len as usize]; + let topics = [[0u8; 32]; 0]; - api::deposit_event(&[0u8; 0], data); + api::deposit_event(&topics, data); } diff --git a/substrate/frame/revive/fixtures/contracts/instantiate_return_code.rs b/substrate/frame/revive/fixtures/contracts/instantiate_return_code.rs index a81ffea943d..c5736850960 100644 --- a/substrate/frame/revive/fixtures/contracts/instantiate_return_code.rs +++ b/substrate/frame/revive/fixtures/contracts/instantiate_return_code.rs @@ -18,7 +18,7 @@ #![no_std] #![no_main] -use common::input; +use common::{input, u256_bytes}; use uapi::{HostFn, HostFnImpl as api}; #[no_mangle] @@ -36,8 +36,8 @@ pub extern "C" fn call() { 0u64, // How much ref_time weight to devote for the execution. 0 = all. 0u64, /* How much proof_size weight to devote for the execution. 0 = * all. */ - None, // No deposit limit. - &10_000u64.to_le_bytes(), // Value to transfer. + None, // No deposit limit. + &u256_bytes(10_000u64), // Value to transfer. input, None, None, diff --git a/substrate/frame/revive/fixtures/contracts/read_only_call.rs b/substrate/frame/revive/fixtures/contracts/read_only_call.rs index 7476b7a8366..ea74d56867f 100644 --- a/substrate/frame/revive/fixtures/contracts/read_only_call.rs +++ b/substrate/frame/revive/fixtures/contracts/read_only_call.rs @@ -39,10 +39,10 @@ pub extern "C" fn call() { api::call( uapi::CallFlags::READ_ONLY, callee_addr, - 0u64, // How much ref_time to devote for the execution. 0 = all. - 0u64, // How much proof_size to devote for the execution. 0 = all. - None, // No deposit limit. - &0u64.to_le_bytes(), // Value transferred to the contract. + 0u64, // How much ref_time to devote for the execution. 0 = all. + 0u64, // How much proof_size to devote for the execution. 0 = all. + None, // No deposit limit. + &[0u8; 32], // Value transferred to the contract. callee_input, None, ) diff --git a/substrate/frame/revive/fixtures/contracts/recurse.rs b/substrate/frame/revive/fixtures/contracts/recurse.rs index c15784b7f24..2e70d67d8c7 100644 --- a/substrate/frame/revive/fixtures/contracts/recurse.rs +++ b/substrate/frame/revive/fixtures/contracts/recurse.rs @@ -43,10 +43,10 @@ pub extern "C" fn call() { api::call( uapi::CallFlags::ALLOW_REENTRY, &addr, - 0u64, // How much ref_time to devote for the execution. 0 = all. - 0u64, // How much deposit_limit to devote for the execution. 0 = all. - None, // No deposit limit. - &0u64.to_le_bytes(), // Value transferred to the contract. + 0u64, // How much ref_time to devote for the execution. 0 = all. + 0u64, // How much deposit_limit to devote for the execution. 0 = all. + None, // No deposit limit. + &[0u8; 32], // Value transferred to the contract. &(calls_left - 1).to_le_bytes(), None, ) diff --git a/substrate/frame/revive/fixtures/contracts/self_destruct.rs b/substrate/frame/revive/fixtures/contracts/self_destruct.rs index 0e1e4d30e6f..524979991ec 100644 --- a/substrate/frame/revive/fixtures/contracts/self_destruct.rs +++ b/substrate/frame/revive/fixtures/contracts/self_destruct.rs @@ -42,10 +42,10 @@ pub extern "C" fn call() { api::call( uapi::CallFlags::ALLOW_REENTRY, &addr, - 0u64, // How much ref_time to devote for the execution. 0 = all. - 0u64, // How much proof_size to devote for the execution. 0 = all. - None, // No deposit limit. - &0u64.to_le_bytes(), // Value to transfer. + 0u64, // How much ref_time to devote for the execution. 0 = all. + 0u64, // How much proof_size to devote for the execution. 0 = all. + None, // No deposit limit. + &[0u8; 32], // Value to transfer. &[0u8; 0], None, ) diff --git a/substrate/frame/revive/fixtures/contracts/transfer_return_code.rs b/substrate/frame/revive/fixtures/contracts/transfer_return_code.rs index 3e1f2757c27..bfeca9b8b4a 100644 --- a/substrate/frame/revive/fixtures/contracts/transfer_return_code.rs +++ b/substrate/frame/revive/fixtures/contracts/transfer_return_code.rs @@ -18,7 +18,7 @@ #![no_std] #![no_main] -extern crate common; +use common::u256_bytes; use uapi::{HostFn, HostFnImpl as api}; #[no_mangle] @@ -28,7 +28,7 @@ pub extern "C" fn deploy() {} #[no_mangle] #[polkavm_derive::polkavm_export] pub extern "C" fn call() { - let ret_code = match api::transfer(&[0u8; 20], &100u64.to_le_bytes()) { + let ret_code = match api::transfer(&[0u8; 20], &u256_bytes(100u64)) { Ok(_) => 0u32, Err(code) => code as u32, }; diff --git a/substrate/frame/revive/src/benchmarking/call_builder.rs b/substrate/frame/revive/src/benchmarking/call_builder.rs index c000817a8a3..020a578c3a3 100644 --- a/substrate/frame/revive/src/benchmarking/call_builder.rs +++ b/substrate/frame/revive/src/benchmarking/call_builder.rs @@ -22,12 +22,14 @@ use crate::{ storage::meter::Meter, transient_storage::MeterEntry, wasm::{ApiVersion, PreparedCall, Runtime}, - BalanceOf, Config, DebugBuffer, Error, GasMeter, Origin, TypeInfo, WasmBlob, Weight, + BalanceOf, Config, DebugBuffer, Error, GasMeter, MomentOf, Origin, TypeInfo, WasmBlob, Weight, }; use alloc::{vec, vec::Vec}; use codec::{Encode, HasCompact}; use core::fmt::Debug; use frame_benchmarking::benchmarking; +use frame_support::traits::IsType; +use sp_core::{H256, U256}; type StackExt<'a, T> = Stack<'a, T, WasmBlob>; @@ -48,6 +50,9 @@ impl Default for CallSetup where T: Config + pallet_balances::Config, as HasCompact>::Type: Clone + Eq + PartialEq + Debug + TypeInfo + Encode, + BalanceOf: Into + TryFrom, + T::Hash: IsType, + MomentOf: Into, { fn default() -> Self { Self::new(WasmModule::dummy()) @@ -57,7 +62,10 @@ where impl CallSetup where T: Config + pallet_balances::Config, + T::Hash: IsType, as HasCompact>::Type: Clone + Eq + PartialEq + Debug + TypeInfo + Encode, + BalanceOf: Into + TryFrom, + MomentOf: Into, { /// Setup a new call for the given module. pub fn new(module: WasmModule) -> Self { diff --git a/substrate/frame/revive/src/benchmarking/mod.rs b/substrate/frame/revive/src/benchmarking/mod.rs index 2c528562284..8cdd7da5db9 100644 --- a/substrate/frame/revive/src/benchmarking/mod.rs +++ b/substrate/frame/revive/src/benchmarking/mod.rs @@ -25,7 +25,6 @@ use self::{call_builder::CallSetup, code::WasmModule}; use crate::{ exec::{Key, MomentOf}, limits, - migration::codegen::LATEST_MIGRATION_VERSION, storage::WriteOutcome, Pallet as Contracts, *, }; @@ -34,7 +33,6 @@ use codec::{Encode, MaxEncodedLen}; use frame_benchmarking::v2::*; use frame_support::{ self, assert_ok, - pallet_prelude::StorageVersion, storage::child, traits::{fungible::InspectHold, Currency}, weights::{Weight, WeightMeter}, @@ -65,14 +63,21 @@ const UNBALANCED_TRIE_LAYERS: u32 = 20; struct Contract { caller: T::AccountId, account_id: T::AccountId, - addr: T::AccountId, } impl Contract where T: Config + pallet_balances::Config, as HasCompact>::Type: Clone + Eq + PartialEq + Debug + TypeInfo + Encode, + BalanceOf: Into + TryFrom, + MomentOf: Into, + T::Hash: IsType, { + /// Returns the address of the contract. + fn address(&self) -> H160 { + T::AddressMapper::to_address(&self.account_id) + } + /// Create new contract and use a default account id as instantiator. fn new(module: WasmModule, data: Vec) -> Result, &'static str> { Self::with_index(0, module, data) @@ -110,7 +115,7 @@ where let address = outcome.result?.addr; let account_id = T::AddressMapper::to_account_id_contract(&address); - let result = Contract { caller, account_id: account_id.clone(), addr: account_id }; + let result = Contract { caller, account_id: account_id.clone() }; ContractInfoOf::::insert(&address, result.info()?); @@ -216,9 +221,12 @@ fn default_deposit_limit() -> BalanceOf { #[benchmarks( where - as codec::HasCompact>::Type: Clone + Eq + PartialEq + core::fmt::Debug + scale_info::TypeInfo + codec::Encode, + as HasCompact>::Type: Clone + Eq + PartialEq + Debug + TypeInfo + Encode, + BalanceOf: From< as Currency>::Balance> + Into + TryFrom, T: Config + pallet_balances::Config, - BalanceOf: From< as Currency>::Balance>, + MomentOf: Into, + ::RuntimeEvent: From>, + T::Hash: IsType, as Currency>::Balance: From>, )] mod benchmarks { @@ -246,73 +254,6 @@ mod benchmarks { Ok(()) } - // This benchmarks the weight of executing Migration::migrate to execute a noop migration. - #[benchmark(pov_mode = Measured)] - fn migration_noop() { - let version = LATEST_MIGRATION_VERSION; - StorageVersion::new(version).put::>(); - #[block] - { - Migration::::migrate(&mut WeightMeter::new()); - } - assert_eq!(StorageVersion::get::>(), version); - } - - // This benchmarks the weight of dispatching migrate to execute 1 `NoopMigration` - #[benchmark(pov_mode = Measured)] - fn migrate() { - let latest_version = LATEST_MIGRATION_VERSION; - StorageVersion::new(latest_version - 2).put::>(); - as frame_support::traits::OnRuntimeUpgrade>::on_runtime_upgrade(); - - #[extrinsic_call] - _(RawOrigin::Signed(whitelisted_caller()), Weight::MAX); - - assert_eq!(StorageVersion::get::>(), latest_version - 1); - } - - // This benchmarks the weight of running on_runtime_upgrade when there are no migration in - // progress. - #[benchmark(pov_mode = Measured)] - fn on_runtime_upgrade_noop() { - let latest_version = LATEST_MIGRATION_VERSION; - StorageVersion::new(latest_version).put::>(); - #[block] - { - as frame_support::traits::OnRuntimeUpgrade>::on_runtime_upgrade(); - } - assert!(MigrationInProgress::::get().is_none()); - } - - // This benchmarks the weight of running on_runtime_upgrade when there is a migration in - // progress. - #[benchmark(pov_mode = Measured)] - fn on_runtime_upgrade_in_progress() { - let latest_version = LATEST_MIGRATION_VERSION; - StorageVersion::new(latest_version - 2).put::>(); - let v = vec![42u8].try_into().ok(); - MigrationInProgress::::set(v.clone()); - #[block] - { - as frame_support::traits::OnRuntimeUpgrade>::on_runtime_upgrade(); - } - assert!(MigrationInProgress::::get().is_some()); - assert_eq!(MigrationInProgress::::get(), v); - } - - // This benchmarks the weight of running on_runtime_upgrade when there is a migration to - // process. - #[benchmark(pov_mode = Measured)] - fn on_runtime_upgrade() { - let latest_version = LATEST_MIGRATION_VERSION; - StorageVersion::new(latest_version - 2).put::>(); - #[block] - { - as frame_support::traits::OnRuntimeUpgrade>::on_runtime_upgrade(); - } - assert!(MigrationInProgress::::get().is_some()); - } - // This benchmarks the overhead of loading a code of size `c` byte from storage and into // the execution engine. This does **not** include the actual execution for which the gas meter // is responsible. This is achieved by generating all code to the `deploy` function @@ -326,7 +267,7 @@ mod benchmarks { let instance = Contract::::with_caller(whitelisted_caller(), WasmModule::sized(c), vec![])?; let value = Pallet::::min_balance(); - let callee = T::AddressMapper::to_address(&instance.addr); + let callee = T::AddressMapper::to_address(&instance.account_id); let storage_deposit = default_deposit_limit::(); #[extrinsic_call] @@ -434,7 +375,7 @@ mod benchmarks { Contract::::with_caller(whitelisted_caller(), WasmModule::dummy(), vec![])?; let value = Pallet::::min_balance(); let origin = RawOrigin::Signed(instance.caller.clone()); - let callee = T::AddressMapper::to_address(&instance.addr); + let callee = T::AddressMapper::to_address(&instance.account_id); let before = T::Currency::balance(&instance.account_id); let storage_deposit = default_deposit_limit::(); #[extrinsic_call] @@ -510,7 +451,7 @@ mod benchmarks { let storage_deposit = default_deposit_limit::(); let hash = >::bare_upload_code(origin.into(), code, storage_deposit)?.code_hash; - let callee = T::AddressMapper::to_address(&instance.addr); + let callee = T::AddressMapper::to_address(&instance.account_id); assert_ne!(instance.info()?.code_hash, hash); #[extrinsic_call] _(RawOrigin::Root, callee, hash); @@ -661,85 +602,67 @@ mod benchmarks { #[benchmark(pov_mode = Measured)] fn seal_balance() { - let len = ::max_encoded_len() as u32; - build_runtime!(runtime, memory: [len.to_le_bytes(), vec![0u8; len as _], ]); + build_runtime!(runtime, memory: [[0u8;32], ]); let result; #[block] { - result = runtime.bench_balance(memory.as_mut_slice(), 4, 0); + result = runtime.bench_balance(memory.as_mut_slice(), 0); } assert_ok!(result); - assert_eq!( - ::decode(&mut &memory[4..]).unwrap(), - runtime.ext().balance().into() - ); + assert_eq!(U256::from_little_endian(&memory[..]), runtime.ext().balance()); } #[benchmark(pov_mode = Measured)] fn seal_value_transferred() { - let len = ::max_encoded_len() as u32; - build_runtime!(runtime, memory: [len.to_le_bytes(), vec![0u8; len as _], ]); + build_runtime!(runtime, memory: [[0u8;32], ]); let result; #[block] { - result = runtime.bench_value_transferred(memory.as_mut_slice(), 4, 0); + result = runtime.bench_value_transferred(memory.as_mut_slice(), 0); } assert_ok!(result); - assert_eq!( - ::decode(&mut &memory[4..]).unwrap(), - runtime.ext().value_transferred().into() - ); + assert_eq!(U256::from_little_endian(&memory[..]), runtime.ext().value_transferred()); } #[benchmark(pov_mode = Measured)] fn seal_minimum_balance() { - let len = ::max_encoded_len() as u32; - build_runtime!(runtime, memory: [len.to_le_bytes(), vec![0u8; len as _], ]); + build_runtime!(runtime, memory: [[0u8;32], ]); let result; #[block] { - result = runtime.bench_minimum_balance(memory.as_mut_slice(), 4, 0); + result = runtime.bench_minimum_balance(memory.as_mut_slice(), 0); } assert_ok!(result); - assert_eq!( - ::decode(&mut &memory[4..]).unwrap(), - runtime.ext().minimum_balance().into() - ); + assert_eq!(U256::from_little_endian(&memory[..]), runtime.ext().minimum_balance()); } #[benchmark(pov_mode = Measured)] fn seal_block_number() { - let len = as MaxEncodedLen>::max_encoded_len() as u32; - build_runtime!(runtime, memory: [len.to_le_bytes(), vec![0u8; len as _], ]); + build_runtime!(runtime, memory: [[0u8;32], ]); let result; #[block] { - result = runtime.bench_block_number(memory.as_mut_slice(), 4, 0); + result = runtime.bench_block_number(memory.as_mut_slice(), 0); } assert_ok!(result); - assert_eq!( - >::decode(&mut &memory[4..]).unwrap(), - runtime.ext().block_number() - ); + assert_eq!(U256::from_little_endian(&memory[..]), runtime.ext().block_number()); } #[benchmark(pov_mode = Measured)] fn seal_now() { - let len = as MaxEncodedLen>::max_encoded_len() as u32; - build_runtime!(runtime, memory: [len.to_le_bytes(), vec![0u8; len as _], ]); + build_runtime!(runtime, memory: [[0u8;32], ]); let result; #[block] { - result = runtime.bench_now(memory.as_mut_slice(), 4, 0); + result = runtime.bench_now(memory.as_mut_slice(), 0); } assert_ok!(result); - assert_eq!(>::decode(&mut &memory[4..]).unwrap(), *runtime.ext().now()); + assert_eq!(U256::from_little_endian(&memory[..]), runtime.ext().now()); } #[benchmark(pov_mode = Measured)] fn seal_weight_to_fee() { - let len = ::max_encoded_len() as u32; - build_runtime!(runtime, memory: [len.to_le_bytes(), vec![0u8; len as _], ]); + build_runtime!(runtime, memory: [[0u8;32], ]); let weight = Weight::from_parts(500_000, 300_000); let result; #[block] @@ -748,15 +671,11 @@ mod benchmarks { memory.as_mut_slice(), weight.ref_time(), weight.proof_size(), - 4, 0, ); } assert_ok!(result); - assert_eq!( - >::decode(&mut &memory[4..]).unwrap(), - runtime.ext().get_weight_price(weight) - ); + assert_eq!(U256::from_little_endian(&memory[..]), runtime.ext().get_weight_price(weight)); } #[benchmark(pov_mode = Measured)] @@ -828,28 +747,37 @@ mod benchmarks { t: Linear<0, { limits::NUM_EVENT_TOPICS as u32 }>, n: Linear<0, { limits::PAYLOAD_BYTES }>, ) { - let topics = (0..t).map(|i| T::Hashing::hash_of(&i)).collect::>().encode(); - let topics_len = topics.len() as u32; - - build_runtime!(runtime, memory: [ - n.to_le_bytes(), - topics, - vec![0u8; n as _], - ]); + let num_topic = t as u32; + let topics = (0..t).map(|i| H256::repeat_byte(i as u8)).collect::>(); + let topics_data = + topics.iter().flat_map(|hash| hash.as_bytes().to_vec()).collect::>(); + let data = vec![42u8; n as _]; + build_runtime!(runtime, instance, memory: [ topics_data, data, ]); let result; #[block] { result = runtime.bench_deposit_event( memory.as_mut_slice(), - 4, // topics_ptr - topics_len, // topics_len - 4 + topics_len, // data_ptr - 0, // data_len + 0, // topics_ptr + num_topic, + topics_data.len() as u32, // data_ptr + n, // data_len ); } - assert_ok!(result); + + let events = System::::events(); + let record = &events[events.len() - 1]; + + assert_eq!( + record.event, + crate::Event::ContractEmitted { contract: instance.address(), data }.into(), + ); + assert_eq!( + record.topics.iter().map(|t| H256::from_slice(t.as_ref())).collect::>(), + topics, + ); } // Benchmark debug_message call @@ -1435,7 +1363,7 @@ mod benchmarks { let account_bytes = account.encode(); let account_len = account_bytes.len() as u32; - let value_bytes = value.encode(); + let value_bytes = Into::::into(value).encode(); let mut memory = memory!(account_bytes, value_bytes,); let result; @@ -1461,10 +1389,10 @@ mod benchmarks { let callee_len = callee_bytes.len() as u32; let value: BalanceOf = t.into(); - let value_bytes = value.encode(); + let value_bytes = Into::::into(value).encode(); let deposit: BalanceOf = (u32::MAX - 100).into(); - let deposit_bytes = deposit.encode(); + let deposit_bytes = Into::::into(deposit).encode(); let deposit_len = deposit_bytes.len() as u32; let mut setup = CallSetup::::default(); @@ -1536,11 +1464,11 @@ mod benchmarks { let hash_len = hash_bytes.len() as u32; let value: BalanceOf = 1u32.into(); - let value_bytes = value.encode(); + let value_bytes = Into::::into(value).encode(); let value_len = value_bytes.len() as u32; let deposit: BalanceOf = 0u32.into(); - let deposit_bytes = deposit.encode(); + let deposit_bytes = Into::::into(deposit).encode(); let deposit_len = deposit_bytes.len() as u32; let mut setup = CallSetup::::default(); diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index 649479f7790..016bdec37af 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -37,7 +37,7 @@ use frame_support::{ traits::{ fungible::{Inspect, Mutate}, tokens::{Fortitude, Preservation}, - Contains, OriginTrait, Time, + Contains, IsType, OriginTrait, Time, }, weights::Weight, Blake2_128Concat, BoundedVec, StorageHasher, @@ -49,7 +49,7 @@ use frame_system::{ use sp_core::{ ecdsa::Public as ECDSAPublic, sr25519::{Public as SR25519Public, Signature as SR25519Signature}, - ConstU32, Get, H160, H256, + ConstU32, Get, H160, H256, U256, }; use sp_io::{crypto::secp256k1_ecdsa_recover_compressed, hashing::blake2_256}; use sp_runtime::{ @@ -61,9 +61,6 @@ pub type AccountIdOf = ::AccountId; pub type MomentOf = <::Time as Time>::Moment; pub type ExecResult = Result; -/// A type that represents a topic of an event. At the moment a hash is used. -pub type TopicOf = ::Hash; - /// Type for variable sized storage key. Used for transparent hashing. type VarSizedKey = BoundedVec>; @@ -184,9 +181,9 @@ pub trait Ext: sealing::Sealed { fn call( &mut self, gas_limit: Weight, - deposit_limit: BalanceOf, + deposit_limit: U256, to: &H160, - value: BalanceOf, + value: U256, input_data: Vec, allows_reentry: bool, read_only: bool, @@ -209,9 +206,9 @@ pub trait Ext: sealing::Sealed { fn instantiate( &mut self, gas_limit: Weight, - deposit_limit: BalanceOf, + deposit_limit: U256, code: H256, - value: BalanceOf, + value: U256, input_data: Vec, salt: Option<&[u8; 32]>, ) -> Result<(H160, ExecReturnValue), ExecError>; @@ -226,7 +223,7 @@ pub trait Ext: sealing::Sealed { fn terminate(&mut self, beneficiary: &H160) -> DispatchResult; /// Transfer some amount of funds into the specified account. - fn transfer(&mut self, to: &H160, value: BalanceOf) -> DispatchResult; + fn transfer(&mut self, to: &H160, value: U256) -> DispatchResult; /// Returns the storage entry of the executing account by the given `key`. /// @@ -304,30 +301,30 @@ pub trait Ext: sealing::Sealed { /// Returns the balance of the current contract. /// /// The `value_transferred` is already added. - fn balance(&self) -> BalanceOf; + fn balance(&self) -> U256; /// Returns the value transferred along with this call. - fn value_transferred(&self) -> BalanceOf; + fn value_transferred(&self) -> U256; - /// Returns a reference to the timestamp of the current block - fn now(&self) -> &MomentOf; + /// Returns the timestamp of the current block + fn now(&self) -> U256; /// Returns the minimum balance that is required for creating an account. - fn minimum_balance(&self) -> BalanceOf; + fn minimum_balance(&self) -> U256; /// Deposit an event with the given topics. /// /// There should not be any duplicates in `topics`. - fn deposit_event(&mut self, topics: Vec>, data: Vec); + fn deposit_event(&mut self, topics: Vec, data: Vec); /// Returns the current block number. - fn block_number(&self) -> BlockNumberFor; + fn block_number(&self) -> U256; /// Returns the maximum allowed size of a storage item. fn max_value_size(&self) -> u32; /// Returns the price for the specified amount of weight. - fn get_weight_price(&self, weight: Weight) -> BalanceOf; + fn get_weight_price(&self, weight: Weight) -> U256; /// Get an immutable reference to the nested gas meter. fn gas_meter(&self) -> &GasMeter; @@ -697,6 +694,9 @@ impl CachedContract { impl<'a, T, E> Stack<'a, T, E> where T: Config, + T::Hash: IsType, + BalanceOf: Into + TryFrom, + MomentOf: Into, E: Executable, { /// Create and run a new call stack by calling into `dest`. @@ -1239,16 +1239,19 @@ where impl<'a, T, E> Ext for Stack<'a, T, E> where T: Config, + T::Hash: IsType, E: Executable, + BalanceOf: Into + TryFrom, + MomentOf: Into, { type T = T; fn call( &mut self, gas_limit: Weight, - deposit_limit: BalanceOf, + deposit_limit: U256, dest: &H160, - value: BalanceOf, + value: U256, input_data: Vec, allows_reentry: bool, read_only: bool, @@ -1277,9 +1280,9 @@ where }); let executable = self.push_frame( FrameArgs::Call { dest, cached_info, delegated_call: None }, - value, + value.try_into().map_err(|_| Error::::BalanceConversionFailed)?, gas_limit, - deposit_limit, + deposit_limit.try_into().map_err(|_| Error::::BalanceConversionFailed)?, // Enable read-only access if requested; cannot disable it if already set. read_only || self.is_read_only(), )?; @@ -1322,9 +1325,9 @@ where fn instantiate( &mut self, gas_limit: Weight, - deposit_limit: BalanceOf, + deposit_limit: U256, code_hash: H256, - value: BalanceOf, + value: U256, input_data: Vec, salt: Option<&[u8; 32]>, ) -> Result<(H160, ExecReturnValue), ExecError> { @@ -1337,9 +1340,9 @@ where salt, input_data: input_data.as_ref(), }, - value, + value.try_into().map_err(|_| Error::::BalanceConversionFailed)?, gas_limit, - deposit_limit, + deposit_limit.try_into().map_err(|_| Error::::BalanceConversionFailed)?, self.is_read_only(), )?; let address = T::AddressMapper::to_address(&self.top_frame().account_id); @@ -1374,12 +1377,12 @@ where Ok(()) } - fn transfer(&mut self, to: &H160, value: BalanceOf) -> DispatchResult { + fn transfer(&mut self, to: &H160, value: U256) -> DispatchResult { Self::transfer( Preservation::Preserve, &self.top_frame().account_id, &T::AddressMapper::to_account_id(to), - value, + value.try_into().map_err(|_| Error::::BalanceConversionFailed)?, ) } @@ -1462,27 +1465,28 @@ where self.caller_is_origin() && self.origin == Origin::Root } - fn balance(&self) -> BalanceOf { + fn balance(&self) -> U256 { T::Currency::reducible_balance( &self.top_frame().account_id, Preservation::Preserve, Fortitude::Polite, ) + .into() } - fn value_transferred(&self) -> BalanceOf { - self.top_frame().value_transferred + fn value_transferred(&self) -> U256 { + self.top_frame().value_transferred.into() } - fn now(&self) -> &MomentOf { - &self.timestamp + fn now(&self) -> U256 { + self.timestamp.into() } - fn minimum_balance(&self) -> BalanceOf { - T::Currency::minimum_balance() + fn minimum_balance(&self) -> U256 { + T::Currency::minimum_balance().into() } - fn deposit_event(&mut self, topics: Vec, data: Vec) { + fn deposit_event(&mut self, topics: Vec, data: Vec) { Contracts::::deposit_indexed_event( topics, Event::ContractEmitted { @@ -1492,16 +1496,16 @@ where ); } - fn block_number(&self) -> BlockNumberFor { - self.block_number + fn block_number(&self) -> U256 { + self.block_number.into() } fn max_value_size(&self) -> u32 { limits::PAYLOAD_BYTES } - fn get_weight_price(&self, weight: Weight) -> BalanceOf { - T::WeightPrice::convert(weight) + fn get_weight_price(&self, weight: Weight) -> U256 { + T::WeightPrice::convert(weight).into() } fn gas_meter(&self) -> &GasMeter { @@ -1864,7 +1868,7 @@ mod tests { let value = 55; let success_ch = MockLoader::insert(Call, move |ctx, _| { - assert_eq!(ctx.ext.value_transferred(), value); + assert_eq!(ctx.ext.value_transferred(), U256::from(value)); Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() }) }); @@ -1896,12 +1900,12 @@ mod tests { let value = 35; let success_ch = MockLoader::insert(Call, move |ctx, _| { - assert_eq!(ctx.ext.value_transferred(), value); + assert_eq!(ctx.ext.value_transferred(), U256::from(value)); Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() }) }); let delegate_ch = MockLoader::insert(Call, move |ctx, _| { - assert_eq!(ctx.ext.value_transferred(), value); + assert_eq!(ctx.ext.value_transferred(), U256::from(value)); let _ = ctx.ext.delegate_call(success_ch, Vec::new())?; Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() }) }); @@ -2112,9 +2116,9 @@ mod tests { // Try to call into yourself. let r = ctx.ext.call( Weight::zero(), - BalanceOf::::zero(), + U256::zero(), &BOB_ADDR, - 0, + U256::zero(), vec![], true, false, @@ -2175,9 +2179,9 @@ mod tests { assert_matches!( ctx.ext.call( Weight::zero(), - BalanceOf::::zero(), + U256::zero(), &CHARLIE_ADDR, - 0, + U256::zero(), vec![], true, false @@ -2316,9 +2320,9 @@ mod tests { // BOB calls CHARLIE ctx.ext.call( Weight::zero(), - BalanceOf::::zero(), + U256::zero(), &CHARLIE_ADDR, - 0, + U256::zero(), vec![], true, false, @@ -2410,9 +2414,9 @@ mod tests { // BOB calls CHARLIE. ctx.ext.call( Weight::zero(), - BalanceOf::::zero(), + U256::zero(), &CHARLIE_ADDR, - 0, + U256::zero(), vec![], true, false, @@ -2448,9 +2452,9 @@ mod tests { assert_matches!( ctx.ext.call( Weight::zero(), - BalanceOf::::zero(), + U256::zero(), &CHARLIE_ADDR, - 0, + U256::zero(), vec![], true, false @@ -2621,9 +2625,9 @@ mod tests { .ext .instantiate( Weight::zero(), - BalanceOf::::zero(), + U256::zero(), dummy_ch, - ::Currency::minimum_balance(), + ::Currency::minimum_balance().into(), vec![], Some(&[48; 32]), ) @@ -2699,9 +2703,9 @@ mod tests { assert_matches!( ctx.ext.instantiate( Weight::zero(), - BalanceOf::::zero(), + U256::zero(), dummy_ch, - ::Currency::minimum_balance(), + ::Currency::minimum_balance().into(), vec![], Some(&[0; 32]), ), @@ -2804,9 +2808,9 @@ mod tests { assert_eq!( ctx.ext.call( Weight::zero(), - BalanceOf::::zero(), + U256::zero(), &CHARLIE_ADDR, - 0, + U256::zero(), vec![], true, false @@ -2820,15 +2824,7 @@ mod tests { let code_charlie = MockLoader::insert(Call, |ctx, _| { assert!(ctx .ext - .call( - Weight::zero(), - BalanceOf::::zero(), - &BOB_ADDR, - 0, - vec![99], - true, - false - ) + .call(Weight::zero(), U256::zero(), &BOB_ADDR, U256::zero(), vec![99], true, false) .is_ok()); exec_trapped() }); @@ -2860,7 +2856,7 @@ mod tests { let addr = ::AddressMapper::to_address(&account_id); assert_matches!( - ctx.ext.call(Weight::zero(), BalanceOf::::zero(), &addr, 0, vec![], + ctx.ext.call(Weight::zero(), U256::zero(), &addr, U256::zero(), vec![], true, false), Err(ExecError{error, ..}) if error == >::ContractNotFound.into() ); exec_success() @@ -2998,7 +2994,7 @@ mod tests { let code_bob = MockLoader::insert(Call, |ctx, _| { let dest = H160::from_slice(ctx.input_data.as_ref()); ctx.ext - .call(Weight::zero(), BalanceOf::::zero(), &dest, 0, vec![], false, false) + .call(Weight::zero(), U256::zero(), &dest, U256::zero(), vec![], false, false) }); let code_charlie = MockLoader::insert(Call, |_, _| exec_success()); @@ -3043,9 +3039,9 @@ mod tests { if ctx.input_data[0] == 0 { ctx.ext.call( Weight::zero(), - BalanceOf::::zero(), + U256::zero(), &CHARLIE_ADDR, - 0, + U256::zero(), vec![], false, false, @@ -3059,9 +3055,9 @@ mod tests { let code_charlie = MockLoader::insert(Call, |ctx, _| { ctx.ext.call( Weight::zero(), - BalanceOf::::zero(), + U256::zero(), &BOB_ADDR, - 0, + U256::zero(), vec![1], true, false, @@ -3251,7 +3247,7 @@ mod tests { ctx.ext .instantiate( Weight::zero(), - BalanceOf::::zero(), + U256::zero(), fail_code, ctx.ext.minimum_balance() * 100, vec![], @@ -3268,7 +3264,7 @@ mod tests { .ext .instantiate( Weight::zero(), - BalanceOf::::zero(), + U256::zero(), success_code, ctx.ext.minimum_balance() * 100, vec![], @@ -3284,7 +3280,7 @@ mod tests { // a plain call should not influence the account counter ctx.ext - .call(Weight::zero(), BalanceOf::::zero(), &addr, 0, vec![], false, false) + .call(Weight::zero(), U256::zero(), &addr, U256::zero(), vec![], false, false) .unwrap(); assert_eq!(System::account_nonce(ALICE), alice_nonce); @@ -3822,9 +3818,9 @@ mod tests { assert_eq!( ctx.ext.call( Weight::zero(), - BalanceOf::::zero(), + U256::zero(), &CHARLIE_ADDR, - 0, + U256::zero(), vec![], true, false, @@ -3849,15 +3845,7 @@ mod tests { let code_charlie = MockLoader::insert(Call, |ctx, _| { assert!(ctx .ext - .call( - Weight::zero(), - BalanceOf::::zero(), - &BOB_ADDR, - 0, - vec![99], - true, - false - ) + .call(Weight::zero(), U256::zero(), &BOB_ADDR, U256::zero(), vec![99], true, false) .is_ok()); // CHARLIE can not read BOB`s storage. assert_eq!(ctx.ext.get_transient_storage(storage_key_1), None); @@ -3934,9 +3922,9 @@ mod tests { assert_eq!( ctx.ext.call( Weight::zero(), - BalanceOf::::zero(), + U256::zero(), &CHARLIE_ADDR, - 0, + U256::zero(), vec![], true, false @@ -3957,15 +3945,7 @@ mod tests { let code_charlie = MockLoader::insert(Call, |ctx, _| { assert!(ctx .ext - .call( - Weight::zero(), - BalanceOf::::zero(), - &BOB_ADDR, - 0, - vec![99], - true, - false - ) + .call(Weight::zero(), U256::zero(), &BOB_ADDR, U256::zero(), vec![99], true, false) .is_ok()); exec_trapped() }); diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 393acc8c985..4c6e5cd26a1 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -27,7 +27,10 @@ mod benchmarking_dummy; mod exec; mod gas; mod primitives; +use crate::exec::MomentOf; +use frame_support::traits::IsType; pub use primitives::*; +use sp_core::U256; mod limits; mod storage; @@ -36,7 +39,6 @@ mod wasm; pub mod chain_extension; pub mod debug; -pub mod migration; pub mod test_utils; pub mod weights; @@ -54,7 +56,7 @@ use environmental::*; use frame_support::{ dispatch::{ DispatchErrorWithPostInfo, DispatchResultWithPostInfo, GetDispatchInfo, Pays, - PostDispatchInfo, RawOrigin, WithPostDispatchInfo, + PostDispatchInfo, RawOrigin, }, ensure, traits::{ @@ -79,7 +81,6 @@ use sp_runtime::{ pub use crate::{ address::{AddressMapper, DefaultAddressMapper}, debug::Tracing, - migration::{MigrateSequence, Migration, NoopMigration}, pallet::*, }; pub use weights::WeightInfo; @@ -129,6 +130,7 @@ pub mod pallet { use crate::debug::Debugger; use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; + use sp_core::U256; use sp_runtime::Perbill; /// The in-code storage version. @@ -206,7 +208,7 @@ pub mod pallet { /// /// # Note /// - /// It is safe to chage this value on a live chain as all refunds are pro rata. + /// It is safe to change this value on a live chain as all refunds are pro rata. #[pallet::constant] #[pallet::no_default_bounds] type DepositPerByte: Get>; @@ -215,7 +217,7 @@ pub mod pallet { /// /// # Note /// - /// It is safe to chage this value on a live chain as all refunds are pro rata. + /// It is safe to change this value on a live chain as all refunds are pro rata. #[pallet::constant] #[pallet::no_default_bounds] type DepositPerItem: Get>; @@ -271,25 +273,6 @@ pub mod pallet { #[pallet::no_default_bounds] type InstantiateOrigin: EnsureOrigin; - /// The sequence of migration steps that will be applied during a migration. - /// - /// # Examples - /// ```ignore - /// use pallet_revive::migration::{v10, v11}; - /// # struct Runtime {}; - /// # struct Currency {}; - /// type Migrations = (v10::Migration, v11::Migration); - /// ``` - /// - /// If you have a single migration step, you can use a tuple with a single element: - /// ```ignore - /// use pallet_revive::migration::v10; - /// # struct Runtime {}; - /// # struct Currency {}; - /// type Migrations = (v10::Migration,); - /// ``` - type Migrations: MigrateSequence; - /// For most production chains, it's recommended to use the `()` implementation of this /// trait. This implementation offers additional logging when the log target /// "runtime::revive" is set to trace. @@ -305,13 +288,13 @@ pub mod pallet { BlockNumberFor, >; - /// The amount of memory in bytes that parachain nodes alot to the runtime. + /// The amount of memory in bytes that parachain nodes a lot to the runtime. /// /// This is used in [`Pallet::integrity_test`] to make sure that the runtime has enough /// memory to support this pallet if set to the correct value. type RuntimeMemory: Get; - /// The amount of memory in bytes that relay chain validators alot to the PoV. + /// The amount of memory in bytes that relay chain validators a lot to the PoV. /// /// This is used in [`Pallet::integrity_test`] to make sure that the runtime has enough /// memory to support this pallet if set to the correct value. @@ -382,7 +365,6 @@ pub mod pallet { type DepositPerByte = DepositPerByte; type DepositPerItem = DepositPerItem; type MaxCodeLen = ConstU32<{ 123 * 1024 }>; - type Migrations = (); type Time = Self; type UnsafeUnstableInterface = ConstBool; type UploadOrigin = EnsureSigned; @@ -549,10 +531,6 @@ pub mod pallet { /// A more detailed error can be found on the node console if debug messages are enabled /// by supplying `-lruntime::revive=debug`. CodeRejected, - /// A pending migration needs to complete before the extrinsic can be called. - MigrationInProgress, - /// Migrate dispatch call was attempted but no migration was performed. - NoMigrationPerformed, /// The contract has reached its maximum number of delegate dependencies. MaxDelegateDependenciesReached, /// The dependency was not found in the contract's delegate dependencies. @@ -569,6 +547,8 @@ pub mod pallet { InvalidStorageFlags, /// PolkaVM failed during code execution. Probably due to a malformed program. ExecutionFailed, + /// Failed to convert a U256 to a Balance. + BalanceConversionFailed, } /// A reason for the pallet contracts placing a hold on funds. @@ -605,12 +585,6 @@ pub mod pallet { pub(crate) type DeletionQueueCounter = StorageValue<_, DeletionQueueManager, ValueQuery>; - /// A migration can span across multiple blocks. This storage defines a cursor to track the - /// progress of the migration, enabling us to resume from the last completed position. - #[pallet::storage] - pub(crate) type MigrationInProgress = - StorageValue<_, migration::Cursor, OptionQuery>; - #[pallet::extra_constants] impl Pallet { #[pallet::constant_name(ApiVersion)] @@ -620,31 +594,17 @@ pub mod pallet { } #[pallet::hooks] - impl Hooks> for Pallet { + impl Hooks> for Pallet + where + T::Hash: IsType, + { fn on_idle(_block: BlockNumberFor, limit: Weight) -> Weight { - use migration::MigrateResult::*; let mut meter = WeightMeter::with_limit(limit); - - loop { - match Migration::::migrate(&mut meter) { - // There is not enough weight to perform a migration. - // We can't do anything more, so we return the used weight. - NoMigrationPerformed | InProgress { steps_done: 0 } => return meter.consumed(), - // Migration is still in progress, we can start the next step. - InProgress { .. } => continue, - // Either no migration is in progress, or we are done with all migrations, we - // can do some more other work with the remaining weight. - Completed | NoMigrationInProgress => break, - } - } - ContractInfo::::process_deletion_queue_batch(&mut meter); meter.consumed() } fn integrity_test() { - Migration::::integrity_test(); - // Total runtime memory limit let max_runtime_mem: u32 = T::RuntimeMemory::get(); // Memory limits for a single contract: @@ -771,7 +731,10 @@ pub mod pallet { #[pallet::call] impl Pallet where + T::Hash: IsType, as HasCompact>::Type: Clone + Eq + PartialEq + Debug + TypeInfo + Encode, + BalanceOf: Into + TryFrom, + MomentOf: Into, { /// Makes a call to an account, optionally transferring some balance. /// @@ -957,7 +920,6 @@ pub mod pallet { origin: OriginFor, code_hash: sp_core::H256, ) -> DispatchResultWithPostInfo { - Migration::::ensure_migrated()?; let origin = ensure_signed(origin)?; >::remove(&origin, code_hash)?; // we waive the fee because removing unused code is beneficial @@ -981,7 +943,6 @@ pub mod pallet { dest: H160, code_hash: sp_core::H256, ) -> DispatchResult { - Migration::::ensure_migrated()?; ensure_root(origin)?; >::try_mutate(&dest, |contract| { let contract = if let Some(contract) = contract { @@ -1000,40 +961,6 @@ pub mod pallet { Ok(()) }) } - - /// When a migration is in progress, this dispatchable can be used to run migration steps. - /// Calls that contribute to advancing the migration have their fees waived, as it's helpful - /// for the chain. Note that while the migration is in progress, the pallet will also - /// leverage the `on_idle` hooks to run migration steps. - #[pallet::call_index(6)] - #[pallet::weight(T::WeightInfo::migrate().saturating_add(*weight_limit))] - pub fn migrate(origin: OriginFor, weight_limit: Weight) -> DispatchResultWithPostInfo { - use migration::MigrateResult::*; - ensure_signed(origin)?; - - let weight_limit = weight_limit.saturating_add(T::WeightInfo::migrate()); - let mut meter = WeightMeter::with_limit(weight_limit); - let result = Migration::::migrate(&mut meter); - - match result { - Completed => Ok(PostDispatchInfo { - actual_weight: Some(meter.consumed()), - pays_fee: Pays::No, - }), - InProgress { steps_done, .. } if steps_done > 0 => Ok(PostDispatchInfo { - actual_weight: Some(meter.consumed()), - pays_fee: Pays::No, - }), - InProgress { .. } => Ok(PostDispatchInfo { - actual_weight: Some(meter.consumed()), - pays_fee: Pays::Yes, - }), - NoMigrationInProgress | NoMigrationPerformed => { - let err: DispatchError = >::NoMigrationPerformed.into(); - Err(err.with_weight(meter.consumed())) - }, - } - } } } @@ -1053,7 +980,12 @@ fn dispatch_result( .map_err(|e| DispatchErrorWithPostInfo { post_info, error: e }) } -impl Pallet { +impl Pallet +where + BalanceOf: Into + TryFrom, + MomentOf: Into, + T::Hash: IsType, +{ /// A generalized version of [`Self::call`]. /// /// Identical to [`Self::call`] but tailored towards being called by other code within the @@ -1078,7 +1010,6 @@ impl Pallet { None }; let try_call = || { - Migration::::ensure_migrated()?; let origin = Origin::from_runtime_origin(origin)?; let mut storage_meter = StorageMeter::new(&origin, storage_deposit_limit, value)?; let result = ExecStack::>::run_call( @@ -1131,7 +1062,6 @@ impl Pallet { let mut debug_message = if debug == DebugInfo::UnsafeDebug { Some(DebugBuffer::default()) } else { None }; let try_instantiate = || { - Migration::::ensure_migrated()?; let instantiate_account = T::InstantiateOrigin::ensure_origin(origin.clone())?; let (executable, upload_deposit) = match code { Code::Upload(code) => { @@ -1192,7 +1122,6 @@ impl Pallet { code: Vec, storage_deposit_limit: BalanceOf, ) -> CodeUploadResult> { - Migration::::ensure_migrated()?; let origin = T::UploadOrigin::ensure_origin(origin)?; let (module, deposit) = Self::try_upload_code(origin, code, storage_deposit_limit, None)?; Ok(CodeUploadReturnValue { code_hash: *module.code_hash(), deposit }) @@ -1200,9 +1129,6 @@ impl Pallet { /// Query storage of a specified contract under a specified key. pub fn get_storage(address: H160, key: [u8; 32]) -> GetStorageResult { - if Migration::::in_progress() { - return Err(ContractAccessError::MigrationInProgress) - } let contract_info = ContractInfoOf::::get(&address).ok_or(ContractAccessError::DoesntExist)?; @@ -1226,24 +1152,6 @@ impl Pallet { Ok((module, deposit)) } - /// Deposit a pallet contracts event. - fn deposit_event(event: Event) { - >::deposit_event(::RuntimeEvent::from(event)) - } - - /// Deposit a pallet contracts indexed event. - fn deposit_indexed_event(topics: Vec, event: Event) { - >::deposit_event_indexed( - &topics, - ::RuntimeEvent::from(event).into(), - ) - } - - /// Return the existential deposit of [`Config::Currency`]. - fn min_balance() -> BalanceOf { - >>::minimum_balance() - } - /// Run the supplied function `f` if no other instance of this pallet is on the stack. fn run_guarded Result>(f: F) -> Result { executing_contract::using_once(&mut false, || { @@ -1264,6 +1172,30 @@ impl Pallet { } } +impl Pallet +where + T: Config, + T::Hash: IsType, +{ + /// Return the existential deposit of [`Config::Currency`]. + fn min_balance() -> BalanceOf { + >>::minimum_balance() + } + + /// Deposit a pallet contracts event. + fn deposit_event(event: Event) { + >::deposit_event(::RuntimeEvent::from(event)) + } + + /// Deposit a pallet contracts indexed event. + fn deposit_indexed_event(topics: Vec, event: Event) { + >::deposit_event_indexed( + &topics.into_iter().map(Into::into).collect::>(), + ::RuntimeEvent::from(event).into(), + ) + } +} + // Set up a global reference to the boolean flag used for the re-entrancy guard. environmental!(executing_contract: bool); diff --git a/substrate/frame/revive/src/migration.rs b/substrate/frame/revive/src/migration.rs deleted file mode 100644 index b67467b322f..00000000000 --- a/substrate/frame/revive/src/migration.rs +++ /dev/null @@ -1,650 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Multi-block Migration framework for pallet-revive. -//! -//! This module allows us to define a migration as a sequence of [`MigrationStep`]s that can be -//! executed across multiple blocks. -//! -//! # Usage -//! -//! A migration step is defined under `src/migration/vX.rs`, where `X` is the version number. -//! For example, `vX.rs` defines a migration from version `X - 1` to version `X`. -//! -//! ## Example: -//! -//! To configure a migration to `v11` for a runtime using `v10` of pallet-revive on the chain, -//! you would set the `Migrations` type as follows: -//! -//! ```ignore -//! use pallet_revive::migration::{v10, v11}; -//! # pub enum Runtime {}; -//! # struct Currency; -//! type Migrations = (v10::Migration, v11::Migration); -//! ``` -//! -//! ## Notes: -//! -//! - Migrations should always be tested with `try-runtime` before being deployed. -//! - By testing with `try-runtime` against a live network, you ensure that all migration steps work -//! and that you have included the required steps. -//! -//! ## Low Level / Implementation Details -//! -//! When a migration starts and [`OnRuntimeUpgrade::on_runtime_upgrade`] is called, instead of -//! performing the actual migration, we set a custom storage item [`MigrationInProgress`]. -//! This storage item defines a [`Cursor`] for the current migration. -//! -//! If the [`MigrationInProgress`] storage item exists, it means a migration is in progress, and its -//! value holds a cursor for the current migration step. These migration steps are executed during -//! [`Hooks::on_idle`] or when the [`Pallet::migrate`] dispatchable is -//! called. -//! -//! While the migration is in progress, all dispatchables except `migrate`, are blocked, and returns -//! a `MigrationInProgress` error. - -include!(concat!(env!("OUT_DIR"), "/migration_codegen.rs")); - -use crate::{weights::WeightInfo, Config, Error, MigrationInProgress, Pallet, Weight, LOG_TARGET}; -use codec::{Codec, Decode}; -use core::marker::PhantomData; -use frame_support::{ - pallet_prelude::*, - traits::{ConstU32, OnRuntimeUpgrade}, - weights::WeightMeter, -}; -use sp_runtime::Saturating; - -#[cfg(feature = "try-runtime")] -use alloc::vec::Vec; -#[cfg(feature = "try-runtime")] -use sp_runtime::TryRuntimeError; - -const PROOF_ENCODE: &str = "Tuple::max_encoded_len() < Cursor::max_encoded_len()` is verified in `Self::integrity_test()`; qed"; -const PROOF_DECODE: &str = - "We encode to the same type in this trait only. No other code touches this item; qed"; - -fn invalid_version(version: StorageVersion) -> ! { - panic!("Required migration {version:?} not supported by this runtime. This is a bug."); -} - -/// The cursor used to encode the position (usually the last iterated key) of the current migration -/// step. -pub type Cursor = BoundedVec>; - -/// IsFinished describes whether a migration is finished or not. -pub enum IsFinished { - Yes, - No, -} - -/// A trait that allows to migrate storage from one version to another. -/// -/// The migration is done in steps. The migration is finished when -/// `step()` returns `IsFinished::Yes`. -pub trait MigrationStep: Codec + MaxEncodedLen + Default { - /// Returns the version of the migration. - const VERSION: u16; - - /// Returns the maximum weight that can be consumed in a single step. - fn max_step_weight() -> Weight; - - /// Process one step of the migration. - /// - /// Returns whether the migration is finished. - fn step(&mut self, meter: &mut WeightMeter) -> IsFinished; - - /// Verify that the migration step fits into `Cursor`, and that `max_step_weight` is not greater - /// than `max_block_weight`. - fn integrity_test(max_block_weight: Weight) { - if Self::max_step_weight().any_gt(max_block_weight) { - panic!( - "Invalid max_step_weight for Migration {}. Value should be lower than {}", - Self::VERSION, - max_block_weight - ); - } - - let len = ::max_encoded_len(); - let max = Cursor::bound(); - if len > max { - panic!( - "Migration {} has size {} which is bigger than the maximum of {}", - Self::VERSION, - len, - max, - ); - } - } - - /// Execute some pre-checks prior to running the first step of this migration. - #[cfg(feature = "try-runtime")] - fn pre_upgrade_step() -> Result, TryRuntimeError> { - Ok(Vec::new()) - } - - /// Execute some post-checks after running the last step of this migration. - #[cfg(feature = "try-runtime")] - fn post_upgrade_step(_state: Vec) -> Result<(), TryRuntimeError> { - Ok(()) - } -} - -/// A noop migration that can be used when there is no migration to be done for a given version. -#[doc(hidden)] -#[derive(frame_support::DefaultNoBound, Encode, Decode, MaxEncodedLen)] -pub struct NoopMigration; - -impl MigrationStep for NoopMigration { - const VERSION: u16 = N; - fn max_step_weight() -> Weight { - Weight::zero() - } - fn step(&mut self, _meter: &mut WeightMeter) -> IsFinished { - log::debug!(target: LOG_TARGET, "Noop migration for version {}", N); - IsFinished::Yes - } -} - -mod private { - use crate::migration::MigrationStep; - pub trait Sealed {} - #[impl_trait_for_tuples::impl_for_tuples(10)] - #[tuple_types_custom_trait_bound(MigrationStep)] - impl Sealed for Tuple {} -} - -/// Defines a sequence of migrations. -/// -/// The sequence must be defined by a tuple of migrations, each of which must implement the -/// `MigrationStep` trait. Migrations must be ordered by their versions with no gaps. -pub trait MigrateSequence: private::Sealed { - /// Returns the range of versions that this migrations sequence can handle. - /// Migrations must be ordered by their versions with no gaps. - /// - /// The following code will fail to compile: - /// - /// ```compile_fail - /// # use pallet_revive::{NoopMigration, MigrateSequence}; - /// let _ = <(NoopMigration<1>, NoopMigration<3>)>::VERSION_RANGE; - /// ``` - /// The following code will compile: - /// ``` - /// # use pallet_revive::{NoopMigration, MigrateSequence}; - /// let _ = <(NoopMigration<1>, NoopMigration<2>)>::VERSION_RANGE; - /// ``` - const VERSION_RANGE: (u16, u16); - - /// Returns the default cursor for the given version. - fn new(version: StorageVersion) -> Cursor; - - #[cfg(feature = "try-runtime")] - fn pre_upgrade_step(_version: StorageVersion) -> Result, TryRuntimeError> { - Ok(Vec::new()) - } - - #[cfg(feature = "try-runtime")] - fn post_upgrade_step(_version: StorageVersion, _state: Vec) -> Result<(), TryRuntimeError> { - Ok(()) - } - - /// Execute the migration step until the available weight is consumed. - fn steps(version: StorageVersion, cursor: &[u8], meter: &mut WeightMeter) -> StepResult; - - /// Verify that the migration step fits into `Cursor`, and that `max_step_weight` is not greater - /// than `max_block_weight`. - fn integrity_test(max_block_weight: Weight); - - /// Returns whether migrating from `in_storage` to `target` is supported. - /// - /// A migration is supported if `VERSION_RANGE` is (in_storage + 1, target). - fn is_upgrade_supported(in_storage: StorageVersion, target: StorageVersion) -> bool { - let (low, high) = Self::VERSION_RANGE; - target == high && in_storage + 1 == low - } -} - -/// Performs all necessary migrations based on `StorageVersion`. -/// -/// If `TEST_ALL_STEPS == true` and `try-runtime` is enabled, this will run all the migrations -/// inside `on_runtime_upgrade`. This should be set to false in tests that want to ensure the step -/// by step migration works. -pub struct Migration(PhantomData); - -#[cfg(feature = "try-runtime")] -impl Migration { - fn run_all_steps() -> Result<(), TryRuntimeError> { - let mut meter = &mut WeightMeter::new(); - let name = >::name(); - loop { - let in_progress_version = >::on_chain_storage_version() + 1; - let state = T::Migrations::pre_upgrade_step(in_progress_version)?; - let before = meter.consumed(); - let status = Self::migrate(&mut meter); - log::info!( - target: LOG_TARGET, - "{name}: Migration step {:?} weight = {}", - in_progress_version, - meter.consumed() - before - ); - T::Migrations::post_upgrade_step(in_progress_version, state)?; - if matches!(status, MigrateResult::Completed) { - break - } - } - - let name = >::name(); - log::info!(target: LOG_TARGET, "{name}: Migration steps weight = {}", meter.consumed()); - Ok(()) - } -} - -impl OnRuntimeUpgrade for Migration { - fn on_runtime_upgrade() -> Weight { - let name = >::name(); - let in_code_version = >::in_code_storage_version(); - let on_chain_version = >::on_chain_storage_version(); - - if on_chain_version == in_code_version { - log::warn!( - target: LOG_TARGET, - "{name}: No Migration performed storage_version = latest_version = {:?}", - &on_chain_version - ); - return T::WeightInfo::on_runtime_upgrade_noop() - } - - // In case a migration is already in progress we create the next migration - // (if any) right when the current one finishes. - if Self::in_progress() { - log::warn!( - target: LOG_TARGET, - "{name}: Migration already in progress {:?}", - &on_chain_version - ); - - return T::WeightInfo::on_runtime_upgrade_in_progress() - } - - log::info!( - target: LOG_TARGET, - "{name}: Upgrading storage from {on_chain_version:?} to {in_code_version:?}.", - ); - - let cursor = T::Migrations::new(on_chain_version + 1); - MigrationInProgress::::set(Some(cursor)); - - #[cfg(feature = "try-runtime")] - if TEST_ALL_STEPS { - Self::run_all_steps().unwrap(); - } - - T::WeightInfo::on_runtime_upgrade() - } - - #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result, TryRuntimeError> { - // We can't really do much here as our migrations do not happen during the runtime upgrade. - // Instead, we call the migrations `pre_upgrade` and `post_upgrade` hooks when we iterate - // over our migrations. - let on_chain_version = >::on_chain_storage_version(); - let in_code_version = >::in_code_storage_version(); - - if on_chain_version == in_code_version { - return Ok(Default::default()) - } - - log::debug!( - target: LOG_TARGET, - "Requested migration of {} from {:?}(on-chain storage version) to {:?}(in-code storage version)", - >::name(), on_chain_version, in_code_version - ); - - ensure!( - T::Migrations::is_upgrade_supported(on_chain_version, in_code_version), - "Unsupported upgrade: VERSION_RANGE should be (on-chain storage version + 1, in-code storage version)" - ); - - Ok(Default::default()) - } - - #[cfg(feature = "try-runtime")] - fn post_upgrade(_state: Vec) -> Result<(), TryRuntimeError> { - if !TEST_ALL_STEPS { - return Ok(()) - } - - log::info!(target: LOG_TARGET, "=== POST UPGRADE CHECKS ==="); - - // Ensure that the hashing algorithm is correct for each storage map. - if let Some(hash) = crate::CodeInfoOf::::iter_keys().next() { - crate::CodeInfoOf::::get(hash).expect("CodeInfo exists for hash; qed"); - } - if let Some(hash) = crate::PristineCode::::iter_keys().next() { - crate::PristineCode::::get(hash).expect("PristineCode exists for hash; qed"); - } - if let Some(account_id) = crate::ContractInfoOf::::iter_keys().next() { - crate::ContractInfoOf::::get(account_id) - .expect("ContractInfo exists for account_id; qed"); - } - if let Some(nonce) = crate::DeletionQueue::::iter_keys().next() { - crate::DeletionQueue::::get(nonce).expect("DeletionQueue exists for nonce; qed"); - } - - Ok(()) - } -} - -/// The result of running the migration. -#[derive(Debug, PartialEq)] -pub enum MigrateResult { - /// No migration was performed - NoMigrationPerformed, - /// No migration currently in progress - NoMigrationInProgress, - /// A migration is in progress - InProgress { steps_done: u32 }, - /// All migrations are completed - Completed, -} - -/// The result of running a migration step. -#[derive(Debug, PartialEq)] -pub enum StepResult { - InProgress { cursor: Cursor, steps_done: u32 }, - Completed { steps_done: u32 }, -} - -impl Migration { - /// Verify that each migration's step of the [`Config::Migrations`] sequence fits into - /// `Cursor`. - pub(crate) fn integrity_test() { - let max_weight = ::BlockWeights::get().max_block; - T::Migrations::integrity_test(max_weight) - } - - /// Execute the multi-step migration. - /// Returns whether or not a migration is in progress - pub(crate) fn migrate(mut meter: &mut WeightMeter) -> MigrateResult { - let name = >::name(); - - if meter.try_consume(T::WeightInfo::migrate()).is_err() { - return MigrateResult::NoMigrationPerformed - } - - MigrationInProgress::::mutate_exists(|progress| { - let Some(cursor_before) = progress.as_mut() else { - meter.consume(T::WeightInfo::migration_noop()); - return MigrateResult::NoMigrationInProgress - }; - - // if a migration is running it is always upgrading to the next version - let storage_version = >::on_chain_storage_version(); - let in_progress_version = storage_version + 1; - - log::info!( - target: LOG_TARGET, - "{name}: Migrating from {:?} to {:?},", - storage_version, - in_progress_version, - ); - - let result = - match T::Migrations::steps(in_progress_version, cursor_before.as_ref(), &mut meter) - { - StepResult::InProgress { cursor, steps_done } => { - *progress = Some(cursor); - MigrateResult::InProgress { steps_done } - }, - StepResult::Completed { steps_done } => { - in_progress_version.put::>(); - if >::in_code_storage_version() != in_progress_version { - log::info!( - target: LOG_TARGET, - "{name}: Next migration is {:?},", - in_progress_version + 1 - ); - *progress = Some(T::Migrations::new(in_progress_version + 1)); - MigrateResult::InProgress { steps_done } - } else { - log::info!( - target: LOG_TARGET, - "{name}: All migrations done. At version {:?},", - in_progress_version - ); - *progress = None; - MigrateResult::Completed - } - }, - }; - - result - }) - } - - pub(crate) fn ensure_migrated() -> DispatchResult { - if Self::in_progress() { - Err(Error::::MigrationInProgress.into()) - } else { - Ok(()) - } - } - - pub(crate) fn in_progress() -> bool { - MigrationInProgress::::exists() - } -} - -#[impl_trait_for_tuples::impl_for_tuples(10)] -#[tuple_types_custom_trait_bound(MigrationStep)] -impl MigrateSequence for Tuple { - const VERSION_RANGE: (u16, u16) = { - let mut versions: (u16, u16) = (0, 0); - for_tuples!( - #( - match versions { - (0, 0) => { - versions = (Tuple::VERSION, Tuple::VERSION); - }, - (min_version, last_version) if Tuple::VERSION == last_version + 1 => { - versions = (min_version, Tuple::VERSION); - }, - _ => panic!("Migrations must be ordered by their versions with no gaps.") - } - )* - ); - versions - }; - - fn new(version: StorageVersion) -> Cursor { - for_tuples!( - #( - if version == Tuple::VERSION { - return Tuple::default().encode().try_into().expect(PROOF_ENCODE) - } - )* - ); - invalid_version(version) - } - - #[cfg(feature = "try-runtime")] - /// Execute the pre-checks of the step associated with this version. - fn pre_upgrade_step(version: StorageVersion) -> Result, TryRuntimeError> { - for_tuples!( - #( - if version == Tuple::VERSION { - return Tuple::pre_upgrade_step() - } - )* - ); - invalid_version(version) - } - - #[cfg(feature = "try-runtime")] - /// Execute the post-checks of the step associated with this version. - fn post_upgrade_step(version: StorageVersion, state: Vec) -> Result<(), TryRuntimeError> { - for_tuples!( - #( - if version == Tuple::VERSION { - return Tuple::post_upgrade_step(state) - } - )* - ); - invalid_version(version) - } - - fn steps(version: StorageVersion, mut cursor: &[u8], meter: &mut WeightMeter) -> StepResult { - for_tuples!( - #( - if version == Tuple::VERSION { - let mut migration = ::decode(&mut cursor) - .expect(PROOF_DECODE); - let max_weight = Tuple::max_step_weight(); - let mut steps_done = 0; - while meter.can_consume(max_weight) { - steps_done.saturating_accrue(1); - if matches!(migration.step(meter), IsFinished::Yes) { - return StepResult::Completed{ steps_done } - } - } - return StepResult::InProgress{cursor: migration.encode().try_into().expect(PROOF_ENCODE), steps_done } - } - )* - ); - invalid_version(version) - } - - fn integrity_test(max_block_weight: Weight) { - for_tuples!( - #( - Tuple::integrity_test(max_block_weight); - )* - ); - } -} - -#[cfg(test)] -mod test { - use super::*; - use crate::{ - migration::codegen::LATEST_MIGRATION_VERSION, - tests::{ExtBuilder, Test}, - }; - - #[derive(Default, Encode, Decode, MaxEncodedLen)] - struct MockMigration { - // MockMigration needs `N` steps to finish - count: u16, - } - - impl MigrationStep for MockMigration { - const VERSION: u16 = N; - fn max_step_weight() -> Weight { - Weight::from_all(1) - } - fn step(&mut self, meter: &mut WeightMeter) -> IsFinished { - assert!(self.count != N); - self.count += 1; - meter.consume(Weight::from_all(1)); - if self.count == N { - IsFinished::Yes - } else { - IsFinished::No - } - } - } - - #[test] - fn test_storage_version_matches_last_migration_file() { - assert_eq!(StorageVersion::new(LATEST_MIGRATION_VERSION), crate::pallet::STORAGE_VERSION); - } - - #[test] - fn version_range_works() { - let range = <(MockMigration<1>, MockMigration<2>)>::VERSION_RANGE; - assert_eq!(range, (1, 2)); - } - - #[test] - fn is_upgrade_supported_works() { - type Migrations = (MockMigration<9>, MockMigration<10>, MockMigration<11>); - assert!(Migrations::is_upgrade_supported(StorageVersion::new(8), StorageVersion::new(11))); - assert!(!Migrations::is_upgrade_supported(StorageVersion::new(9), StorageVersion::new(11))); - assert!(!Migrations::is_upgrade_supported(StorageVersion::new(8), StorageVersion::new(12))); - } - - #[test] - fn steps_works() { - type Migrations = (MockMigration<2>, MockMigration<3>); - let version = StorageVersion::new(2); - let mut cursor = Migrations::new(version); - - let mut meter = WeightMeter::with_limit(Weight::from_all(1)); - let result = Migrations::steps(version, &cursor, &mut meter); - cursor = alloc::vec![1u8, 0].try_into().unwrap(); - assert_eq!(result, StepResult::InProgress { cursor: cursor.clone(), steps_done: 1 }); - assert_eq!(meter.consumed(), Weight::from_all(1)); - - let mut meter = WeightMeter::with_limit(Weight::from_all(1)); - assert_eq!( - Migrations::steps(version, &cursor, &mut meter), - StepResult::Completed { steps_done: 1 } - ); - } - - #[test] - fn no_migration_in_progress_works() { - type TestMigration = Migration; - - ExtBuilder::default().build().execute_with(|| { - assert_eq!(StorageVersion::get::>(), LATEST_MIGRATION_VERSION); - assert_eq!( - TestMigration::migrate(&mut WeightMeter::new()), - MigrateResult::NoMigrationInProgress - ) - }); - } - - #[test] - fn migration_works() { - type TestMigration = Migration; - - ExtBuilder::default() - .set_storage_version(LATEST_MIGRATION_VERSION - 2) - .build() - .execute_with(|| { - assert_eq!(StorageVersion::get::>(), LATEST_MIGRATION_VERSION - 2); - TestMigration::on_runtime_upgrade(); - for (version, status) in [ - (LATEST_MIGRATION_VERSION - 1, MigrateResult::InProgress { steps_done: 1 }), - (LATEST_MIGRATION_VERSION, MigrateResult::Completed), - ] { - assert_eq!(TestMigration::migrate(&mut WeightMeter::new()), status); - assert_eq!( - >::on_chain_storage_version(), - StorageVersion::new(version) - ); - } - - assert_eq!( - TestMigration::migrate(&mut WeightMeter::new()), - MigrateResult::NoMigrationInProgress - ); - assert_eq!(StorageVersion::get::>(), LATEST_MIGRATION_VERSION); - }); - } -} diff --git a/substrate/frame/revive/src/primitives.rs b/substrate/frame/revive/src/primitives.rs index 98e8879457b..1b48527d23d 100644 --- a/substrate/frame/revive/src/primitives.rs +++ b/substrate/frame/revive/src/primitives.rs @@ -103,8 +103,6 @@ pub enum ContractAccessError { DoesntExist, /// Storage key cannot be decoded from the provided input data. KeyDecodingFailed, - /// Storage is migrating. Try again later. - MigrationInProgress, } /// Output of a contract call or instantiation which ran to completion. diff --git a/substrate/frame/revive/src/storage.rs b/substrate/frame/revive/src/storage.rs index ef7ce2db32c..9939de1dfd1 100644 --- a/substrate/frame/revive/src/storage.rs +++ b/substrate/frame/revive/src/storage.rs @@ -33,11 +33,12 @@ use codec::{Decode, Encode, MaxEncodedLen}; use core::marker::PhantomData; use frame_support::{ storage::child::{self, ChildInfo}, + traits::IsType, weights::{Weight, WeightMeter}, CloneNoBound, DefaultNoBound, }; use scale_info::TypeInfo; -use sp_core::{ConstU32, Get, H160}; +use sp_core::{ConstU32, Get, H160, H256}; use sp_io::KillStorageResult; use sp_runtime::{ traits::{Hash, Saturating, Zero}, @@ -77,7 +78,10 @@ pub struct ContractInfo { delegate_dependencies: DelegateDependencyMap, } -impl ContractInfo { +impl ContractInfo +where + T::Hash: IsType, +{ /// Constructs a new contract info **without** writing it to storage. /// /// This returns an `Err` if an contract with the supplied `account` already exists diff --git a/substrate/frame/revive/src/storage/meter.rs b/substrate/frame/revive/src/storage/meter.rs index f6ad4c5fc34..9d70ddf8587 100644 --- a/substrate/frame/revive/src/storage/meter.rs +++ b/substrate/frame/revive/src/storage/meter.rs @@ -21,17 +21,17 @@ use crate::{ address::AddressMapper, storage::ContractInfo, AccountIdOf, BalanceOf, CodeInfo, Config, Error, Event, HoldReason, Inspect, Origin, Pallet, StorageDeposit as Deposit, System, LOG_TARGET, }; - use alloc::vec::Vec; use core::{fmt::Debug, marker::PhantomData}; use frame_support::{ traits::{ fungible::{Mutate, MutateHold}, tokens::{Fortitude, Fortitude::Polite, Precision, Preservation, Restriction}, - Get, + Get, IsType, }, DefaultNoBound, RuntimeDebugNoBound, }; +use sp_core::H256; use sp_runtime::{ traits::{Saturating, Zero}, DispatchError, FixedPointNumber, FixedU128, @@ -400,6 +400,7 @@ where impl RawMeter where T: Config, + T::Hash: IsType, E: Ext, { /// Charges `diff` from the meter. @@ -503,7 +504,10 @@ where } } -impl Ext for ReservingExt { +impl Ext for ReservingExt +where + T::Hash: IsType, +{ fn check_limit( origin: &T::AccountId, limit: BalanceOf, diff --git a/substrate/frame/revive/src/test_utils/builder.rs b/substrate/frame/revive/src/test_utils/builder.rs index b17067769c0..b17d7628fb8 100644 --- a/substrate/frame/revive/src/test_utils/builder.rs +++ b/substrate/frame/revive/src/test_utils/builder.rs @@ -54,6 +54,9 @@ macro_rules! builder { impl $name where as HasCompact>::Type: Clone + Eq + PartialEq + Debug + TypeInfo + Encode, + BalanceOf: Into + TryFrom, + crate::MomentOf: Into, + T::Hash: frame_support::traits::IsType, { $( #[doc = concat!("Set the ", stringify!($field))] diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index 447d55f0dd8..f2944c7932a 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -33,7 +33,6 @@ use crate::{ }, exec::Key, limits, - migration::codegen::LATEST_MIGRATION_VERSION, primitives::CodeUploadReturnValue, storage::DeletionQueueManager, test_utils::*, @@ -41,8 +40,8 @@ use crate::{ wasm::Memory, weights::WeightInfo, BalanceOf, Code, CodeInfoOf, CollectEvents, Config, ContractInfo, ContractInfoOf, DebugInfo, - DefaultAddressMapper, DeletionQueueCounter, Error, HoldReason, MigrationInProgress, Origin, - Pallet, PristineCode, H160, + DefaultAddressMapper, DeletionQueueCounter, Error, HoldReason, Origin, Pallet, PristineCode, + H160, }; use crate::test_utils::builder::Contract; @@ -490,7 +489,6 @@ impl Config for Test { type UnsafeUnstableInterface = UnstableInterface; type UploadOrigin = EnsureAccount; type InstantiateOrigin = EnsureAccount; - type Migrations = crate::migration::codegen::BenchMigrations; type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent; type Debug = TestDebug; } @@ -523,10 +521,6 @@ impl ExtBuilder { pub fn set_associated_consts(&self) { EXISTENTIAL_DEPOSIT.with(|v| *v.borrow_mut() = self.existential_deposit); } - pub fn set_storage_version(mut self, version: u16) -> Self { - self.storage_version = Some(StorageVersion::new(version)); - self - } pub fn build(self) -> sp_io::TestExternalities { sp_tracing::try_init_simple(); self.set_associated_consts(); @@ -593,6 +587,7 @@ impl Default for Origin { mod run_tests { use super::*; use pretty_assertions::{assert_eq, assert_ne}; + use sp_core::U256; // Perform a call to a plain account. // The actual transfer fails because we can only call contracts. @@ -616,66 +611,6 @@ mod run_tests { }); } - #[test] - fn migration_on_idle_hooks_works() { - // Defines expectations of how many migration steps can be done given the weight limit. - let tests = [ - (Weight::zero(), LATEST_MIGRATION_VERSION - 2), - (::WeightInfo::migrate() + 1.into(), LATEST_MIGRATION_VERSION - 1), - (Weight::MAX, LATEST_MIGRATION_VERSION), - ]; - - for (weight, expected_version) in tests { - ExtBuilder::default() - .set_storage_version(LATEST_MIGRATION_VERSION - 2) - .build() - .execute_with(|| { - MigrationInProgress::::set(Some(Default::default())); - Contracts::on_idle(System::block_number(), weight); - assert_eq!(StorageVersion::get::>(), expected_version); - }); - } - } - - #[test] - fn migration_in_progress_works() { - let (wasm, code_hash) = compile_module("dummy").unwrap(); - - ExtBuilder::default().existential_deposit(1).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - MigrationInProgress::::set(Some(Default::default())); - - assert_err!( - Contracts::upload_code( - RuntimeOrigin::signed(ALICE), - vec![], - deposit_limit::(), - ), - Error::::MigrationInProgress, - ); - assert_err!( - Contracts::remove_code(RuntimeOrigin::signed(ALICE), code_hash), - Error::::MigrationInProgress, - ); - assert_err!( - Contracts::set_code(RuntimeOrigin::signed(ALICE), BOB_ADDR, code_hash), - Error::::MigrationInProgress, - ); - assert_err_ignore_postinfo!( - builder::call(BOB_ADDR).build(), - Error::::MigrationInProgress - ); - assert_err_ignore_postinfo!( - builder::instantiate_with_code(wasm).value(100_000).build(), - Error::::MigrationInProgress, - ); - assert_err_ignore_postinfo!( - builder::instantiate(code_hash).value(100_000).build(), - Error::::MigrationInProgress, - ); - }); - } - #[test] fn instantiate_and_call_and_deposit_event() { let (wasm, code_hash) = compile_module("event_and_return_on_deploy").unwrap(); @@ -745,7 +680,7 @@ mod run_tests { contract: addr, data: vec![1, 2, 3, 4] }), - topics: vec![], + topics: vec![H256::repeat_byte(42)], }, EventRecord { phase: Phase::Initialization, @@ -3402,7 +3337,7 @@ mod run_tests { assert_err_ignore_postinfo!( builder::call(addr_caller) .storage_deposit_limit(13) - .data((100u32, &addr_callee, 0u64).encode()) + .data((100u32, &addr_callee, U256::from(0u64)).encode()) .build(), >::StorageDepositLimitExhausted, ); @@ -3416,7 +3351,7 @@ mod run_tests { assert_err_ignore_postinfo!( builder::call(addr_caller) .storage_deposit_limit(14) - .data((101u32, &addr_callee, 0u64).encode()) + .data((101u32, &addr_callee, U256::from(0u64)).encode()) .build(), >::StorageDepositLimitExhausted, ); @@ -3429,7 +3364,7 @@ mod run_tests { assert_err_ignore_postinfo!( builder::call(addr_caller) .storage_deposit_limit(16) - .data((102u32, &addr_callee, 1u64).encode()) + .data((102u32, &addr_callee, U256::from(1u64)).encode()) .build(), >::StorageDepositLimitExhausted, ); @@ -3440,7 +3375,7 @@ mod run_tests { assert_err_ignore_postinfo!( builder::call(addr_caller) .storage_deposit_limit(0) - .data((87u32, &addr_callee, 0u64).encode()) + .data((87u32, &addr_callee, U256::from(0u64)).encode()) .build(), >::StorageDepositLimitExhausted, ); @@ -3450,7 +3385,9 @@ mod run_tests { // Require more than the sender's balance. // We don't set a special limit for the nested call. assert_err_ignore_postinfo!( - builder::call(addr_caller).data((512u32, &addr_callee, 1u64).encode()).build(), + builder::call(addr_caller) + .data((512u32, &addr_callee, U256::from(1u64)).encode()) + .build(), >::StorageDepositLimitExhausted, ); @@ -3459,7 +3396,7 @@ mod run_tests { // enforced as callee frees up storage. This should pass. assert_ok!(builder::call(addr_caller) .storage_deposit_limit(1) - .data((87u32, &addr_callee, 1u64).encode()) + .data((87u32, &addr_callee, U256::from(1u64)).encode()) .build()); }); } @@ -3500,7 +3437,7 @@ mod run_tests { builder::call(addr_caller) .origin(RuntimeOrigin::signed(BOB)) .storage_deposit_limit(callee_info_len + 2 + ED + 1) - .data((0u32, &code_hash_callee, 0u64).encode()) + .data((0u32, &code_hash_callee, U256::from(0u64)).encode()) .build(), >::StorageDepositLimitExhausted, ); @@ -3514,7 +3451,7 @@ mod run_tests { builder::call(addr_caller) .origin(RuntimeOrigin::signed(BOB)) .storage_deposit_limit(callee_info_len + 2 + ED + 2) - .data((1u32, &code_hash_callee, 0u64).encode()) + .data((1u32, &code_hash_callee, U256::from(0u64)).encode()) .build(), >::StorageDepositLimitExhausted, ); @@ -3528,7 +3465,10 @@ mod run_tests { builder::call(addr_caller) .origin(RuntimeOrigin::signed(BOB)) .storage_deposit_limit(callee_info_len + 2 + ED + 2) - .data((0u32, &code_hash_callee, callee_info_len + 2 + ED + 1).encode()) + .data( + (0u32, &code_hash_callee, U256::from(callee_info_len + 2 + ED + 1)) + .encode() + ) .build(), >::StorageDepositLimitExhausted, ); @@ -3543,7 +3483,10 @@ mod run_tests { builder::call(addr_caller) .origin(RuntimeOrigin::signed(BOB)) .storage_deposit_limit(callee_info_len + 2 + ED + 3) // enough parent limit - .data((1u32, &code_hash_callee, callee_info_len + 2 + ED + 2).encode()) + .data( + (1u32, &code_hash_callee, U256::from(callee_info_len + 2 + ED + 2)) + .encode() + ) .build(), >::StorageDepositLimitExhausted, ); @@ -3554,7 +3497,7 @@ mod run_tests { let result = builder::bare_call(addr_caller) .origin(RuntimeOrigin::signed(BOB)) .storage_deposit_limit(callee_info_len + 2 + ED + 4) - .data((1u32, &code_hash_callee, callee_info_len + 2 + ED + 3).encode()) + .data((1u32, &code_hash_callee, U256::from(callee_info_len + 2 + ED + 3)).encode()) .build(); let returned = result.result.unwrap(); diff --git a/substrate/frame/revive/src/wasm/mod.rs b/substrate/frame/revive/src/wasm/mod.rs index 9024390fd24..5813903326b 100644 --- a/substrate/frame/revive/src/wasm/mod.rs +++ b/substrate/frame/revive/src/wasm/mod.rs @@ -45,9 +45,9 @@ use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ dispatch::DispatchResult, ensure, - traits::{fungible::MutateHold, tokens::Precision::BestEffort}, + traits::{fungible::MutateHold, tokens::Precision::BestEffort, IsType}, }; -use sp_core::Get; +use sp_core::{Get, H256, U256}; use sp_runtime::DispatchError; /// Validated Wasm module ready for execution. @@ -123,7 +123,11 @@ impl Token for CodeLoadToken { } } -impl WasmBlob { +impl WasmBlob +where + T::Hash: IsType, + BalanceOf: Into + TryFrom, +{ /// We only check for size and nothing else when the code is uploaded. pub fn from_code( code: Vec, @@ -251,7 +255,11 @@ pub struct PreparedCall<'a, E: Ext> { api_version: ApiVersion, } -impl<'a, E: Ext> PreparedCall<'a, E> { +impl<'a, E: Ext> PreparedCall<'a, E> +where + BalanceOf: Into, + BalanceOf: TryFrom, +{ pub fn call(mut self) -> ExecResult { let exec_result = loop { let interrupt = self.instance.run(); @@ -315,7 +323,10 @@ impl WasmBlob { } } -impl Executable for WasmBlob { +impl Executable for WasmBlob +where + BalanceOf: Into + TryFrom, +{ fn from_storage( code_hash: sp_core::H256, gas_meter: &mut GasMeter, diff --git a/substrate/frame/revive/src/wasm/runtime.rs b/substrate/frame/revive/src/wasm/runtime.rs index 51c72349384..528b0ababfa 100644 --- a/substrate/frame/revive/src/wasm/runtime.rs +++ b/substrate/frame/revive/src/wasm/runtime.rs @@ -19,12 +19,12 @@ use crate::{ address::AddressMapper, - exec::{ExecError, ExecResult, Ext, Key, TopicOf}, + exec::{ExecError, ExecResult, Ext, Key}, gas::{ChargedAmount, Token}, limits, primitives::ExecReturnValue, weights::WeightInfo, - BalanceOf, Config, Error, LOG_TARGET, SENTINEL, + Config, Error, LOG_TARGET, SENTINEL, }; use alloc::{boxed::Box, vec, vec::Vec}; use codec::{Decode, DecodeLimit, Encode, MaxEncodedLen}; @@ -35,15 +35,22 @@ use frame_support::{ }; use pallet_revive_proc_macro::define_env; use pallet_revive_uapi::{CallFlags, ReturnErrorCode, ReturnFlags, StorageFlags}; -use sp_core::{H160, H256}; +use sp_core::{H160, H256, U256}; use sp_io::hashing::{blake2_128, blake2_256, keccak_256, sha2_256}; -use sp_runtime::{traits::Zero, DispatchError, RuntimeDebug}; +use sp_runtime::{DispatchError, RuntimeDebug}; type CallOf = ::RuntimeCall; /// The maximum nesting depth a contract can use when encoding types. const MAX_DECODE_NESTING: u32 = 256; +/// Encode a `U256` into a 32 byte buffer. +fn as_bytes(u: U256) -> [u8; 32] { + let mut bytes = [0u8; 32]; + u.to_little_endian(&mut bytes); + bytes +} + #[derive(Clone, Copy)] pub enum ApiVersion { /// Expose all APIs even unversioned ones. Only used for testing and benchmarking. @@ -84,6 +91,32 @@ pub trait Memory { Ok(buf) } + /// Same as `read` but reads into a fixed size buffer. + fn read_array(&self, ptr: u32) -> Result<[u8; N], DispatchError> { + let mut buf = [0u8; N]; + self.read_into_buf(ptr, &mut buf)?; + Ok(buf) + } + + /// Read a `u32` from the sandbox memory. + fn read_u32(&self, ptr: u32) -> Result { + let buf: [u8; 4] = self.read_array(ptr)?; + Ok(u32::from_le_bytes(buf)) + } + + /// Read a `U256` from the sandbox memory. + fn read_u256(&self, ptr: u32) -> Result { + let buf: [u8; 32] = self.read_array(ptr)?; + Ok(U256::from_little_endian(&buf)) + } + + /// Read a `H256` from the sandbox memory. + fn read_h256(&self, ptr: u32) -> Result { + let mut code_hash = H256::default(); + self.read_into_buf(ptr, code_hash.as_bytes_mut())?; + Ok(code_hash) + } + /// Read designated chunk from the sandbox memory and attempt to decode into the specified type. /// /// Returns `Err` if one of the following conditions occurs: @@ -647,7 +680,7 @@ impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { } let buf_len = buf.len() as u32; - let len: u32 = memory.read_as(out_len_ptr)?; + let len = memory.read_u32(out_len_ptr)?; if len < buf_len { return Err(Error::::OutputBufferTooSmall.into()) @@ -963,13 +996,13 @@ impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { CallType::Call { callee_ptr, value_ptr, deposit_ptr, weight } => { let mut callee = H160::zero(); memory.read_into_buf(callee_ptr, callee.as_bytes_mut())?; - let deposit_limit: BalanceOf<::T> = if deposit_ptr == SENTINEL { - BalanceOf::<::T>::zero() + let deposit_limit = if deposit_ptr == SENTINEL { + U256::zero() } else { - memory.read_as(deposit_ptr)? + memory.read_u256(deposit_ptr)? }; let read_only = flags.contains(CallFlags::READ_ONLY); - let value: BalanceOf<::T> = memory.read_as(value_ptr)?; + let value = memory.read_u256(value_ptr)?; if value > 0u32.into() { // If the call value is non-zero and state change is not allowed, issue an // error. @@ -992,7 +1025,8 @@ impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { if flags.intersects(CallFlags::ALLOW_REENTRY | CallFlags::READ_ONLY) { return Err(Error::::InvalidCallFlags.into()) } - let code_hash = memory.read_as(code_hash_ptr)?; + + let code_hash = memory.read_h256(code_hash_ptr)?; self.ext.delegate_call(code_hash, input_data) }, }; @@ -1036,19 +1070,15 @@ impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { salt_ptr: u32, ) -> Result { self.charge_gas(RuntimeCosts::Instantiate { input_data_len })?; - let deposit_limit: BalanceOf<::T> = if deposit_ptr == SENTINEL { - BalanceOf::<::T>::zero() - } else { - memory.read_as(deposit_ptr)? - }; - let value: BalanceOf<::T> = memory.read_as(value_ptr)?; - let code_hash: H256 = memory.read_as(code_hash_ptr)?; + let deposit_limit: U256 = + if deposit_ptr == SENTINEL { U256::zero() } else { memory.read_u256(deposit_ptr)? }; + let value = memory.read_u256(value_ptr)?; + let code_hash = memory.read_h256(code_hash_ptr)?; let input_data = memory.read(input_data_ptr, input_data_len)?; let salt = if salt_ptr == SENTINEL { None } else { - let mut salt = [0u8; 32]; - memory.read_into_buf(salt_ptr, salt.as_mut_slice())?; + let salt: [u8; 32] = memory.read_array(salt_ptr)?; Some(salt) }; let instantiate_outcome = self.ext.instantiate( @@ -1194,7 +1224,7 @@ pub mod env { self.charge_gas(RuntimeCosts::Transfer)?; let mut callee = H160::zero(); memory.read_into_buf(address_ptr, callee.as_bytes_mut())?; - let value: BalanceOf<::T> = memory.read_as(value_ptr)?; + let value: U256 = memory.read_u256(value_ptr)?; let result = self.ext.transfer(&callee, value); match result { Ok(()) => Ok(ReturnErrorCode::Success), @@ -1374,7 +1404,7 @@ pub mod env { self.write_fixed_sandbox_output( memory, out_ptr, - &value.encode(), + &value.as_bytes(), false, already_charged, )?; @@ -1389,11 +1419,11 @@ pub mod env { #[api_version(0)] fn own_code_hash(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { self.charge_gas(RuntimeCosts::OwnCodeHash)?; - let code_hash_encoded = &self.ext.own_code_hash().encode(); + let code_hash = *self.ext.own_code_hash(); Ok(self.write_fixed_sandbox_output( memory, out_ptr, - code_hash_encoded, + code_hash.as_bytes(), false, already_charged, )?) @@ -1439,14 +1469,12 @@ pub mod env { ref_time_limit: u64, proof_size_limit: u64, out_ptr: u32, - out_len_ptr: u32, ) -> Result<(), TrapReason> { let weight = Weight::from_parts(ref_time_limit, proof_size_limit); self.charge_gas(RuntimeCosts::WeightToFee)?; - Ok(self.write_sandbox_output( + Ok(self.write_fixed_sandbox_output( memory, out_ptr, - out_len_ptr, &self.ext.get_weight_price(weight).encode(), false, already_charged, @@ -1477,18 +1505,12 @@ pub mod env { /// Stores the *free* balance of the current account into the supplied buffer. /// See [`pallet_revive_uapi::HostFn::balance`]. #[api_version(0)] - fn balance( - &mut self, - memory: &mut M, - out_ptr: u32, - out_len_ptr: u32, - ) -> Result<(), TrapReason> { + fn balance(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { self.charge_gas(RuntimeCosts::Balance)?; - Ok(self.write_sandbox_output( + Ok(self.write_fixed_sandbox_output( memory, out_ptr, - out_len_ptr, - &self.ext.balance().encode(), + &as_bytes(self.ext.balance()), false, already_charged, )?) @@ -1497,18 +1519,12 @@ pub mod env { /// Stores the value transferred along with this call/instantiate into the supplied buffer. /// See [`pallet_revive_uapi::HostFn::value_transferred`]. #[api_version(0)] - fn value_transferred( - &mut self, - memory: &mut M, - out_ptr: u32, - out_len_ptr: u32, - ) -> Result<(), TrapReason> { + fn value_transferred(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { self.charge_gas(RuntimeCosts::ValueTransferred)?; - Ok(self.write_sandbox_output( + Ok(self.write_fixed_sandbox_output( memory, out_ptr, - out_len_ptr, - &self.ext.value_transferred().encode(), + &as_bytes(self.ext.value_transferred()), false, already_charged, )?) @@ -1517,13 +1533,12 @@ pub mod env { /// Load the latest block timestamp into the supplied buffer /// See [`pallet_revive_uapi::HostFn::now`]. #[api_version(0)] - fn now(&mut self, memory: &mut M, out_ptr: u32, out_len_ptr: u32) -> Result<(), TrapReason> { + fn now(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { self.charge_gas(RuntimeCosts::Now)?; - Ok(self.write_sandbox_output( + Ok(self.write_fixed_sandbox_output( memory, out_ptr, - out_len_ptr, - &self.ext.now().encode(), + &as_bytes(self.ext.now()), false, already_charged, )?) @@ -1532,18 +1547,12 @@ pub mod env { /// Stores the minimum balance (a.k.a. existential deposit) into the supplied buffer. /// See [`pallet_revive_uapi::HostFn::minimum_balance`]. #[api_version(0)] - fn minimum_balance( - &mut self, - memory: &mut M, - out_ptr: u32, - out_len_ptr: u32, - ) -> Result<(), TrapReason> { + fn minimum_balance(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { self.charge_gas(RuntimeCosts::MinimumBalance)?; - Ok(self.write_sandbox_output( + Ok(self.write_fixed_sandbox_output( memory, out_ptr, - out_len_ptr, - &self.ext.minimum_balance().encode(), + &as_bytes(self.ext.minimum_balance()), false, already_charged, )?) @@ -1557,50 +1566,47 @@ pub mod env { &mut self, memory: &mut M, topics_ptr: u32, - topics_len: u32, + num_topic: u32, data_ptr: u32, data_len: u32, ) -> Result<(), TrapReason> { - let num_topic = topics_len - .checked_div(core::mem::size_of::>() as u32) - .ok_or("Zero sized topics are not allowed")?; self.charge_gas(RuntimeCosts::DepositEvent { num_topic, len: data_len })?; + + if num_topic > limits::NUM_EVENT_TOPICS { + return Err(Error::::TooManyTopics.into()); + } + if data_len > self.ext.max_value_size() { return Err(Error::::ValueTooLarge.into()); } - let topics: Vec::T>> = match topics_len { + let topics: Vec = match num_topic { 0 => Vec::new(), - _ => memory.read_as_unbounded(topics_ptr, topics_len)?, + _ => { + let mut v = Vec::with_capacity(num_topic as usize); + let topics_len = num_topic * H256::len_bytes() as u32; + let buf = memory.read(topics_ptr, topics_len)?; + for chunk in buf.chunks_exact(H256::len_bytes()) { + v.push(H256::from_slice(chunk)); + } + v + }, }; - // If there are more than `event_topics`, then trap. - if topics.len() as u32 > limits::NUM_EVENT_TOPICS { - return Err(Error::::TooManyTopics.into()); - } - let event_data = memory.read(data_ptr, data_len)?; - self.ext.deposit_event(topics, event_data); - Ok(()) } /// Stores the current block number of the current contract into the supplied buffer. /// See [`pallet_revive_uapi::HostFn::block_number`]. #[api_version(0)] - fn block_number( - &mut self, - memory: &mut M, - out_ptr: u32, - out_len_ptr: u32, - ) -> Result<(), TrapReason> { + fn block_number(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { self.charge_gas(RuntimeCosts::BlockNumber)?; - Ok(self.write_sandbox_output( + Ok(self.write_fixed_sandbox_output( memory, out_ptr, - out_len_ptr, - &self.ext.block_number().encode(), + &as_bytes(self.ext.block_number()), false, already_charged, )?) @@ -1884,7 +1890,7 @@ pub mod env { code_hash_ptr: u32, ) -> Result { self.charge_gas(RuntimeCosts::SetCodeHash)?; - let code_hash: H256 = memory.read_as(code_hash_ptr)?; + let code_hash: H256 = memory.read_h256(code_hash_ptr)?; match self.ext.set_code_hash(code_hash) { Err(err) => { let code = Self::err_into_return_code(err)?; @@ -1926,7 +1932,7 @@ pub mod env { code_hash_ptr: u32, ) -> Result<(), TrapReason> { self.charge_gas(RuntimeCosts::LockDelegateDependency)?; - let code_hash = memory.read_as(code_hash_ptr)?; + let code_hash = memory.read_h256(code_hash_ptr)?; self.ext.lock_delegate_dependency(code_hash)?; Ok(()) } @@ -1941,7 +1947,7 @@ pub mod env { code_hash_ptr: u32, ) -> Result<(), TrapReason> { self.charge_gas(RuntimeCosts::UnlockDelegateDependency)?; - let code_hash = memory.read_as(code_hash_ptr)?; + let code_hash = memory.read_h256(code_hash_ptr)?; self.ext.unlock_delegate_dependency(&code_hash)?; Ok(()) } diff --git a/substrate/frame/revive/src/weights.rs b/substrate/frame/revive/src/weights.rs index 7974cc1260e..8913592c13b 100644 --- a/substrate/frame/revive/src/weights.rs +++ b/substrate/frame/revive/src/weights.rs @@ -51,19 +51,6 @@ use core::marker::PhantomData; pub trait WeightInfo { fn on_process_deletion_queue_batch() -> Weight; fn on_initialize_per_trie_key(k: u32, ) -> Weight; - fn v9_migration_step(c: u32, ) -> Weight; - fn v10_migration_step() -> Weight; - fn v11_migration_step(k: u32, ) -> Weight; - fn v12_migration_step(c: u32, ) -> Weight; - fn v13_migration_step() -> Weight; - fn v14_migration_step() -> Weight; - fn v15_migration_step() -> Weight; - fn v16_migration_step() -> Weight; - fn migration_noop() -> Weight; - fn migrate() -> Weight; - fn on_runtime_upgrade_noop() -> Weight; - fn on_runtime_upgrade_in_progress() -> Weight; - fn on_runtime_upgrade() -> Weight; fn call_with_code_per_byte(c: u32, ) -> Weight; fn instantiate_with_code(c: u32, i: u32) -> Weight; fn instantiate(i: u32) -> Weight; @@ -162,182 +149,6 @@ impl WeightInfo for SubstrateWeight { .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(k.into()))) .saturating_add(Weight::from_parts(0, 70).saturating_mul(k.into())) } - /// Storage: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc553022fca90611ba8b7942f8bdb3b97f6580` (r:2 w:1) - /// Proof: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc553022fca90611ba8b7942f8bdb3b97f6580` (r:2 w:1) - /// The range of component `c` is `[0, 125952]`. - fn v9_migration_step(c: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `211 + c * (1 ±0)` - // Estimated: `6149 + c * (1 ±0)` - // Minimum execution time: 7_783_000 picoseconds. - Weight::from_parts(4_462_075, 6149) - // Standard Error: 5 - .saturating_add(Weight::from_parts(1_634, 0).saturating_mul(c.into())) - .saturating_add(T::DbWeight::get().reads(2_u64)) - .saturating_add(T::DbWeight::get().writes(1_u64)) - .saturating_add(Weight::from_parts(0, 1).saturating_mul(c.into())) - } - /// Storage: `Contracts::ContractInfoOf` (r:2 w:1) - /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) - /// Storage: `System::Account` (r:1 w:0) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) - fn v10_migration_step() -> Weight { - // Proof Size summary in bytes: - // Measured: `510` - // Estimated: `6450` - // Minimum execution time: 15_971_000 picoseconds. - Weight::from_parts(16_730_000, 6450) - .saturating_add(T::DbWeight::get().reads(3_u64)) - .saturating_add(T::DbWeight::get().writes(1_u64)) - } - /// Storage: `Contracts::DeletionQueue` (r:1 w:1025) - /// Proof: `Contracts::DeletionQueue` (`max_values`: None, `max_size`: Some(142), added: 2617, mode: `Measured`) - /// Storage: `Contracts::DeletionQueueCounter` (r:0 w:1) - /// Proof: `Contracts::DeletionQueueCounter` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) - /// The range of component `k` is `[0, 1024]`. - fn v11_migration_step(k: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `171 + k * (1 ±0)` - // Estimated: `3635 + k * (1 ±0)` - // Minimum execution time: 3_149_000 picoseconds. - Weight::from_parts(3_264_000, 3635) - // Standard Error: 559 - .saturating_add(Weight::from_parts(1_111_209, 0).saturating_mul(k.into())) - .saturating_add(T::DbWeight::get().reads(1_u64)) - .saturating_add(T::DbWeight::get().writes(2_u64)) - .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(k.into()))) - .saturating_add(Weight::from_parts(0, 1).saturating_mul(k.into())) - } - /// Storage: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc553053f13fd319a03c211337c76e0fe776df` (r:2 w:0) - /// Proof: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc553053f13fd319a03c211337c76e0fe776df` (r:2 w:0) - /// Storage: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc553022fca90611ba8b7942f8bdb3b97f6580` (r:1 w:1) - /// Proof: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc553022fca90611ba8b7942f8bdb3b97f6580` (r:1 w:1) - /// Storage: `System::Account` (r:1 w:0) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) - /// Storage: `Contracts::CodeInfoOf` (r:0 w:1) - /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) - /// The range of component `c` is `[0, 125952]`. - fn v12_migration_step(c: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `325 + c * (1 ±0)` - // Estimated: `6263 + c * (1 ±0)` - // Minimum execution time: 15_072_000 picoseconds. - Weight::from_parts(15_721_891, 6263) - // Standard Error: 2 - .saturating_add(Weight::from_parts(428, 0).saturating_mul(c.into())) - .saturating_add(T::DbWeight::get().reads(4_u64)) - .saturating_add(T::DbWeight::get().writes(2_u64)) - .saturating_add(Weight::from_parts(0, 1).saturating_mul(c.into())) - } - /// Storage: `Contracts::ContractInfoOf` (r:2 w:1) - /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) - fn v13_migration_step() -> Weight { - // Proof Size summary in bytes: - // Measured: `440` - // Estimated: `6380` - // Minimum execution time: 12_047_000 picoseconds. - Weight::from_parts(12_500_000, 6380) - .saturating_add(T::DbWeight::get().reads(2_u64)) - .saturating_add(T::DbWeight::get().writes(1_u64)) - } - /// Storage: `Contracts::CodeInfoOf` (r:2 w:0) - /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) - /// Storage: `Balances::Holds` (r:1 w:0) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(193), added: 2668, mode: `Measured`) - fn v14_migration_step() -> Weight { - // Proof Size summary in bytes: - // Measured: `352` - // Estimated: `6292` - // Minimum execution time: 47_488_000 picoseconds. - Weight::from_parts(48_482_000, 6292) - .saturating_add(T::DbWeight::get().reads(4_u64)) - .saturating_add(T::DbWeight::get().writes(1_u64)) - } - /// Storage: `Contracts::ContractInfoOf` (r:2 w:1) - /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) - /// Storage: `System::Account` (r:2 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) - fn v15_migration_step() -> Weight { - // Proof Size summary in bytes: - // Measured: `594` - // Estimated: `6534` - // Minimum execution time: 52_801_000 picoseconds. - Weight::from_parts(54_230_000, 6534) - .saturating_add(T::DbWeight::get().reads(4_u64)) - .saturating_add(T::DbWeight::get().writes(2_u64)) - } - /// Storage: `Contracts::ContractInfoOf` (r:2 w:1) - /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) - fn v16_migration_step() -> Weight { - // Proof Size summary in bytes: - // Measured: `409` - // Estimated: `6349` - // Minimum execution time: 11_618_000 picoseconds. - Weight::from_parts(12_068_000, 6349) - .saturating_add(T::DbWeight::get().reads(2_u64)) - .saturating_add(T::DbWeight::get().writes(1_u64)) - } - /// Storage: `Contracts::MigrationInProgress` (r:1 w:1) - /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) - fn migration_noop() -> Weight { - // Proof Size summary in bytes: - // Measured: `142` - // Estimated: `1627` - // Minimum execution time: 2_131_000 picoseconds. - Weight::from_parts(2_255_000, 1627) - .saturating_add(T::DbWeight::get().reads(1_u64)) - .saturating_add(T::DbWeight::get().writes(1_u64)) - } - /// Storage: `Contracts::MigrationInProgress` (r:1 w:1) - /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) - /// Storage: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:1) - /// Proof: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:1) - fn migrate() -> Weight { - // Proof Size summary in bytes: - // Measured: `166` - // Estimated: `3631` - // Minimum execution time: 10_773_000 picoseconds. - Weight::from_parts(11_118_000, 3631) - .saturating_add(T::DbWeight::get().reads(2_u64)) - .saturating_add(T::DbWeight::get().writes(2_u64)) - } - /// Storage: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:0) - /// Proof: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:0) - fn on_runtime_upgrade_noop() -> Weight { - // Proof Size summary in bytes: - // Measured: `142` - // Estimated: `3607` - // Minimum execution time: 4_371_000 picoseconds. - Weight::from_parts(4_624_000, 3607) - .saturating_add(T::DbWeight::get().reads(1_u64)) - } - /// Storage: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:0) - /// Proof: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:0) - /// Storage: `Contracts::MigrationInProgress` (r:1 w:0) - /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) - fn on_runtime_upgrade_in_progress() -> Weight { - // Proof Size summary in bytes: - // Measured: `167` - // Estimated: `3632` - // Minimum execution time: 5_612_000 picoseconds. - Weight::from_parts(5_838_000, 3632) - .saturating_add(T::DbWeight::get().reads(2_u64)) - } - /// Storage: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:0) - /// Proof: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:0) - /// Storage: `Contracts::MigrationInProgress` (r:1 w:1) - /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) - fn on_runtime_upgrade() -> Weight { - // Proof Size summary in bytes: - // Measured: `142` - // Estimated: `3607` - // Minimum execution time: 5_487_000 picoseconds. - Weight::from_parts(5_693_000, 3607) - .saturating_add(T::DbWeight::get().reads(2_u64)) - .saturating_add(T::DbWeight::get().writes(1_u64)) - } /// Storage: `Contracts::MigrationInProgress` (r:1 w:0) /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) /// Storage: `Contracts::ContractInfoOf` (r:1 w:1) @@ -1152,182 +963,6 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(k.into()))) .saturating_add(Weight::from_parts(0, 70).saturating_mul(k.into())) } - /// Storage: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc553022fca90611ba8b7942f8bdb3b97f6580` (r:2 w:1) - /// Proof: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc553022fca90611ba8b7942f8bdb3b97f6580` (r:2 w:1) - /// The range of component `c` is `[0, 125952]`. - fn v9_migration_step(c: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `211 + c * (1 ±0)` - // Estimated: `6149 + c * (1 ±0)` - // Minimum execution time: 7_783_000 picoseconds. - Weight::from_parts(4_462_075, 6149) - // Standard Error: 5 - .saturating_add(Weight::from_parts(1_634, 0).saturating_mul(c.into())) - .saturating_add(RocksDbWeight::get().reads(2_u64)) - .saturating_add(RocksDbWeight::get().writes(1_u64)) - .saturating_add(Weight::from_parts(0, 1).saturating_mul(c.into())) - } - /// Storage: `Contracts::ContractInfoOf` (r:2 w:1) - /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) - /// Storage: `System::Account` (r:1 w:0) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) - fn v10_migration_step() -> Weight { - // Proof Size summary in bytes: - // Measured: `510` - // Estimated: `6450` - // Minimum execution time: 15_971_000 picoseconds. - Weight::from_parts(16_730_000, 6450) - .saturating_add(RocksDbWeight::get().reads(3_u64)) - .saturating_add(RocksDbWeight::get().writes(1_u64)) - } - /// Storage: `Contracts::DeletionQueue` (r:1 w:1025) - /// Proof: `Contracts::DeletionQueue` (`max_values`: None, `max_size`: Some(142), added: 2617, mode: `Measured`) - /// Storage: `Contracts::DeletionQueueCounter` (r:0 w:1) - /// Proof: `Contracts::DeletionQueueCounter` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) - /// The range of component `k` is `[0, 1024]`. - fn v11_migration_step(k: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `171 + k * (1 ±0)` - // Estimated: `3635 + k * (1 ±0)` - // Minimum execution time: 3_149_000 picoseconds. - Weight::from_parts(3_264_000, 3635) - // Standard Error: 559 - .saturating_add(Weight::from_parts(1_111_209, 0).saturating_mul(k.into())) - .saturating_add(RocksDbWeight::get().reads(1_u64)) - .saturating_add(RocksDbWeight::get().writes(2_u64)) - .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(k.into()))) - .saturating_add(Weight::from_parts(0, 1).saturating_mul(k.into())) - } - /// Storage: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc553053f13fd319a03c211337c76e0fe776df` (r:2 w:0) - /// Proof: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc553053f13fd319a03c211337c76e0fe776df` (r:2 w:0) - /// Storage: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc553022fca90611ba8b7942f8bdb3b97f6580` (r:1 w:1) - /// Proof: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc553022fca90611ba8b7942f8bdb3b97f6580` (r:1 w:1) - /// Storage: `System::Account` (r:1 w:0) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) - /// Storage: `Contracts::CodeInfoOf` (r:0 w:1) - /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) - /// The range of component `c` is `[0, 125952]`. - fn v12_migration_step(c: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `325 + c * (1 ±0)` - // Estimated: `6263 + c * (1 ±0)` - // Minimum execution time: 15_072_000 picoseconds. - Weight::from_parts(15_721_891, 6263) - // Standard Error: 2 - .saturating_add(Weight::from_parts(428, 0).saturating_mul(c.into())) - .saturating_add(RocksDbWeight::get().reads(4_u64)) - .saturating_add(RocksDbWeight::get().writes(2_u64)) - .saturating_add(Weight::from_parts(0, 1).saturating_mul(c.into())) - } - /// Storage: `Contracts::ContractInfoOf` (r:2 w:1) - /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) - fn v13_migration_step() -> Weight { - // Proof Size summary in bytes: - // Measured: `440` - // Estimated: `6380` - // Minimum execution time: 12_047_000 picoseconds. - Weight::from_parts(12_500_000, 6380) - .saturating_add(RocksDbWeight::get().reads(2_u64)) - .saturating_add(RocksDbWeight::get().writes(1_u64)) - } - /// Storage: `Contracts::CodeInfoOf` (r:2 w:0) - /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) - /// Storage: `Balances::Holds` (r:1 w:0) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(193), added: 2668, mode: `Measured`) - fn v14_migration_step() -> Weight { - // Proof Size summary in bytes: - // Measured: `352` - // Estimated: `6292` - // Minimum execution time: 47_488_000 picoseconds. - Weight::from_parts(48_482_000, 6292) - .saturating_add(RocksDbWeight::get().reads(4_u64)) - .saturating_add(RocksDbWeight::get().writes(1_u64)) - } - /// Storage: `Contracts::ContractInfoOf` (r:2 w:1) - /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) - /// Storage: `System::Account` (r:2 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) - fn v15_migration_step() -> Weight { - // Proof Size summary in bytes: - // Measured: `594` - // Estimated: `6534` - // Minimum execution time: 52_801_000 picoseconds. - Weight::from_parts(54_230_000, 6534) - .saturating_add(RocksDbWeight::get().reads(4_u64)) - .saturating_add(RocksDbWeight::get().writes(2_u64)) - } - /// Storage: `Contracts::ContractInfoOf` (r:2 w:1) - /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) - fn v16_migration_step() -> Weight { - // Proof Size summary in bytes: - // Measured: `409` - // Estimated: `6349` - // Minimum execution time: 11_618_000 picoseconds. - Weight::from_parts(12_068_000, 6349) - .saturating_add(RocksDbWeight::get().reads(2_u64)) - .saturating_add(RocksDbWeight::get().writes(1_u64)) - } - /// Storage: `Contracts::MigrationInProgress` (r:1 w:1) - /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) - fn migration_noop() -> Weight { - // Proof Size summary in bytes: - // Measured: `142` - // Estimated: `1627` - // Minimum execution time: 2_131_000 picoseconds. - Weight::from_parts(2_255_000, 1627) - .saturating_add(RocksDbWeight::get().reads(1_u64)) - .saturating_add(RocksDbWeight::get().writes(1_u64)) - } - /// Storage: `Contracts::MigrationInProgress` (r:1 w:1) - /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) - /// Storage: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:1) - /// Proof: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:1) - fn migrate() -> Weight { - // Proof Size summary in bytes: - // Measured: `166` - // Estimated: `3631` - // Minimum execution time: 10_773_000 picoseconds. - Weight::from_parts(11_118_000, 3631) - .saturating_add(RocksDbWeight::get().reads(2_u64)) - .saturating_add(RocksDbWeight::get().writes(2_u64)) - } - /// Storage: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:0) - /// Proof: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:0) - fn on_runtime_upgrade_noop() -> Weight { - // Proof Size summary in bytes: - // Measured: `142` - // Estimated: `3607` - // Minimum execution time: 4_371_000 picoseconds. - Weight::from_parts(4_624_000, 3607) - .saturating_add(RocksDbWeight::get().reads(1_u64)) - } - /// Storage: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:0) - /// Proof: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:0) - /// Storage: `Contracts::MigrationInProgress` (r:1 w:0) - /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) - fn on_runtime_upgrade_in_progress() -> Weight { - // Proof Size summary in bytes: - // Measured: `167` - // Estimated: `3632` - // Minimum execution time: 5_612_000 picoseconds. - Weight::from_parts(5_838_000, 3632) - .saturating_add(RocksDbWeight::get().reads(2_u64)) - } - /// Storage: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:0) - /// Proof: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:0) - /// Storage: `Contracts::MigrationInProgress` (r:1 w:1) - /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) - fn on_runtime_upgrade() -> Weight { - // Proof Size summary in bytes: - // Measured: `142` - // Estimated: `3607` - // Minimum execution time: 5_487_000 picoseconds. - Weight::from_parts(5_693_000, 3607) - .saturating_add(RocksDbWeight::get().reads(2_u64)) - .saturating_add(RocksDbWeight::get().writes(1_u64)) - } /// Storage: `Contracts::MigrationInProgress` (r:1 w:0) /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) /// Storage: `Contracts::ContractInfoOf` (r:1 w:1) diff --git a/substrate/frame/revive/uapi/src/host.rs b/substrate/frame/revive/uapi/src/host.rs index f52ea957402..101ae9aca46 100644 --- a/substrate/frame/revive/uapi/src/host.rs +++ b/substrate/frame/revive/uapi/src/host.rs @@ -58,21 +58,17 @@ pub trait HostFn: private::Sealed { /// Stores the *free* balance of the current account into the supplied buffer. /// - /// If the available space in `output` is less than the size of the value a trap is triggered. - /// /// # Parameters /// /// - `output`: A reference to the output data buffer to write the balance. - fn balance(output: &mut &mut [u8]); + fn balance(output: &mut [u8; 32]); /// Stores the current block number of the current contract into the supplied buffer. /// - /// If the available space in `output` is less than the size of the value a trap is triggered. - /// /// # Parameters /// /// - `output`: A reference to the output data buffer to write the block number. - fn block_number(output: &mut &mut [u8]); + fn block_number(output: &mut [u8; 32]); /// Call (possibly transferring some amount of funds) into the specified account. /// @@ -83,11 +79,10 @@ pub trait HostFn: private::Sealed { /// otherwise. /// - `ref_time_limit`: how much *ref_time* Weight to devote to the execution. /// - `proof_size_limit`: how much *proof_size* Weight to devote to the execution. - /// - `deposit`: The storage deposit limit for instantiation. Should be decodable as a - /// `Option`. Traps otherwise. Passing `None` means setting no specific limit for - /// the call, which implies storage usage up to the limit of the parent call. - /// - `value`: The value to transfer into the contract. Should be decodable as a `T::Balance`. - /// Traps otherwise. + /// - `deposit`: The storage deposit limit for instantiation. Passing `None` means setting no + /// specific limit for the call, which implies storage usage up to the limit of the parent + /// call. + /// - `value`: The value to transfer into the contract. /// - `input`: The input data buffer used to call the contract. /// - `output`: A reference to the output data buffer to write the call output buffer. If `None` /// is provided then the output buffer is not copied. @@ -106,8 +101,8 @@ pub trait HostFn: private::Sealed { callee: &[u8; 20], ref_time_limit: u64, proof_size_limit: u64, - deposit: Option<&[u8]>, - value: &[u8], + deposit: Option<&[u8; 32]>, + value: &[u8; 32], input_data: &[u8], output: Option<&mut &mut [u8]>, ) -> Result; @@ -287,8 +282,8 @@ pub trait HostFn: private::Sealed { /// /// # Parameters /// - /// - `topics`: The topics list encoded as `Vec`. It can't contain duplicates. - fn deposit_event(topics: &[u8], data: &[u8]); + /// - `topics`: The topics list. It can't contain duplicates. + fn deposit_event(topics: &[[u8; 32]], data: &[u8]); /// Recovers the ECDSA public key from the given message hash and signature. /// @@ -374,11 +369,10 @@ pub trait HostFn: private::Sealed { /// - `code_hash`: The hash of the code to be instantiated. /// - `ref_time_limit`: how much *ref_time* Weight to devote to the execution. /// - `proof_size_limit`: how much *proof_size* Weight to devote to the execution. - /// - `deposit`: The storage deposit limit for instantiation. Should be decodable as a - /// `Option`. Traps otherwise. Passing `None` means setting no specific limit for - /// the call, which implies storage usage up to the limit of the parent call. - /// - `value`: The value to transfer into the contract. Should be decodable as a `T::Balance`. - /// Traps otherwise. + /// - `deposit`: The storage deposit limit for instantiation. Passing `None` means setting no + /// specific limit for the call, which implies storage usage up to the limit of the parent + /// call. + /// - `value`: The value to transfer into the contract. /// - `input`: The input data buffer. /// - `address`: A reference to the address buffer to write the address of the contract. If /// `None` is provided then the output buffer is not copied. @@ -402,8 +396,8 @@ pub trait HostFn: private::Sealed { code_hash: &[u8; 32], ref_time_limit: u64, proof_size_limit: u64, - deposit: Option<&[u8]>, - value: &[u8], + deposit: Option<&[u8; 32]>, + value: &[u8; 32], input: &[u8], address: Option<&mut [u8; 20]>, output: Option<&mut &mut [u8]>, @@ -422,14 +416,11 @@ pub trait HostFn: private::Sealed { fn is_contract(address: &[u8; 20]) -> bool; /// Stores the minimum balance (a.k.a. existential deposit) into the supplied buffer. - /// The data is encoded as `T::Balance`. - /// - /// If the available space in `output` is less than the size of the value a trap is triggered. /// /// # Parameters /// /// - `output`: A reference to the output data buffer to write the minimum balance. - fn minimum_balance(output: &mut &mut [u8]); + fn minimum_balance(output: &mut [u8; 32]); /// Retrieve the code hash of the currently executing contract. /// @@ -440,12 +431,10 @@ pub trait HostFn: private::Sealed { /// Load the latest block timestamp into the supplied buffer /// - /// If the available space in `output` is less than the size of the value a trap is triggered. - /// /// # Parameters /// /// - `output`: A reference to the output data buffer to write the timestamp. - fn now(output: &mut &mut [u8]); + fn now(output: &mut [u8; 32]); /// Removes the delegate dependency from the contract. /// @@ -548,12 +537,12 @@ pub trait HostFn: private::Sealed { /// # Parameters /// /// - `address`: The address of the account to transfer funds to. - /// - `value`: The value to transfer. Should be decodable as a `T::Balance`. Traps otherwise. + /// - `value`: The U256 value to transfer. /// /// # Errors /// /// - [TransferFailed][`crate::ReturnErrorCode::TransferFailed] - fn transfer(address: &[u8; 20], value: &[u8]) -> Result; + fn transfer(address: &[u8; 20], value: &[u8; 32]) -> Result; /// Remove the calling account and transfer remaining **free** balance. /// @@ -573,26 +562,20 @@ pub trait HostFn: private::Sealed { fn terminate(beneficiary: &[u8; 20]) -> !; /// Stores the value transferred along with this call/instantiate into the supplied buffer. - /// The data is encoded as `T::Balance`. - /// - /// If the available space in `output` is less than the size of the value a trap is triggered. /// /// # Parameters /// /// - `output`: A reference to the output data buffer to write the transferred value. - fn value_transferred(output: &mut &mut [u8]); + fn value_transferred(output: &mut [u8; 32]); /// Stores the price for the specified amount of gas into the supplied buffer. - /// The data is encoded as `T::Balance`. - /// - /// If the available space in `output` is less than the size of the value a trap is triggered. /// /// # Parameters /// /// - `ref_time_limit`: The *ref_time* Weight limit to query the price for. /// - `proof_size_limit`: The *proof_size* Weight limit to query the price for. /// - `output`: A reference to the output data buffer to write the price. - fn weight_to_fee(ref_time_limit: u64, proof_size_limit: u64, output: &mut &mut [u8]); + fn weight_to_fee(ref_time_limit: u64, proof_size_limit: u64, output: &mut [u8; 32]); /// Execute an XCM program locally, using the contract's address as the origin. /// This is equivalent to dispatching `pallet_xcm::execute` through call_runtime, except that diff --git a/substrate/frame/revive/uapi/src/host/riscv32.rs b/substrate/frame/revive/uapi/src/host/riscv32.rs index c8218bb8f73..b7b660c4083 100644 --- a/substrate/frame/revive/uapi/src/host/riscv32.rs +++ b/substrate/frame/revive/uapi/src/host/riscv32.rs @@ -79,24 +79,19 @@ mod sys { pub fn caller_is_origin() -> ReturnCode; pub fn caller_is_root() -> ReturnCode; pub fn address(out_ptr: *mut u8); - pub fn weight_to_fee( - ref_time: u64, - proof_size: u64, - out_ptr: *mut u8, - out_len_ptr: *mut u32, - ); + pub fn weight_to_fee(ref_time: u64, proof_size: u64, out_ptr: *mut u8); pub fn weight_left(out_ptr: *mut u8, out_len_ptr: *mut u32); - pub fn balance(out_ptr: *mut u8, out_len_ptr: *mut u32); - pub fn value_transferred(out_ptr: *mut u8, out_len_ptr: *mut u32); - pub fn now(out_ptr: *mut u8, out_len_ptr: *mut u32); - pub fn minimum_balance(out_ptr: *mut u8, out_len_ptr: *mut u32); + pub fn balance(out_ptr: *mut u8); + pub fn value_transferred(out_ptr: *mut u8); + pub fn now(out_ptr: *mut u8); + pub fn minimum_balance(out_ptr: *mut u8); pub fn deposit_event( - topics_ptr: *const u8, - topics_len: u32, + topics_ptr: *const [u8; 32], + num_topic: u32, data_ptr: *const u8, data_len: u32, ); - pub fn block_number(out_ptr: *mut u8, out_len_ptr: *mut u32); + pub fn block_number(out_ptr: *mut u8); pub fn hash_sha2_256(input_ptr: *const u8, input_len: u32, out_ptr: *mut u8); pub fn hash_keccak_256(input_ptr: *const u8, input_len: u32, out_ptr: *mut u8); pub fn hash_blake2_256(input_ptr: *const u8, input_len: u32, out_ptr: *mut u8); @@ -136,21 +131,20 @@ mod sys { } } +/// A macro to implement all Host functions with a signature of `fn(&mut [u8; n])`. macro_rules! impl_wrapper_for { - ( $( $name:ident, )* ) => { - $( - fn $name(output: &mut &mut [u8]) { - let mut output_len = output.len() as u32; - unsafe { - sys::$name( - output.as_mut_ptr(), - &mut output_len, - ) - } - extract_from_slice(output, output_len as usize) - } - )* - } + (@impl_fn $name:ident, $n: literal) => { + fn $name(output: &mut [u8; $n]) { + unsafe { sys::$name(output.as_mut_ptr()) } + } + }; + + () => {}; + + ([u8; $n: literal] => $($name:ident),*; $($tail:tt)*) => { + $(impl_wrapper_for!(@impl_fn $name, $n);)* + impl_wrapper_for!($($tail)*); + }; } macro_rules! impl_hash_fn { @@ -185,7 +179,7 @@ fn ptr_len_or_sentinel(data: &mut Option<&mut &mut [u8]>) -> (*mut u8, u32) { } #[inline(always)] -fn ptr_or_sentinel(data: &Option<&[u8]>) -> *const u8 { +fn ptr_or_sentinel(data: &Option<&[u8; 32]>) -> *const u8 { match data { Some(ref data) => data.as_ptr(), None => crate::SENTINEL as _, @@ -197,8 +191,8 @@ impl HostFn for HostFnImpl { code_hash: &[u8; 32], ref_time_limit: u64, proof_size_limit: u64, - deposit_limit: Option<&[u8]>, - value: &[u8], + deposit_limit: Option<&[u8; 32]>, + value: &[u8; 32], input: &[u8], mut address: Option<&mut [u8; 20]>, mut output: Option<&mut &mut [u8]>, @@ -253,8 +247,8 @@ impl HostFn for HostFnImpl { callee: &[u8; 20], ref_time_limit: u64, proof_size_limit: u64, - deposit_limit: Option<&[u8]>, - value: &[u8], + deposit_limit: Option<&[u8; 32]>, + value: &[u8; 32], input: &[u8], mut output: Option<&mut &mut [u8]>, ) -> Result { @@ -327,12 +321,12 @@ impl HostFn for HostFnImpl { ret_code.into() } - fn transfer(address: &[u8; 20], value: &[u8]) -> Result { + fn transfer(address: &[u8; 20], value: &[u8; 32]) -> Result { let ret_code = unsafe { sys::transfer(address.as_ptr(), value.as_ptr()) }; ret_code.into() } - fn deposit_event(topics: &[u8], data: &[u8]) { + fn deposit_event(topics: &[[u8; 32]], data: &[u8]) { unsafe { sys::deposit_event( topics.as_ptr(), @@ -449,33 +443,19 @@ impl HostFn for HostFnImpl { ret_code.into() } - fn address(output: &mut [u8; 20]) { - unsafe { sys::address(output.as_mut_ptr()) } - } - - fn caller(output: &mut [u8; 20]) { - unsafe { sys::caller(output.as_mut_ptr()) } - } - impl_wrapper_for! { - block_number, balance, - value_transferred,now, minimum_balance, - weight_left, + [u8; 32] => block_number, balance, value_transferred, now, minimum_balance; + [u8; 20] => address, caller; } - fn weight_to_fee(ref_time_limit: u64, proof_size_limit: u64, output: &mut &mut [u8]) { + fn weight_left(output: &mut &mut [u8]) { let mut output_len = output.len() as u32; - { - unsafe { - sys::weight_to_fee( - ref_time_limit, - proof_size_limit, - output.as_mut_ptr(), - &mut output_len, - ) - }; - } - extract_from_slice(output, output_len as usize); + unsafe { sys::weight_left(output.as_mut_ptr(), &mut output_len) } + extract_from_slice(output, output_len as usize) + } + + fn weight_to_fee(ref_time_limit: u64, proof_size_limit: u64, output: &mut [u8; 32]) { + unsafe { sys::weight_to_fee(ref_time_limit, proof_size_limit, output.as_mut_ptr()) }; } impl_hash_fn!(sha2_256, 32); -- GitLab From 083f5273fa9d03b2188c0c41aa21f8b45c59a733 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Sep 2024 07:05:54 +0000 Subject: [PATCH 022/125] Bump the known_good_semver group with 3 updates (#5636) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the known_good_semver group with 3 updates: [serde](https://github.com/serde-rs/serde), [serde_derive](https://github.com/serde-rs/serde) and [serde_json](https://github.com/serde-rs/json). Updates `serde` from 1.0.209 to 1.0.210
Release notes

Sourced from serde's releases.

v1.0.210

  • Support serializing and deserializing IpAddr and SocketAddr in no-std mode on Rust 1.77+ (#2816, thanks @​MathiasKoch)
  • Make serde::ser::StdError and serde::de::StdError equivalent to core::error::Error on Rust 1.81+ (#2818)
Commits
  • 89c4b02 Release 1.0.210
  • eeb8e44 Merge pull request #2818 from dtolnay/coreerror
  • 785c2d9 Stabilize no-std StdError trait
  • d549f04 Reformat parse_ip_impl definition and calls
  • 4c0dd63 Delete attr support from core::net deserialization macros
  • 26fb134 Relocate cfg attrs out of parse_ip_impl and parse_socket_impl
  • 07e614b Merge pull request #2817 from dtolnay/corenet
  • b1f899f Delete doc(cfg) attribute from impls that are supported in no-std
  • b4f860e Merge pull request #2816 from MathiasKoch/chore/core-net
  • d940fe1 Reuse existing Buf wrapper as replacement for std::io::Write
  • Additional commits viewable in compare view

Updates `serde_derive` from 1.0.209 to 1.0.210
Release notes

Sourced from serde_derive's releases.

v1.0.210

  • Support serializing and deserializing IpAddr and SocketAddr in no-std mode on Rust 1.77+ (#2816, thanks @​MathiasKoch)
  • Make serde::ser::StdError and serde::de::StdError equivalent to core::error::Error on Rust 1.81+ (#2818)
Commits
  • 89c4b02 Release 1.0.210
  • eeb8e44 Merge pull request #2818 from dtolnay/coreerror
  • 785c2d9 Stabilize no-std StdError trait
  • d549f04 Reformat parse_ip_impl definition and calls
  • 4c0dd63 Delete attr support from core::net deserialization macros
  • 26fb134 Relocate cfg attrs out of parse_ip_impl and parse_socket_impl
  • 07e614b Merge pull request #2817 from dtolnay/corenet
  • b1f899f Delete doc(cfg) attribute from impls that are supported in no-std
  • b4f860e Merge pull request #2816 from MathiasKoch/chore/core-net
  • d940fe1 Reuse existing Buf wrapper as replacement for std::io::Write
  • Additional commits viewable in compare view

Updates `serde_json` from 1.0.127 to 1.0.128
Release notes

Sourced from serde_json's releases.

1.0.128

  • Support serializing maps containing 128-bit integer keys to serde_json::Value (#1188, thanks @​Mrreadiness)
Commits
  • d96b1d9 Release 1.0.128
  • 599228d Merge pull request #1188 from Mrreadiness/feat/add-hashmap-key-128-serializer
  • 5416cee feat: add support for 128 bit HashMap key serialization
  • 27a4ca9 Upload CI Cargo.lock for reproducing failures
  • See full diff in compare view

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 12 ++++++------ Cargo.toml | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 508fc2e8922..e8510a4c6a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19104,9 +19104,9 @@ checksum = "f97841a747eef040fcd2e7b3b9a220a7205926e60488e673d9e4926d27772ce5" [[package]] name = "serde" -version = "1.0.209" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] @@ -19131,9 +19131,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.209" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2 1.0.82", "quote 1.0.37", @@ -19162,9 +19162,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.127" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ "indexmap 2.2.3", "itoa", diff --git a/Cargo.toml b/Cargo.toml index 6bd401d8e15..50c7225c749 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1185,10 +1185,10 @@ secp256k1 = { version = "0.28.0", default-features = false } secrecy = { version = "0.8.0", default-features = false } seedling-runtime = { path = "cumulus/parachains/runtimes/starters/seedling" } separator = { version = "0.4.1" } -serde = { version = "1.0.209", default-features = false } +serde = { version = "1.0.210", default-features = false } serde-big-array = { version = "0.3.2" } serde_derive = { version = "1.0.117" } -serde_json = { version = "1.0.127", default-features = false } +serde_json = { version = "1.0.128", default-features = false } serde_yaml = { version = "0.9" } serial_test = { version = "2.0.0" } sha1 = { version = "0.10.6" } -- GitLab From def35b92d120a78869cc2e06d34d575eea80faff Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Sep 2024 07:06:51 +0000 Subject: [PATCH 023/125] Bump lazy_static from 1.4.0 to 1.5.0 (#5639) Bumps [lazy_static](https://github.com/rust-lang-nursery/lazy-static.rs) from 1.4.0 to 1.5.0.
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=lazy_static&package-manager=cargo&previous-version=1.4.0&new-version=1.5.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e8510a4c6a5..f6071a97915 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7729,9 +7729,9 @@ dependencies = [ [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "lazycell" diff --git a/Cargo.toml b/Cargo.toml index 50c7225c749..174811663de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -822,7 +822,7 @@ kvdb-memorydb = { version = "0.13.0" } kvdb-rocksdb = { version = "0.19.0" } kvdb-shared-tests = { version = "0.11.0" } landlock = { version = "0.3.0" } -lazy_static = { version = "1.4.0" } +lazy_static = { version = "1.5.0" } libc = { version = "0.2.155" } libfuzzer-sys = { version = "0.4" } libp2p = { version = "0.52.4" } -- GitLab From f5783cc68b53b06decb97cdddbfb511280ffdc45 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Sep 2024 07:07:23 +0000 Subject: [PATCH 024/125] Bump proc-macro2 from 1.0.82 to 1.0.86 (#5638) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [proc-macro2](https://github.com/dtolnay/proc-macro2) from 1.0.82 to 1.0.86.
Release notes

Sourced from proc-macro2's releases.

1.0.86

  • Documentation improvements

1.0.85

  • Mark some tests as only for 64-bit targets (#463)

1.0.84

1.0.83

  • Optimize the representation of Ident (#462)
Commits
  • aa9476b Release 1.0.86
  • 1961358 Merge pull request #466 from dtolnay/buildrs
  • e1bd2cc Bring build script comments up to date
  • 5b27127 Merge pull request #465 from dtolnay/ignorereason
  • 0da4629 Fill in ignore reasons in all #[ignore] attributes
  • 5ee1cab Release 1.0.85
  • aa64c20 Merge pull request #464 from dtolnay/testsize
  • bc9f4d9 Ignore size tests on non-64bit target
  • 1160ec3 Make size tests #[ignore] in cfg(randomize_layout)
  • 33c9578 Release 1.0.84
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=proc-macro2&package-manager=cargo&previous-version=1.0.82&new-version=1.0.86)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 220 ++++++++++++++++++++++++++--------------------------- Cargo.toml | 2 +- 2 files changed, 111 insertions(+), 111 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f6071a97915..e1b0b3f8f9e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -157,7 +157,7 @@ dependencies = [ "dunce", "heck 0.4.1", "proc-macro-error", - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.65", "syn-solidity", @@ -284,7 +284,7 @@ dependencies = [ "include_dir", "itertools 0.10.5", "proc-macro-error", - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.65", ] @@ -518,7 +518,7 @@ checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" dependencies = [ "num-bigint", "num-traits", - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 1.0.109", ] @@ -620,7 +620,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea" dependencies = [ - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 1.0.109", ] @@ -724,7 +724,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "726535892e8eae7e70657b4c8ea93d26b8553afb1ce617caee529ef96d7dee6c" dependencies = [ - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 1.0.109", "synstructure 0.12.6", @@ -736,7 +736,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7378575ff571966e99a744addeff0bff98b8ada0dedf1956d59e634db95eaac1" dependencies = [ - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.65", "synstructure 0.13.1", @@ -748,7 +748,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2777730b2039ac0f95f093556e61b6d26cebed5393ca6f152717777cec3a42ed" dependencies = [ - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 1.0.109", ] @@ -759,7 +759,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.65", ] @@ -1290,7 +1290,7 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.65", ] @@ -1307,7 +1307,7 @@ version = "0.1.80" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.65", ] @@ -1366,7 +1366,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fee3da8ef1276b0bee5dd1c7258010d8fffd31801447323115a25560e1327b89" dependencies = [ "proc-macro-error", - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 1.0.109", ] @@ -1503,7 +1503,7 @@ dependencies = [ "lazycell", "peeking_take_while", "prettyplease 0.2.12", - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "regex", "rustc-hash 1.1.0", @@ -2846,7 +2846,7 @@ checksum = "ae6371b8bdc8b7d3959e9cf7b22d4435ef3e79e138688421ec654acf8c81b008" dependencies = [ "heck 0.4.1", "proc-macro-error", - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 1.0.109", ] @@ -2858,7 +2858,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d029b67f89d30bbb547c89fd5161293c0aec155fc691d7924b64550662db93e" dependencies = [ "heck 0.5.0", - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.65", ] @@ -3046,7 +3046,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d51beaa537d73d2d1ff34ee70bc095f170420ab2ec5d687ecd3ec2b0d092514b" dependencies = [ "nom", - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 1.0.109", ] @@ -4169,7 +4169,7 @@ name = "cumulus-pallet-parachain-system-proc-macro" version = "0.6.0" dependencies = [ "proc-macro-crate 3.1.0", - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.65", ] @@ -4705,7 +4705,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83fdaf97f4804dcebfa5862639bc9ce4121e82140bec2a987ac5140294865b5b" dependencies = [ - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.65", ] @@ -4744,7 +4744,7 @@ dependencies = [ "cc", "codespan-reporting", "once_cell", - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "scratch", "syn 2.0.65", @@ -4762,7 +4762,7 @@ version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50c49547d73ba8dcfd4ad7325d64c6d5391ff4224d498fc39a6f3f49825a530d" dependencies = [ - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.65", ] @@ -4868,7 +4868,7 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" dependencies = [ - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 1.0.109", ] @@ -4879,7 +4879,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d65d7ce8132b7c0e54497a4d9a55a1c2a0912a0d786cf894472ba818fba45762" dependencies = [ - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.65", ] @@ -4890,7 +4890,7 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" dependencies = [ - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.65", ] @@ -4902,7 +4902,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" dependencies = [ "convert_case", - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "rustc_version 0.4.0", "syn 1.0.109", @@ -4998,7 +4998,7 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.65", ] @@ -5058,7 +5058,7 @@ dependencies = [ "common-path", "derive-syn-parse", "once_cell", - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "regex", "syn 2.0.65", @@ -5107,7 +5107,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "558e40ea573c374cf53507fd240b7ee2f5477df7cfebdb97323ec61c719399c5" dependencies = [ - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 1.0.109", ] @@ -5255,7 +5255,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9720bba047d567ffc8a3cba48bf19126600e249ab7f128e9233e6376976a116" dependencies = [ "heck 0.4.1", - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 1.0.109", ] @@ -5267,7 +5267,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ffccbb6966c05b32ef8fbac435df276c4ae4d3dc55a8cd0eb9745e6c12f546a" dependencies = [ "heck 0.4.1", - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.65", ] @@ -5287,7 +5287,7 @@ version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e9a1f9f7d83e59740248a6e14ecf93929ade55027844dfcea78beafccc15745" dependencies = [ - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.65", ] @@ -5298,7 +5298,7 @@ version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fd000fd6988e73bbe993ea3db9b1aa64906ab88766d654973924340c8cddb42" dependencies = [ - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.65", ] @@ -5501,7 +5501,7 @@ dependencies = [ "file-guard", "fs-err", "prettyplease 0.2.12", - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.65", ] @@ -5573,7 +5573,7 @@ dependencies = [ "expander", "indexmap 2.2.3", "proc-macro-crate 3.1.0", - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.65", ] @@ -5905,7 +5905,7 @@ dependencies = [ "frame-support", "parity-scale-codec", "proc-macro-crate 3.1.0", - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "scale-info", "sp-arithmetic", @@ -6100,7 +6100,7 @@ dependencies = [ "parity-scale-codec", "pretty_assertions", "proc-macro-warning 1.0.0", - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "regex", "scale-info", @@ -6119,7 +6119,7 @@ version = "10.0.0" dependencies = [ "frame-support-procedural-tools-derive", "proc-macro-crate 3.1.0", - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.65", ] @@ -6128,7 +6128,7 @@ dependencies = [ name = "frame-support-procedural-tools-derive" version = "11.0.0" dependencies = [ - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.65", ] @@ -6379,7 +6379,7 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.65", ] @@ -7172,7 +7172,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" dependencies = [ - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 1.0.109", ] @@ -7192,7 +7192,7 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b139284b5cf57ecfa712bcc66950bb635b31aff41c188e8a4cfc758eca374a3f" dependencies = [ - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", ] @@ -7551,7 +7551,7 @@ checksum = "6b07a2daf52077ab1b197aea69a5c990c060143835bf04c77070e98903791715" dependencies = [ "heck 0.5.0", "proc-macro-crate 3.1.0", - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.65", ] @@ -8137,7 +8137,7 @@ checksum = "c4d5ec2a3df00c7836d7696c136274c9c59705bac69133253696a6c932cd1d74" dependencies = [ "heck 0.4.1", "proc-macro-warning 0.4.2", - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.65", ] @@ -8565,7 +8565,7 @@ dependencies = [ "const-random", "derive-syn-parse", "macro_magic_core_macros", - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.65", ] @@ -8576,7 +8576,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b02abfe41815b5bd98dbd4260173db2c116dda171dc0fe7838cb206333b83308" dependencies = [ - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.65", ] @@ -8921,7 +8921,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ce75669015c4f47b289fd4d4f56e894e4c96003ffdf3ac51313126f94c6cbb" dependencies = [ "cfg-if", - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 1.0.109", ] @@ -8933,7 +8933,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af7cbce79ec385a1d4f54baa90a76401eb15d9cab93685f62e7e9f942aa00ae2" dependencies = [ "cfg-if", - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.65", ] @@ -9045,7 +9045,7 @@ checksum = "fc076939022111618a5026d3be019fd8b366e76314538ff9a1b59ffbcbf98bcd" dependencies = [ "proc-macro-crate 1.3.1", "proc-macro-error", - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 1.0.109", "synstructure 0.12.6", @@ -9093,7 +9093,7 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91761aed67d03ad966ef783ae962ef9bbaca728d2dd7ceb7939ec110fffad998" dependencies = [ - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 1.0.109", ] @@ -9484,7 +9484,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.65", ] @@ -9660,7 +9660,7 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.65", ] @@ -9727,7 +9727,7 @@ dependencies = [ "itertools 0.11.0", "petgraph", "proc-macro-crate 3.1.0", - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 1.0.109", ] @@ -10442,7 +10442,7 @@ dependencies = [ name = "pallet-contracts-proc-macro" version = "18.0.0" dependencies = [ - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.65", ] @@ -11545,7 +11545,7 @@ dependencies = [ name = "pallet-revive-proc-macro" version = "0.1.0" dependencies = [ - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.65", ] @@ -11789,7 +11789,7 @@ name = "pallet-staking-reward-curve" version = "11.0.0" dependencies = [ "proc-macro-crate 3.1.0", - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "sp-runtime", "syn 2.0.65", @@ -12435,7 +12435,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d830939c76d294956402033aee57a6da7b438f2294eb94864c37b0569053a42c" dependencies = [ "proc-macro-crate 3.1.0", - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 1.0.109", ] @@ -12464,7 +12464,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f557c32c6d268a07c921471619c0295f5efad3a0e76d4f97a05c091a51d110b2" dependencies = [ - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "syn 1.0.109", "synstructure 0.12.6", ] @@ -12884,7 +12884,7 @@ checksum = "68ca01446f50dbda87c1786af8770d535423fa8a53aec03b8f4e3d7eb10e0929" dependencies = [ "pest", "pest_meta", - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.65", ] @@ -12925,7 +12925,7 @@ version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.65", ] @@ -15342,7 +15342,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c4fdfc49717fb9a196e74a5d28e0bc764eb394a2c803eb11133a31ac996c60c" dependencies = [ "polkavm-common 0.9.0", - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.65", ] @@ -15354,7 +15354,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7855353a5a783dd5d09e3b915474bddf66575f5a3cf45dec8d1c5e051ba320dc" dependencies = [ "polkavm-common 0.10.0", - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.65", ] @@ -15592,7 +15592,7 @@ version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c8646e95016a7a6c4adea95bafa8a16baab64b583356217f2c85db4a39d9a86" dependencies = [ - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "syn 1.0.109", ] @@ -15602,7 +15602,7 @@ version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c64d9ba0963cdcea2e1b2230fbae2bab30eb25a174be395c41e764bfb65dd62" dependencies = [ - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "syn 2.0.65", ] @@ -15663,7 +15663,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" dependencies = [ "proc-macro-error-attr", - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 1.0.109", "version_check", @@ -15675,7 +15675,7 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "version_check", ] @@ -15692,7 +15692,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d1eaa7fa0aa1929ffdf7eeb6eac234dde6268914a14ad44d23521ab6a9b258e" dependencies = [ - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.65", ] @@ -15703,7 +15703,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b698b0b09d40e9b7c1a47b132d66a8b54bcd20583d9b6d06e4535e383b4405c" dependencies = [ - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.65", ] @@ -15719,9 +15719,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.82" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] @@ -15784,7 +15784,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" dependencies = [ - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.65", ] @@ -15892,7 +15892,7 @@ checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" dependencies = [ "anyhow", "itertools 0.10.5", - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 1.0.109", ] @@ -15905,7 +15905,7 @@ checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" dependencies = [ "anyhow", "itertools 0.11.0", - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.65", ] @@ -16145,7 +16145,7 @@ version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", ] [[package]] @@ -16344,7 +16344,7 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6" dependencies = [ - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.65", ] @@ -16894,7 +16894,7 @@ checksum = "d428f8247852f894ee1be110b375111b586d4fa431f6c46e64ba5a0dcccbe605" dependencies = [ "cfg-if", "glob", - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "regex", "relative-path", @@ -17388,7 +17388,7 @@ name = "sc-chain-spec-derive" version = "11.0.0" dependencies = [ "proc-macro-crate 3.1.0", - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.65", ] @@ -18683,7 +18683,7 @@ name = "sc-tracing-proc-macro" version = "11.0.0" dependencies = [ "proc-macro-crate 3.1.0", - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.65", ] @@ -18797,7 +18797,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d35494501194174bda522a32605929eefc9ecf7e0a326c26db1fdd85881eb62" dependencies = [ "proc-macro-crate 3.1.0", - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 1.0.109", ] @@ -18835,7 +18835,7 @@ version = "0.8.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0f696e21e10fa546b7ffb1c9672c6de8fbc7a81acf59524386d8639bf12737" dependencies = [ - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "serde_derive_internals", "syn 1.0.109", @@ -19135,7 +19135,7 @@ version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.65", ] @@ -19146,7 +19146,7 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" dependencies = [ - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 1.0.109", ] @@ -19237,7 +19237,7 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91d129178576168c589c9ec973feedf7d3126c01ac2bf08795109aa35b69fb8f" dependencies = [ - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.65", ] @@ -20112,7 +20112,7 @@ dependencies = [ "blake2 0.10.6", "expander", "proc-macro-crate 3.1.0", - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.65", ] @@ -20515,7 +20515,7 @@ name = "sp-debug-derive" version = "8.0.0" source = "git+https://github.com/paritytech/polkadot-sdk#82912acb33a9030c0ef3bf590a34fca09b72dc5f" dependencies = [ - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.65", ] @@ -20524,7 +20524,7 @@ dependencies = [ name = "sp-debug-derive" version = "14.0.0" dependencies = [ - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.65", ] @@ -20797,7 +20797,7 @@ source = "git+https://github.com/paritytech/polkadot-sdk#82912acb33a9030c0ef3bf5 dependencies = [ "Inflector", "proc-macro-crate 1.3.1", - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.65", ] @@ -20809,7 +20809,7 @@ dependencies = [ "Inflector", "expander", "proc-macro-crate 3.1.0", - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.65", ] @@ -21070,7 +21070,7 @@ name = "sp-version-proc-macro" version = "13.0.0" dependencies = [ "parity-scale-codec", - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "sp-version", "syn 2.0.65", @@ -21155,7 +21155,7 @@ checksum = "5e6915280e2d0db8911e5032a5c275571af6bdded2916abd691a659be25d3439" dependencies = [ "Inflector", "num-format", - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "serde", "serde_json", @@ -21180,7 +21180,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f07d54c4d01a1713eb363b55ba51595da15f6f1211435b71466460da022aa140" dependencies = [ - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 1.0.109", ] @@ -21375,7 +21375,7 @@ checksum = "70a2595fc3aa78f2d0e45dd425b22282dd863273761cc77780914b2cf3003acf" dependencies = [ "cfg_aliases", "memchr", - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 1.0.109", ] @@ -21448,7 +21448,7 @@ checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" dependencies = [ "heck 0.3.3", "proc-macro-error", - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 1.0.109", ] @@ -21484,7 +21484,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" dependencies = [ "heck 0.4.1", - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "rustversion", "syn 1.0.109", @@ -21497,7 +21497,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" dependencies = [ "heck 0.4.1", - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "rustversion", "syn 2.0.65", @@ -21510,7 +21510,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6cf59daf282c0a494ba14fd21610a0325f9f90ec9d1231dea26bcb1d696c946" dependencies = [ "heck 0.4.1", - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "rustversion", "syn 2.0.65", @@ -21961,7 +21961,7 @@ version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "unicode-ident", ] @@ -21972,7 +21972,7 @@ version = "2.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2863d96a84c6439701d7a38f9de935ec562c8832cc55d1dde0f513b52fad106" dependencies = [ - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "unicode-ident", ] @@ -21984,7 +21984,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86b837ef12ab88835251726eb12237655e61ec8dc8a280085d1961cdc3dfd047" dependencies = [ "paste", - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.65", ] @@ -21995,7 +21995,7 @@ version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" dependencies = [ - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 1.0.109", "unicode-xid 0.2.4", @@ -22007,7 +22007,7 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.65", ] @@ -22126,7 +22126,7 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5999e24eaa32083191ba4e425deb75cdf25efefabe5aaccb7446dd0d4122a3f5" dependencies = [ - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.65", ] @@ -22290,7 +22290,7 @@ version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10ac1c5050e43014d16b2f94d0d2ce79e65ffdd8b38d8048f9c8f6a8a6da62ac" dependencies = [ - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 1.0.109", ] @@ -22301,7 +22301,7 @@ version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.65", ] @@ -22467,7 +22467,7 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.65", ] @@ -22685,7 +22685,7 @@ version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.65", ] @@ -22727,7 +22727,7 @@ dependencies = [ "assert_matches", "expander", "proc-macro-crate 3.1.0", - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.65", ] @@ -23298,7 +23298,7 @@ dependencies = [ "bumpalo", "log", "once_cell", - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.65", "wasm-bindgen-shared", @@ -23332,7 +23332,7 @@ version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.65", "wasm-bindgen-backend", @@ -23365,7 +23365,7 @@ version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ecb993dd8c836930ed130e020e77d9b2e65dd0fbab1b67c790b0f5d80b11a575" dependencies = [ - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", ] @@ -24440,7 +24440,7 @@ name = "xcm-procedural" version = "7.0.0" dependencies = [ "Inflector", - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "staging-xcm", "syn 2.0.65", @@ -24606,7 +24606,7 @@ version = "0.7.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.65", ] @@ -24626,7 +24626,7 @@ version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ - "proc-macro2 1.0.82", + "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.65", ] diff --git a/Cargo.toml b/Cargo.toml index 174811663de..e2611982a98 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1079,7 +1079,7 @@ pretty_assertions = { version = "1.3.0" } primitive-types = { version = "0.12.1", default-features = false } proc-macro-crate = { version = "3.0.0" } proc-macro-warning = { version = "1.0.0", default-features = false } -proc-macro2 = { version = "1.0.64" } +proc-macro2 = { version = "1.0.86" } procfs = { version = "0.16.0" } prometheus = { version = "0.13.0", default-features = false } prometheus-endpoint = { path = "substrate/utils/prometheus", default-features = false, package = "substrate-prometheus-endpoint" } -- GitLab From a3eda0a1bdc67fc14efbf64c1f0046d452f55e31 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Mon, 9 Sep 2024 10:34:53 +0200 Subject: [PATCH 025/125] [CI] Remove duplicate jobs (#5602) All of these things are already tested in Github Actions: https://github.com/paritytech/polkadot-sdk/blob/b3c2a25b73bb4854f26204068f0aec3e8577196c/.github/workflows/checks-quick.yml#L50 Q: the `job-starter` seems to be used by tests, so am keeping it, but not sure how useful it is. --------- Co-authored-by: Alexander Samusev <41779041+alvicsam@users.noreply.github.com> Co-authored-by: alvicsam --- .github/workflows/checks-quick.yml | 26 ++++++++++++++++++++ .gitlab/pipeline/check.yml | 39 ------------------------------ 2 files changed, 26 insertions(+), 39 deletions(-) diff --git a/.github/workflows/checks-quick.yml b/.github/workflows/checks-quick.yml index ee5ac31e9ca..96f214e9427 100644 --- a/.github/workflows/checks-quick.yml +++ b/.github/workflows/checks-quick.yml @@ -181,3 +181,29 @@ jobs: env: ASSERT_REGEX: "FAIL-CI" GIT_DEPTH: 1 + + confirm-required-checks-quick-jobs-passed: + runs-on: ubuntu-latest + name: All quick checks passed + # If any new job gets added, be sure to add it to this array + needs: + - fmt + - check-dependency-rules + - check-rust-feature-propagation + - test-rust-features + - check-toml-format + - check-workspace + - check-markdown + - check-umbrella + - check-fail-ci + if: always() && !cancelled() + steps: + - run: | + tee resultfile <<< '${{ toJSON(needs) }}' + FAILURES=$(cat resultfile | grep '"result": "failure"' | wc -l) + if [ $FAILURES -gt 0 ]; then + echo "### At least one required job failed ❌" >> $GITHUB_STEP_SUMMARY + exit 1 + else + echo '### Good job! All the required jobs passed 🚀' >> $GITHUB_STEP_SUMMARY + fi diff --git a/.gitlab/pipeline/check.yml b/.gitlab/pipeline/check.yml index 2212c1aeb0a..7d1f37dddd5 100644 --- a/.gitlab/pipeline/check.yml +++ b/.gitlab/pipeline/check.yml @@ -1,25 +1,3 @@ -# from substrate -# not sure if it's needed in monorepo -check-dependency-rules: - stage: check - extends: - - .kubernetes-env - - .test-refs-no-trigger-prs-only - variables: - CI_IMAGE: "paritytech/tools:latest" - allow_failure: true - script: - - cd substrate/ - - ../.gitlab/ensure-deps.sh - -test-rust-features: - stage: check - extends: - - .kubernetes-env - - .test-refs-no-trigger-prs-only - script: - - bash .gitlab/rust-features.sh . - job-starter: stage: check image: paritytech/tools:latest @@ -29,20 +7,3 @@ job-starter: allow_failure: true script: - echo ok - -check-rust-feature-propagation: - stage: check - extends: - - .kubernetes-env - - .common-refs - script: - - zepter run check - -check-toml-format: - stage: check - extends: - - .kubernetes-env - - .common-refs - script: - - taplo format --check --config .config/taplo.toml - - echo "Please run `taplo format --config .config/taplo.toml` to fix any toml formatting issues" -- GitLab From c72f9ab594c8c8ea3f51323de8e1846c75f4a428 Mon Sep 17 00:00:00 2001 From: Nick Vikeras Date: Mon, 9 Sep 2024 05:12:56 -0500 Subject: [PATCH 026/125] Plumb RPC listener up to caller (#5038) # Description This PR allows the RPC server's socket address to be returned when initializing the server. This allows the library consumer to easily programmatically determine which port the RPC server is listening on. My use case for this is automated testing. I'd like to be able to simply specify that the server bind to port '0' and then test against whatever port the OS assigns dynamically. I will have many RPC servers running in parallel across many tests within a single process, and I don't want to have to deal with port conflicts. ## Integration Integration is straightforward. My main concern is that I am making non-backwards-compatible changes to public library functions. Let me know if I should leave backwards-compatible wrappers in place for any/all of the public functions that were modified. ## Review Notes The rationale for making the new listen_addresses field on the RpcHandlers struct a ```[MultiAddr]``` rather than ```SocketAddr``` is because I wanted it to be transport-agnostic as well as capable of supporting multiple listening addresses in case that is ever required by the RPC server in the future. # Checklist * [x] My PR includes a detailed description as outlined in the "Description" and its two subsections above. * [ ] My PR follows the [labeling requirements](CONTRIBUTING.md#Process) of this project (at minimum one label for `T` required) * External contributors: ask maintainers to put the right label on your PR. * [x] I have made corresponding changes to the documentation (if applicable) * [ ] I have added tests that prove my fix is effective or that my feature works (if applicable) 1. I didn't understand what the 'T' label meant. Am I supposed to open a github Issue for my PR? 2. I didn't see an easy way to add tests since the functions I am modifying are not directly called by any tests. --------- Co-authored-by: Niklas Adolfsson --- prdoc/pr_5038.prdoc | 15 +++++++++ substrate/client/rpc-servers/src/lib.rs | 42 ++++++++++++++++++++--- substrate/client/service/src/builder.rs | 16 +++++++-- substrate/client/service/src/lib.rs | 45 +++++++++++++------------ 4 files changed, 89 insertions(+), 29 deletions(-) create mode 100644 prdoc/pr_5038.prdoc diff --git a/prdoc/pr_5038.prdoc b/prdoc/pr_5038.prdoc new file mode 100644 index 00000000000..2bab8ef69f8 --- /dev/null +++ b/prdoc/pr_5038.prdoc @@ -0,0 +1,15 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Plumb RPC listener up to caller + +doc: + - audience: Node Dev + description: + This PR allows the RPC server's socket address to be returned when initializing the server. + This allows the library consumer to easily programmatically determine which port the RPC server is listening on. +crates: + - name: sc-rpc-server + bump: major + - name: sc-service + bump: major diff --git a/substrate/client/rpc-servers/src/lib.rs b/substrate/client/rpc-servers/src/lib.rs index ca74c2371c2..0472a0a2f63 100644 --- a/substrate/client/rpc-servers/src/lib.rs +++ b/substrate/client/rpc-servers/src/lib.rs @@ -23,11 +23,13 @@ pub mod middleware; pub mod utils; -use std::{error::Error as StdError, time::Duration}; +use std::{error::Error as StdError, net::SocketAddr, time::Duration}; use jsonrpsee::{ core::BoxError, - server::{serve_with_graceful_shutdown, stop_channel, ws, PingConfig, StopHandle}, + server::{ + serve_with_graceful_shutdown, stop_channel, ws, PingConfig, ServerHandle, StopHandle, + }, Methods, RpcModule, }; use middleware::NodeHealthProxyLayer; @@ -46,8 +48,38 @@ pub use utils::{RpcEndpoint, RpcMethods}; const MEGABYTE: u32 = 1024 * 1024; -/// Type alias for the JSON-RPC server. -pub type Server = jsonrpsee::server::ServerHandle; +/// Type to encapsulate the server handle and listening address. +pub struct Server { + /// Handle to the rpc server + handle: ServerHandle, + /// Listening address of the server + listen_addrs: Vec, +} + +impl Server { + /// Creates a new Server. + pub fn new(handle: ServerHandle, listen_addrs: Vec) -> Server { + Server { handle, listen_addrs } + } + + /// Returns the `jsonrpsee::server::ServerHandle` for this Server. Can be used to stop the + /// server. + pub fn handle(&self) -> &ServerHandle { + &self.handle + } + + /// The listen address for the running RPC service. + pub fn listen_addrs(&self) -> &[SocketAddr] { + &self.listen_addrs + } +} + +impl Drop for Server { + fn drop(&mut self) { + // This doesn't not wait for the server to be stopped but fires the signal. + let _ = self.handle.stop(); + } +} /// Trait for providing subscription IDs that can be cloned. pub trait SubscriptionIdProvider: @@ -273,5 +305,5 @@ where // This is to make it work with old scripts/utils that parse the logs. log::info!("Running JSON-RPC server: addr={}", format_listen_addrs(&local_addrs)); - Ok(server_handle) + Ok(Server::new(server_handle, local_addrs)) } diff --git a/substrate/client/service/src/builder.rs b/substrate/client/service/src/builder.rs index 0dc28d1361c..28a76847ac0 100644 --- a/substrate/client/service/src/builder.rs +++ b/substrate/client/service/src/builder.rs @@ -19,7 +19,7 @@ use crate::{ build_network_future, build_system_rpc_future, client::{Client, ClientConfig}, - config::{Configuration, ExecutorConfiguration, KeystoreConfig, PrometheusConfig}, + config::{Configuration, ExecutorConfiguration, KeystoreConfig, Multiaddr, PrometheusConfig}, error::Error, metrics::MetricsService, start_rpc_servers, BuildGenesisBlock, GenesisBlockBuilder, RpcHandlers, SpawnTaskHandle, @@ -43,6 +43,7 @@ use sc_executor::{ use sc_keystore::LocalKeystore; use sc_network::{ config::{FullNetworkConfiguration, SyncMode}, + multiaddr::Protocol, service::{ traits::{PeerStore, RequestResponseConfig}, NotificationMetrics, @@ -527,13 +528,24 @@ where gen_rpc_module, rpc_id_provider, )?; + + let listen_addrs = rpc_server_handle + .listen_addrs() + .into_iter() + .map(|socket_addr| { + let mut multiaddr: Multiaddr = socket_addr.ip().into(); + multiaddr.push(Protocol::Tcp(socket_addr.port())); + multiaddr + }) + .collect(); + let in_memory_rpc = { let mut module = gen_rpc_module()?; module.extensions_mut().insert(DenyUnsafe::No); module }; - let in_memory_rpc_handle = RpcHandlers::new(Arc::new(in_memory_rpc)); + let in_memory_rpc_handle = RpcHandlers::new(Arc::new(in_memory_rpc), listen_addrs); // Spawn informant task spawn_handle.spawn( diff --git a/substrate/client/service/src/lib.rs b/substrate/client/service/src/lib.rs index 251eef97be8..babb76f022f 100644 --- a/substrate/client/service/src/lib.rs +++ b/substrate/client/service/src/lib.rs @@ -34,6 +34,7 @@ mod client; mod metrics; mod task_manager; +use crate::config::Multiaddr; use std::{ collections::HashMap, net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}, @@ -50,6 +51,7 @@ use sc_network::{ }; use sc_network_sync::SyncingService; use sc_network_types::PeerId; +use sc_rpc_server::Server; use sc_utils::mpsc::TracingUnboundedReceiver; use sp_blockchain::HeaderMetadata; use sp_consensus::SyncOracle; @@ -101,14 +103,22 @@ use tokio::runtime::Handle; const DEFAULT_PROTOCOL_ID: &str = "sup"; -/// RPC handlers that can perform RPC queries. +/// A running RPC service that can perform in-memory RPC queries. #[derive(Clone)] -pub struct RpcHandlers(Arc>); +pub struct RpcHandlers { + // This is legacy and may be removed at some point, it was for WASM stuff before smoldot was a + // thing. https://github.com/paritytech/polkadot-sdk/pull/5038#discussion_r1694971805 + rpc_module: Arc>, + + // This can be used to introspect the port the RPC server is listening on. SDK consumers are + // depending on this and it should be supported even if in-memory query support is removed. + listen_addresses: Vec, +} impl RpcHandlers { /// Create PRC handlers instance. - pub fn new(inner: Arc>) -> Self { - Self(inner) + pub fn new(rpc_module: Arc>, listen_addresses: Vec) -> Self { + Self { rpc_module, listen_addresses } } /// Starts an RPC query. @@ -130,12 +140,17 @@ impl RpcHandlers { // This limit is used to prevent panics and is large enough. const TOKIO_MPSC_MAX_SIZE: usize = tokio::sync::Semaphore::MAX_PERMITS; - self.0.raw_json_request(json_query, TOKIO_MPSC_MAX_SIZE).await + self.rpc_module.raw_json_request(json_query, TOKIO_MPSC_MAX_SIZE).await } /// Provides access to the underlying `RpcModule` pub fn handle(&self) -> Arc> { - self.0.clone() + self.rpc_module.clone() + } + + /// Provides access to listen addresses + pub fn listen_addresses(&self) -> &[Multiaddr] { + &self.listen_addresses[..] } } @@ -363,20 +378,6 @@ pub async fn build_system_rpc_future< debug!("`NetworkWorker` has terminated, shutting down the system RPC future."); } -// Wrapper for HTTP and WS servers that makes sure they are properly shut down. -mod waiting { - pub struct Server(pub Option); - - impl Drop for Server { - fn drop(&mut self) { - if let Some(server) = self.0.take() { - // This doesn't not wait for the server to be stopped but fires the signal. - let _ = server.stop(); - } - } - } -} - /// Starts RPC servers. pub fn start_rpc_servers( rpc_configuration: &RpcConfiguration, @@ -384,7 +385,7 @@ pub fn start_rpc_servers( tokio_handle: &Handle, gen_rpc_module: R, rpc_id_provider: Option>, -) -> Result, error::Error> +) -> Result where R: Fn() -> Result, Error>, { @@ -451,7 +452,7 @@ where match tokio::task::block_in_place(|| { tokio_handle.block_on(sc_rpc_server::start_server(server_config)) }) { - Ok(server) => Ok(Box::new(waiting::Server(Some(server)))), + Ok(server) => Ok(server), Err(e) => Err(Error::Application(e)), } } -- GitLab From f4eb41773611008040c9d4d8a8e6b7323eccfca1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Mon, 9 Sep 2024 14:07:43 +0200 Subject: [PATCH 027/125] pallet-utility: Improve weight annotations (#5644) Prevent allocations when calculating the weights. --------- Signed-off-by: Oliver Tale-Yazdi Co-authored-by: Oliver Tale-Yazdi --- prdoc/pr_5644.prdoc | 8 +++ substrate/frame/utility/src/lib.rs | 80 ++++++++++++------------------ 2 files changed, 39 insertions(+), 49 deletions(-) create mode 100644 prdoc/pr_5644.prdoc diff --git a/prdoc/pr_5644.prdoc b/prdoc/pr_5644.prdoc new file mode 100644 index 00000000000..3300d557fce --- /dev/null +++ b/prdoc/pr_5644.prdoc @@ -0,0 +1,8 @@ +title: 'pallet-utility: Improve weight annotations' +doc: +- audience: Runtime Dev + description: |- + Prevent allocations when calculating the weights. +crates: +- name: pallet-utility + bump: patch diff --git a/substrate/frame/utility/src/lib.rs b/substrate/frame/utility/src/lib.rs index 3ce5b4ff864..ed5544fe55c 100644 --- a/substrate/frame/utility/src/lib.rs +++ b/substrate/frame/utility/src/lib.rs @@ -74,7 +74,7 @@ pub use pallet::*; #[frame_support::pallet] pub mod pallet { use super::*; - use frame_support::pallet_prelude::*; + use frame_support::{dispatch::DispatchClass, pallet_prelude::*}; use frame_system::pallet_prelude::*; #[pallet::pallet] @@ -183,21 +183,8 @@ pub mod pallet { /// event is deposited. #[pallet::call_index(0)] #[pallet::weight({ - let dispatch_infos = calls.iter().map(|call| call.get_dispatch_info()).collect::>(); - let dispatch_weight = dispatch_infos.iter() - .map(|di| di.weight) - .fold(Weight::zero(), |total: Weight, weight: Weight| total.saturating_add(weight)) - .saturating_add(T::WeightInfo::batch(calls.len() as u32)); - let dispatch_class = { - let all_operational = dispatch_infos.iter() - .map(|di| di.class) - .all(|class| class == DispatchClass::Operational); - if all_operational { - DispatchClass::Operational - } else { - DispatchClass::Normal - } - }; + let (dispatch_weight, dispatch_class) = Pallet::::weight_and_dispatch_class(&calls); + let dispatch_weight = dispatch_weight.saturating_add(T::WeightInfo::batch(calls.len() as u32)); (dispatch_weight, dispatch_class) })] pub fn batch( @@ -233,13 +220,13 @@ pub mod pallet { // Take the weight of this function itself into account. let base_weight = T::WeightInfo::batch(index.saturating_add(1) as u32); // Return the actual used weight + base_weight of this call. - return Ok(Some(base_weight + weight).into()) + return Ok(Some(base_weight.saturating_add(weight)).into()) } Self::deposit_event(Event::ItemCompleted); } Self::deposit_event(Event::BatchCompleted); let base_weight = T::WeightInfo::batch(calls_len as u32); - Ok(Some(base_weight + weight).into()) + Ok(Some(base_weight.saturating_add(weight)).into()) } /// Send a call through an indexed pseudonym of the sender. @@ -305,21 +292,8 @@ pub mod pallet { /// - O(C) where C is the number of calls to be batched. #[pallet::call_index(2)] #[pallet::weight({ - let dispatch_infos = calls.iter().map(|call| call.get_dispatch_info()).collect::>(); - let dispatch_weight = dispatch_infos.iter() - .map(|di| di.weight) - .fold(Weight::zero(), |total: Weight, weight: Weight| total.saturating_add(weight)) - .saturating_add(T::WeightInfo::batch_all(calls.len() as u32)); - let dispatch_class = { - let all_operational = dispatch_infos.iter() - .map(|di| di.class) - .all(|class| class == DispatchClass::Operational); - if all_operational { - DispatchClass::Operational - } else { - DispatchClass::Normal - } - }; + let (dispatch_weight, dispatch_class) = Pallet::::weight_and_dispatch_class(&calls); + let dispatch_weight = dispatch_weight.saturating_add(T::WeightInfo::batch_all(calls.len() as u32)); (dispatch_weight, dispatch_class) })] pub fn batch_all( @@ -359,7 +333,7 @@ pub mod pallet { // Take the weight of this function itself into account. let base_weight = T::WeightInfo::batch_all(index.saturating_add(1) as u32); // Return the actual used weight + base_weight of this call. - err.post_info = Some(base_weight + weight).into(); + err.post_info = Some(base_weight.saturating_add(weight)).into(); err })?; Self::deposit_event(Event::ItemCompleted); @@ -414,21 +388,8 @@ pub mod pallet { /// - O(C) where C is the number of calls to be batched. #[pallet::call_index(4)] #[pallet::weight({ - let dispatch_infos = calls.iter().map(|call| call.get_dispatch_info()).collect::>(); - let dispatch_weight = dispatch_infos.iter() - .map(|di| di.weight) - .fold(Weight::zero(), |total: Weight, weight: Weight| total.saturating_add(weight)) - .saturating_add(T::WeightInfo::force_batch(calls.len() as u32)); - let dispatch_class = { - let all_operational = dispatch_infos.iter() - .map(|di| di.class) - .all(|class| class == DispatchClass::Operational); - if all_operational { - DispatchClass::Operational - } else { - DispatchClass::Normal - } - }; + let (dispatch_weight, dispatch_class) = Pallet::::weight_and_dispatch_class(&calls); + let dispatch_weight = dispatch_weight.saturating_add(T::WeightInfo::force_batch(calls.len() as u32)); (dispatch_weight, dispatch_class) })] pub fn force_batch( @@ -494,6 +455,27 @@ pub mod pallet { res.map(|_| ()).map_err(|e| e.error) } } + + impl Pallet { + /// Get the accumulated `weight` and the dispatch class for the given `calls`. + fn weight_and_dispatch_class( + calls: &[::RuntimeCall], + ) -> (Weight, DispatchClass) { + let dispatch_infos = calls.iter().map(|call| call.get_dispatch_info()); + let (dispatch_weight, dispatch_class) = dispatch_infos.fold( + (Weight::zero(), DispatchClass::Operational), + |(total_weight, dispatch_class): (Weight, DispatchClass), di| { + ( + total_weight.saturating_add(di.weight), + // If not all are `Operational`, we want to use `DispatchClass::Normal`. + if di.class == DispatchClass::Normal { di.class } else { dispatch_class }, + ) + }, + ); + + (dispatch_weight, dispatch_class) + } + } } /// A pallet identifier. These are per pallet and should be stored in a registry somewhere. -- GitLab From 030cb4a71b0b390626a586bfe7117b7c66b4700c Mon Sep 17 00:00:00 2001 From: Nazar Mokrynskyi Date: Mon, 9 Sep 2024 16:27:22 +0300 Subject: [PATCH 028/125] Allow to disable gap creation during block import (#5343) This feature is helpful for us with custom sync protocol that is similar to Warp sync except we do not ever sync the gap and don't want it to exist in the first place (see https://github.com/paritytech/polkadot-sdk/issues/5333 and its references for motivation). Otherwise we had to resort to this: https://github.com/autonomys/polkadot-sdk/commit/d5375125ca7c59fcc1ac72dc1b2ac251cbc80323 --------- Co-authored-by: Davide Galassi --- prdoc/pr_5343.prdoc | 19 +++++++++++++++++++ substrate/client/api/src/backend.rs | 3 +++ substrate/client/api/src/in_mem.rs | 2 ++ .../consensus/common/src/block_import.rs | 3 +++ substrate/client/db/src/lib.rs | 11 +++++++++-- substrate/client/service/src/client/client.rs | 3 +++ 6 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 prdoc/pr_5343.prdoc diff --git a/prdoc/pr_5343.prdoc b/prdoc/pr_5343.prdoc new file mode 100644 index 00000000000..3cec70de93c --- /dev/null +++ b/prdoc/pr_5343.prdoc @@ -0,0 +1,19 @@ +title: Allow to disable gap creation during block import + +doc: + - audience: Node Dev + description: | + New property `BlockImportParams::create_gap` allows to change whether to create block gap in case block + has no parent (defaults to `true` keeping existing behavior), which is helpful for sync protocols that do not need + to sync the gap after this happens. `BlockImportOperation::create_gap()` method was also introduced, though in + most cases `BlockImportParams::create_gap` will be used. + +crates: + - name: sc-client-api + bump: major + - name: sc-consensus + bump: minor + - name: sc-client-db + bump: minor + - name: sc-service + bump: minor diff --git a/substrate/client/api/src/backend.rs b/substrate/client/api/src/backend.rs index 0b2a3495240..9c9601a912a 100644 --- a/substrate/client/api/src/backend.rs +++ b/substrate/client/api/src/backend.rs @@ -232,6 +232,9 @@ pub trait BlockImportOperation { /// Add a transaction index operation. fn update_transaction_index(&mut self, index: Vec) -> sp_blockchain::Result<()>; + + /// Configure whether to create a block gap if newly imported block is missing parent + fn set_create_gap(&mut self, create_gap: bool); } /// Interface for performing operations on the backend. diff --git a/substrate/client/api/src/in_mem.rs b/substrate/client/api/src/in_mem.rs index ba89aede914..c045a393bb2 100644 --- a/substrate/client/api/src/in_mem.rs +++ b/substrate/client/api/src/in_mem.rs @@ -584,6 +584,8 @@ impl backend::BlockImportOperation for BlockImportOperatio ) -> sp_blockchain::Result<()> { Ok(()) } + + fn set_create_gap(&mut self, _create_gap: bool) {} } /// In-memory backend. Keeps all states and blocks in memory. diff --git a/substrate/client/consensus/common/src/block_import.rs b/substrate/client/consensus/common/src/block_import.rs index 4d7b89f37d8..0fcf96a9636 100644 --- a/substrate/client/consensus/common/src/block_import.rs +++ b/substrate/client/consensus/common/src/block_import.rs @@ -214,6 +214,8 @@ pub struct BlockImportParams { pub fork_choice: Option, /// Re-validate existing block. pub import_existing: bool, + /// Whether to create "block gap" in case this block doesn't have parent. + pub create_gap: bool, /// Cached full header hash (with post-digests applied). pub post_hash: Option, } @@ -234,6 +236,7 @@ impl BlockImportParams { auxiliary: Vec::new(), fork_choice: None, import_existing: false, + create_gap: true, post_hash: None, } } diff --git a/substrate/client/db/src/lib.rs b/substrate/client/db/src/lib.rs index 4559a01e57e..72707c306f5 100644 --- a/substrate/client/db/src/lib.rs +++ b/substrate/client/db/src/lib.rs @@ -834,6 +834,7 @@ pub struct BlockImportOperation { finalized_blocks: Vec<(Block::Hash, Option)>, set_head: Option, commit_state: bool, + create_gap: bool, index_ops: Vec, } @@ -988,6 +989,10 @@ impl sc_client_api::backend::BlockImportOperation self.index_ops = index_ops; Ok(()) } + + fn set_create_gap(&mut self, create_gap: bool) { + self.create_gap = create_gap; + } } struct StorageDb { @@ -1709,8 +1714,9 @@ impl Backend { unreachable!("Unsupported block gap. TODO: https://github.com/paritytech/polkadot-sdk/issues/5406") }, } - } else if number > best_num + One::one() && - number > One::one() && self.blockchain.header(parent_hash)?.is_none() + } else if operation.create_gap && + number > best_num + One::one() && + self.blockchain.header(parent_hash)?.is_none() { let gap = BlockGap { start: best_num + One::one(), @@ -2072,6 +2078,7 @@ impl sc_client_api::backend::Backend for Backend { finalized_blocks: Vec::new(), set_head: None, commit_state: false, + create_gap: true, index_ops: Default::default(), }) } diff --git a/substrate/client/service/src/client/client.rs b/substrate/client/service/src/client/client.rs index 8b699c7faff..ce5b92551bf 100644 --- a/substrate/client/service/src/client/client.rs +++ b/substrate/client/service/src/client/client.rs @@ -513,6 +513,7 @@ where fork_choice, intermediates, import_existing, + create_gap, .. } = import_block; @@ -537,6 +538,8 @@ where *self.importing_block.write() = Some(hash); + operation.op.set_create_gap(create_gap); + let result = self.execute_and_import_block( operation, origin, -- GitLab From 2d4e89f091c103813e462bd9cb188473b2673708 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Mon, 9 Sep 2024 17:12:51 +0200 Subject: [PATCH 029/125] [Bot] Revive prdoc bot (#5648) Prdoc bot was deleted in https://github.com/paritytech/polkadot-sdk/pull/5457 just after being added in https://github.com/paritytech/polkadot-sdk/pull/5331 without replacement. Now bringing it back until it is integrated into the new command structure. Formatting is now also fixed, such that the title is always first and the description renders correctly. --------- Signed-off-by: Oliver Tale-Yazdi --- .github/scripts/generate-prdoc.py | 26 +++++++-- .github/workflows/command-prdoc.yml | 90 +++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/command-prdoc.yml diff --git a/.github/scripts/generate-prdoc.py b/.github/scripts/generate-prdoc.py index ba7def20fcb..a6a97008dca 100644 --- a/.github/scripts/generate-prdoc.py +++ b/.github/scripts/generate-prdoc.py @@ -48,9 +48,8 @@ def create_prdoc(pr, audience, title, description, patch, bump, force): else: print(f"No preexisting PrDoc for PR {pr}") - prdoc = { "doc": [{}], "crates": [] } + prdoc = { "title": title, "doc": [{}], "crates": [] } - prdoc["title"] = title prdoc["doc"][0]["audience"] = audience prdoc["doc"][0]["description"] = description @@ -58,13 +57,19 @@ def create_prdoc(pr, audience, title, description, patch, bump, force): modified_paths = [] for diff in whatthepatch.parse_patch(patch): - modified_paths.append(diff.header.new_path) + new_path = diff.header.new_path + # Sometimes this lib returns `/dev/null` as the new path... + if not new_path.startswith("/dev"): + modified_paths.append(new_path) modified_crates = {} for p in modified_paths: # Go up until we find a Cargo.toml p = os.path.join(workspace.path, p) while not os.path.exists(os.path.join(p, "Cargo.toml")): + print(f"Could not find Cargo.toml in {p}") + if p == '/': + exit(1) p = os.path.dirname(p) with open(os.path.join(p, "Cargo.toml")) as f: @@ -95,9 +100,19 @@ def create_prdoc(pr, audience, title, description, patch, bump, force): # write the parsed PR documentation back to the file with open(path, "w") as f: - yaml.dump(prdoc, f) + yaml.dump(prdoc, f, sort_keys=False) print(f"PrDoc for PR {pr} written to {path}") +# Make the `description` a multiline string instead of escaping \r\n. +def setup_yaml(): + def yaml_multiline_string_presenter(dumper, data): + if len(data.splitlines()) > 1: + data = '\n'.join([line.rstrip() for line in data.strip().splitlines()]) + return dumper.represent_scalar('tag:yaml.org,2002:str', data, style='|') + return dumper.represent_scalar('tag:yaml.org,2002:str', data) + + yaml.add_representer(str, yaml_multiline_string_presenter) + def parse_args(): parser = argparse.ArgumentParser() parser.add_argument("--pr", type=int, required=True) @@ -108,6 +123,7 @@ def parse_args(): if __name__ == "__main__": args = parse_args() - force = True if args.force.lower() == "true" else False + force = True if (args.force or "false").lower() == "true" else False print(f"Args: {args}, force: {force}") + setup_yaml() from_pr_number(args.pr, args.audience, args.bump, force) diff --git a/.github/workflows/command-prdoc.yml b/.github/workflows/command-prdoc.yml new file mode 100644 index 00000000000..3a08b9a5fb2 --- /dev/null +++ b/.github/workflows/command-prdoc.yml @@ -0,0 +1,90 @@ +name: Command PrDoc + +on: + workflow_dispatch: + inputs: + pr: + type: number + description: Number of the Pull Request + required: true + bump: + type: choice + description: Default bump level for all crates + default: "TODO" + required: true + options: + - "TODO" + - "no change" + - "patch" + - "minor" + - "major" + audience: + type: choice + description: Audience of the PrDoc + default: "TODO" + required: true + options: + - "TODO" + - "Runtime Dev" + - "Runtime User" + - "Node Dev" + - "Node User" + overwrite: + type: choice + description: Overwrite existing PrDoc + default: "true" + required: true + options: + - "true" + - "false" + +concurrency: + group: command-prdoc + cancel-in-progress: true + +jobs: + set-image: + runs-on: ubuntu-latest + outputs: + IMAGE: ${{ steps.set_image.outputs.IMAGE }} + steps: + - name: Checkout + uses: actions/checkout@v4 + - id: set_image + run: cat .github/env >> $GITHUB_OUTPUT + cmd-prdoc: + needs: [set-image] + runs-on: ubuntu-latest + timeout-minutes: 20 + container: + image: ${{ needs.set-image.outputs.IMAGE }} + permissions: + contents: write + pull-requests: write + steps: + - name: Download repo + uses: actions/checkout@v4 + - name: Install gh cli + id: gh + uses: ./.github/actions/set-up-gh + with: + pr-number: ${{ inputs.pr }} + GH_TOKEN: ${{ github.token }} + - name: Generate PrDoc + run: | + python3 -m pip install -q cargo-workspace PyGithub whatthepatch pyyaml toml + + python3 .github/scripts/generate-prdoc.py --pr "${{ inputs.pr }}" --bump "${{ inputs.bump }}" --audience "${{ inputs.audience }}" --force "${{ inputs.overwrite }}" + + - name: Report failure + if: ${{ failure() }} + run: gh pr comment ${{ inputs.pr }} --body "

Command failed ❌

Run by @${{ github.actor }} for ${{ github.workflow }} failed. See logs here." + env: + RUN: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_TOKEN: ${{ github.token }} + - name: Push Commit + uses: stefanzweifel/git-auto-commit-action@v5 + with: + commit_message: Add PrDoc (auto generated) + branch: ${{ steps.gh.outputs.branch }} + file_pattern: 'prdoc/*.prdoc' -- GitLab From d2e962fc1ec5c33b33ca39029d9a525404048491 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Mon, 9 Sep 2024 19:21:51 +0200 Subject: [PATCH 030/125] Install prdoc from Parity fork (#5625) Prdoc is now published as a Parity fork under the [`parity-prdoc`](https://crates.io/crates/parity-prdoc) crate after the directions diverged from the ideas of the original creator (discussions [here](https://github.com/paritytech/prdoc/pull/40) and [here](https://github.com/paritytech/prdoc/issues/36)). Now updating the install instructions here. --------- Signed-off-by: Oliver Tale-Yazdi --- .github/workflows/check-prdoc.yml | 2 +- docs/contributor/prdoc.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/check-prdoc.yml b/.github/workflows/check-prdoc.yml index 69311c41dd6..6c8f1ed7a30 100644 --- a/.github/workflows/check-prdoc.yml +++ b/.github/workflows/check-prdoc.yml @@ -6,7 +6,7 @@ on: merge_group: env: - IMAGE: docker.io/paritytech/prdoc:v0.0.8 + IMAGE: docker.io/paritytech/prdoc:v0.1.1 API_BASE: https://api.github.com/repos REPO: ${{ github.repository }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/docs/contributor/prdoc.md b/docs/contributor/prdoc.md index 0c8165af40f..3dddcc10f5d 100644 --- a/docs/contributor/prdoc.md +++ b/docs/contributor/prdoc.md @@ -14,7 +14,7 @@ the [CODEOWNERS](../../.github/CODEOWNERS) for advice. A `.prdoc` file is a YAML file with a defined structure (ie JSON Schema). Please follow these steps to generate one: -1. Install the [`prdoc` CLI](https://github.com/paritytech/prdoc) by running `cargo install prdoc`. +1. Install the [`prdoc` CLI](https://github.com/paritytech/prdoc) by running `cargo install parity-prdoc`. 1. Open a Pull Request and get the PR number. 1. Generate the file with `prdoc generate `. The output filename will be printed. 1. Optional: Install the `prdoc/schema_user.json` schema in your editor, for example -- GitLab From f3f377f5ec7009036a33a822fdbb8439ca696ca7 Mon Sep 17 00:00:00 2001 From: PG Herveou Date: Mon, 9 Sep 2024 22:25:42 +0200 Subject: [PATCH 031/125] [pallet-revive] move event topics in event's body (#5640) Fix https://github.com/paritytech/polkadot-sdk/issues/5629 --- prdoc/pr_5640.prdoc | 10 ++++++++++ substrate/frame/revive/src/benchmarking/mod.rs | 6 +----- substrate/frame/revive/src/exec.rs | 10 ++++------ substrate/frame/revive/src/lib.rs | 11 +++-------- substrate/frame/revive/src/tests.rs | 5 +++-- 5 files changed, 21 insertions(+), 21 deletions(-) create mode 100644 prdoc/pr_5640.prdoc diff --git a/prdoc/pr_5640.prdoc b/prdoc/pr_5640.prdoc new file mode 100644 index 00000000000..fdd7f5e1b89 --- /dev/null +++ b/prdoc/pr_5640.prdoc @@ -0,0 +1,10 @@ +title: "[pallet-revive] Move event's topics" + +doc: + - audience: Runtime Dev + description: | + Move event's topics inside body + +crates: + - name: pallet-revive + bump: major diff --git a/substrate/frame/revive/src/benchmarking/mod.rs b/substrate/frame/revive/src/benchmarking/mod.rs index 8cdd7da5db9..8601f5f5354 100644 --- a/substrate/frame/revive/src/benchmarking/mod.rs +++ b/substrate/frame/revive/src/benchmarking/mod.rs @@ -772,11 +772,7 @@ mod benchmarks { assert_eq!( record.event, - crate::Event::ContractEmitted { contract: instance.address(), data }.into(), - ); - assert_eq!( - record.topics.iter().map(|t| H256::from_slice(t.as_ref())).collect::>(), - topics, + crate::Event::ContractEmitted { contract: instance.address(), data, topics }.into(), ); } diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index 016bdec37af..468f5aa8240 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -1487,13 +1487,11 @@ where } fn deposit_event(&mut self, topics: Vec, data: Vec) { - Contracts::::deposit_indexed_event( + Contracts::::deposit_event(Event::ContractEmitted { + contract: T::AddressMapper::to_address(self.account_id()), + data, topics, - Event::ContractEmitted { - contract: T::AddressMapper::to_address(self.account_id()), - data, - }, - ); + }); } fn block_number(&self) -> U256 { diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 4c6e5cd26a1..d1e17fb7b39 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -406,6 +406,9 @@ pub mod pallet { /// Data supplied by the contract. Metadata generated during contract compilation /// is needed to decode it. data: Vec, + /// A list of topics used to index the event. + /// Number of topics is capped by [`limits::NUM_EVENT_TOPICS`]. + topics: Vec, }, /// A code with the specified hash was removed. @@ -1186,14 +1189,6 @@ where fn deposit_event(event: Event) { >::deposit_event(::RuntimeEvent::from(event)) } - - /// Deposit a pallet contracts indexed event. - fn deposit_indexed_event(topics: Vec, event: Event) { - >::deposit_event_indexed( - &topics.into_iter().map(Into::into).collect::>(), - ::RuntimeEvent::from(event).into(), - ) - } } // Set up a global reference to the boolean flag used for the re-entrancy guard. diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index f2944c7932a..73914c9aae0 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -678,9 +678,10 @@ mod run_tests { phase: Phase::Initialization, event: RuntimeEvent::Contracts(crate::Event::ContractEmitted { contract: addr, - data: vec![1, 2, 3, 4] + data: vec![1, 2, 3, 4], + topics: vec![H256::repeat_byte(42)], }), - topics: vec![H256::repeat_byte(42)], + topics: vec![], }, EventRecord { phase: Phase::Initialization, -- GitLab From aec2b10539251fc20450f8efa453f21dee6b95a1 Mon Sep 17 00:00:00 2001 From: Guillaume Thiolliere Date: Tue, 10 Sep 2024 07:31:11 +0900 Subject: [PATCH 032/125] frame pallet macro: fix span for error on wrong returned type. (#5580) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Explicitly give the types in some generated code so that the error shows up good when user code is wrong. --------- Co-authored-by: Bastian Köcher --- prdoc/pr_5580.prdoc | 13 +++++++ .../procedural/src/pallet/expand/call.rs | 36 +++++++++++++----- .../procedural/src/pallet/parse/call.rs | 11 ++---- .../procedural/src/pallet/parse/helper.rs | 23 +++++++++--- .../tests/pallet_ui/call_span_for_error.rs | 37 +++++++++++++++++++ .../pallet_ui/call_span_for_error.stderr | 26 +++++++++++++ 6 files changed, 125 insertions(+), 21 deletions(-) create mode 100644 prdoc/pr_5580.prdoc create mode 100644 substrate/frame/support/test/tests/pallet_ui/call_span_for_error.rs create mode 100644 substrate/frame/support/test/tests/pallet_ui/call_span_for_error.stderr diff --git a/prdoc/pr_5580.prdoc b/prdoc/pr_5580.prdoc new file mode 100644 index 00000000000..e03b946070a --- /dev/null +++ b/prdoc/pr_5580.prdoc @@ -0,0 +1,13 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Fix error message on pallet macro + +doc: + - audience: Runtime Dev + description: | + Improve error message for pallet macro generated code. + +crates: + - name: frame-support-procedural + bump: patch diff --git a/substrate/frame/support/procedural/src/pallet/expand/call.rs b/substrate/frame/support/procedural/src/pallet/expand/call.rs index f395872c8a8..5dc8dc3146c 100644 --- a/substrate/frame/support/procedural/src/pallet/expand/call.rs +++ b/substrate/frame/support/procedural/src/pallet/expand/call.rs @@ -18,7 +18,7 @@ use crate::{ pallet::{ expand::warnings::{weight_constant_warning, weight_witness_warning}, - parse::call::CallWeightDef, + parse::{call::CallWeightDef, helper::CallReturnType}, Def, }, COUNTER, @@ -197,18 +197,36 @@ pub fn expand_call(def: &mut Def) -> proc_macro2::TokenStream { let capture_docs = if cfg!(feature = "no-metadata-docs") { "never" } else { "always" }; // Wrap all calls inside of storage layers - if let Some(syn::Item::Impl(item_impl)) = def - .call - .as_ref() - .map(|c| &mut def.item.content.as_mut().expect("Checked by def parser").1[c.index]) - { - item_impl.items.iter_mut().for_each(|i| { - if let syn::ImplItem::Fn(method) = i { + if let Some(call) = def.call.as_ref() { + let item_impl = + &mut def.item.content.as_mut().expect("Checked by def parser").1[call.index]; + let syn::Item::Impl(item_impl) = item_impl else { + unreachable!("Checked by def parser"); + }; + + item_impl.items.iter_mut().enumerate().for_each(|(i, item)| { + if let syn::ImplItem::Fn(method) = item { + let return_type = + &call.methods.get(i).expect("def should be consistent with item").return_type; + + let (ok_type, err_type) = match return_type { + CallReturnType::DispatchResult => ( + quote::quote!(()), + quote::quote!(#frame_support::pallet_prelude::DispatchError), + ), + CallReturnType::DispatchResultWithPostInfo => ( + quote::quote!(#frame_support::dispatch::PostDispatchInfo), + quote::quote!(#frame_support::dispatch::DispatchErrorWithPostInfo), + ), + }; + let block = &method.block; method.block = syn::parse_quote! {{ // We execute all dispatchable in a new storage layer, allowing them // to return an error at any point, and undoing any storage changes. - #frame_support::storage::with_storage_layer(|| #block) + #frame_support::storage::with_storage_layer::<#ok_type, #err_type, _>( + || #block + ) }}; } }); diff --git a/substrate/frame/support/procedural/src/pallet/parse/call.rs b/substrate/frame/support/procedural/src/pallet/parse/call.rs index 4e09b86fdde..68c2cb8bd1b 100644 --- a/substrate/frame/support/procedural/src/pallet/parse/call.rs +++ b/substrate/frame/support/procedural/src/pallet/parse/call.rs @@ -89,6 +89,8 @@ pub struct CallVariantDef { pub cfg_attrs: Vec, /// The optional `feeless_if` attribute on the `pallet::call`. pub feeless_check: Option, + /// The return type of the call: `DispatchInfo` or `DispatchResultWithPostInfo`. + pub return_type: helper::CallReturnType, } /// Attributes for functions in call impl block. @@ -260,13 +262,7 @@ impl CallDef { }, } - if let syn::ReturnType::Type(_, type_) = &method.sig.output { - helper::check_pallet_call_return_type(type_)?; - } else { - let msg = "Invalid pallet::call, require return type \ - DispatchResultWithPostInfo"; - return Err(syn::Error::new(method.sig.span(), msg)) - } + let return_type = helper::check_pallet_call_return_type(&method.sig)?; let cfg_attrs: Vec = helper::get_item_cfg_attrs(&method.attrs); let mut call_idx_attrs = vec![]; @@ -447,6 +443,7 @@ impl CallDef { attrs: method.attrs.clone(), cfg_attrs, feeless_check, + return_type, }); } else { let msg = "Invalid pallet::call, only method accepted"; diff --git a/substrate/frame/support/procedural/src/pallet/parse/helper.rs b/substrate/frame/support/procedural/src/pallet/parse/helper.rs index d4f58a4c56d..d5ae607d90f 100644 --- a/substrate/frame/support/procedural/src/pallet/parse/helper.rs +++ b/substrate/frame/support/procedural/src/pallet/parse/helper.rs @@ -597,25 +597,38 @@ pub fn check_type_value_gen( Ok(i) } +/// The possible return type of a dispatchable. +#[derive(Clone)] +pub enum CallReturnType { + DispatchResult, + DispatchResultWithPostInfo, +} + /// Check the keyword `DispatchResultWithPostInfo` or `DispatchResult`. -pub fn check_pallet_call_return_type(type_: &syn::Type) -> syn::Result<()> { - pub struct Checker; +pub fn check_pallet_call_return_type(sig: &syn::Signature) -> syn::Result { + let syn::ReturnType::Type(_, type_) = &sig.output else { + let msg = "Invalid pallet::call, require return type \ + DispatchResultWithPostInfo"; + return Err(syn::Error::new(sig.span(), msg)) + }; + + pub struct Checker(CallReturnType); impl syn::parse::Parse for Checker { fn parse(input: syn::parse::ParseStream) -> syn::Result { let lookahead = input.lookahead1(); if lookahead.peek(keyword::DispatchResultWithPostInfo) { input.parse::()?; - Ok(Self) + Ok(Self(CallReturnType::DispatchResultWithPostInfo)) } else if lookahead.peek(keyword::DispatchResult) { input.parse::()?; - Ok(Self) + Ok(Self(CallReturnType::DispatchResult)) } else { Err(lookahead.error()) } } } - syn::parse2::(type_.to_token_stream()).map(|_| ()) + syn::parse2::(type_.to_token_stream()).map(|c| c.0) } pub(crate) fn two128_str(s: &str) -> TokenStream { diff --git a/substrate/frame/support/test/tests/pallet_ui/call_span_for_error.rs b/substrate/frame/support/test/tests/pallet_ui/call_span_for_error.rs new file mode 100644 index 00000000000..08b42c29a68 --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/call_span_for_error.rs @@ -0,0 +1,37 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#[frame_support::pallet(dev_mode)] +mod pallet { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::call] + impl Pallet { + pub fn foo(origin: OriginFor) -> DispatchResultWithPostInfo { + return Err(DispatchError::BadOrigin); + } + } +} + +fn main() {} diff --git a/substrate/frame/support/test/tests/pallet_ui/call_span_for_error.stderr b/substrate/frame/support/test/tests/pallet_ui/call_span_for_error.stderr new file mode 100644 index 00000000000..8f3003c0222 --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/call_span_for_error.stderr @@ -0,0 +1,26 @@ +error[E0308]: mismatched types + --> tests/pallet_ui/call_span_for_error.rs:32:15 + | +32 | return Err(DispatchError::BadOrigin); + | --- ^^^^^^^^^^^^^^^^^^^^^^^^ expected `DispatchErrorWithPostInfo`, found `DispatchError` + | | + | arguments to this enum variant are incorrect + | + = note: expected struct `DispatchErrorWithPostInfo` + found enum `frame_support::pallet_prelude::DispatchError` +help: the type constructed contains `frame_support::pallet_prelude::DispatchError` due to the type of the argument passed + --> tests/pallet_ui/call_span_for_error.rs:32:11 + | +32 | return Err(DispatchError::BadOrigin); + | ^^^^------------------------^ + | | + | this argument influences the type of `Err` +note: tuple variant defined here + --> $RUST/core/src/result.rs + | + | Err(#[stable(feature = "rust1", since = "1.0.0")] E), + | ^^^ +help: call `Into::into` on this expression to convert `frame_support::pallet_prelude::DispatchError` into `DispatchErrorWithPostInfo` + | +32 | return Err(DispatchError::BadOrigin.into()); + | +++++++ -- GitLab From 9930d2137fcbc55e32476627ec2e30071b393533 Mon Sep 17 00:00:00 2001 From: Vedhavyas Singareddi Date: Tue, 10 Sep 2024 11:35:41 +0530 Subject: [PATCH 033/125] Update `RuntimeVerison` type and use `system_version` to derive extrinsics root `StateVersion` instead of `V0` (#4257) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR - Renames `RuntimeVersion::state_version` to `system_version` - Uses `Runtime::system_version` to derive extrinsics root `StateVersion` instead of default `StateVersion::V0` This PR should not be breaking any existing chains so long as they use same `RuntimeVersion::state_version` for `Runtime::system_version` Using `RuntimeVersion::system_version = 2` will make the extrinsics root to use `StateVersion::V1` instead of `V0` RFC for this change - https://github.com/polkadot-fellows/RFCs/pull/42 --------- Co-authored-by: Bastian Köcher Co-authored-by: Koute Co-authored-by: Nazar Mokrynskyi --- Cargo.lock | 1 + cumulus/client/network/src/tests.rs | 2 +- cumulus/client/pov-recovery/src/tests.rs | 2 +- cumulus/pallets/parachain-system/src/mock.rs | 2 +- .../assets/asset-hub-rococo/src/lib.rs | 2 +- .../assets/asset-hub-westend/src/lib.rs | 2 +- .../bridge-hubs/bridge-hub-rococo/src/lib.rs | 2 +- .../bridge-hubs/bridge-hub-westend/src/lib.rs | 2 +- .../collectives-westend/src/lib.rs | 2 +- .../contracts/contracts-rococo/src/lib.rs | 2 +- .../coretime/coretime-rococo/src/lib.rs | 2 +- .../coretime/coretime-westend/src/lib.rs | 2 +- .../glutton/glutton-westend/src/lib.rs | 2 +- .../runtimes/people/people-rococo/src/lib.rs | 2 +- .../runtimes/people/people-westend/src/lib.rs | 2 +- .../runtimes/starters/seedling/src/lib.rs | 2 +- .../runtimes/starters/shell/src/lib.rs | 2 +- .../runtimes/testing/penpal/src/lib.rs | 2 +- .../testing/rococo-parachain/src/lib.rs | 2 +- cumulus/test/runtime/src/lib.rs | 4 +- .../chain_spec_runtime/src/runtime.rs | 2 +- polkadot/runtime/rococo/src/lib.rs | 2 +- polkadot/runtime/test-runtime/src/lib.rs | 2 +- polkadot/runtime/westend/src/lib.rs | 2 +- prdoc/pr_4257.prdoc | 76 +++ substrate/bin/node/runtime/src/lib.rs | 2 +- substrate/client/block-builder/src/lib.rs | 2 +- substrate/client/executor/src/wasm_runtime.rs | 12 +- .../rpc-spec-v2/src/chain_head/tests.rs | 2 +- substrate/client/rpc/src/state/tests.rs | 3 +- .../support/test/compile_pass/src/lib.rs | 2 +- substrate/frame/system/src/lib.rs | 20 +- substrate/frame/system/src/mock.rs | 2 +- substrate/frame/system/src/tests.rs | 5 +- substrate/primitives/api/src/lib.rs | 2 +- substrate/primitives/storage/src/lib.rs | 1 + .../primitives/version/proc-macro/Cargo.toml | 1 + .../proc-macro/src/decl_runtime_version.rs | 44 +- substrate/primitives/version/src/lib.rs | 432 +++++++++++++++++- substrate/test-utils/runtime/src/lib.rs | 2 +- templates/minimal/runtime/src/lib.rs | 2 +- templates/parachain/runtime/src/lib.rs | 2 +- templates/solochain/runtime/src/lib.rs | 2 +- 43 files changed, 584 insertions(+), 79 deletions(-) create mode 100644 prdoc/pr_4257.prdoc diff --git a/Cargo.lock b/Cargo.lock index e1b0b3f8f9e..66fd0d05cc3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21070,6 +21070,7 @@ name = "sp-version-proc-macro" version = "13.0.0" dependencies = [ "parity-scale-codec", + "proc-macro-warning 1.0.0", "proc-macro2 1.0.86", "quote 1.0.37", "sp-version", diff --git a/cumulus/client/network/src/tests.rs b/cumulus/client/network/src/tests.rs index cde73c4c518..81c2d9f24f2 100644 --- a/cumulus/client/network/src/tests.rs +++ b/cumulus/client/network/src/tests.rs @@ -323,7 +323,7 @@ impl RelayChainInterface for DummyRelayChainInterface { impl_version: 0, apis: Cow::Owned(apis), transaction_version: 5, - state_version: 1, + system_version: 1, }) } } diff --git a/cumulus/client/pov-recovery/src/tests.rs b/cumulus/client/pov-recovery/src/tests.rs index 6f274ed18b6..f300bdc5f2b 100644 --- a/cumulus/client/pov-recovery/src/tests.rs +++ b/cumulus/client/pov-recovery/src/tests.rs @@ -329,7 +329,7 @@ impl RelayChainInterface for Relaychain { impl_version: 0, apis: Cow::Owned(apis), transaction_version: 5, - state_version: 1, + system_version: 1, }) } diff --git a/cumulus/pallets/parachain-system/src/mock.rs b/cumulus/pallets/parachain-system/src/mock.rs index b4d118aadf0..247de3a29b6 100644 --- a/cumulus/pallets/parachain-system/src/mock.rs +++ b/cumulus/pallets/parachain-system/src/mock.rs @@ -64,7 +64,7 @@ parameter_types! { impl_version: 1, apis: sp_version::create_apis_vec!([]), transaction_version: 1, - state_version: 1, + system_version: 1, }; pub const ParachainId: ParaId = ParaId::new(200); pub const ReservedXcmpWeight: Weight = Weight::zero(); diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs index 2f3fb6b68c4..a4a2554b7af 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs @@ -128,7 +128,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 16, - state_version: 1, + system_version: 1, }; /// The version information used to identify this runtime when compiled natively. diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs index 97dbe7c361c..6da2a0bc7b9 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -126,7 +126,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 16, - state_version: 1, + system_version: 1, }; /// The version information used to identify this runtime when compiled natively. diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs index 14409ce4642..6c6e2ec7efd 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs @@ -238,7 +238,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 5, - state_version: 1, + system_version: 1, }; /// The version information used to identify this runtime when compiled natively. diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs index 5717db456a7..ddd40dbf60e 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs @@ -213,7 +213,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 5, - state_version: 1, + system_version: 1, }; /// The version information used to identify this runtime when compiled natively. diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs index dea2eb03db3..f22feb70382 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs @@ -129,7 +129,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 6, - state_version: 1, + system_version: 1, }; /// The version information used to identify this runtime when compiled natively. diff --git a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs index bf173fb618a..55770515d73 100644 --- a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs @@ -148,7 +148,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 7, - state_version: 1, + system_version: 1, }; /// The version information used to identify this runtime when compiled natively. diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs index 25324bf1776..aea2bf232cb 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs @@ -150,7 +150,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 2, - state_version: 1, + system_version: 1, }; /// The version information used to identify this runtime when compiled natively. diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs index a3051e4bf27..218afaab924 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs @@ -149,7 +149,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 2, - state_version: 1, + system_version: 1, }; /// The version information used to identify this runtime when compiled natively. diff --git a/cumulus/parachains/runtimes/glutton/glutton-westend/src/lib.rs b/cumulus/parachains/runtimes/glutton/glutton-westend/src/lib.rs index 942e11e0b25..abf13a596a7 100644 --- a/cumulus/parachains/runtimes/glutton/glutton-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/glutton/glutton-westend/src/lib.rs @@ -106,7 +106,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1, - state_version: 1, + system_version: 1, }; /// The version information used to identify this runtime when compiled natively. diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs b/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs index 77bfb99669c..cb9177d0c23 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs @@ -138,7 +138,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1, - state_version: 1, + system_version: 1, }; /// The version information used to identify this runtime when compiled natively. diff --git a/cumulus/parachains/runtimes/people/people-westend/src/lib.rs b/cumulus/parachains/runtimes/people/people-westend/src/lib.rs index 3343d2be749..9813c5cb6ac 100644 --- a/cumulus/parachains/runtimes/people/people-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/people/people-westend/src/lib.rs @@ -138,7 +138,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1, - state_version: 1, + system_version: 1, }; /// The version information used to identify this runtime when compiled natively. diff --git a/cumulus/parachains/runtimes/starters/seedling/src/lib.rs b/cumulus/parachains/runtimes/starters/seedling/src/lib.rs index 1fe72604d37..f126ee861fa 100644 --- a/cumulus/parachains/runtimes/starters/seedling/src/lib.rs +++ b/cumulus/parachains/runtimes/starters/seedling/src/lib.rs @@ -81,7 +81,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 2, - state_version: 0, + system_version: 1, }; /// The version information used to identify this runtime when compiled natively. diff --git a/cumulus/parachains/runtimes/starters/shell/src/lib.rs b/cumulus/parachains/runtimes/starters/shell/src/lib.rs index 1dfbe2b6c41..fac2d1312c0 100644 --- a/cumulus/parachains/runtimes/starters/shell/src/lib.rs +++ b/cumulus/parachains/runtimes/starters/shell/src/lib.rs @@ -89,7 +89,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1, - state_version: 0, + system_version: 1, }; /// The version information used to identify this runtime when compiled natively. diff --git a/cumulus/parachains/runtimes/testing/penpal/src/lib.rs b/cumulus/parachains/runtimes/testing/penpal/src/lib.rs index 7d19c0ed8d8..266894c3e4e 100644 --- a/cumulus/parachains/runtimes/testing/penpal/src/lib.rs +++ b/cumulus/parachains/runtimes/testing/penpal/src/lib.rs @@ -249,7 +249,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1, - state_version: 1, + system_version: 1, }; /// This determines the average expected block time that we are targeting. diff --git a/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs b/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs index dff7046f197..34646f84aed 100644 --- a/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs +++ b/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs @@ -113,7 +113,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 6, - state_version: 0, + system_version: 0, }; pub const MILLISECS_PER_BLOCK: u64 = 6000; diff --git a/cumulus/test/runtime/src/lib.rs b/cumulus/test/runtime/src/lib.rs index 274f16ab630..ba0a3487011 100644 --- a/cumulus/test/runtime/src/lib.rs +++ b/cumulus/test/runtime/src/lib.rs @@ -132,7 +132,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, - state_version: 1, + system_version: 1, }; #[cfg(feature = "increment-spec-version")] @@ -146,7 +146,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, - state_version: 1, + system_version: 1, }; pub const EPOCH_DURATION_IN_BLOCKS: u32 = 10 * MINUTES; diff --git a/docs/sdk/src/reference_docs/chain_spec_runtime/src/runtime.rs b/docs/sdk/src/reference_docs/chain_spec_runtime/src/runtime.rs index 195d1b12447..5be3a59dc7b 100644 --- a/docs/sdk/src/reference_docs/chain_spec_runtime/src/runtime.rs +++ b/docs/sdk/src/reference_docs/chain_spec_runtime/src/runtime.rs @@ -46,7 +46,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, - state_version: 1, + system_version: 1, }; /// The signed extensions that are added to the runtime. diff --git a/polkadot/runtime/rococo/src/lib.rs b/polkadot/runtime/rococo/src/lib.rs index 6b046e19083..6ec49c5830f 100644 --- a/polkadot/runtime/rococo/src/lib.rs +++ b/polkadot/runtime/rococo/src/lib.rs @@ -174,7 +174,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 26, - state_version: 1, + system_version: 1, }; /// The BABE epoch configuration at genesis. diff --git a/polkadot/runtime/test-runtime/src/lib.rs b/polkadot/runtime/test-runtime/src/lib.rs index 72d024e9a87..b0323156911 100644 --- a/polkadot/runtime/test-runtime/src/lib.rs +++ b/polkadot/runtime/test-runtime/src/lib.rs @@ -125,7 +125,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1, - state_version: 1, + system_version: 1, }; /// The BABE epoch configuration at genesis. diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index b02c2d8c671..d0c1cd89de3 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -175,7 +175,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 26, - state_version: 1, + system_version: 1, }; /// The BABE epoch configuration at genesis. diff --git a/prdoc/pr_4257.prdoc b/prdoc/pr_4257.prdoc new file mode 100644 index 00000000000..860b85a4888 --- /dev/null +++ b/prdoc/pr_4257.prdoc @@ -0,0 +1,76 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Rename `state_version` in `RuntimeVersion` to `system_version`. + +doc: + - audience: Runtime Dev + description: | + This PR renames `state_version` in `RuntimeVersion` to `system_version`. `system_version=2` signifies + that extrinsic root derivation uses `StateVersion::V1`. + + - audience: Runtime User + description: | + `RuntimeVersion`'s `state_version` is renamed to `system_version`. Applications using that type and its field + must update their code to reflect the changes. For easier migration serde serialization produces both new + `systemVersion` and old `stateVersion` fields and deserialization supports `stateVersion` as an alias as too. + +crates: + - name: frame-system + bump: major + - name: sp-api + bump: none + - name: sp-version + bump: major + - name: sp-storage + bump: minor + - name: sp-version-proc-macro + bump: minor + - name: sc-block-builder + bump: major + - name: sc-executor + bump: major + - name: sc-rpc + bump: none + - name: sc-rpc-spec-v2 + bump: none + - name: cumulus-pallet-parachain-system + bump: none + - name: cumulus-client-pov-recovery + bump: none + - name: cumulus-client-network + bump: none + - name: rococo-runtime + bump: major + - name: westend-runtime + bump: major + - name: asset-hub-rococo-runtime + bump: major + - name: asset-hub-westend-runtime + bump: major + - name: bridge-hub-rococo-runtime + bump: major + - name: bridge-hub-westend-runtime + bump: major + - name: collectives-westend-runtime + bump: major + - name: coretime-rococo-runtime + bump: major + - name: coretime-westend-runtime + bump: major + - name: people-rococo-runtime + bump: major + - name: people-westend-runtime + bump: major + - name: penpal-runtime + bump: major + - name: contracts-rococo-runtime + bump: major + - name: glutton-westend-runtime + bump: major + - name: seedling-runtime + bump: major + - name: shell-runtime + bump: major + - name: rococo-parachain-runtime + bump: major diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index caebd63408d..c8409078af5 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -171,7 +171,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 2, - state_version: 1, + system_version: 1, }; /// The BABE epoch configuration at genesis. diff --git a/substrate/client/block-builder/src/lib.rs b/substrate/client/block-builder/src/lib.rs index 2f22cd42591..d02d0e32180 100644 --- a/substrate/client/block-builder/src/lib.rs +++ b/substrate/client/block-builder/src/lib.rs @@ -320,7 +320,7 @@ where header.extrinsics_root().clone(), HashingFor::::ordered_trie_root( self.extrinsics.iter().map(Encode::encode).collect(), - sp_runtime::StateVersion::V0, + self.api.version(self.parent_hash)?.extrinsics_root_state_version(), ), ); diff --git a/substrate/client/executor/src/wasm_runtime.rs b/substrate/client/executor/src/wasm_runtime.rs index be8344ba79b..77dfc09c880 100644 --- a/substrate/client/executor/src/wasm_runtime.rs +++ b/substrate/client/executor/src/wasm_runtime.rs @@ -480,7 +480,7 @@ mod tests { let version = decode_version(&old_runtime_version.encode()).unwrap(); assert_eq!(1, version.transaction_version); - assert_eq!(0, version.state_version); + assert_eq!(0, version.system_version); } #[test] @@ -507,12 +507,12 @@ mod tests { impl_version: 1, apis: create_apis_vec!([(>::ID, 3)]), transaction_version: 3, - state_version: 4, + system_version: 4, }; let version = decode_version(&old_runtime_version.encode()).unwrap(); assert_eq!(3, version.transaction_version); - assert_eq!(0, version.state_version); + assert_eq!(0, version.system_version); let old_runtime_version = RuntimeVersion { spec_name: "test".into(), @@ -522,12 +522,12 @@ mod tests { impl_version: 1, apis: create_apis_vec!([(>::ID, 4)]), transaction_version: 3, - state_version: 4, + system_version: 4, }; let version = decode_version(&old_runtime_version.encode()).unwrap(); assert_eq!(3, version.transaction_version); - assert_eq!(4, version.state_version); + assert_eq!(4, version.system_version); } #[test] @@ -545,7 +545,7 @@ mod tests { impl_version: 100, apis: create_apis_vec!([(>::ID, 4)]), transaction_version: 100, - state_version: 1, + system_version: 1, }; let embedded = sp_version::embed::embed_runtime_version(&wasm, runtime_version.clone()) diff --git a/substrate/client/rpc-spec-v2/src/chain_head/tests.rs b/substrate/client/rpc-spec-v2/src/chain_head/tests.rs index a638a9c7ec5..30a01b93b31 100644 --- a/substrate/client/rpc-spec-v2/src/chain_head/tests.rs +++ b/substrate/client/rpc-spec-v2/src/chain_head/tests.rs @@ -346,7 +346,7 @@ async fn follow_with_runtime() { [\"0x37e397fc7c91f5e4\",2],[\"0xd2bc9897eed08f15\",3],[\"0x40fe3ad401f8959a\",6],\ [\"0xbc9d89904f5b923f\",1],[\"0xc6e9a76309f39b09\",2],[\"0xdd718d5cc53262d4\",1],\ [\"0xcbca25e39f142387\",2],[\"0xf78b278be53f454c\",2],[\"0xab3c0572291feb8b\",1],\ - [\"0xed99c5acb25eedf5\",3],[\"0xfbc577b9d747efd6\",1]],\"transactionVersion\":1,\"stateVersion\":1}"; + [\"0xed99c5acb25eedf5\",3],[\"0xfbc577b9d747efd6\",1]],\"transactionVersion\":1,\"systemVersion\":1}"; let runtime: RuntimeVersion = serde_json::from_str(runtime_str).unwrap(); diff --git a/substrate/client/rpc/src/state/tests.rs b/substrate/client/rpc/src/state/tests.rs index eef79507034..6b711f2425e 100644 --- a/substrate/client/rpc/src/state/tests.rs +++ b/substrate/client/rpc/src/state/tests.rs @@ -476,7 +476,8 @@ async fn should_return_runtime_version() { [\"0x37e397fc7c91f5e4\",2],[\"0xd2bc9897eed08f15\",3],[\"0x40fe3ad401f8959a\",6],\ [\"0xbc9d89904f5b923f\",1],[\"0xc6e9a76309f39b09\",2],[\"0xdd718d5cc53262d4\",1],\ [\"0xcbca25e39f142387\",2],[\"0xf78b278be53f454c\",2],[\"0xab3c0572291feb8b\",1],\ - [\"0xed99c5acb25eedf5\",3],[\"0xfbc577b9d747efd6\",1]],\"transactionVersion\":1,\"stateVersion\":1}"; + [\"0xed99c5acb25eedf5\",3],[\"0xfbc577b9d747efd6\",1]],\"transactionVersion\":1,\"systemVersion\":1,\ + \"stateVersion\":1}"; let runtime_version = api.runtime_version(None.into()).unwrap(); let serialized = serde_json::to_string(&runtime_version).unwrap(); diff --git a/substrate/frame/support/test/compile_pass/src/lib.rs b/substrate/frame/support/test/compile_pass/src/lib.rs index 37af683fbc7..677ef4e94c8 100644 --- a/substrate/frame/support/test/compile_pass/src/lib.rs +++ b/substrate/frame/support/test/compile_pass/src/lib.rs @@ -40,7 +40,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { impl_version: 0, apis: sp_version::create_apis_vec!([]), transaction_version: 0, - state_version: 0, + system_version: 0, }; pub type Signature = sr25519::Signature; diff --git a/substrate/frame/system/src/lib.rs b/substrate/frame/system/src/lib.rs index abacfa7b62c..662b7f1a94b 100644 --- a/substrate/frame/system/src/lib.rs +++ b/substrate/frame/system/src/lib.rs @@ -175,6 +175,7 @@ pub use extensions::{ pub use extensions::check_mortality::CheckMortality as CheckEra; pub use frame_support::dispatch::RawOrigin; use frame_support::traits::{PostInherents, PostTransactions, PreInherents}; +use sp_core::storage::StateVersion; pub use weights::WeightInfo; const LOG_TARGET: &str = "runtime::system"; @@ -182,17 +183,20 @@ const LOG_TARGET: &str = "runtime::system"; /// Compute the trie root of a list of extrinsics. /// /// The merkle proof is using the same trie as runtime state with -/// `state_version` 0. -pub fn extrinsics_root(extrinsics: &[E]) -> H::Output { - extrinsics_data_root::(extrinsics.iter().map(codec::Encode::encode).collect()) +/// `state_version` 0 or 1. +pub fn extrinsics_root( + extrinsics: &[E], + state_version: StateVersion, +) -> H::Output { + extrinsics_data_root::(extrinsics.iter().map(codec::Encode::encode).collect(), state_version) } /// Compute the trie root of a list of extrinsics. /// /// The merkle proof is using the same trie as runtime state with -/// `state_version` 0. -pub fn extrinsics_data_root(xts: Vec>) -> H::Output { - H::ordered_trie_root(xts, sp_core::storage::StateVersion::V0) +/// `state_version` 0 or 1. +pub fn extrinsics_data_root(xts: Vec>, state_version: StateVersion) -> H::Output { + H::ordered_trie_root(xts, state_version) } /// An object to track the currently used extrinsic weight in a block. @@ -1847,7 +1851,9 @@ impl Pallet { let extrinsics = (0..ExtrinsicCount::::take().unwrap_or_default()) .map(ExtrinsicData::::take) .collect(); - let extrinsics_root = extrinsics_data_root::(extrinsics); + let extrinsics_root_state_version = T::Version::get().extrinsics_root_state_version(); + let extrinsics_root = + extrinsics_data_root::(extrinsics, extrinsics_root_state_version); // move block hash pruning window by one block let block_hash_count = T::BlockHashCount::get(); diff --git a/substrate/frame/system/src/mock.rs b/substrate/frame/system/src/mock.rs index fff848b3b0e..f43ffe3c87e 100644 --- a/substrate/frame/system/src/mock.rs +++ b/substrate/frame/system/src/mock.rs @@ -40,7 +40,7 @@ parameter_types! { impl_version: 1, apis: sp_version::create_apis_vec!([]), transaction_version: 1, - state_version: 1, + system_version: 1, }; pub const DbWeight: RuntimeDbWeight = RuntimeDbWeight { read: 10, diff --git a/substrate/frame/system/src/tests.rs b/substrate/frame/system/src/tests.rs index b2cd017e1e2..534ba1e863f 100644 --- a/substrate/frame/system/src/tests.rs +++ b/substrate/frame/system/src/tests.rs @@ -789,7 +789,10 @@ fn extrinsics_root_is_calculated_correctly() { System::note_finished_extrinsics(); let header = System::finalize(); - let ext_root = extrinsics_data_root::(vec![vec![1], vec![2]]); + let ext_root = extrinsics_data_root::( + vec![vec![1], vec![2]], + sp_core::storage::StateVersion::V0, + ); assert_eq!(ext_root, *header.extrinsics_root()); }); } diff --git a/substrate/primitives/api/src/lib.rs b/substrate/primitives/api/src/lib.rs index d254bf20601..4b5c35562bd 100644 --- a/substrate/primitives/api/src/lib.rs +++ b/substrate/primitives/api/src/lib.rs @@ -341,7 +341,7 @@ pub use sp_api_proc_macro::decl_runtime_apis; /// // Here we are exposing the runtime api versions. /// apis: RUNTIME_API_VERSIONS, /// transaction_version: 1, -/// state_version: 1, +/// system_version: 1, /// }; /// /// # fn main() {} diff --git a/substrate/primitives/storage/src/lib.rs b/substrate/primitives/storage/src/lib.rs index 3b9afae4ca0..4b25f85fba6 100644 --- a/substrate/primitives/storage/src/lib.rs +++ b/substrate/primitives/storage/src/lib.rs @@ -444,6 +444,7 @@ impl TryFrom for StateVersion { match val { 0 => Ok(StateVersion::V0), 1 => Ok(StateVersion::V1), + 2 => Ok(StateVersion::V1), _ => Err(()), } } diff --git a/substrate/primitives/version/proc-macro/Cargo.toml b/substrate/primitives/version/proc-macro/Cargo.toml index 35c49360b7f..a3be654547d 100644 --- a/substrate/primitives/version/proc-macro/Cargo.toml +++ b/substrate/primitives/version/proc-macro/Cargo.toml @@ -20,6 +20,7 @@ proc-macro = true [dependencies] codec = { features = ["derive"], workspace = true, default-features = true } +proc-macro-warning = { workspace = true } proc-macro2 = { workspace = true } quote = { workspace = true } syn = { features = ["extra-traits", "fold", "full", "visit"], workspace = true } diff --git a/substrate/primitives/version/proc-macro/src/decl_runtime_version.rs b/substrate/primitives/version/proc-macro/src/decl_runtime_version.rs index 3671d4aff6b..b4f749c90f5 100644 --- a/substrate/primitives/version/proc-macro/src/decl_runtime_version.rs +++ b/substrate/primitives/version/proc-macro/src/decl_runtime_version.rs @@ -17,6 +17,7 @@ use codec::Encode; use proc_macro2::{Span, TokenStream}; +use proc_macro_warning::Warning; use quote::quote; use syn::{ parse::{Error, Result}, @@ -37,13 +38,19 @@ pub fn decl_runtime_version_impl(input: proc_macro::TokenStream) -> proc_macro:: } fn decl_runtime_version_impl_inner(item: ItemConst) -> Result { - let runtime_version = ParseRuntimeVersion::parse_expr(&item.expr)?.build(item.expr.span())?; + let (parsed_runtime_version, warnings) = ParseRuntimeVersion::parse_expr(&item.expr)?; + let runtime_version = parsed_runtime_version.build(item.expr.span())?; let link_section = generate_emit_link_section_decl(&runtime_version.encode(), "runtime_version"); Ok(quote! { #item #link_section + const _:() = { + #( + #warnings + )* + }; }) } @@ -63,7 +70,7 @@ struct RuntimeVersion { impl_version: u32, apis: u8, transaction_version: u32, - state_version: u8, + system_version: u8, } #[derive(Default, Debug)] @@ -74,11 +81,11 @@ struct ParseRuntimeVersion { spec_version: Option, impl_version: Option, transaction_version: Option, - state_version: Option, + system_version: Option, } impl ParseRuntimeVersion { - fn parse_expr(init_expr: &Expr) -> Result { + fn parse_expr(init_expr: &Expr) -> Result<(ParseRuntimeVersion, Vec)> { let init_expr = match init_expr { Expr::Struct(ref e) => e, _ => @@ -86,13 +93,14 @@ impl ParseRuntimeVersion { }; let mut parsed = ParseRuntimeVersion::default(); + let mut warnings = vec![]; for field_value in init_expr.fields.iter() { - parsed.parse_field_value(field_value)?; + warnings.append(&mut parsed.parse_field_value(field_value)?) } - Ok(parsed) + Ok((parsed, warnings)) } - fn parse_field_value(&mut self, field_value: &FieldValue) -> Result<()> { + fn parse_field_value(&mut self, field_value: &FieldValue) -> Result> { let field_name = match field_value.member { syn::Member::Named(ref ident) => ident, syn::Member::Unnamed(_) => @@ -112,6 +120,7 @@ impl ParseRuntimeVersion { } } + let mut warnings = vec![]; if field_name == "spec_name" { parse_once(&mut self.spec_name, field_value, Self::parse_str_literal)?; } else if field_name == "impl_name" { @@ -125,7 +134,16 @@ impl ParseRuntimeVersion { } else if field_name == "transaction_version" { parse_once(&mut self.transaction_version, field_value, Self::parse_num_literal)?; } else if field_name == "state_version" { - parse_once(&mut self.state_version, field_value, Self::parse_num_literal_u8)?; + let warning = Warning::new_deprecated("RuntimeVersion") + .old("state_version") + .new("system_version)") + .help_link("https://github.com/paritytech/polkadot-sdk/pull/4257") + .span(field_name.span()) + .build_or_panic(); + warnings.push(warning); + parse_once(&mut self.system_version, field_value, Self::parse_num_literal_u8)?; + } else if field_name == "system_version" { + parse_once(&mut self.system_version, field_value, Self::parse_num_literal_u8)?; } else if field_name == "apis" { // Intentionally ignored // @@ -136,7 +154,7 @@ impl ParseRuntimeVersion { return Err(Error::new(field_name.span(), "unknown field")) } - Ok(()) + Ok(warnings) } fn parse_num_literal(expr: &Expr) -> Result { @@ -198,7 +216,7 @@ impl ParseRuntimeVersion { spec_version, impl_version, transaction_version, - state_version, + system_version, } = self; Ok(RuntimeVersion { @@ -208,7 +226,7 @@ impl ParseRuntimeVersion { spec_version: required!(spec_version), impl_version: required!(impl_version), transaction_version: required!(transaction_version), - state_version: required!(state_version), + system_version: required!(system_version), apis: 0, }) } @@ -240,7 +258,7 @@ mod tests { impl_version: 1, apis: 0, transaction_version: 2, - state_version: 1, + system_version: 1, } .encode(); @@ -255,7 +273,7 @@ mod tests { impl_version: 1, apis: Cow::Owned(vec![]), transaction_version: 2, - state_version: 1, + system_version: 1, }, ); } diff --git a/substrate/primitives/version/src/lib.rs b/substrate/primitives/version/src/lib.rs index 55dea364eef..a9f1c237306 100644 --- a/substrate/primitives/version/src/lib.rs +++ b/substrate/primitives/version/src/lib.rs @@ -35,12 +35,12 @@ extern crate alloc; +#[cfg(any(feature = "std", feature = "serde"))] +use alloc::fmt; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; #[cfg(feature = "std")] use std::collections::HashSet; -#[cfg(feature = "std")] -use std::fmt; #[doc(hidden)] pub use alloc::borrow::Cow; @@ -83,7 +83,7 @@ pub mod embed; /// impl_version: 1, /// apis: RUNTIME_API_VERSIONS, /// transaction_version: 2, -/// state_version: 1, +/// system_version: 1, /// }; /// /// # const RUNTIME_API_VERSIONS: sp_version::ApisVec = sp_version::create_apis_vec!([]); @@ -160,8 +160,6 @@ macro_rules! create_apis_vec { /// `authoring_version`, absolutely not `impl_version` since they change the semantics of the /// runtime. #[derive(Clone, PartialEq, Eq, Encode, Default, sp_runtime::RuntimeDebug, TypeInfo)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct RuntimeVersion { /// Identifies the different Substrate runtimes. There'll be at least polkadot and node. /// A different on-chain spec_name to that of the native runtime would normally result @@ -200,13 +198,6 @@ pub struct RuntimeVersion { pub impl_version: u32, /// List of supported API "features" along with their versions. - #[cfg_attr( - feature = "serde", - serde( - serialize_with = "apis_serialize::serialize", - deserialize_with = "apis_serialize::deserialize", - ) - )] pub apis: ApisVec, /// All existing calls (dispatchables) are fully compatible when this number doesn't change. If @@ -230,9 +221,406 @@ pub struct RuntimeVersion { /// This number should never decrease. pub transaction_version: u32, - /// Version of the state implementation used by this runtime. + /// Version of the system implementation used by this runtime. /// Use of an incorrect version is consensus breaking. - pub state_version: u8, + pub system_version: u8, +} + +// Manual implementation in order to sprinkle `stateVersion` at the end for migration purposes +// after the field was renamed from `state_version` to `system_version` +#[cfg(feature = "serde")] +impl serde::Serialize for RuntimeVersion { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + + let mut state = serializer.serialize_struct("RuntimeVersion", 9)?; + state.serialize_field("specName", &self.spec_name)?; + state.serialize_field("implName", &self.impl_name)?; + state.serialize_field("authoringVersion", &self.authoring_version)?; + state.serialize_field("specVersion", &self.spec_version)?; + state.serialize_field("implVersion", &self.impl_version)?; + state.serialize_field("apis", { + struct SerializeWith<'a>(&'a ApisVec); + + impl<'a> serde::Serialize for SerializeWith<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + apis_serialize::serialize(self.0, serializer) + } + } + + &SerializeWith(&self.apis) + })?; + state.serialize_field("transactionVersion", &self.transaction_version)?; + state.serialize_field("systemVersion", &self.system_version)?; + state.serialize_field("stateVersion", &self.system_version)?; + state.end() + } +} + +// Manual implementation in order to allow both old `stateVersion` and new `systemVersion` to be +// present at the same time +#[cfg(feature = "serde")] +impl<'de> serde::Deserialize<'de> for RuntimeVersion { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + use core::marker::PhantomData; + + enum Field { + SpecName, + ImplName, + AuthoringVersion, + SpecVersion, + ImplVersion, + Apis, + TransactionVersion, + SystemVersion, + Ignore, + } + + struct FieldVisitor; + + impl<'de> serde::de::Visitor<'de> for FieldVisitor { + type Value = Field; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("field identifier") + } + + fn visit_u64(self, value: u64) -> Result + where + E: serde::de::Error, + { + match value { + 0 => Ok(Field::SpecName), + 1 => Ok(Field::ImplName), + 2 => Ok(Field::AuthoringVersion), + 3 => Ok(Field::SpecVersion), + 4 => Ok(Field::ImplVersion), + 5 => Ok(Field::Apis), + 6 => Ok(Field::TransactionVersion), + 7 => Ok(Field::SystemVersion), + _ => Ok(Field::Ignore), + } + } + + fn visit_str(self, value: &str) -> Result + where + E: serde::de::Error, + { + match value { + "specName" => Ok(Field::SpecName), + "implName" => Ok(Field::ImplName), + "authoringVersion" => Ok(Field::AuthoringVersion), + "specVersion" => Ok(Field::SpecVersion), + "implVersion" => Ok(Field::ImplVersion), + "apis" => Ok(Field::Apis), + "transactionVersion" => Ok(Field::TransactionVersion), + "systemVersion" | "stateVersion" => Ok(Field::SystemVersion), + _ => Ok(Field::Ignore), + } + } + + fn visit_bytes(self, value: &[u8]) -> Result + where + E: serde::de::Error, + { + match value { + b"specName" => Ok(Field::SpecName), + b"implName" => Ok(Field::ImplName), + b"authoringVersion" => Ok(Field::AuthoringVersion), + b"specVersion" => Ok(Field::SpecVersion), + b"implVersion" => Ok(Field::ImplVersion), + b"apis" => Ok(Field::Apis), + b"transactionVersion" => Ok(Field::TransactionVersion), + b"systemVersion" | b"stateVersion" => Ok(Field::SystemVersion), + _ => Ok(Field::Ignore), + } + } + } + + impl<'de> serde::Deserialize<'de> for Field { + #[inline] + fn deserialize(deserializer: E) -> Result + where + E: serde::Deserializer<'de>, + { + deserializer.deserialize_identifier(FieldVisitor) + } + } + + struct Visitor<'de> { + lifetime: PhantomData<&'de ()>, + } + impl<'de> serde::de::Visitor<'de> for Visitor<'de> { + type Value = RuntimeVersion; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("struct RuntimeVersion") + } + + #[inline] + fn visit_seq(self, mut seq: A) -> Result + where + A: serde::de::SeqAccess<'de>, + { + let spec_name = match seq.next_element()? { + Some(spec_name) => spec_name, + None => + return Err(serde::de::Error::invalid_length( + 0usize, + &"struct RuntimeVersion with 8 elements", + )), + }; + let impl_name = match seq.next_element()? { + Some(impl_name) => impl_name, + None => + return Err(serde::de::Error::invalid_length( + 1usize, + &"struct RuntimeVersion with 8 elements", + )), + }; + let authoring_version = match seq.next_element()? { + Some(authoring_version) => authoring_version, + None => + return Err(serde::de::Error::invalid_length( + 2usize, + &"struct RuntimeVersion with 8 elements", + )), + }; + let spec_version = match seq.next_element()? { + Some(spec_version) => spec_version, + None => + return Err(serde::de::Error::invalid_length( + 3usize, + &"struct RuntimeVersion with 8 elements", + )), + }; + let impl_version = match seq.next_element()? { + Some(impl_version) => impl_version, + None => + return Err(serde::de::Error::invalid_length( + 4usize, + &"struct RuntimeVersion with 8 elements", + )), + }; + let apis = match { + struct DeserializeWith<'de> { + value: ApisVec, + + phantom: PhantomData, + lifetime: PhantomData<&'de ()>, + } + impl<'de> serde::Deserialize<'de> for DeserializeWith<'de> { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + Ok(DeserializeWith { + value: apis_serialize::deserialize(deserializer)?, + phantom: PhantomData, + lifetime: PhantomData, + }) + } + } + seq.next_element::>()?.map(|wrap| wrap.value) + } { + Some(apis) => apis, + None => + return Err(serde::de::Error::invalid_length( + 5usize, + &"struct RuntimeVersion with 8 elements", + )), + }; + let transaction_version = match seq.next_element()? { + Some(transaction_version) => transaction_version, + None => + return Err(serde::de::Error::invalid_length( + 6usize, + &"struct RuntimeVersion with 8 elements", + )), + }; + let system_version = match seq.next_element()? { + Some(system_version) => system_version, + None => + return Err(serde::de::Error::invalid_length( + 7usize, + &"struct RuntimeVersion with 8 elements", + )), + }; + Ok(RuntimeVersion { + spec_name, + impl_name, + authoring_version, + spec_version, + impl_version, + apis, + transaction_version, + system_version, + }) + } + + #[inline] + fn visit_map(self, mut map: A) -> Result + where + A: serde::de::MapAccess<'de>, + { + let mut spec_name: Option = None; + let mut impl_name: Option = None; + let mut authoring_version: Option = None; + let mut spec_version: Option = None; + let mut impl_version: Option = None; + let mut apis: Option = None; + let mut transaction_version: Option = None; + let mut system_version: Option = None; + + while let Some(key) = map.next_key()? { + match key { + Field::SpecName => { + if spec_name.is_some() { + return Err(::duplicate_field( + "specName", + )); + } + spec_name = Some(map.next_value()?); + }, + Field::ImplName => { + if impl_name.is_some() { + return Err(::duplicate_field( + "implName", + )); + } + impl_name = Some(map.next_value()?); + }, + Field::AuthoringVersion => { + if authoring_version.is_some() { + return Err(::duplicate_field( + "authoringVersion", + )); + } + authoring_version = Some(map.next_value()?); + }, + Field::SpecVersion => { + if spec_version.is_some() { + return Err(::duplicate_field( + "specVersion", + )); + } + spec_version = Some(map.next_value()?); + }, + Field::ImplVersion => { + if impl_version.is_some() { + return Err(::duplicate_field( + "implVersion", + )); + } + impl_version = Some(map.next_value()?); + }, + Field::Apis => { + if apis.is_some() { + return Err(::duplicate_field("apis")); + } + apis = Some({ + struct DeserializeWith<'de> { + value: ApisVec, + lifetime: PhantomData<&'de ()>, + } + impl<'de> serde::Deserialize<'de> for DeserializeWith<'de> { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + Ok(DeserializeWith { + value: apis_serialize::deserialize(deserializer)?, + lifetime: PhantomData, + }) + } + } + + map.next_value::>()?.value + }); + }, + Field::TransactionVersion => { + if transaction_version.is_some() { + return Err(::duplicate_field( + "transactionVersion", + )); + } + transaction_version = Some(map.next_value()?); + }, + Field::SystemVersion => + if let Some(system_version) = system_version { + let new_value = map.next_value::()?; + if system_version != new_value { + return Err(::custom( + alloc::format!( + r#"Duplicated "stateVersion" and "systemVersion" \ + fields must have the same value, but different values \ + were provided: {system_version} vs {new_value}"# + ), + )); + } + } else { + system_version = Some(map.next_value()?); + }, + _ => { + let _ = map.next_value::()?; + }, + } + } + let spec_name = spec_name + .ok_or_else(|| ::missing_field("specName"))?; + let impl_name = impl_name + .ok_or_else(|| ::missing_field("implName"))?; + let authoring_version = authoring_version.ok_or_else(|| { + ::missing_field("authoringVersion") + })?; + let spec_version = spec_version + .ok_or_else(|| ::missing_field("specVersion"))?; + let impl_version = impl_version + .ok_or_else(|| ::missing_field("implVersion"))?; + let apis = + apis.ok_or_else(|| ::missing_field("apis"))?; + let transaction_version = transaction_version.ok_or_else(|| { + ::missing_field("transactionVersion") + })?; + let system_version = system_version.ok_or_else(|| { + ::missing_field("systemVersion") + })?; + Ok(RuntimeVersion { + spec_name, + impl_name, + authoring_version, + spec_version, + impl_version, + apis, + transaction_version, + system_version, + }) + } + } + + const FIELDS: &[&str] = &[ + "specName", + "implName", + "authoringVersion", + "specVersion", + "implVersion", + "apis", + "transactionVersion", + "stateVersion", + "systemVersion", + ]; + + deserializer.deserialize_struct("RuntimeVersion", FIELDS, Visitor { lifetime: PhantomData }) + } } impl RuntimeVersion { @@ -257,7 +645,7 @@ impl RuntimeVersion { if core_version.is_some() { core_version } else { core_version_from_apis(&apis) }; let transaction_version = if core_version.map(|v| v >= 3).unwrap_or(false) { Decode::decode(input)? } else { 1 }; - let state_version = + let system_version = if core_version.map(|v| v >= 4).unwrap_or(false) { Decode::decode(input)? } else { 0 }; Ok(RuntimeVersion { spec_name, @@ -267,7 +655,7 @@ impl RuntimeVersion { impl_version, apis, transaction_version, - state_version, + system_version, }) } } @@ -334,7 +722,17 @@ impl RuntimeVersion { /// Otherwise, V1 trie version will be use. pub fn state_version(&self) -> StateVersion { // If version > than 1, keep using latest version. - self.state_version.try_into().unwrap_or(StateVersion::V1) + self.system_version.try_into().unwrap_or(StateVersion::V1) + } + + /// Returns the state version to use for Extrinsics root. + pub fn extrinsics_root_state_version(&self) -> StateVersion { + match self.system_version { + // for system version 0 and 1, return V0 + 0 | 1 => StateVersion::V0, + // anything above 1, return V1 + _ => StateVersion::V1, + } } } diff --git a/substrate/test-utils/runtime/src/lib.rs b/substrate/test-utils/runtime/src/lib.rs index 514f3bcba20..840081003b8 100644 --- a/substrate/test-utils/runtime/src/lib.rs +++ b/substrate/test-utils/runtime/src/lib.rs @@ -119,7 +119,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { impl_version: 2, apis: RUNTIME_API_VERSIONS, transaction_version: 1, - state_version: 1, + system_version: 1, }; fn version() -> RuntimeVersion { diff --git a/templates/minimal/runtime/src/lib.rs b/templates/minimal/runtime/src/lib.rs index 474d9ddfb9e..cce13c48af7 100644 --- a/templates/minimal/runtime/src/lib.rs +++ b/templates/minimal/runtime/src/lib.rs @@ -46,7 +46,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, - state_version: 1, + system_version: 1, }; /// The version information used to identify this runtime when compiled natively. diff --git a/templates/parachain/runtime/src/lib.rs b/templates/parachain/runtime/src/lib.rs index 83ae15700a9..ccec648ce4c 100644 --- a/templates/parachain/runtime/src/lib.rs +++ b/templates/parachain/runtime/src/lib.rs @@ -172,7 +172,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { impl_version: 0, apis: apis::RUNTIME_API_VERSIONS, transaction_version: 1, - state_version: 1, + system_version: 1, }; #[docify::export] diff --git a/templates/solochain/runtime/src/lib.rs b/templates/solochain/runtime/src/lib.rs index 6cbfbb87960..ce38c65479e 100644 --- a/templates/solochain/runtime/src/lib.rs +++ b/templates/solochain/runtime/src/lib.rs @@ -71,7 +71,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { impl_version: 1, apis: apis::RUNTIME_API_VERSIONS, transaction_version: 1, - state_version: 1, + system_version: 1, }; mod block_times { -- GitLab From 7c90f51dfd541db6df057d3c0ff787e92104c221 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Sep 2024 06:15:10 +0000 Subject: [PATCH 034/125] Bump the ci_dependencies group with 2 updates (#5637) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the ci_dependencies group with 2 updates: [actions/checkout](https://github.com/actions/checkout) and [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request). Updates `actions/checkout` from 2 to 4
Release notes

Sourced from actions/checkout's releases.

v4.0.0

What's Changed

New Contributors

Full Changelog: https://github.com/actions/checkout/compare/v3...v4.0.0

v3.6.0

What's Changed

New Contributors

Full Changelog: https://github.com/actions/checkout/compare/v3.5.3...v3.6.0

v3.5.3

What's Changed

New Contributors

Full Changelog: https://github.com/actions/checkout/compare/v3...v3.5.3

v3.5.2

What's Changed

Full Changelog: https://github.com/actions/checkout/compare/v3.5.1...v3.5.2

v3.5.1

What's Changed

New Contributors

... (truncated)

Commits

Updates `peter-evans/create-pull-request` from 6.1.0 to 7.0.1
Release notes

Sourced from peter-evans/create-pull-request's releases.

Create Pull Request v7.0.1

⚙️ Fixes an issue affecting one particular use case where the action fails on diff --stat with fatal: ambiguous argument.

What's Changed

Full Changelog: https://github.com/peter-evans/create-pull-request/compare/v7.0.0...v7.0.1

Create Pull Request v7.0.0

:sparkles: Now supports commit signing with bot-generated tokens! See "What's new" below. :writing_hand::robot:

Behaviour changes

  • Action input git-token has been renamed branch-token, to be more clear about its purpose. The branch-token is the token that the action will use to create and update the branch.
  • The action now handles requests that have been rate-limited by GitHub. Requests hitting a primary rate limit will retry twice, for a total of three attempts. Requests hitting a secondary rate limit will not be retried.
  • The pull-request-operation output now returns none when no operation was executed.
  • Removed deprecated output environment variable PULL_REQUEST_NUMBER. Please use the pull-request-number action output instead.

What's new

  • The action can now sign commits as github-actions[bot] when using GITHUB_TOKEN, or your own bot when using GitHub App tokens. See commit signing for details.
  • Action input draft now accepts a new value always-true. This will set the pull request to draft status when the pull request is updated, as well as on creation.
  • A new action input maintainer-can-modify indicates whether maintainers can modify the pull request. The default is true, which retains the existing behaviour of the action.
  • A new output pull-request-commits-verified returns true or false, indicating whether GitHub considers the signature of the branch's commits to be verified.

What's Changed

... (truncated)

Commits
  • 8867c4a fix: handle ambiguous argument failure on diff stat (#3312)
  • 6073f54 build(deps-dev): bump @​typescript-eslint/eslint-plugin (#3291)
  • 6d01b56 build(deps-dev): bump eslint-plugin-import from 2.29.1 to 2.30.0 (#3290)
  • 25cf845 build(deps-dev): bump @​typescript-eslint/parser from 7.17.0 to 7.18.0 (#3289)
  • d87b980 build(deps-dev): bump @​types/node from 18.19.46 to 18.19.48 (#3288)
  • 119d131 build(deps): bump peter-evans/create-pull-request from 6 to 7 (#3283)
  • 73e6230 docs: update readme
  • c0348e8 ci: add v7 to workflow
  • 4320041 feat: signed commits (v7) (#3057)
  • 0c2a66f build(deps-dev): bump ts-jest from 29.2.4 to 29.2.5 (#3256)
  • Additional commits viewable in compare view

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/check-licenses.yml | 2 +- .github/workflows/check-links.yml | 2 +- .github/workflows/check-prdoc.yml | 2 +- .github/workflows/check-semver.yml | 2 +- .github/workflows/checks-quick.yml | 16 ++++++++-------- .github/workflows/checks.yml | 6 +++--- .github/workflows/misc-sync-templates.yml | 4 ++-- .github/workflows/publish-check-crates.yml | 2 +- .github/workflows/publish-claim-crates.yml | 2 +- .github/workflows/release-10_rc-automation.yml | 2 +- .../release-30_publish_release_draft.yml | 6 +++--- .github/workflows/release-50_publish-docker.yml | 8 ++++---- .github/workflows/release-branchoff-stable.yml | 4 ++-- .github/workflows/release-check-runtimes.yml | 4 ++-- .github/workflows/release-clobber-stable.yml | 2 +- .github/workflows/release-srtool.yml | 4 ++-- .../workflows/tests-linux-stable-coverage.yml | 2 +- .github/workflows/tests-misc.yml | 16 ++++++++-------- 18 files changed, 43 insertions(+), 43 deletions(-) diff --git a/.github/workflows/check-licenses.yml b/.github/workflows/check-licenses.yml index a7498604897..e3fc27f0a94 100644 --- a/.github/workflows/check-licenses.yml +++ b/.github/workflows/check-licenses.yml @@ -16,7 +16,7 @@ jobs: NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - name: Checkout sources - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.1.7 - uses: actions/setup-node@v4.0.3 with: node-version: "18.x" diff --git a/.github/workflows/check-links.yml b/.github/workflows/check-links.yml index d10f34e6fae..1060d252fd2 100644 --- a/.github/workflows/check-links.yml +++ b/.github/workflows/check-links.yml @@ -25,7 +25,7 @@ jobs: # This should restore from the most recent one: restore-keys: cache-lychee- - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.0 (22. Sep 2023) + - uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.1.0 (22. Sep 2023) - name: Lychee link checker uses: lycheeverse/lychee-action@2b973e86fc7b1f6b36a93795fe2c9c6ae1118621 # for v1.9.1 (10. Jan 2024) diff --git a/.github/workflows/check-prdoc.yml b/.github/workflows/check-prdoc.yml index 6c8f1ed7a30..8f60b9cccf8 100644 --- a/.github/workflows/check-prdoc.yml +++ b/.github/workflows/check-prdoc.yml @@ -20,7 +20,7 @@ jobs: if: github.event.pull_request.number != '' steps: - name: Checkout repo - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7 + uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc #v4.1.7 # we cannot show the version in this step (ie before checking out the repo) # due to https://github.com/paritytech/prdoc/issues/15 - name: Check if PRdoc is required diff --git a/.github/workflows/check-semver.yml b/.github/workflows/check-semver.yml index 15eb32f4062..2c06df5a509 100644 --- a/.github/workflows/check-semver.yml +++ b/.github/workflows/check-semver.yml @@ -18,7 +18,7 @@ jobs: container: image: docker.io/paritytech/ci-unified:bullseye-1.77.0-2024-04-10-v20240408 steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.1.7 with: fetch-depth: 2 diff --git a/.github/workflows/checks-quick.yml b/.github/workflows/checks-quick.yml index 96f214e9427..c936e7c8938 100644 --- a/.github/workflows/checks-quick.yml +++ b/.github/workflows/checks-quick.yml @@ -35,14 +35,14 @@ jobs: container: image: ${{ needs.set-image.outputs.IMAGE }} steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.1.7 - name: Cargo fmt run: cargo +nightly fmt --all -- --check check-dependency-rules: runs-on: ubuntu-latest timeout-minutes: 20 steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.1.7 - name: check dependency rules run: | cd substrate/ @@ -54,7 +54,7 @@ jobs: container: image: ${{ needs.set-image.outputs.IMAGE }} steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.1.7 - name: fetch deps run: | # Pull all dependencies eagerly: @@ -70,7 +70,7 @@ jobs: container: image: ${{ needs.set-image.outputs.IMAGE }} steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.1.7 - name: run rust features run: bash .gitlab/rust-features.sh . check-toml-format: @@ -80,7 +80,7 @@ jobs: container: image: ${{ needs.set-image.outputs.IMAGE }} steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.1.7 - name: check toml format run: | taplo format --check --config .config/taplo.toml @@ -89,7 +89,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 20 steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.0 (22. Sep 2023) + - uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.1.0 (22. Sep 2023) - name: install python deps run: | sudo apt-get update && sudo apt-get install -y python3-pip python3 @@ -109,7 +109,7 @@ jobs: timeout-minutes: 20 steps: - name: Checkout sources - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.1.7 - name: Setup Node.js uses: actions/setup-node@v4.0.3 with: @@ -134,7 +134,7 @@ jobs: container: image: ${{ needs.set-image.outputs.IMAGE }} steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.0 (22. Sep 2023) + - uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.1.0 (22. Sep 2023) - name: install python deps run: pip3 install "cargo-workspace>=1.2.4" toml - name: check umbrella correctness diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 9de879d8367..cba7df51742 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -54,7 +54,7 @@ jobs: RUSTFLAGS: "-D warnings" SKIP_WASM_BUILD: 1 steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.1.7 - name: script run: | forklift cargo clippy --all-targets --locked --workspace @@ -67,7 +67,7 @@ jobs: container: image: ${{ needs.set-image.outputs.IMAGE }} steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.1.7 - name: script run: | forklift cargo check --locked --all --features try-runtime @@ -86,7 +86,7 @@ jobs: container: image: ${{ needs.set-image.outputs.IMAGE }} steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.1.7 - name: script run: | cd substrate/primitives/core diff --git a/.github/workflows/misc-sync-templates.yml b/.github/workflows/misc-sync-templates.yml index c06beb5e98e..658da4451dc 100644 --- a/.github/workflows/misc-sync-templates.yml +++ b/.github/workflows/misc-sync-templates.yml @@ -157,7 +157,7 @@ jobs: timeout-minutes: 90 - name: Create PR on failure if: failure() && steps.check-compilation.outcome == 'failure' - uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c # v5 + uses: peter-evans/create-pull-request@8867c4aba1b742c39f8d0ba35429c2dfa4b6cb20 # v5 with: path: "${{ env.template-path }}" token: ${{ steps.app_token.outputs.token }} @@ -167,7 +167,7 @@ jobs: body: "The template has NOT been successfully built and needs to be inspected." branch: "update-template/${{ github.event.inputs.stable_release_branch }}" - name: Create PR on success - uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c # v5 + uses: peter-evans/create-pull-request@8867c4aba1b742c39f8d0ba35429c2dfa4b6cb20 # v5 with: path: "${{ env.template-path }}" token: ${{ steps.app_token.outputs.token }} diff --git a/.github/workflows/publish-check-crates.yml b/.github/workflows/publish-check-crates.yml index 9f96b92e0ce..77653cd43b6 100644 --- a/.github/workflows/publish-check-crates.yml +++ b/.github/workflows/publish-check-crates.yml @@ -12,7 +12,7 @@ jobs: check-publish: runs-on: ubuntu-latest steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.1.7 - name: Rust Cache uses: Swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2.7.3 diff --git a/.github/workflows/publish-claim-crates.yml b/.github/workflows/publish-claim-crates.yml index bee709a1207..77f04861bff 100644 --- a/.github/workflows/publish-claim-crates.yml +++ b/.github/workflows/publish-claim-crates.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest environment: master steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.1.7 - name: Rust Cache uses: Swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2.7.3 diff --git a/.github/workflows/release-10_rc-automation.yml b/.github/workflows/release-10_rc-automation.yml index 2d91850b82c..195c14dbd5a 100644 --- a/.github/workflows/release-10_rc-automation.yml +++ b/.github/workflows/release-10_rc-automation.yml @@ -26,7 +26,7 @@ jobs: steps: - name: Checkout sources - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.1.7 with: fetch-depth: 0 diff --git a/.github/workflows/release-30_publish_release_draft.yml b/.github/workflows/release-30_publish_release_draft.yml index 4343dbf915a..dd6a111d67e 100644 --- a/.github/workflows/release-30_publish_release_draft.yml +++ b/.github/workflows/release-30_publish_release_draft.yml @@ -36,7 +36,7 @@ jobs: binary: [ [frame-omni-bencher, frame-omni-bencher], [staging-chain-spec-builder, chain-spec-builder] ] steps: - name: Checkout sources - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.0.0 + uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.0.0 - name: Install protobuf-compiler run: | @@ -63,7 +63,7 @@ jobs: asset_upload_url: ${{ steps.create-release.outputs.upload_url }} steps: - name: Checkout - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.0.0 + uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.0.0 - name: Download artifacts uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 @@ -134,7 +134,7 @@ jobs: steps: - name: Checkout sources - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.0.0 + uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.0.0 - name: Download artifacts uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 diff --git a/.github/workflows/release-50_publish-docker.yml b/.github/workflows/release-50_publish-docker.yml index c5d214ec68a..72e01a4833e 100644 --- a/.github/workflows/release-50_publish-docker.yml +++ b/.github/workflows/release-50_publish-docker.yml @@ -86,7 +86,7 @@ jobs: steps: - name: Checkout sources - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.1.7 - name: Validate inputs id: validate_inputs @@ -111,7 +111,7 @@ jobs: steps: - name: Checkout sources - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.1.7 #TODO: this step will be needed when automated triggering will work #this step runs only if the workflow is triggered automatically when new release is published @@ -159,7 +159,7 @@ jobs: steps: - name: Checkout sources - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.1.7 - name: Download artifacts uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 @@ -295,7 +295,7 @@ jobs: environment: release steps: - name: Checkout sources - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.1.7 - name: Set up Docker Buildx uses: docker/setup-buildx-action@988b5a0280414f521da01fcc63a27aeeb4b104db # v3.6.1 diff --git a/.github/workflows/release-branchoff-stable.yml b/.github/workflows/release-branchoff-stable.yml index c236a66a9fa..c4c50f5398e 100644 --- a/.github/workflows/release-branchoff-stable.yml +++ b/.github/workflows/release-branchoff-stable.yml @@ -26,7 +26,7 @@ jobs: steps: - name: Checkout sources - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.1.7 - name: Validate inputs id: validate_inputs @@ -60,7 +60,7 @@ jobs: pip install git+https://github.com/paritytech-release/pgpkms.git@5a8f82fbb607ea102d8c178e761659de54c7af69 - name: Checkout sources - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.1.7 with: ref: master diff --git a/.github/workflows/release-check-runtimes.yml b/.github/workflows/release-check-runtimes.yml index 930b8da772d..6666c115562 100644 --- a/.github/workflows/release-check-runtimes.yml +++ b/.github/workflows/release-check-runtimes.yml @@ -35,7 +35,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout the repo - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.1.7 - name: Get list id: get-list @@ -56,7 +56,7 @@ jobs: steps: - name: Checkout the repo - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.1.7 - name: Fetch release artifacts based on release id env: diff --git a/.github/workflows/release-clobber-stable.yml b/.github/workflows/release-clobber-stable.yml index 50c20563b43..0d2ce78ab78 100644 --- a/.github/workflows/release-clobber-stable.yml +++ b/.github/workflows/release-clobber-stable.yml @@ -24,7 +24,7 @@ jobs: AUDITED: audited steps: - name: Checkout - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.1.7 - name: Prechecks run: | diff --git a/.github/workflows/release-srtool.yml b/.github/workflows/release-srtool.yml index 262203f0500..83119dd4ed2 100644 --- a/.github/workflows/release-srtool.yml +++ b/.github/workflows/release-srtool.yml @@ -28,7 +28,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.0.0 + uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.0.0 with: fetch-depth: 0 @@ -69,7 +69,7 @@ jobs: matrix: ${{ fromJSON(needs.find-runtimes.outputs.runtime) }} steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.0.0 + - uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.0.0 with: fetch-depth: 0 diff --git a/.github/workflows/tests-linux-stable-coverage.yml b/.github/workflows/tests-linux-stable-coverage.yml index ddf0642a404..4c0a2629e41 100644 --- a/.github/workflows/tests-linux-stable-coverage.yml +++ b/.github/workflows/tests-linux-stable-coverage.yml @@ -137,7 +137,7 @@ jobs: needs: [upload-reports] if: github.event_name == 'pull_request' steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - uses: actions-ecosystem/action-remove-labels@v1 with: labels: GHA-coverage \ No newline at end of file diff --git a/.github/workflows/tests-misc.yml b/.github/workflows/tests-misc.yml index 8e8f5770e92..97ad86e3998 100644 --- a/.github/workflows/tests-misc.yml +++ b/.github/workflows/tests-misc.yml @@ -60,7 +60,7 @@ jobs: RUST_BACKTRACE: 1 steps: - name: Checkout - uses: actions/checkout@v4.1.7 + uses: actions/checkout@v4 - name: script run: | @@ -185,7 +185,7 @@ jobs: needs: [set-image, cargo-check-benches] steps: - name: Checkout - uses: actions/checkout@v4.1.7 + uses: actions/checkout@v4 - name: Download artifact (master run) uses: actions/download-artifact@v4.1.8 @@ -225,7 +225,7 @@ jobs: image: ${{ needs.set-image.outputs.IMAGE }} steps: - name: Checkout - uses: actions/checkout@v4.1.7 + uses: actions/checkout@v4 - name: Run tests id: tests @@ -257,7 +257,7 @@ jobs: image: ${{ needs.set-image.outputs.IMAGE }} steps: - name: Checkout - uses: actions/checkout@v4.1.7 + uses: actions/checkout@v4 - name: script run: | @@ -272,7 +272,7 @@ jobs: image: ${{ needs.set-image.outputs.IMAGE }} steps: - name: Checkout - uses: actions/checkout@v4.1.7 + uses: actions/checkout@v4 - name: script run: | @@ -301,7 +301,7 @@ jobs: --config=patch.crates-io.honggfuzz.rev="205f7c8c059a0d98fe1cb912cdac84f324cb6981" steps: - name: Checkout - uses: actions/checkout@v4.1.7 + uses: actions/checkout@v4 - name: Run honggfuzz run: | @@ -332,7 +332,7 @@ jobs: index: [1, 2, 3, 4, 5, 6, 7] # 7 parallel jobs steps: - name: Checkout - uses: actions/checkout@v4.1.7 + uses: actions/checkout@v4 - name: Check Rust run: | @@ -359,7 +359,7 @@ jobs: # index: [ 1,2,3,4,5,6,7,8,9,10 ] # 10 parallel jobs # steps: # - name: Checkout - # uses: actions/checkout@v4.1.7 + # uses: actions/checkout@v4 # - name: Install dependencies # uses: ./.github/actions/set-up-mac -- GitLab From 8236718e961cb357d46368946f0544ec8ce6718e Mon Sep 17 00:00:00 2001 From: Liu-Cheng Xu Date: Tue, 10 Sep 2024 17:07:03 +0800 Subject: [PATCH 035/125] Fix edge case where state sync is not triggered (#5635) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR addresses an issue where state sync may fail to start if the conditions required for its initiation are not met when a finalized block notification is received. `pending_state_sync_attempt` is introduced to trigger the state sync later when the conditions are satisfied. This issue was spotted when I worked on #5406, specifically, `queue_blocks` was not empty when the finalized block notification was received, and then the state sync was stalled. cc @dmitry-markin --------- Co-authored-by: Dmitry Markin Co-authored-by: Bastian Köcher --- prdoc/pr_5635.prdoc | 13 ++ .../network/sync/src/strategy/chain_sync.rs | 162 +++++++++++------- 2 files changed, 109 insertions(+), 66 deletions(-) create mode 100644 prdoc/pr_5635.prdoc diff --git a/prdoc/pr_5635.prdoc b/prdoc/pr_5635.prdoc new file mode 100644 index 00000000000..168d65970c9 --- /dev/null +++ b/prdoc/pr_5635.prdoc @@ -0,0 +1,13 @@ +title: Fix edge case where state sync is not triggered + +doc: + - audience: Node Dev + description: | + There is an edge case where the finalized block notification is received, but the conditions required to initiate the + state sync are not fully met. In such cases, state sync would fail to start as expected and remain stalled. + This patch addresses it by storing the pending attempt and trying to start the state sync later when the conditions + are satisfied. + +crates: + - name: sc-network-sync + bump: patch diff --git a/substrate/client/network/sync/src/strategy/chain_sync.rs b/substrate/client/network/sync/src/strategy/chain_sync.rs index f29ed1b083e..cca83a5055c 100644 --- a/substrate/client/network/sync/src/strategy/chain_sync.rs +++ b/substrate/client/network/sync/src/strategy/chain_sync.rs @@ -254,6 +254,14 @@ pub struct ChainSync { /// A set of hashes of blocks that are being downloaded or have been /// downloaded and are queued for import. queue_blocks: HashSet, + /// A pending attempt to start the state sync. + /// + /// The initiation of state sync may be deferred in cases where other conditions + /// are not yet met when the finalized block notification is received, such as + /// when `queue_blocks` is not empty or there are no peers. This field holds the + /// necessary information to attempt the state sync at a later point when + /// conditions are satisfied. + pending_state_sync_attempt: Option<(B::Hash, NumberFor, bool)>, /// Fork sync targets. fork_targets: HashMap>, /// A set of peers for which there might be potential block requests @@ -376,6 +384,7 @@ where extra_justifications: ExtraRequests::new("justification", metrics_registry), mode, queue_blocks: Default::default(), + pending_state_sync_attempt: None, fork_targets: Default::default(), allowed_requests: Default::default(), max_parallel_downloads, @@ -497,7 +506,7 @@ where "💔 New peer {} with unknown genesis hash {} ({}).", peer_id, best_hash, best_number, ); - return Err(BadPeer(peer_id, rep::GENESIS_MISMATCH)) + return Err(BadPeer(peer_id, rep::GENESIS_MISMATCH)); } // If there are more than `MAJOR_SYNC_BLOCKS` in the import queue then we have @@ -521,7 +530,7 @@ where state: PeerSyncState::Available, }, ); - return Ok(None) + return Ok(None); } // If we are at genesis, just start downloading. @@ -644,14 +653,14 @@ where if self.is_known(hash) { debug!(target: LOG_TARGET, "Refusing to sync known hash {hash:?}"); - return + return; } trace!(target: LOG_TARGET, "Downloading requested old fork {hash:?}"); for peer_id in &peers { if let Some(peer) = self.peers.get_mut(peer_id) { if let PeerSyncState::AncestorSearch { .. } = peer.state { - continue + continue; } if number > peer.best_number { @@ -748,14 +757,14 @@ where blocks } else { debug!(target: LOG_TARGET, "Unexpected gap block response from {peer_id}"); - return Err(BadPeer(*peer_id, rep::NO_BLOCK)) + return Err(BadPeer(*peer_id, rep::NO_BLOCK)); } }, PeerSyncState::DownloadingStale(_) => { peer.state = PeerSyncState::Available; if blocks.is_empty() { debug!(target: LOG_TARGET, "Empty block response from {peer_id}"); - return Err(BadPeer(*peer_id, rep::NO_BLOCK)) + return Err(BadPeer(*peer_id, rep::NO_BLOCK)); } validate_blocks::(&blocks, peer_id, Some(request))?; blocks @@ -796,14 +805,14 @@ where target: LOG_TARGET, "Invalid response when searching for ancestor from {peer_id}", ); - return Err(BadPeer(*peer_id, rep::UNKNOWN_ANCESTOR)) + return Err(BadPeer(*peer_id, rep::UNKNOWN_ANCESTOR)); }, (_, Err(e)) => { info!( target: LOG_TARGET, "❌ Error answering legitimate blockchain query: {e}", ); - return Err(BadPeer(*peer_id, rep::BLOCKCHAIN_READ_ERROR)) + return Err(BadPeer(*peer_id, rep::BLOCKCHAIN_READ_ERROR)); }, }; if matching_hash.is_some() { @@ -837,7 +846,7 @@ where target: LOG_TARGET, "Ancestry search: genesis mismatch for peer {peer_id}", ); - return Err(BadPeer(*peer_id, rep::GENESIS_MISMATCH)) + return Err(BadPeer(*peer_id, rep::GENESIS_MISMATCH)); } if let Some((next_state, next_num)) = handle_ancestor_search_state(state, *current, matching_hash.is_some()) @@ -852,7 +861,7 @@ where peer_id: *peer_id, request, }); - return Ok(()) + return Ok(()); } else { // Ancestry search is complete. Check if peer is on a stale fork unknown // to us and add it to sync targets if necessary. @@ -892,7 +901,7 @@ where .insert(*peer_id); } peer.state = PeerSyncState::Available; - return Ok(()) + return Ok(()); } }, PeerSyncState::Available | @@ -925,7 +934,7 @@ where } } else { // We don't know of this peer, so we also did not request anything from it. - return Err(BadPeer(*peer_id, rep::NOT_REQUESTED)) + return Err(BadPeer(*peer_id, rep::NOT_REQUESTED)); }; self.validate_and_queue_blocks(new_blocks, gap); @@ -947,7 +956,7 @@ where target: LOG_TARGET, "💔 Called on_block_justification with a peer ID of an unknown peer", ); - return Ok(()) + return Ok(()); }; self.allowed_requests.add(&peer_id); @@ -964,7 +973,7 @@ where hash, block.hash, ); - return Err(BadPeer(peer_id, rep::BAD_JUSTIFICATION)) + return Err(BadPeer(peer_id, rep::BAD_JUSTIFICATION)); } block @@ -990,7 +999,7 @@ where number, justifications, }); - return Ok(()) + return Ok(()); } } @@ -1013,26 +1022,11 @@ where }); if let ChainSyncMode::LightState { skip_proofs, .. } = &self.mode { - if self.state_sync.is_none() && !self.peers.is_empty() && self.queue_blocks.is_empty() { - // Finalized a recent block. - let mut heads: Vec<_> = self.peers.values().map(|peer| peer.best_number).collect(); - heads.sort(); - let median = heads[heads.len() / 2]; - if number + STATE_SYNC_FINALITY_THRESHOLD.saturated_into() >= median { - if let Ok(Some(header)) = self.client.header(*hash) { - log::debug!( - target: LOG_TARGET, - "Starting state sync for #{number} ({hash})", - ); - self.state_sync = Some(StateSync::new( - self.client.clone(), - header, - None, - None, - *skip_proofs, - )); - self.allowed_requests.set_all(); - } + if self.state_sync.is_none() { + if !self.peers.is_empty() && self.queue_blocks.is_empty() { + self.attempt_state_sync(*hash, number, *skip_proofs); + } else { + self.pending_state_sync_attempt.replace((*hash, number, *skip_proofs)); } } } @@ -1045,6 +1039,35 @@ where } } + fn attempt_state_sync( + &mut self, + finalized_hash: B::Hash, + finalized_number: NumberFor, + skip_proofs: bool, + ) { + let mut heads: Vec<_> = self.peers.values().map(|peer| peer.best_number).collect(); + heads.sort(); + let median = heads[heads.len() / 2]; + if finalized_number + STATE_SYNC_FINALITY_THRESHOLD.saturated_into() >= median { + if let Ok(Some(header)) = self.client.header(finalized_hash) { + log::debug!( + target: LOG_TARGET, + "Starting state sync for #{finalized_number} ({finalized_hash})", + ); + self.state_sync = + Some(StateSync::new(self.client.clone(), header, None, None, skip_proofs)); + self.allowed_requests.set_all(); + } else { + log::error!( + target: LOG_TARGET, + "Failed to start state sync: header for finalized block \ + #{finalized_number} ({finalized_hash}) is not available", + ); + debug_assert!(false); + } + } + } + /// Submit a validated block announcement. /// /// Returns new best hash & best number of the peer if they are updated. @@ -1067,12 +1090,12 @@ where peer } else { error!(target: LOG_TARGET, "💔 Called `on_validated_block_announce` with a bad peer ID {peer_id}"); - return Some((hash, number)) + return Some((hash, number)); }; if let PeerSyncState::AncestorSearch { .. } = peer.state { trace!(target: LOG_TARGET, "Peer {} is in the ancestor search state.", peer_id); - return None + return None; } let peer_info = is_best.then(|| { @@ -1102,7 +1125,7 @@ where if let Some(target) = self.fork_targets.get_mut(&hash) { target.peers.insert(peer_id); } - return peer_info + return peer_info; } if ancient_parent { @@ -1113,7 +1136,7 @@ where hash, announce.header, ); - return peer_info + return peer_info; } if self.status().state == SyncState::Idle { @@ -1281,7 +1304,7 @@ where for (n, peer) in self.peers.iter_mut() { if let PeerSyncState::AncestorSearch { .. } = peer.state { // Wait for ancestry search to complete first. - continue + continue; } let new_common_number = if peer.best_number >= number { number } else { peer.best_number }; @@ -1401,7 +1424,7 @@ where /// What is the status of the block corresponding to the given hash? fn block_status(&self, hash: &B::Hash) -> Result { if self.queue_blocks.contains(hash) { - return Ok(BlockStatus::Queued) + return Ok(BlockStatus::Queued); } self.client.block_status(*hash) } @@ -1521,12 +1544,12 @@ where /// Get block requests scheduled by sync to be sent out. fn block_requests(&mut self) -> Vec<(PeerId, BlockRequest)> { if self.allowed_requests.is_empty() || self.state_sync.is_some() { - return Vec::new() + return Vec::new(); } if self.queue_blocks.len() > MAX_IMPORTING_BLOCKS { trace!(target: LOG_TARGET, "Too many blocks in the queue."); - return Vec::new() + return Vec::new(); } let is_major_syncing = self.status().state.is_major_syncing(); let attrs = self.required_block_attributes(); @@ -1550,7 +1573,7 @@ where !allowed_requests.contains(&id) || !disconnected_peers.is_peer_available(&id) { - return None + return None; } // If our best queued is more than `MAX_BLOCKS_TO_LOOK_BACKWARDS` blocks away from @@ -1648,17 +1671,17 @@ where /// Get a state request scheduled by sync to be sent out (if any). fn state_request(&mut self) -> Option<(PeerId, OpaqueStateRequest)> { if self.allowed_requests.is_empty() { - return None + return None; } if self.state_sync.is_some() && self.peers.iter().any(|(_, peer)| peer.state == PeerSyncState::DownloadingState) { // Only one pending state request is allowed. - return None + return None; } if let Some(sync) = &self.state_sync { if sync.is_complete() { - return None + return None; } for (id, peer) in self.peers.iter_mut() { @@ -1670,7 +1693,7 @@ where let request = sync.next_request(); trace!(target: LOG_TARGET, "New StateRequest for {}: {:?}", id, request); self.allowed_requests.clear(); - return Some((*id, OpaqueStateRequest(Box::new(request)))) + return Some((*id, OpaqueStateRequest(Box::new(request)))); } } } @@ -1709,7 +1732,7 @@ where sync.import(*response) } else { debug!(target: LOG_TARGET, "Ignored obsolete state response from {peer_id}"); - return Err(BadPeer(*peer_id, rep::NOT_REQUESTED)) + return Err(BadPeer(*peer_id, rep::NOT_REQUESTED)); }; match import_result { @@ -1765,16 +1788,17 @@ where } for (result, hash) in results { if has_error { - break + break; } has_error |= result.is_err(); match result { - Ok(BlockImportStatus::ImportedKnown(number, peer_id)) => + Ok(BlockImportStatus::ImportedKnown(number, peer_id)) => { if let Some(peer) = peer_id { self.update_peer_common_number(&peer, number); - }, + } + }, Ok(BlockImportStatus::ImportedUnknown(number, aux, peer_id)) => { if aux.clear_justification_requests { trace!( @@ -1882,6 +1906,12 @@ where /// Get pending actions to perform. #[must_use] pub fn actions(&mut self) -> impl Iterator> { + if !self.peers.is_empty() && self.queue_blocks.is_empty() { + if let Some((hash, number, skip_proofs)) = self.pending_state_sync_attempt.take() { + self.attempt_state_sync(hash, number, skip_proofs); + } + } + let block_requests = self .block_requests() .into_iter() @@ -1964,7 +1994,7 @@ fn handle_ancestor_search_state( if block_hash_match && next_distance_to_tip == One::one() { // We found the ancestor in the first step so there is no need to execute binary // search. - return None + return None; } if block_hash_match { let left = curr_block_num; @@ -1983,7 +2013,7 @@ fn handle_ancestor_search_state( }, AncestorSearchState::BinarySearch(mut left, mut right) => { if left >= curr_block_num { - return None + return None; } if block_hash_match { left = curr_block_num; @@ -2014,7 +2044,7 @@ fn peer_block_request( ) -> Option<(Range>, BlockRequest)> { if best_num >= peer.best_number { // Will be downloaded as alternative fork instead. - return None + return None; } else if peer.common_number < finalized { trace!( target: LOG_TARGET, @@ -2103,7 +2133,7 @@ fn fork_sync_request( hash, r.number, ); - return false + return false; } if check_block(hash) != BlockStatus::Unknown { trace!( @@ -2112,7 +2142,7 @@ fn fork_sync_request( hash, r.number, ); - return false + return false; } true }); @@ -2121,7 +2151,7 @@ fn fork_sync_request( } for (hash, r) in fork_targets { if !r.peers.contains(&id) { - continue + continue; } // Download the fork only if it is behind or not too far ahead our tip of the chain // Otherwise it should be downloaded in full sync mode. @@ -2148,7 +2178,7 @@ fn fork_sync_request( direction: Direction::Descending, max: Some(count), }, - )) + )); } else { trace!(target: LOG_TARGET, "Fork too far in the future: {:?} (#{})", hash, r.number); } @@ -2167,7 +2197,7 @@ where T: HeaderMetadata + ?Sized, { if base == block { - return Ok(false) + return Ok(false); } let ancestor = sp_blockchain::lowest_common_ancestor(client, *block, *base)?; @@ -2194,7 +2224,7 @@ pub fn validate_blocks( blocks.len(), ); - return Err(BadPeer(*peer_id, rep::NOT_REQUESTED)) + return Err(BadPeer(*peer_id, rep::NOT_REQUESTED)); } let block_header = @@ -2214,7 +2244,7 @@ pub fn validate_blocks( block_header, ); - return Err(BadPeer(*peer_id, rep::NOT_REQUESTED)) + return Err(BadPeer(*peer_id, rep::NOT_REQUESTED)); } if request.fields.contains(BlockAttributes::HEADER) && @@ -2225,7 +2255,7 @@ pub fn validate_blocks( "Missing requested header for a block in response from {peer_id}.", ); - return Err(BadPeer(*peer_id, rep::BAD_RESPONSE)) + return Err(BadPeer(*peer_id, rep::BAD_RESPONSE)); } if request.fields.contains(BlockAttributes::BODY) && blocks.iter().any(|b| b.body.is_none()) @@ -2235,7 +2265,7 @@ pub fn validate_blocks( "Missing requested body for a block in response from {peer_id}.", ); - return Err(BadPeer(*peer_id, rep::BAD_RESPONSE)) + return Err(BadPeer(*peer_id, rep::BAD_RESPONSE)); } } @@ -2250,7 +2280,7 @@ pub fn validate_blocks( b.hash, hash, ); - return Err(BadPeer(*peer_id, rep::BAD_BLOCK)) + return Err(BadPeer(*peer_id, rep::BAD_BLOCK)); } } if let (Some(header), Some(body)) = (&b.header, &b.body) { @@ -2268,7 +2298,7 @@ pub fn validate_blocks( expected, got, ); - return Err(BadPeer(*peer_id, rep::BAD_BLOCK)) + return Err(BadPeer(*peer_id, rep::BAD_BLOCK)); } } } -- GitLab From 9079f36b99ee0910762ce6020ca9dfd6671f77b3 Mon Sep 17 00:00:00 2001 From: Alexander Samusev <41779041+alvicsam@users.noreply.github.com> Date: Tue, 10 Sep 2024 11:48:09 +0200 Subject: [PATCH 036/125] [WIP][ci] GHA improvements (#5653) Changes in PR: - enables `Check Cargo Check Runtimes` (it was accidentally disabled) - reorder jobs in test-misc to make it faster - add `quick-benchmarks-omni` to `check-frame-omni-bencher` (the workflow is currently disabled) --- .../workflows/check-cargo-check-runtimes.yml | 33 ++++++++----- .../workflows/check-frame-omni-bencher.yml | 48 ++++++++++++++++--- .github/workflows/tests-misc.yml | 11 +++-- 3 files changed, 68 insertions(+), 24 deletions(-) diff --git a/.github/workflows/check-cargo-check-runtimes.yml b/.github/workflows/check-cargo-check-runtimes.yml index 6325033d214..ea0a4b94321 100644 --- a/.github/workflows/check-cargo-check-runtimes.yml +++ b/.github/workflows/check-cargo-check-runtimes.yml @@ -2,27 +2,34 @@ name: Check Cargo Check Runtimes on: pull_request: - types: [opened, synchronize, reopened, ready_for_review, labeled] + types: [opened, synchronize, reopened, ready_for_review] # Jobs in this workflow depend on each other, only for limiting peak amount of spawned workers jobs: - # GitHub Actions allows using 'env' in a container context. - # However, env variables don't work for forks: https://github.com/orgs/community/discussions/44322 - # This workaround sets the container image for each job using 'set-image' job output. set-image: - if: contains(github.event.label.name, 'GHA-migration') || contains(github.event.pull_request.labels.*.name, 'GHA-migration') + # GitHub Actions allows using 'env' in a container context. + # However, env variables don't work for forks: https://github.com/orgs/community/discussions/44322 + # This workaround sets the container image for each job using 'set-image' job output. runs-on: ubuntu-latest - timeout-minutes: 20 outputs: IMAGE: ${{ steps.set_image.outputs.IMAGE }} + RUNNER: ${{ steps.set_runner.outputs.RUNNER }} steps: - name: Checkout uses: actions/checkout@v4 - id: set_image run: cat .github/env >> $GITHUB_OUTPUT + - id: set_runner + run: | + # Run merge queues on persistent runners + if [[ $GITHUB_REF_NAME == *"gh-readonly-queue"* ]]; then + echo "RUNNER=arc-runners-polkadot-sdk-beefy-persistent" >> $GITHUB_OUTPUT + else + echo "RUNNER=arc-runners-polkadot-sdk-beefy" >> $GITHUB_OUTPUT + fi check-runtime-assets: - runs-on: arc-runners-polkadot-sdk-beefy + runs-on: ${{ needs.set-image.outputs.RUNNER }} needs: [set-image] timeout-minutes: 20 container: @@ -36,7 +43,7 @@ jobs: root: cumulus/parachains/runtimes/assets check-runtime-collectives: - runs-on: arc-runners-polkadot-sdk-beefy + runs-on: ${{ needs.set-image.outputs.RUNNER }} needs: [check-runtime-assets, set-image] timeout-minutes: 20 container: @@ -50,7 +57,7 @@ jobs: root: cumulus/parachains/runtimes/collectives check-runtime-coretime: - runs-on: arc-runners-polkadot-sdk-beefy + runs-on: ${{ needs.set-image.outputs.RUNNER }} container: image: ${{ needs.set-image.outputs.IMAGE }} needs: [check-runtime-assets, set-image] @@ -64,7 +71,7 @@ jobs: root: cumulus/parachains/runtimes/coretime check-runtime-bridge-hubs: - runs-on: arc-runners-polkadot-sdk-beefy + runs-on: ${{ needs.set-image.outputs.RUNNER }} container: image: ${{ needs.set-image.outputs.IMAGE }} needs: [set-image] @@ -78,7 +85,7 @@ jobs: root: cumulus/parachains/runtimes/bridge-hubs check-runtime-contracts: - runs-on: arc-runners-polkadot-sdk-beefy + runs-on: ${{ needs.set-image.outputs.RUNNER }} container: image: ${{ needs.set-image.outputs.IMAGE }} needs: [check-runtime-collectives, set-image] @@ -92,7 +99,7 @@ jobs: root: cumulus/parachains/runtimes/contracts check-runtime-starters: - runs-on: arc-runners-polkadot-sdk-beefy + runs-on: ${{ needs.set-image.outputs.RUNNER }} container: image: ${{ needs.set-image.outputs.IMAGE }} needs: [check-runtime-assets, set-image] @@ -106,7 +113,7 @@ jobs: root: cumulus/parachains/runtimes/starters check-runtime-testing: - runs-on: arc-runners-polkadot-sdk-beefy + runs-on: ${{ needs.set-image.outputs.RUNNER }} container: image: ${{ needs.set-image.outputs.IMAGE }} needs: [check-runtime-starters, set-image] diff --git a/.github/workflows/check-frame-omni-bencher.yml b/.github/workflows/check-frame-omni-bencher.yml index e9db2d91297..e035a30c7c2 100644 --- a/.github/workflows/check-frame-omni-bencher.yml +++ b/.github/workflows/check-frame-omni-bencher.yml @@ -5,7 +5,7 @@ on: branches: - master pull_request: - types: [ opened, synchronize, reopened, ready_for_review, labeled ] + types: [opened, synchronize, reopened, ready_for_review, labeled] merge_group: concurrency: @@ -28,19 +28,46 @@ jobs: # However, env variables don't work for forks: https://github.com/orgs/community/discussions/44322 # This workaround sets the container image for each job using 'set-image' job output. runs-on: ubuntu-latest - needs: changes - if: ${{ needs.changes.outputs.rust }} outputs: IMAGE: ${{ steps.set_image.outputs.IMAGE }} + RUNNER: ${{ steps.set_runner.outputs.RUNNER }} steps: - name: Checkout uses: actions/checkout@v4 - id: set_image run: cat .github/env >> $GITHUB_OUTPUT + - id: set_runner + run: | + # Run merge queues on persistent runners + if [[ $GITHUB_REF_NAME == *"gh-readonly-queue"* ]]; then + echo "RUNNER=arc-runners-polkadot-sdk-beefy-persistent" >> $GITHUB_OUTPUT + else + echo "RUNNER=arc-runners-polkadot-sdk-beefy" >> $GITHUB_OUTPUT + fi + + quick-benchmarks-omni: + runs-on: ${{ needs.set-image.outputs.RUNNER }} + needs: [set-image, changes] + if: ${{ needs.changes.outputs.rust }} + env: + RUSTFLAGS: "-C debug-assertions" + RUST_BACKTRACE: "full" + WASM_BUILD_NO_COLOR: 1 + WASM_BUILD_RUSTFLAGS: "-C debug-assertions" + timeout-minutes: 30 + container: + image: ${{ needs.set-image.outputs.IMAGE }} + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: script + run: | + forklift cargo build --locked --quiet --release -p asset-hub-westend-runtime --features runtime-benchmarks + forklift cargo run --locked --release -p frame-omni-bencher --quiet -- v1 benchmark pallet --runtime target/release/wbuild/asset-hub-westend-runtime/asset_hub_westend_runtime.compact.compressed.wasm --all --steps 2 --repeat 1 --quiet run-frame-omni-bencher: - runs-on: arc-runners-polkadot-sdk-beefy - needs: [ set-image, changes ] # , build-frame-omni-bencher ] + runs-on: ${{ needs.set-image.outputs.RUNNER }} + needs: [set-image, changes] # , build-frame-omni-bencher ] if: ${{ needs.changes.outputs.rust }} timeout-minutes: 30 strategy: @@ -81,5 +108,14 @@ jobs: runs-on: ubuntu-latest name: All benchmarks passed needs: run-frame-omni-bencher + if: always() && !cancelled() steps: - - run: echo '### Good job! All the benchmarks passed 🚀' >> $GITHUB_STEP_SUMMARY + - run: | + tee resultfile <<< '${{ toJSON(needs) }}' + FAILURES=$(cat resultfile | grep '"result": "failure"' | wc -l) + if [ $FAILURES -gt 0 ]; then + echo "### At least one required job failed ❌" >> $GITHUB_STEP_SUMMARY + exit 1 + else + echo '### Good job! All the required jobs passed 🚀' >> $GITHUB_STEP_SUMMARY + fi diff --git a/.github/workflows/tests-misc.yml b/.github/workflows/tests-misc.yml index 97ad86e3998..e4f0d6575c5 100644 --- a/.github/workflows/tests-misc.yml +++ b/.github/workflows/tests-misc.yml @@ -93,7 +93,7 @@ jobs: test-frame-ui: timeout-minutes: 60 - needs: [set-image, test-frame-examples-compile-to-wasm] + needs: [set-image] runs-on: ${{ needs.set-image.outputs.RUNNER }} container: image: ${{ needs.set-image.outputs.IMAGE }} @@ -121,7 +121,7 @@ jobs: test-deterministic-wasm: timeout-minutes: 20 - needs: [set-image] + needs: [set-image, test-frame-examples-compile-to-wasm] runs-on: ${{ needs.set-image.outputs.RUNNER }} container: image: ${{ needs.set-image.outputs.IMAGE }} @@ -148,7 +148,7 @@ jobs: timeout-minutes: 60 strategy: matrix: - branch: [ master, current ] + branch: [master, current] runs-on: ${{ needs.set-image.outputs.RUNNER }} container: image: ${{ needs.set-image.outputs.IMAGE }} @@ -158,7 +158,7 @@ jobs: with: # if branch is master, use the branch, otherwise set empty string, so it uses the current context # either PR (including forks) or merge group (main repo) - ref: ${{ matrix.branch == 'master' && matrix.branch || '' }} + ref: ${{ matrix.branch == 'master' && matrix.branch || '' }} - name: script run: | @@ -181,7 +181,8 @@ jobs: node-bench-regression-guard: timeout-minutes: 20 if: always() && !cancelled() - runs-on: arc-runners-polkadot-sdk + # runs-on: arc-runners-polkadot-sdk + runs-on: ubuntu-latest needs: [set-image, cargo-check-benches] steps: - name: Checkout -- GitLab From d887804b235003282b26d4202f40c79fb302f16c Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Tue, 10 Sep 2024 15:28:28 +0200 Subject: [PATCH 037/125] [Bot] Use correct token in backport bot (#5654) The backport bot does currently not trigger the CI when opening a MR, like here: https://github.com/paritytech/polkadot-sdk/pull/5651 Devs need to push an empty commit manually. Now using a token that will also trigger the CI. --------- Signed-off-by: Oliver Tale-Yazdi --- .github/workflows/command-backport.yml | 32 ++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/.github/workflows/command-backport.yml b/.github/workflows/command-backport.yml index 1ad68d96a63..85e7b77801d 100644 --- a/.github/workflows/command-backport.yml +++ b/.github/workflows/command-backport.yml @@ -27,15 +27,24 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Generate token + id: generate_token + uses: tibdex/github-app-token@v2.1.0 + with: + app_id: ${{ secrets.CMD_BOT_APP_ID }} + private_key: ${{ secrets.CMD_BOT_APP_KEY }} + - name: Create backport pull requests uses: korthout/backport-action@v3 id: backport with: target_branches: stable2407 stable2409 merge_commits: skip - github_token: ${{ secrets.GITHUB_TOKEN }} + github_token: ${{ steps.generate_token.outputs.token }} pull_description: | - Backport #${pull_number} into `${target_branch}` (cc @${pull_author}). + Backport #${pull_number} into `${target_branch}` from ${pull_author}. + + See the [documentation](https://github.com/paritytech/polkadot-sdk/blob/master/docs/BACKPORT.md) on how to use this bot. tests/construct_runtime_ui/undefined_event_part.rs:66:1 + | +66 | / construct_runtime! { +67 | | pub struct Runtime +68 | | { +69 | | System: frame_system expanded::{}::{Pallet, Call, Storage, Config, Event}, +70 | | Pallet: pallet expanded::{}::{Pallet, Event}, +71 | | } +72 | | } + | |_^ could not find `Event` in `pallet` + | + = note: this error originates in the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) +help: consider importing one of these items + | +18 + use frame_support_test::Event; + | +18 + use frame_system::Event; + | diff --git a/substrate/frame/support/test/tests/enum_deprecation.rs b/substrate/frame/support/test/tests/enum_deprecation.rs new file mode 100644 index 00000000000..ed9b2b5a735 --- /dev/null +++ b/substrate/frame/support/test/tests/enum_deprecation.rs @@ -0,0 +1,173 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#![allow(useless_deprecated, deprecated, clippy::deprecated_semver)] + +use std::collections::BTreeMap; + +use frame_support::{ + derive_impl, + dispatch::Parameter, + parameter_types, + traits::{ConstU32, StorageVersion}, + OrdNoBound, PartialOrdNoBound, +}; +use scale_info::TypeInfo; + +parameter_types! { + /// Used to control if the storage version should be updated. + storage UpdateStorageVersion: bool = false; +} + +pub struct SomeType1; +impl From for u64 { + fn from(_t: SomeType1) -> Self { + 0u64 + } +} + +pub trait SomeAssociation1 { + type _1: Parameter + codec::MaxEncodedLen + TypeInfo; +} +impl SomeAssociation1 for u64 { + type _1 = u64; +} + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + + pub(crate) const STORAGE_VERSION: StorageVersion = StorageVersion::new(10); + + #[pallet::config] + pub trait Config: frame_system::Config + where + ::AccountId: From + SomeAssociation1, + { + type Balance: Parameter + Default + TypeInfo; + + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + } + + #[pallet::pallet] + #[pallet::storage_version(STORAGE_VERSION)] + pub struct Pallet(_); + + #[pallet::error] + #[derive(PartialEq, Eq)] + pub enum Error { + /// error doc comment put in metadata + InsufficientProposersBalance, + NonExistentStorageValue, + Code(u8), + #[codec(skip)] + Skipped(u128), + CompactU8(#[codec(compact)] u8), + } + + #[pallet::event] + pub enum Event + where + T::AccountId: SomeAssociation1 + From, + { + #[deprecated = "second"] + A, + #[deprecated = "first"] + #[codec(index = 0)] + B, + } + + #[pallet::origin] + #[derive( + EqNoBound, + RuntimeDebugNoBound, + CloneNoBound, + PartialEqNoBound, + PartialOrdNoBound, + OrdNoBound, + Encode, + Decode, + TypeInfo, + MaxEncodedLen, + )] + pub struct Origin(PhantomData); +} + +frame_support::parameter_types!( + pub const MyGetParam3: u32 = 12; +); + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for Runtime { + type BaseCallFilter = frame_support::traits::Everything; + type RuntimeOrigin = RuntimeOrigin; + type Nonce = u64; + type RuntimeCall = RuntimeCall; + type Hash = sp_runtime::testing::H256; + type Hashing = sp_runtime::traits::BlakeTwo256; + type AccountId = u64; + type Lookup = sp_runtime::traits::IdentityLookup; + type Block = Block; + type RuntimeEvent = RuntimeEvent; + type MaxConsumers = ConstU32<16>; +} +impl pallet::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Balance = u64; +} + +pub type Header = sp_runtime::generic::Header; +pub type Block = sp_runtime::generic::Block; +pub type UncheckedExtrinsic = + sp_runtime::testing::TestXt>; + +frame_support::construct_runtime!( + pub struct Runtime { + // Exclude part `Storage` in order not to check its metadata in tests. + System: frame_system exclude_parts { Pallet, Storage }, + Example: pallet, + + } +); + +#[test] +fn pallet_metadata() { + use sp_metadata_ir::{DeprecationInfoIR, DeprecationStatusIR}; + let pallets = Runtime::metadata_ir().pallets; + let example = pallets[0].clone(); + { + // Example pallet events are partially and fully deprecated + let meta = example.event.unwrap(); + assert_eq!( + // Result should be this, but instead we get the result below + // see: https://github.com/paritytech/parity-scale-codec/issues/507 + // + // DeprecationInfoIR::VariantsDeprecated(BTreeMap::from([ + // (codec::Compact(0), DeprecationStatusIR::Deprecated { note: "first", since: None + // }), ( + // codec::Compact(1), + // DeprecationStatusIR::Deprecated { note: "second", since: None } + // ) + // ])), + DeprecationInfoIR::VariantsDeprecated(BTreeMap::from([( + codec::Compact(0), + DeprecationStatusIR::Deprecated { note: "first", since: None } + ),])), + meta.deprecation_info + ); + } +} diff --git a/substrate/frame/support/test/tests/instance.rs b/substrate/frame/support/test/tests/instance.rs index 30b8338bc5c..7f8423a0127 100644 --- a/substrate/frame/support/test/tests/instance.rs +++ b/substrate/frame/support/test/tests/instance.rs @@ -441,6 +441,7 @@ fn expected_metadata() -> PalletStorageMetadataIR { ty: StorageEntryTypeIR::Plain(scale_info::meta_type::()), default: vec![0, 0, 0, 0], docs: vec![], + deprecation_info: sp_metadata_ir::DeprecationStatusIR::NotDeprecated, }, StorageEntryMetadataIR { name: "Map", @@ -452,6 +453,7 @@ fn expected_metadata() -> PalletStorageMetadataIR { }, default: [0u8; 8].to_vec(), docs: vec![], + deprecation_info: sp_metadata_ir::DeprecationStatusIR::NotDeprecated, }, StorageEntryMetadataIR { name: "DoubleMap", @@ -463,6 +465,7 @@ fn expected_metadata() -> PalletStorageMetadataIR { }, default: [0u8; 8].to_vec(), docs: vec![], + deprecation_info: sp_metadata_ir::DeprecationStatusIR::NotDeprecated, }, ], } diff --git a/substrate/frame/support/test/tests/pallet.rs b/substrate/frame/support/test/tests/pallet.rs index eed8a22e8e7..7f1ce0556ea 100644 --- a/substrate/frame/support/test/tests/pallet.rs +++ b/substrate/frame/support/test/tests/pallet.rs @@ -14,6 +14,9 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. +#![allow(useless_deprecated, deprecated, clippy::deprecated_semver)] + +use std::collections::BTreeMap; use frame_support::{ assert_ok, derive_impl, @@ -212,6 +215,7 @@ pub mod pallet { /// call foo doc comment put in metadata #[pallet::call_index(0)] #[pallet::weight(Weight::from_parts(*foo as u64, 0))] + #[deprecated = "test"] pub fn foo( origin: OriginFor, #[pallet::compact] foo: u32, @@ -272,6 +276,7 @@ pub mod pallet { pub enum Error { /// error doc comment put in metadata InsufficientProposersBalance, + #[deprecated = "test"] NonExistentStorageValue, Code(u8), #[codec(skip)] @@ -283,6 +288,7 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(fn deposit_event)] + #[deprecated = "test"] pub enum Event where T::AccountId: SomeAssociation1 + From, @@ -486,7 +492,7 @@ pub mod pallet { let _ = T::AccountId::from(SomeType1); // Test for where clause let _ = T::AccountId::from(SomeType5); // Test for where clause if matches!(call, Call::foo_storage_layer { .. }) { - return Ok(ValidTransaction::default()) + return Ok(ValidTransaction::default()); } Err(TransactionValidityError::Invalid(InvalidTransaction::Call)) } @@ -553,6 +559,7 @@ pub mod pallet { // Test that a pallet with non generic event and generic genesis_config is correctly handled // and that a pallet with the attribute without_storage_info is correctly handled. #[frame_support::pallet] +#[deprecated = "test"] pub mod pallet2 { use super::{SomeAssociation1, SomeType1, UpdateStorageVersion}; use frame_support::pallet_prelude::*; @@ -2478,3 +2485,56 @@ fn test_error_feature_parsing() { pallet::Error::__Ignore(_, _) => (), } } + +#[test] +fn pallet_metadata() { + use sp_metadata_ir::{DeprecationInfoIR, DeprecationStatusIR}; + let pallets = Runtime::metadata_ir().pallets; + let example = pallets[0].clone(); + let example2 = pallets[1].clone(); + { + // Example2 pallet is deprecated + assert_eq!( + &DeprecationStatusIR::Deprecated { note: "test", since: None }, + &example2.deprecation_info + ) + } + { + // Example pallet calls is fully and partially deprecated + let meta = &example.calls.unwrap(); + assert_eq!( + DeprecationInfoIR::VariantsDeprecated(BTreeMap::from([( + codec::Compact(0), + DeprecationStatusIR::Deprecated { note: "test", since: None } + )])), + meta.deprecation_info + ) + } + { + // Example pallet errors are partially and fully deprecated + let meta = &example.error.unwrap(); + assert_eq!( + DeprecationInfoIR::VariantsDeprecated(BTreeMap::from([( + codec::Compact(2), + DeprecationStatusIR::Deprecated { note: "test", since: None } + )])), + meta.deprecation_info + ) + } + { + // Example pallet events are partially and fully deprecated + let meta = example.event.unwrap(); + assert_eq!( + DeprecationInfoIR::ItemDeprecated(DeprecationStatusIR::Deprecated { + note: "test", + since: None + }), + meta.deprecation_info + ); + } + { + // Example2 pallet events are not deprecated + let meta = example2.event.unwrap(); + assert_eq!(DeprecationInfoIR::NotDeprecated, meta.deprecation_info); + } +} diff --git a/substrate/frame/support/test/tests/runtime_metadata.rs b/substrate/frame/support/test/tests/runtime_metadata.rs index 48e4d975eb0..81377210eb4 100644 --- a/substrate/frame/support/test/tests/runtime_metadata.rs +++ b/substrate/frame/support/test/tests/runtime_metadata.rs @@ -14,11 +14,13 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. +#![allow(useless_deprecated, deprecated, clippy::deprecated_semver)] use frame_support::{derive_impl, traits::ConstU32}; use scale_info::{form::MetaForm, meta_type}; use sp_metadata_ir::{ - RuntimeApiMetadataIR, RuntimeApiMethodMetadataIR, RuntimeApiMethodParamMetadataIR, + DeprecationStatusIR, RuntimeApiMetadataIR, RuntimeApiMethodMetadataIR, + RuntimeApiMethodParamMetadataIR, }; use sp_runtime::traits::Block as BlockT; @@ -64,12 +66,16 @@ sp_api::decl_runtime_apis! { /// ApiWithCustomVersion trait documentation /// /// Documentation on multiline. + #[deprecated] pub trait Api { fn test(data: u64); /// something_with_block. fn something_with_block(block: Block) -> Block; + #[deprecated = "example"] fn function_with_two_args(data: u64, block: Block); + #[deprecated(note = "example", since = "example")] fn same_name(); + #[deprecated(note = "example")] fn wild_card(_: u32); } } @@ -128,6 +134,7 @@ fn runtime_metadata() { }], output: meta_type::<()>(), docs: vec![], + deprecation_info: DeprecationStatusIR::NotDeprecated, }, RuntimeApiMethodMetadataIR { name: "something_with_block", @@ -137,6 +144,7 @@ fn runtime_metadata() { }], output: meta_type::(), docs: maybe_docs(vec![" something_with_block."]), + deprecation_info: DeprecationStatusIR::NotDeprecated, }, RuntimeApiMethodMetadataIR { name: "function_with_two_args", @@ -152,13 +160,21 @@ fn runtime_metadata() { ], output: meta_type::<()>(), docs: vec![], + deprecation_info: DeprecationStatusIR::Deprecated { + note: "example", + since: None, + } }, RuntimeApiMethodMetadataIR { name: "same_name", inputs: vec![], output: meta_type::<()>(), docs: vec![], - }, + deprecation_info: DeprecationStatusIR::Deprecated { + note: "example", + since: Some("example"), + } + }, RuntimeApiMethodMetadataIR { name: "wild_card", inputs: vec![RuntimeApiMethodParamMetadataIR:: { @@ -167,6 +183,10 @@ fn runtime_metadata() { }], output: meta_type::<()>(), docs: vec![], + deprecation_info: DeprecationStatusIR::Deprecated { + note: "example", + since: None, + } }, ], docs: maybe_docs(vec![ @@ -174,6 +194,8 @@ fn runtime_metadata() { "", " Documentation on multiline.", ]), + deprecation_info: DeprecationStatusIR::DeprecatedWithoutNote, + }, RuntimeApiMetadataIR { name: "Core", @@ -183,6 +205,7 @@ fn runtime_metadata() { inputs: vec![], output: meta_type::(), docs: maybe_docs(vec![" Returns the version of the runtime."]), + deprecation_info: DeprecationStatusIR::NotDeprecated, }, RuntimeApiMethodMetadataIR { name: "execute_block", @@ -192,6 +215,8 @@ fn runtime_metadata() { }], output: meta_type::<()>(), docs: maybe_docs(vec![" Execute the given block."]), + deprecation_info: DeprecationStatusIR::NotDeprecated, + }, RuntimeApiMethodMetadataIR { name: "initialize_block", @@ -201,11 +226,13 @@ fn runtime_metadata() { }], output: meta_type::(), docs: maybe_docs(vec![" Initialize a block with the given header and return the runtime executive mode."]), + deprecation_info: DeprecationStatusIR::NotDeprecated, }, ], docs: maybe_docs(vec![ " The `Core` runtime api that every Substrate runtime needs to implement.", ]), + deprecation_info: DeprecationStatusIR::NotDeprecated, }, ]; diff --git a/substrate/primitives/api/proc-macro/src/impl_runtime_apis.rs b/substrate/primitives/api/proc-macro/src/impl_runtime_apis.rs index 2c423f8c28d..21397abc8fc 100644 --- a/substrate/primitives/api/proc-macro/src/impl_runtime_apis.rs +++ b/substrate/primitives/api/proc-macro/src/impl_runtime_apis.rs @@ -471,7 +471,7 @@ fn extend_with_api_version(mut trait_: Path, version: Option) -> Path { v } else { // nothing to do - return trait_ + return trait_; }; let trait_name = &mut trait_ @@ -762,7 +762,7 @@ fn generate_runtime_api_versions(impls: &[ItemImpl]) -> Result { error.combine(Error::new(other_span, "First trait implementation.")); - return Err(error) + return Err(error); } let id: Path = parse_quote!( #path ID ); @@ -892,7 +892,7 @@ fn extract_cfg_api_version(attrs: &Vec, span: Span) -> Result, span: Span) -> Result Each runtime API can have only one version.", API_VERSION_ATTRIBUTE ), - )) + )); } // Parse the runtime version if there exists one. diff --git a/substrate/primitives/api/proc-macro/src/runtime_metadata.rs b/substrate/primitives/api/proc-macro/src/runtime_metadata.rs index 9944927d557..4cba524dbe2 100644 --- a/substrate/primitives/api/proc-macro/src/runtime_metadata.rs +++ b/substrate/primitives/api/proc-macro/src/runtime_metadata.rs @@ -94,7 +94,7 @@ pub fn generate_decl_runtime_metadata(decl: &ItemTrait) -> TokenStream2 { let is_changed_in = method.attrs.iter().any(|attr| attr.path().is_ident(CHANGED_IN_ATTRIBUTE)); if is_changed_in { - continue + continue; } let mut inputs = Vec::new(); @@ -131,6 +131,10 @@ pub fn generate_decl_runtime_metadata(decl: &ItemTrait) -> TokenStream2 { // Include the method metadata only if its `cfg` features are enabled. let attrs = filter_cfg_attributes(&method.attrs); + let deprecation = match crate::utils::get_deprecation(&crate_, &method.attrs) { + Ok(deprecation) => deprecation, + Err(e) => return e.into_compile_error(), + }; methods.push(quote!( #( #attrs )* #crate_::metadata_ir::RuntimeApiMethodMetadataIR { @@ -138,6 +142,7 @@ pub fn generate_decl_runtime_metadata(decl: &ItemTrait) -> TokenStream2 { inputs: #crate_::vec![ #( #inputs, )* ], output: #output, docs: #docs, + deprecation_info: #deprecation, } )); } @@ -145,6 +150,10 @@ pub fn generate_decl_runtime_metadata(decl: &ItemTrait) -> TokenStream2 { let trait_name_ident = &decl.ident; let trait_name = trait_name_ident.to_string(); let docs = collect_docs(&decl.attrs, &crate_); + let deprecation = match crate::utils::get_deprecation(&crate_, &decl.attrs) { + Ok(deprecation) => deprecation, + Err(e) => return e.into_compile_error(), + }; let attrs = filter_cfg_attributes(&decl.attrs); // The trait generics where already extended with `Block: BlockT`. let mut generics = decl.generics.clone(); @@ -174,6 +183,7 @@ pub fn generate_decl_runtime_metadata(decl: &ItemTrait) -> TokenStream2 { name: #trait_name, methods: #crate_::vec![ #( #methods, )* ], docs: #docs, + deprecation_info: #deprecation, } } } @@ -187,7 +197,7 @@ pub fn generate_decl_runtime_metadata(decl: &ItemTrait) -> TokenStream2 { /// exposed by `generate_decl_runtime_metadata`. pub fn generate_impl_runtime_metadata(impls: &[ItemImpl]) -> Result { if impls.is_empty() { - return Ok(quote!()) + return Ok(quote!()); } let crate_ = generate_crate_access(); diff --git a/substrate/primitives/api/proc-macro/src/utils.rs b/substrate/primitives/api/proc-macro/src/utils.rs index 36577670a40..94da6748cbd 100644 --- a/substrate/primitives/api/proc-macro/src/utils.rs +++ b/substrate/primitives/api/proc-macro/src/utils.rs @@ -21,8 +21,9 @@ use proc_macro2::{Span, TokenStream}; use proc_macro_crate::{crate_name, FoundCrate}; use quote::{format_ident, quote}; use syn::{ - parse_quote, spanned::Spanned, token::And, Attribute, Error, FnArg, GenericArgument, Ident, - ImplItem, ItemImpl, Pat, Path, PathArguments, Result, ReturnType, Signature, Type, TypePath, + parse_quote, punctuated::Punctuated, spanned::Spanned, token::And, Attribute, Error, Expr, + ExprLit, FnArg, GenericArgument, Ident, ImplItem, ItemImpl, Lit, Meta, MetaNameValue, Pat, + Path, PathArguments, Result, ReturnType, Signature, Token, Type, TypePath, }; /// Generates the access to the `sc_client` crate. @@ -33,7 +34,7 @@ pub fn generate_crate_access() -> TokenStream { let renamed_name = Ident::new(&renamed_name, Span::call_site()); quote!(#renamed_name::__private) }, - Err(e) => + Err(e) => { if let Ok(FoundCrate::Name(name)) = crate_name(&"polkadot-sdk-frame").or_else(|_| crate_name(&"frame")) { @@ -47,7 +48,8 @@ pub fn generate_crate_access() -> TokenStream { } else { let err = Error::new(Span::call_site(), e).to_compile_error(); quote!( #err ) - }, + } + }, } } @@ -144,7 +146,7 @@ pub fn extract_parameter_names_types_and_borrows( return Err(Error::new(input.span(), "`self` parameter not supported!")), FnArg::Receiver(recv) => if recv.mutability.is_some() || recv.reference.is_none() { - return Err(Error::new(recv.span(), "Only `&self` is supported!")) + return Err(Error::new(recv.span(), "Only `&self` is supported!")); }, } } @@ -284,6 +286,85 @@ pub fn filter_cfg_attributes(attrs: &[syn::Attribute]) -> Vec { attrs.iter().filter(|a| a.path().is_ident("cfg")).cloned().collect() } +fn deprecation_msg_formatter(msg: &str) -> String { + format!( + r#"{msg} + help: the following are the possible correct uses +| +| #[deprecated = "reason"] +| +| #[deprecated(/*opt*/ since = "version", /*opt*/ note = "reason")] +| +| #[deprecated] +|"# + ) +} + +fn parse_deprecated_meta(crate_: &TokenStream, attr: &syn::Attribute) -> Result { + match &attr.meta { + Meta::List(meta_list) => { + let parsed = meta_list + .parse_args_with(Punctuated::::parse_terminated) + .map_err(|e| Error::new(attr.span(), e.to_string()))?; + let (note, since) = parsed.iter().try_fold((None, None), |mut acc, item| { + let value = match &item.value { + Expr::Lit(ExprLit { lit: lit @ Lit::Str(_), .. }) => Ok(lit), + _ => Err(Error::new( + attr.span(), + deprecation_msg_formatter( + "Invalid deprecation attribute: expected string literal", + ), + )), + }?; + if item.path.is_ident("note") { + acc.0.replace(value); + } else if item.path.is_ident("since") { + acc.1.replace(value); + } + Ok::<(Option<&syn::Lit>, Option<&syn::Lit>), Error>(acc) + })?; + note.map_or_else( + || Err(Error::new(attr.span(), deprecation_msg_formatter( + "Invalid deprecation attribute: missing `note`"))), + |note| { + let since = if let Some(str) = since { + quote! { Some(#str) } + } else { + quote! { None } + }; + let doc = quote! { #crate_::metadata_ir::DeprecationStatusIR::Deprecated { note: #note, since: #since }}; + Ok(doc) + }, + ) + }, + Meta::NameValue(MetaNameValue { + value: Expr::Lit(ExprLit { lit: lit @ Lit::Str(_), .. }), + .. + }) => { + // #[deprecated = "lit"] + let doc = quote! { #crate_::metadata_ir::DeprecationStatusIR::Deprecated { note: #lit, since: None } }; + Ok(doc) + }, + Meta::Path(_) => { + // #[deprecated] + Ok(quote! { #crate_::metadata_ir::DeprecationStatusIR::DeprecatedWithoutNote }) + }, + _ => Err(Error::new( + attr.span(), + deprecation_msg_formatter("Invalid deprecation attribute: expected string literal"), + )), + } +} + +/// collects deprecation attribute if its present. +pub fn get_deprecation(crate_: &TokenStream, attrs: &[syn::Attribute]) -> Result { + attrs + .iter() + .find(|a| a.path().is_ident("deprecated")) + .map(|a| parse_deprecated_meta(&crate_, a)) + .unwrap_or_else(|| Ok(quote! {#crate_::metadata_ir::DeprecationStatusIR::NotDeprecated})) +} + #[cfg(test)] mod tests { use assert_matches::assert_matches; @@ -330,4 +411,38 @@ mod tests { assert_eq!(cfg_std, filtered[0]); assert_eq!(cfg_benchmarks, filtered[1]); } + + #[test] + fn check_deprecated_attr() { + const FIRST: &'static str = "hello"; + const SECOND: &'static str = "WORLD"; + + let simple: Attribute = parse_quote!(#[deprecated]); + let simple_path: Attribute = parse_quote!(#[deprecated = #FIRST]); + let meta_list: Attribute = parse_quote!(#[deprecated(note = #FIRST)]); + let meta_list_with_since: Attribute = + parse_quote!(#[deprecated(note = #FIRST, since = #SECOND)]); + let extra_fields: Attribute = + parse_quote!(#[deprecated(note = #FIRST, since = #SECOND, extra = "Test")]); + assert_eq!( + get_deprecation("e! { crate }, &[simple]).unwrap().to_string(), + quote! { crate::metadata_ir::DeprecationStatusIR::DeprecatedWithoutNote }.to_string() + ); + assert_eq!( + get_deprecation("e! { crate }, &[simple_path]).unwrap().to_string(), + quote! { crate::metadata_ir::DeprecationStatusIR::Deprecated { note: #FIRST, since: None } }.to_string() + ); + assert_eq!( + get_deprecation("e! { crate }, &[meta_list]).unwrap().to_string(), + quote! { crate::metadata_ir::DeprecationStatusIR::Deprecated { note: #FIRST, since: None } }.to_string() + ); + assert_eq!( + get_deprecation("e! { crate }, &[meta_list_with_since]).unwrap().to_string(), + quote! { crate::metadata_ir::DeprecationStatusIR::Deprecated { note: #FIRST, since: Some(#SECOND) }}.to_string() + ); + assert_eq!( + get_deprecation("e! { crate }, &[extra_fields]).unwrap().to_string(), + quote! { crate::metadata_ir::DeprecationStatusIR::Deprecated { note: #FIRST, since: Some(#SECOND) }}.to_string() + ); + } } diff --git a/substrate/primitives/api/src/lib.rs b/substrate/primitives/api/src/lib.rs index 4b5c35562bd..700e212688c 100644 --- a/substrate/primitives/api/src/lib.rs +++ b/substrate/primitives/api/src/lib.rs @@ -258,6 +258,11 @@ pub const MAX_EXTRINSIC_DEPTH: u32 = 256; /// ``` /// Note that the latest version (4 in our example above) always contains all methods from all /// the versions before. +/// +/// ## Note on deprecation. +/// +/// - Usage of `deprecated` attribute will propagate deprecation information to the metadata. +/// - For general usage examples of `deprecated` attribute please refer to pub use sp_api_proc_macro::decl_runtime_apis; /// Tags given trait implementations as runtime apis. diff --git a/substrate/primitives/api/test/tests/ui/deprecation_info.rs b/substrate/primitives/api/test/tests/ui/deprecation_info.rs new file mode 100644 index 00000000000..61f93fcd921 --- /dev/null +++ b/substrate/primitives/api/test/tests/ui/deprecation_info.rs @@ -0,0 +1,29 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +sp_api::decl_runtime_apis! { + pub trait Api { + #[deprecated(unknown_kw = "test")] + fn test(); + #[deprecated(since = 5)] + fn test2(); + #[deprecated = 5] + fn test3(); + } +} + +fn main() {} diff --git a/substrate/primitives/api/test/tests/ui/deprecation_info.stderr b/substrate/primitives/api/test/tests/ui/deprecation_info.stderr new file mode 100644 index 00000000000..2466c3ea5d5 --- /dev/null +++ b/substrate/primitives/api/test/tests/ui/deprecation_info.stderr @@ -0,0 +1,25 @@ +error: Invalid deprecation attribute: missing `note` + help: the following are the possible correct uses + | + | #[deprecated = "reason"] + | + | #[deprecated(/*opt*/ since = "version", /*opt*/ note = "reason")] + | + | #[deprecated] + | + --> tests/ui/deprecation_info.rs:20:3 + | +20 | #[deprecated(unknown_kw = "test")] + | ^ + +error[E0541]: unknown meta item 'unknown_kw' + --> tests/ui/deprecation_info.rs:20:16 + | +20 | #[deprecated(unknown_kw = "test")] + | ^^^^^^^^^^^^^^^^^^^ expected one of `since`, `note` + +error[E0565]: literal in `deprecated` value must be a string + --> tests/ui/deprecation_info.rs:22:24 + | +22 | #[deprecated(since = 5)] + | ^ diff --git a/substrate/primitives/metadata-ir/src/types.rs b/substrate/primitives/metadata-ir/src/types.rs index b05f26ff55d..4ebe8c25a67 100644 --- a/substrate/primitives/metadata-ir/src/types.rs +++ b/substrate/primitives/metadata-ir/src/types.rs @@ -15,11 +15,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -use codec::Encode; +use codec::{Compact, Encode}; use scale_info::{ form::{Form, MetaForm, PortableForm}, - prelude::vec::Vec, - IntoPortable, MetaType, Registry, + prelude::{collections::BTreeMap, vec::Vec}, + IntoPortable, Registry, }; /// The intermediate representation for the runtime metadata. @@ -52,6 +52,8 @@ pub struct RuntimeApiMetadataIR { pub methods: Vec>, /// Trait documentation. pub docs: Vec, + /// Deprecation info + pub deprecation_info: DeprecationStatusIR, } impl IntoPortable for RuntimeApiMetadataIR { @@ -62,6 +64,7 @@ impl IntoPortable for RuntimeApiMetadataIR { name: self.name.into_portable(registry), methods: registry.map_into_portable(self.methods), docs: registry.map_into_portable(self.docs), + deprecation_info: self.deprecation_info.into_portable(registry), } } } @@ -77,6 +80,8 @@ pub struct RuntimeApiMethodMetadataIR { pub output: T::Type, /// Method documentation. pub docs: Vec, + /// Deprecation info + pub deprecation_info: DeprecationStatusIR, } impl IntoPortable for RuntimeApiMethodMetadataIR { @@ -88,6 +93,7 @@ impl IntoPortable for RuntimeApiMethodMetadataIR { inputs: registry.map_into_portable(self.inputs), output: registry.register_type(&self.output), docs: registry.map_into_portable(self.docs), + deprecation_info: self.deprecation_info.into_portable(registry), } } } @@ -132,6 +138,8 @@ pub struct PalletMetadataIR { pub index: u8, /// Pallet documentation. pub docs: Vec, + /// Deprecation info + pub deprecation_info: DeprecationStatusIR, } impl IntoPortable for PalletMetadataIR { @@ -147,6 +155,7 @@ impl IntoPortable for PalletMetadataIR { error: self.error.map(|error| error.into_portable(registry)), index: self.index, docs: registry.map_into_portable(self.docs), + deprecation_info: self.deprecation_info.into_portable(registry), } } } @@ -245,6 +254,8 @@ pub struct StorageEntryMetadataIR { pub default: Vec, /// Storage entry documentation. pub docs: Vec, + /// Deprecation info + pub deprecation_info: DeprecationStatusIR, } impl IntoPortable for StorageEntryMetadataIR { @@ -257,6 +268,7 @@ impl IntoPortable for StorageEntryMetadataIR { ty: self.ty.into_portable(registry), default: self.default, docs: registry.map_into_portable(self.docs), + deprecation_info: self.deprecation_info.into_portable(registry), } } } @@ -331,19 +343,18 @@ impl IntoPortable for StorageEntryTypeIR { pub struct PalletCallMetadataIR { /// The corresponding enum type for the pallet call. pub ty: T::Type, + /// Deprecation status of the pallet call + pub deprecation_info: DeprecationInfoIR, } impl IntoPortable for PalletCallMetadataIR { type Output = PalletCallMetadataIR; fn into_portable(self, registry: &mut Registry) -> Self::Output { - PalletCallMetadataIR { ty: registry.register_type(&self.ty) } - } -} - -impl From for PalletCallMetadataIR { - fn from(ty: MetaType) -> Self { - Self { ty } + PalletCallMetadataIR { + ty: registry.register_type(&self.ty), + deprecation_info: self.deprecation_info.into_portable(registry), + } } } @@ -352,19 +363,18 @@ impl From for PalletCallMetadataIR { pub struct PalletEventMetadataIR { /// The Event type. pub ty: T::Type, + /// Deprecation info of the event + pub deprecation_info: DeprecationInfoIR, } impl IntoPortable for PalletEventMetadataIR { type Output = PalletEventMetadataIR; fn into_portable(self, registry: &mut Registry) -> Self::Output { - PalletEventMetadataIR { ty: registry.register_type(&self.ty) } - } -} - -impl From for PalletEventMetadataIR { - fn from(ty: MetaType) -> Self { - Self { ty } + PalletEventMetadataIR { + ty: registry.register_type(&self.ty), + deprecation_info: self.deprecation_info.into_portable(registry), + } } } @@ -379,6 +389,8 @@ pub struct PalletConstantMetadataIR { pub value: Vec, /// Documentation of the constant. pub docs: Vec, + /// Deprecation info + pub deprecation_info: DeprecationStatusIR, } impl IntoPortable for PalletConstantMetadataIR { @@ -390,6 +402,7 @@ impl IntoPortable for PalletConstantMetadataIR { ty: registry.register_type(&self.ty), value: self.value, docs: registry.map_into_portable(self.docs), + deprecation_info: self.deprecation_info.into_portable(registry), } } } @@ -399,19 +412,18 @@ impl IntoPortable for PalletConstantMetadataIR { pub struct PalletErrorMetadataIR { /// The error type information. pub ty: T::Type, + /// Deprecation info + pub deprecation_info: DeprecationInfoIR, } impl IntoPortable for PalletErrorMetadataIR { type Output = PalletErrorMetadataIR; fn into_portable(self, registry: &mut Registry) -> Self::Output { - PalletErrorMetadataIR { ty: registry.register_type(&self.ty) } - } -} - -impl From for PalletErrorMetadataIR { - fn from(ty: MetaType) -> Self { - Self { ty } + PalletErrorMetadataIR { + ty: registry.register_type(&self.ty), + deprecation_info: self.deprecation_info.into_portable(registry), + } } } @@ -451,3 +463,61 @@ impl IntoPortable for OuterEnumsIR { } } } + +/// Deprecation status for an entry inside MetadataIR +#[derive(Clone, PartialEq, Eq, Encode, Debug)] +pub enum DeprecationStatusIR { + /// Entry is not deprecated + NotDeprecated, + /// Deprecated without a note. + DeprecatedWithoutNote, + /// Entry is deprecated with an note and an optional `since` field. + Deprecated { + /// Note explaining the deprecation + note: T::String, + /// Optional value for denoting version when the deprecation occured + since: Option, + }, +} +impl IntoPortable for DeprecationStatusIR { + type Output = DeprecationStatusIR; + + fn into_portable(self, registry: &mut Registry) -> Self::Output { + match self { + Self::Deprecated { note, since } => { + let note = note.into_portable(registry); + let since = since.map(|x| x.into_portable(registry)); + DeprecationStatusIR::Deprecated { note, since } + }, + Self::DeprecatedWithoutNote => DeprecationStatusIR::DeprecatedWithoutNote, + Self::NotDeprecated => DeprecationStatusIR::NotDeprecated, + } + } +} +/// Deprecation info for an enums/errors/calls. +/// Denotes full/partial deprecation of the type +#[derive(Clone, PartialEq, Eq, Encode, Debug)] +pub enum DeprecationInfoIR { + /// Type is not deprecated + NotDeprecated, + /// Entry is fully deprecated. + ItemDeprecated(DeprecationStatusIR), + /// Entry is partially deprecated. + VariantsDeprecated(BTreeMap, DeprecationStatusIR>), +} +impl IntoPortable for DeprecationInfoIR { + type Output = DeprecationInfoIR; + + fn into_portable(self, registry: &mut Registry) -> Self::Output { + match self { + Self::VariantsDeprecated(entries) => { + let entries = + entries.into_iter().map(|(k, entry)| (k, entry.into_portable(registry))); + DeprecationInfoIR::VariantsDeprecated(entries.collect()) + }, + Self::ItemDeprecated(deprecation) => + DeprecationInfoIR::ItemDeprecated(deprecation.into_portable(registry)), + Self::NotDeprecated => DeprecationInfoIR::NotDeprecated, + } + } +} -- GitLab From a34cc8dff09dc3840a304befc2415343244b5fd0 Mon Sep 17 00:00:00 2001 From: Alexandru Gheorghe <49718502+alexggh@users.noreply.github.com> Date: Thu, 12 Sep 2024 12:37:07 +0300 Subject: [PATCH 049/125] [4 / 5] Make approval-voting runnable on a worker thread (#4846) This is part of the work to further optimize the approval subsystems, if you want to understand the full context start with reading https://github.com/paritytech/polkadot-sdk/pull/4849#issue-2364261568, # Description This PR contain changes to make possible the run of single approval-voting instance on a worker thread, so that it can be instantiated by the approval-voting-parallel subsystem. This does not contain any functional changes it just decouples the subsystem from the subsystem Context and introduces more specific trait dependencies for each function instead of all of them requiring a context. This change can be merged independent of the followup PRs. --------- Signed-off-by: Alexandru Gheorghe Co-authored-by: Andrei Sandu <54316454+sandreim@users.noreply.github.com> --- polkadot/cli/src/command.rs | 8 +- polkadot/node/core/approval-voting/Cargo.toml | 1 + .../node/core/approval-voting/src/import.rs | 114 ++--- polkadot/node/core/approval-voting/src/lib.rs | 392 ++++++++++++------ .../node/core/approval-voting/src/tests.rs | 25 +- polkadot/node/service/src/lib.rs | 13 +- polkadot/node/service/src/overseer.rs | 1 + .../subsystem-bench/src/lib/approval/mod.rs | 3 +- prdoc/pr_4846.prdoc | 13 + 9 files changed, 382 insertions(+), 188 deletions(-) create mode 100644 prdoc/pr_4846.prdoc diff --git a/polkadot/cli/src/command.rs b/polkadot/cli/src/command.rs index 168a645430e..2947867c516 100644 --- a/polkadot/cli/src/command.rs +++ b/polkadot/cli/src/command.rs @@ -373,16 +373,16 @@ pub fn run() -> Result<()> { Ok(runner.async_run(|mut config| { let (client, backend, _, task_manager) = polkadot_service::new_chain_ops(&mut config, None)?; + let task_handle = task_manager.spawn_handle(); let aux_revert = Box::new(|client, backend, blocks| { - polkadot_service::revert_backend(client, backend, blocks, config).map_err( - |err| { + polkadot_service::revert_backend(client, backend, blocks, config, task_handle) + .map_err(|err| { match err { polkadot_service::Error::Blockchain(err) => err.into(), // Generic application-specific error. err => sc_cli::Error::Application(err.into()), } - }, - ) + }) }); Ok(( cmd.run(client, backend, Some(aux_revert)).map_err(Error::SubstrateCli), diff --git a/polkadot/node/core/approval-voting/Cargo.toml b/polkadot/node/core/approval-voting/Cargo.toml index bc0187bf492..e678118440f 100644 --- a/polkadot/node/core/approval-voting/Cargo.toml +++ b/polkadot/node/core/approval-voting/Cargo.toml @@ -22,6 +22,7 @@ kvdb = { workspace = true } derive_more = { workspace = true, default-features = true } thiserror = { workspace = true } itertools = { workspace = true } +async-trait = { workspace = true } polkadot-node-subsystem = { workspace = true, default-features = true } polkadot-node-subsystem-util = { workspace = true, default-features = true } diff --git a/polkadot/node/core/approval-voting/src/import.rs b/polkadot/node/core/approval-voting/src/import.rs index b163d718eb2..bf6ea0c9814 100644 --- a/polkadot/node/core/approval-voting/src/import.rs +++ b/polkadot/node/core/approval-voting/src/import.rs @@ -44,6 +44,7 @@ use polkadot_node_subsystem::{ overseer, RuntimeApiError, SubsystemError, SubsystemResult, }; use polkadot_node_subsystem_util::{determine_new_blocks, runtime::RuntimeInfo}; +use polkadot_overseer::SubsystemSender; use polkadot_primitives::{ node_features, BlockNumber, CandidateEvent, CandidateHash, CandidateReceipt, ConsensusLog, CoreIndex, GroupIndex, Hash, Header, SessionIndex, @@ -111,8 +112,8 @@ enum ImportedBlockInfoError { /// Computes information about the imported block. Returns an error if the info couldn't be /// extracted. #[overseer::contextbounds(ApprovalVoting, prefix = self::overseer)] -async fn imported_block_info( - ctx: &mut Context, +async fn imported_block_info>( + sender: &mut Sender, env: ImportedBlockInfoEnv<'_>, block_hash: Hash, block_header: &Header, @@ -124,11 +125,12 @@ async fn imported_block_info( // fetch candidates let included_candidates: Vec<_> = { let (c_tx, c_rx) = oneshot::channel(); - ctx.send_message(RuntimeApiMessage::Request( - block_hash, - RuntimeApiRequest::CandidateEvents(c_tx), - )) - .await; + sender + .send_message(RuntimeApiMessage::Request( + block_hash, + RuntimeApiRequest::CandidateEvents(c_tx), + )) + .await; let events: Vec = match c_rx.await { Ok(Ok(events)) => events, @@ -151,11 +153,12 @@ async fn imported_block_info( // short, that shouldn't happen. let session_index = { let (s_tx, s_rx) = oneshot::channel(); - ctx.send_message(RuntimeApiMessage::Request( - block_header.parent_hash, - RuntimeApiRequest::SessionIndexForChild(s_tx), - )) - .await; + sender + .send_message(RuntimeApiMessage::Request( + block_header.parent_hash, + RuntimeApiRequest::SessionIndexForChild(s_tx), + )) + .await; let session_index = match s_rx.await { Ok(Ok(s)) => s, @@ -201,11 +204,12 @@ async fn imported_block_info( // by one block. This gives us the opposite invariant for sessions - the parent block's // post-state gives us the canonical information about the session index for any of its // children, regardless of which slot number they might be produced at. - ctx.send_message(RuntimeApiMessage::Request( - block_hash, - RuntimeApiRequest::CurrentBabeEpoch(s_tx), - )) - .await; + sender + .send_message(RuntimeApiMessage::Request( + block_hash, + RuntimeApiRequest::CurrentBabeEpoch(s_tx), + )) + .await; match s_rx.await { Ok(Ok(s)) => s, @@ -216,7 +220,7 @@ async fn imported_block_info( }; let extended_session_info = - get_extended_session_info(env.runtime_info, ctx.sender(), block_hash, session_index).await; + get_extended_session_info(env.runtime_info, sender, block_hash, session_index).await; let enable_v2_assignments = extended_session_info.map_or(false, |extended_session_info| { *extended_session_info .node_features @@ -225,7 +229,7 @@ async fn imported_block_info( .unwrap_or(&false) }); - let session_info = get_session_info(env.runtime_info, ctx.sender(), block_hash, session_index) + let session_info = get_session_info(env.runtime_info, sender, block_hash, session_index) .await .ok_or(ImportedBlockInfoError::SessionInfoUnavailable)?; @@ -329,9 +333,15 @@ pub struct BlockImportedCandidates { /// * and return information about all candidates imported under each block. /// /// It is the responsibility of the caller to schedule wakeups for each block. -#[overseer::contextbounds(ApprovalVoting, prefix = self::overseer)] -pub(crate) async fn handle_new_head( - ctx: &mut Context, +pub(crate) async fn handle_new_head< + Sender: SubsystemSender + + SubsystemSender + + SubsystemSender, + AVSender: SubsystemSender, + B: Backend, +>( + sender: &mut Sender, + approval_voting_sender: &mut AVSender, state: &State, db: &mut OverlayedBackend<'_, B>, session_info_provider: &mut RuntimeInfo, @@ -349,7 +359,7 @@ pub(crate) async fn handle_new_head( let header = { let (h_tx, h_rx) = oneshot::channel(); - ctx.send_message(ChainApiMessage::BlockHeader(head, h_tx)).await; + sender.send_message(ChainApiMessage::BlockHeader(head, h_tx)).await; match h_rx.await? { Err(e) => { gum::debug!( @@ -375,7 +385,7 @@ pub(crate) async fn handle_new_head( let lower_bound_number = finalized_number.unwrap_or(lower_bound_number).max(lower_bound_number); let new_blocks = determine_new_blocks( - ctx.sender(), + sender, |h| db.load_block_entry(h).map(|e| e.is_some()), head, &header, @@ -401,12 +411,15 @@ pub(crate) async fn handle_new_head( keystore: &state.keystore, }; - match imported_block_info(ctx, env, block_hash, &block_header, finalized_number).await { + match imported_block_info(sender, env, block_hash, &block_header, finalized_number) + .await + { Ok(i) => imported_blocks_and_info.push((block_hash, block_header, i)), Err(error) => { // It's possible that we've lost a race with finality. let (tx, rx) = oneshot::channel(); - ctx.send_message(ChainApiMessage::FinalizedBlockHash(block_header.number, tx)) + sender + .send_message(ChainApiMessage::FinalizedBlockHash(block_header.number, tx)) .await; let lost_to_finality = match rx.await { @@ -450,17 +463,11 @@ pub(crate) async fn handle_new_head( force_approve, } = imported_block_info; - let session_info = match get_session_info( - session_info_provider, - ctx.sender(), - head, - session_index, - ) - .await - { - Some(session_info) => session_info, - None => return Ok(Vec::new()), - }; + let session_info = + match get_session_info(session_info_provider, sender, head, session_index).await { + Some(session_info) => session_info, + None => return Ok(Vec::new()), + }; let (block_tick, no_show_duration) = { let block_tick = slot_number_to_tick(state.slot_duration_millis, slot); @@ -510,7 +517,7 @@ pub(crate) async fn handle_new_head( }; // If all bits are already set, then send an approve message. if approved_bitfield.count_ones() == approved_bitfield.len() { - ctx.send_message(ChainSelectionMessage::Approved(block_hash)).await; + sender.send_message(ChainSelectionMessage::Approved(block_hash)).await; } let block_entry = v3::BlockEntry { @@ -567,7 +574,7 @@ pub(crate) async fn handle_new_head( // Notify chain-selection of all approved hashes. for hash in approved_hashes { - ctx.send_message(ChainSelectionMessage::Approved(hash)).await; + sender.send_message(ChainSelectionMessage::Approved(hash)).await; } } @@ -603,7 +610,8 @@ pub(crate) async fn handle_new_head( "Informing distribution of newly imported chain", ); - ctx.send_unbounded_message(ApprovalDistributionMessage::NewBlocks(approval_meta)); + approval_voting_sender + .send_unbounded_message(ApprovalDistributionMessage::NewBlocks(approval_meta)); Ok(imported_candidates) } @@ -620,7 +628,10 @@ pub(crate) mod tests { approval::v1::{VrfSignature, VrfTranscript}, DISPUTE_WINDOW, }; - use polkadot_node_subsystem::messages::{AllMessages, ApprovalVotingMessage}; + use polkadot_node_subsystem::{ + messages::{AllMessages, ApprovalVotingMessage}, + SubsystemContext, + }; use polkadot_node_subsystem_test_helpers::make_subsystem_context; use polkadot_node_subsystem_util::database::Database; use polkadot_primitives::{ @@ -662,7 +673,7 @@ pub(crate) mod tests { State { keystore: Arc::new(LocalKeystore::in_memory()), slot_duration_millis: 6_000, - clock: Box::new(MockClock::default()), + clock: Arc::new(MockClock::default()), assignment_criteria: Box::new(MockAssignmentCriteria::default()), spans: HashMap::new(), per_block_assignments_gathering_times: LruMap::new(ByLength::new( @@ -806,8 +817,9 @@ pub(crate) mod tests { keystore: &LocalKeystore::in_memory(), }; - let info = - imported_block_info(&mut ctx, env, hash, &header, &Some(4)).await.unwrap(); + let info = imported_block_info(ctx.sender(), env, hash, &header, &Some(4)) + .await + .unwrap(); assert_eq!(info.included_candidates, included_candidates); assert_eq!(info.session_index, session); @@ -953,7 +965,7 @@ pub(crate) mod tests { keystore: &LocalKeystore::in_memory(), }; - let info = imported_block_info(&mut ctx, env, hash, &header, &Some(4)).await; + let info = imported_block_info(ctx.sender(), env, hash, &header, &Some(4)).await; assert_matches!(info, Err(ImportedBlockInfoError::VrfInfoUnavailable)); }) @@ -1092,7 +1104,7 @@ pub(crate) mod tests { keystore: &LocalKeystore::in_memory(), }; - let info = imported_block_info(&mut ctx, env, hash, &header, &Some(6)).await; + let info = imported_block_info(ctx.sender(), env, hash, &header, &Some(6)).await; assert_matches!(info, Err(ImportedBlockInfoError::BlockAlreadyFinalized)); }) @@ -1128,7 +1140,8 @@ pub(crate) mod tests { #[test] fn imported_block_info_extracts_force_approve() { let pool = TaskExecutor::new(); - let (mut ctx, mut handle) = make_subsystem_context(pool.clone()); + let (mut ctx, mut handle) = + make_subsystem_context::(pool.clone()); let session = 5; let session_info = dummy_session_info(session); @@ -1191,7 +1204,7 @@ pub(crate) mod tests { }; let info = - imported_block_info(&mut ctx, env, hash, &header, &Some(4)).await.unwrap(); + imported_block_info(ctx.sender(), env, hash, &header, &Some(4)).await.unwrap(); assert_eq!(info.included_candidates, included_candidates); assert_eq!(info.session_index, session); @@ -1384,8 +1397,11 @@ pub(crate) mod tests { let test_fut = { Box::pin(async move { let mut overlay_db = OverlayedBackend::new(&db); + + let mut approval_voting_sender = ctx.sender().clone(); let result = handle_new_head( - &mut ctx, + ctx.sender(), + &mut approval_voting_sender, &state, &mut overlay_db, &mut session_info_provider, diff --git a/polkadot/node/core/approval-voting/src/lib.rs b/polkadot/node/core/approval-voting/src/lib.rs index 942922cba6d..2149ce81fa8 100644 --- a/polkadot/node/core/approval-voting/src/lib.rs +++ b/polkadot/node/core/approval-voting/src/lib.rs @@ -165,7 +165,8 @@ pub struct ApprovalVotingSubsystem { db: Arc, mode: Mode, metrics: Metrics, - clock: Box, + clock: Arc, + spawner: Arc, } #[derive(Clone)] @@ -484,6 +485,7 @@ impl ApprovalVotingSubsystem { keystore: Arc, sync_oracle: Box, metrics: Metrics, + spawner: Arc, ) -> Self { ApprovalVotingSubsystem::with_config_and_clock( config, @@ -491,7 +493,8 @@ impl ApprovalVotingSubsystem { keystore, sync_oracle, metrics, - Box::new(SystemClock {}), + Arc::new(SystemClock {}), + spawner, ) } @@ -502,7 +505,8 @@ impl ApprovalVotingSubsystem { keystore: Arc, sync_oracle: Box, metrics: Metrics, - clock: Box, + clock: Arc, + spawner: Arc, ) -> Self { ApprovalVotingSubsystem { keystore, @@ -512,6 +516,7 @@ impl ApprovalVotingSubsystem { mode: Mode::Syncing(sync_oracle), metrics, clock, + spawner, } } @@ -551,12 +556,21 @@ fn db_sanity_check(db: Arc, config: DatabaseConfig) -> SubsystemRe #[overseer::subsystem(ApprovalVoting, error = SubsystemError, prefix = self::overseer)] impl ApprovalVotingSubsystem { - fn start(self, ctx: Context) -> SpawnedSubsystem { + fn start(self, mut ctx: Context) -> SpawnedSubsystem { let backend = DbBackend::new(self.db.clone(), self.db_config); - let future = - run::(ctx, self, Box::new(RealAssignmentCriteria), backend) - .map_err(|e| SubsystemError::with_origin("approval-voting", e)) - .boxed(); + let to_other_subsystems = ctx.sender().clone(); + let to_approval_distr = ctx.sender().clone(); + + let future = run::( + ctx, + to_other_subsystems, + to_approval_distr, + self, + Box::new(RealAssignmentCriteria), + backend, + ) + .map_err(|e| SubsystemError::with_origin("approval-voting", e)) + .boxed(); SpawnedSubsystem { name: "approval-voting-subsystem", future } } @@ -825,7 +839,7 @@ where struct State { keystore: Arc, slot_duration_millis: u64, - clock: Box, + clock: Arc, assignment_criteria: Box, spans: HashMap, // Per block, candidate records about how long we take until we gather enough @@ -961,20 +975,20 @@ impl State { } // Returns the approval voting params from the RuntimeApi. - #[overseer::contextbounds(ApprovalVoting, prefix = self::overseer)] - async fn get_approval_voting_params_or_default( + async fn get_approval_voting_params_or_default>( &self, - ctx: &mut Context, + sender: &mut Sender, session_index: SessionIndex, block_hash: Hash, ) -> Option { let (s_tx, s_rx) = oneshot::channel(); - ctx.send_message(RuntimeApiMessage::Request( - block_hash, - RuntimeApiRequest::ApprovalVotingParams(session_index, s_tx), - )) - .await; + sender + .send_message(RuntimeApiMessage::Request( + block_hash, + RuntimeApiRequest::ApprovalVotingParams(session_index, s_tx), + )) + .await; match s_rx.await { Ok(Ok(params)) => { @@ -1143,9 +1157,36 @@ enum Action { Conclude, } +/// Trait for providing approval voting subsystem with work. +#[async_trait::async_trait] +pub trait ApprovalVotingWorkProvider { + async fn recv(&mut self) -> SubsystemResult>; +} + +#[async_trait::async_trait] +#[overseer::contextbounds(ApprovalVoting, prefix = self::overseer)] +impl ApprovalVotingWorkProvider for Context { + async fn recv(&mut self) -> SubsystemResult> { + self.recv().await + } +} + #[overseer::contextbounds(ApprovalVoting, prefix = self::overseer)] -async fn run( - mut ctx: Context, +async fn run< + B, + WorkProvider: ApprovalVotingWorkProvider, + Sender: SubsystemSender + + SubsystemSender + + SubsystemSender + + SubsystemSender + + SubsystemSender + + SubsystemSender + + Clone, + ADSender: SubsystemSender, +>( + mut work_provider: WorkProvider, + mut to_other_subsystems: Sender, + mut to_approval_distr: ADSender, mut subsystem: ApprovalVotingSubsystem, assignment_criteria: Box, mut backend: B, @@ -1169,19 +1210,11 @@ where no_show_stats: NoShowStats::default(), }; - // `None` on start-up. Gets initialized/updated on leaf update - let mut session_info_provider = RuntimeInfo::new_with_config(RuntimeInfoConfig { - keystore: None, - session_cache_lru_size: DISPUTE_WINDOW.get(), - }); - let mut wakeups = Wakeups::default(); - let mut currently_checking_set = CurrentlyCheckingSet::default(); - let mut delayed_approvals_timers = DelayedApprovalTimer::default(); - let mut approvals_cache = LruMap::new(ByLength::new(APPROVAL_CACHE_SIZE)); - let mut last_finalized_height: Option = { let (tx, rx) = oneshot::channel(); - ctx.send_message(ChainApiMessage::FinalizedBlockNumber(tx)).await; + to_other_subsystems + .send_message(ChainApiMessage::FinalizedBlockNumber(tx)) + .await; match rx.await? { Ok(number) => Some(number), Err(err) => { @@ -1191,13 +1224,24 @@ where } }; + // `None` on start-up. Gets initialized/updated on leaf update + let mut session_info_provider = RuntimeInfo::new_with_config(RuntimeInfoConfig { + keystore: None, + session_cache_lru_size: DISPUTE_WINDOW.get(), + }); + + let mut wakeups = Wakeups::default(); + let mut currently_checking_set = CurrentlyCheckingSet::default(); + let mut delayed_approvals_timers = DelayedApprovalTimer::default(); + let mut approvals_cache = LruMap::new(ByLength::new(APPROVAL_CACHE_SIZE)); + loop { let mut overlayed_db = OverlayedBackend::new(&backend); let actions = futures::select! { (_tick, woken_block, woken_candidate) = wakeups.next(&*state.clock).fuse() => { subsystem.metrics.on_wakeup(); process_wakeup( - &mut ctx, + &mut to_other_subsystems, &mut state, &mut overlayed_db, &mut session_info_provider, @@ -1207,9 +1251,11 @@ where &wakeups, ).await? } - next_msg = ctx.recv().fuse() => { + next_msg = work_provider.recv().fuse() => { let mut actions = handle_from_overseer( - &mut ctx, + &mut to_other_subsystems, + &mut to_approval_distr, + &subsystem.spawner, &mut state, &mut overlayed_db, &mut session_info_provider, @@ -1269,7 +1315,8 @@ where &mut overlayed_db, &mut session_info_provider, &state, - &mut ctx, + &mut to_other_subsystems, + &mut to_approval_distr, block_hash, validator_index, &subsystem.metrics, @@ -1291,7 +1338,9 @@ where }; if handle_actions( - &mut ctx, + &mut to_other_subsystems, + &mut to_approval_distr, + &subsystem.spawner, &mut state, &mut overlayed_db, &mut session_info_provider, @@ -1318,6 +1367,63 @@ where Ok(()) } +// Starts a worker thread that runs the approval voting subsystem. +pub async fn start_approval_worker< + WorkProvider: ApprovalVotingWorkProvider + Send + 'static, + Sender: SubsystemSender + + SubsystemSender + + SubsystemSender + + SubsystemSender + + SubsystemSender + + SubsystemSender + + Clone, + ADSender: SubsystemSender, +>( + work_provider: WorkProvider, + to_other_subsystems: Sender, + to_approval_distr: ADSender, + config: Config, + db: Arc, + keystore: Arc, + sync_oracle: Box, + metrics: Metrics, + spawner: Arc, + task_name: &'static str, + group_name: &'static str, + clock: Arc, +) -> SubsystemResult<()> { + let approval_voting = ApprovalVotingSubsystem::with_config_and_clock( + config, + db.clone(), + keystore, + sync_oracle, + metrics, + clock, + spawner, + ); + let backend = DbBackend::new(db.clone(), approval_voting.db_config); + let spawner = approval_voting.spawner.clone(); + spawner.spawn_blocking( + task_name, + Some(group_name), + Box::pin(async move { + if let Err(err) = run( + work_provider, + to_other_subsystems, + to_approval_distr, + approval_voting, + Box::new(RealAssignmentCriteria), + backend, + ) + .await + { + gum::error!(target: LOG_TARGET, ?err, "Approval voting worker stopped processing messages"); + }; + }), + ); + Ok(()) +} + // Handle actions is a function that accepts a set of instructions // and subsequently updates the underlying approvals_db in accordance // with the linear set of instructions passed in. Therefore, actions @@ -1338,8 +1444,19 @@ where // // returns `true` if any of the actions was a `Conclude` command. #[overseer::contextbounds(ApprovalVoting, prefix = self::overseer)] -async fn handle_actions( - ctx: &mut Context, +async fn handle_actions< + Sender: SubsystemSender + + SubsystemSender + + SubsystemSender + + SubsystemSender + + SubsystemSender + + SubsystemSender + + Clone, + ADSender: SubsystemSender, +>( + sender: &mut Sender, + approval_voting_sender: &mut ADSender, + spawn_handle: &Arc, state: &mut State, overlayed_db: &mut OverlayedBackend<'_, impl Backend>, session_info_provider: &mut RuntimeInfo, @@ -1371,7 +1488,8 @@ async fn handle_actions( // Note that chaining these iterators is O(n) as we must consume // the prior iterator. let next_actions: Vec = issue_approval( - ctx, + sender, + approval_voting_sender, state, overlayed_db, session_info_provider, @@ -1422,10 +1540,12 @@ async fn handle_actions( let validator_index = indirect_cert.validator; if distribute_assignment { - ctx.send_unbounded_message(ApprovalDistributionMessage::DistributeAssignment( - indirect_cert, - claimed_candidate_indices, - )); + approval_voting_sender.send_unbounded_message( + ApprovalDistributionMessage::DistributeAssignment( + indirect_cert, + claimed_candidate_indices, + ), + ); } match approvals_cache.get(&candidate_hash) { @@ -1440,7 +1560,8 @@ async fn handle_actions( actions_iter = new_actions.into_iter(); }, None => { - let ctx = &mut *ctx; + let sender = sender.clone(); + let spawn_handle = spawn_handle.clone(); currently_checking_set .insert_relay_block_hash( @@ -1449,7 +1570,8 @@ async fn handle_actions( relay_block_hash, async move { launch_approval( - ctx, + sender, + spawn_handle, metrics.clone(), session, candidate, @@ -1478,13 +1600,13 @@ async fn handle_actions( }) .with_string_tag("block-hash", format!("{:?}", block_hash)) .with_stage(jaeger::Stage::ApprovalChecking); - ctx.send_message(ChainSelectionMessage::Approved(block_hash)).await; + sender.send_message(ChainSelectionMessage::Approved(block_hash)).await; }, Action::BecomeActive => { *mode = Mode::Active; let (messages, next_actions) = distribution_messages_for_activation( - ctx, + sender, overlayed_db, state, delayed_approvals_timers, @@ -1492,7 +1614,7 @@ async fn handle_actions( ) .await?; - ctx.send_messages(messages.into_iter()).await; + approval_voting_sender.send_messages(messages.into_iter()).await; let next_actions: Vec = next_actions.into_iter().map(|v| v.clone()).chain(actions_iter).collect(); @@ -1566,8 +1688,8 @@ fn get_assignment_core_indices( } #[overseer::contextbounds(ApprovalVoting, prefix = self::overseer)] -async fn distribution_messages_for_activation( - ctx: &mut Context, +async fn distribution_messages_for_activation>( + sender: &mut Sender, db: &OverlayedBackend<'_, impl Backend>, state: &State, delayed_approvals_timers: &mut DelayedApprovalTimer, @@ -1693,7 +1815,7 @@ async fn distribution_messages_for_activation( let ExtendedSessionInfo { ref executor_params, .. } = match get_extended_session_info( session_info_provider, - ctx.sender(), + sender, block_entry.block_hash(), block_entry.session(), ) @@ -1791,9 +1913,16 @@ async fn distribution_messages_for_activation( } // Handle an incoming signal from the overseer. Returns true if execution should conclude. -#[overseer::contextbounds(ApprovalVoting, prefix = self::overseer)] -async fn handle_from_overseer( - ctx: &mut Context, +async fn handle_from_overseer< + Sender: SubsystemSender + + SubsystemSender + + SubsystemSender + + Clone, + ADSender: SubsystemSender, +>( + sender: &mut Sender, + approval_voting_sender: &mut ADSender, + spawn_handle: &Arc, state: &mut State, db: &mut OverlayedBackend<'_, impl Backend>, session_info_provider: &mut RuntimeInfo, @@ -1811,7 +1940,8 @@ async fn handle_from_overseer( jaeger::PerLeafSpan::new(activated.span, "approval-voting"); state.spans.insert(head, approval_voting_span); match import::handle_new_head( - ctx, + sender, + approval_voting_sender, state, db, session_info_provider, @@ -1894,14 +2024,9 @@ async fn handle_from_overseer( }, FromOrchestra::Communication { msg } => match msg { ApprovalVotingMessage::ImportAssignment(checked_assignment, tx) => { - let (check_outcome, actions) = import_assignment( - ctx.sender(), - state, - db, - session_info_provider, - checked_assignment, - ) - .await?; + let (check_outcome, actions) = + import_assignment(sender, state, db, session_info_provider, checked_assignment) + .await?; // approval-distribution makes sure this assignment is valid and expected, // so this import should never fail, if it does it might mean one of two things, // there is a bug in the code or the two subsystems got out of sync. @@ -1912,16 +2037,9 @@ async fn handle_from_overseer( actions }, ApprovalVotingMessage::ImportApproval(a, tx) => { - let result = import_approval( - ctx.sender(), - state, - db, - session_info_provider, - metrics, - a, - &wakeups, - ) - .await?; + let result = + import_approval(sender, state, db, session_info_provider, metrics, a, &wakeups) + .await?; // approval-distribution makes sure this vote is valid and expected, // so this import should never fail, if it does it might mean one of two things, // there is a bug in the code or the two subsystems got out of sync. @@ -1941,7 +2059,7 @@ async fn handle_from_overseer( .with_stage(jaeger::Stage::ApprovalChecking) .with_string_tag("leaf", format!("{:?}", target)); match handle_approved_ancestor( - ctx, + sender, db, target, lower_bound, @@ -1964,7 +2082,14 @@ async fn handle_from_overseer( }, ApprovalVotingMessage::GetApprovalSignaturesForCandidate(candidate_hash, tx) => { metrics.on_candidate_signatures_request(); - get_approval_signatures_for_candidate(ctx, db, candidate_hash, tx).await?; + get_approval_signatures_for_candidate( + approval_voting_sender.clone(), + spawn_handle, + db, + candidate_hash, + tx, + ) + .await?; Vec::new() }, }, @@ -1978,8 +2103,11 @@ async fn handle_from_overseer( /// This involves an unbounded message send to approval-distribution, the caller has to ensure that /// calls to this function are infrequent and bounded. #[overseer::contextbounds(ApprovalVoting, prefix = self::overseer)] -async fn get_approval_signatures_for_candidate( - ctx: &mut Context, +async fn get_approval_signatures_for_candidate< + Sender: SubsystemSender, +>( + mut sender: Sender, + spawn_handle: &Arc, db: &OverlayedBackend<'_, impl Backend>, candidate_hash: CandidateHash, tx: oneshot::Sender, ValidatorSignature)>>, @@ -2038,7 +2166,6 @@ async fn get_approval_signatures_for_candidate( } } - let mut sender = ctx.sender().clone(); let get_approvals = async move { let (tx_distribution, rx_distribution) = oneshot::channel(); sender.send_unbounded_message(ApprovalDistributionMessage::GetApprovalSignatures( @@ -2118,12 +2245,17 @@ async fn get_approval_signatures_for_candidate( ?candidate_hash, "Spawning task for fetching signatures from approval-distribution" ); - ctx.spawn("get-approval-signatures", Box::pin(get_approvals)) + spawn_handle.spawn( + "get-approval-signatures", + Some("approval-voting-subsystem"), + Box::pin(get_approvals), + ); + Ok(()) } #[overseer::contextbounds(ApprovalVoting, prefix = self::overseer)] -async fn handle_approved_ancestor( - ctx: &mut Context, +async fn handle_approved_ancestor>( + sender: &mut Sender, db: &OverlayedBackend<'_, impl Backend>, target: Hash, lower_bound: BlockNumber, @@ -2143,7 +2275,7 @@ async fn handle_approved_ancestor( let target_number = { let (tx, rx) = oneshot::channel(); - ctx.send_message(ChainApiMessage::BlockNumber(target, tx)).await; + sender.send_message(ChainApiMessage::BlockNumber(target, tx)).await; match rx.await { Ok(Ok(Some(n))) => n, @@ -2164,12 +2296,13 @@ async fn handle_approved_ancestor( let ancestry = if target_number > lower_bound + 1 { let (tx, rx) = oneshot::channel(); - ctx.send_message(ChainApiMessage::Ancestors { - hash: target, - k: (target_number - (lower_bound + 1)) as usize, - response_channel: tx, - }) - .await; + sender + .send_message(ChainApiMessage::Ancestors { + hash: target, + k: (target_number - (lower_bound + 1)) as usize, + response_channel: tx, + }) + .await; match rx.await { Ok(Ok(a)) => a, @@ -3107,9 +3240,8 @@ fn should_trigger_assignment( } } -#[overseer::contextbounds(ApprovalVoting, prefix = self::overseer)] -async fn process_wakeup( - ctx: &mut Context, +async fn process_wakeup>( + sender: &mut Sender, state: &mut State, db: &mut OverlayedBackend<'_, impl Backend>, session_info_provider: &mut RuntimeInfo, @@ -3140,7 +3272,7 @@ async fn process_wakeup( let ExtendedSessionInfo { ref session_info, ref executor_params, .. } = match get_extended_session_info( session_info_provider, - ctx.sender(), + sender, block_entry.block_hash(), block_entry.session(), ) @@ -3282,7 +3414,7 @@ async fn process_wakeup( // Note that this function also schedules a wakeup as necessary. actions.extend( advance_approval_state( - ctx.sender(), + sender, state, db, session_info_provider, @@ -3303,8 +3435,14 @@ async fn process_wakeup( // spawned. When the background work is no longer needed, the `AbortHandle` should be dropped // to cancel the background work and any requests it has spawned. #[overseer::contextbounds(ApprovalVoting, prefix = self::overseer)] -async fn launch_approval( - ctx: &mut Context, +async fn launch_approval< + Sender: SubsystemSender + + SubsystemSender + + SubsystemSender + + SubsystemSender, +>( + mut sender: Sender, + spawn_handle: Arc, metrics: Metrics, session_index: SessionIndex, candidate: CandidateReceipt, @@ -3355,14 +3493,15 @@ async fn launch_approval( .with_stage(jaeger::Stage::ApprovalChecking); let timer = metrics.time_recover_and_approve(); - ctx.send_message(AvailabilityRecoveryMessage::RecoverAvailableData( - candidate.clone(), - session_index, - Some(backing_group), - core_index, - a_tx, - )) - .await; + sender + .send_message(AvailabilityRecoveryMessage::RecoverAvailableData( + candidate.clone(), + session_index, + Some(backing_group), + core_index, + a_tx, + )) + .await; let request_validation_result_span = span .child("request-validation-result") @@ -3371,15 +3510,18 @@ async fn launch_approval( .with_string_tag("block-hash", format!("{:?}", block_hash)) .with_stage(jaeger::Stage::ApprovalChecking); - ctx.send_message(RuntimeApiMessage::Request( - block_hash, - RuntimeApiRequest::ValidationCodeByHash(candidate.descriptor.validation_code_hash, code_tx), - )) - .await; + sender + .send_message(RuntimeApiMessage::Request( + block_hash, + RuntimeApiRequest::ValidationCodeByHash( + candidate.descriptor.validation_code_hash, + code_tx, + ), + )) + .await; let candidate = candidate.clone(); let metrics_guard = StaleGuard(Some(metrics)); - let mut sender = ctx.sender().clone(); let background = async move { // Force the move of the timer into the background task. let _timer = timer; @@ -3509,14 +3651,19 @@ async fn launch_approval( } }; let (background, remote_handle) = background.remote_handle(); - ctx.spawn("approval-checks", Box::pin(background)).map(move |()| remote_handle) + spawn_handle.spawn("approval-checks", Some("approval-voting-subsystem"), Box::pin(background)); + Ok(remote_handle) } // Issue and import a local approval vote. Should only be invoked after approval checks // have been done. #[overseer::contextbounds(ApprovalVoting, prefix = self::overseer)] -async fn issue_approval( - ctx: &mut Context, +async fn issue_approval< + Sender: SubsystemSender, + ADSender: SubsystemSender, +>( + sender: &mut Sender, + approval_voting_sender: &mut ADSender, state: &mut State, db: &mut OverlayedBackend<'_, impl Backend>, session_info_provider: &mut RuntimeInfo, @@ -3595,7 +3742,7 @@ async fn issue_approval( let session_info = match get_session_info( session_info_provider, - ctx.sender(), + sender, block_entry.parent_hash(), block_entry.session(), ) @@ -3637,7 +3784,7 @@ async fn issue_approval( ); let actions = advance_approval_state( - ctx.sender(), + sender, state, db, session_info_provider, @@ -3654,7 +3801,8 @@ async fn issue_approval( db, session_info_provider, state, - ctx, + sender, + approval_voting_sender, block_hash, validator_index, metrics, @@ -3673,11 +3821,15 @@ async fn issue_approval( // Create signature for the approved candidates pending signatures #[overseer::contextbounds(ApprovalVoting, prefix = self::overseer)] -async fn maybe_create_signature( +async fn maybe_create_signature< + Sender: SubsystemSender, + ADSender: SubsystemSender, +>( db: &mut OverlayedBackend<'_, impl Backend>, session_info_provider: &mut RuntimeInfo, state: &State, - ctx: &mut Context, + sender: &mut Sender, + approval_voting_sender: &mut ADSender, block_hash: Hash, validator_index: ValidatorIndex, metrics: &Metrics, @@ -3696,7 +3848,7 @@ async fn maybe_create_signature( }; let approval_params = state - .get_approval_voting_params_or_default(ctx, block_entry.session(), block_hash) + .get_approval_voting_params_or_default(sender, block_entry.session(), block_hash) .await .unwrap_or_default(); @@ -3716,7 +3868,7 @@ async fn maybe_create_signature( let session_info = match get_session_info( session_info_provider, - ctx.sender(), + sender, block_entry.parent_hash(), block_entry.session(), ) @@ -3797,7 +3949,7 @@ async fn maybe_create_signature( metrics.on_approval_produced(); - ctx.send_unbounded_message(ApprovalDistributionMessage::DistributeApproval( + approval_voting_sender.send_unbounded_message(ApprovalDistributionMessage::DistributeApproval( IndirectSignedApprovalVoteV2 { block_hash: block_entry.block_hash(), candidate_indices: candidates_indices, @@ -3838,7 +3990,7 @@ fn issue_local_invalid_statement( candidate_hash: CandidateHash, candidate: CandidateReceipt, ) where - Sender: overseer::ApprovalVotingSenderTrait, + Sender: SubsystemSender, { // We need to send an unbounded message here to break a cycle: // DisputeCoordinatorMessage::IssueLocalStatement -> diff --git a/polkadot/node/core/approval-voting/src/tests.rs b/polkadot/node/core/approval-voting/src/tests.rs index 7126f209a94..65aa4f894c2 100644 --- a/polkadot/node/core/approval-voting/src/tests.rs +++ b/polkadot/node/core/approval-voting/src/tests.rs @@ -35,11 +35,11 @@ use polkadot_node_subsystem::{ messages::{ AllMessages, ApprovalVotingMessage, AssignmentCheckResult, AvailabilityRecoveryMessage, }, - ActiveLeavesUpdate, + ActiveLeavesUpdate, SubsystemContext, }; use polkadot_node_subsystem_test_helpers as test_helpers; use polkadot_node_subsystem_util::TimeoutExt; -use polkadot_overseer::HeadSupportsParachains; +use polkadot_overseer::{HeadSupportsParachains, SpawnGlue}; use polkadot_primitives::{ ApprovalVote, CandidateCommitments, CandidateEvent, CoreIndex, DisputeStatement, GroupIndex, Header, Id as ParaId, IndexedVec, NodeFeatures, ValidDisputeStatementKind, ValidationCode, @@ -536,7 +536,7 @@ impl Default for HarnessConfig { struct TestHarness { virtual_overseer: VirtualOverseer, - clock: Box, + clock: Arc, sync_oracle_handle: TestSyncOracleHandle, } @@ -550,8 +550,8 @@ fn test_harness>( config; let pool = sp_core::testing::TaskExecutor::new(); - let (context, virtual_overseer) = - polkadot_node_subsystem_test_helpers::make_subsystem_context(pool); + let (mut context, virtual_overseer) = + polkadot_node_subsystem_test_helpers::make_subsystem_context(pool.clone()); let keystore = LocalKeystore::in_memory(); let _ = keystore.sr25519_generate_new( @@ -559,12 +559,14 @@ fn test_harness>( Some(&Sr25519Keyring::Alice.to_seed()), ); - let clock = Box::new(clock); + let clock = Arc::new(clock); let db = kvdb_memorydb::create(test_constants::NUM_COLUMNS); let db = polkadot_node_subsystem_util::database::kvdb_impl::DbAdapter::new(db, &[]); - + let sender = context.sender().clone(); let subsystem = run( context, + sender.clone(), + sender.clone(), ApprovalVotingSubsystem::with_config_and_clock( Config { col_approval_data: test_constants::TEST_CONFIG.col_approval_data, @@ -575,6 +577,7 @@ fn test_harness>( sync_oracle, Metrics::default(), clock.clone(), + Arc::new(SpawnGlue(pool)), ), assignment_criteria, backend, @@ -4114,7 +4117,7 @@ async fn handle_approval_on_max_coalesce_count( async fn handle_approval_on_max_wait_time( virtual_overseer: &mut VirtualOverseer, candidate_indices: Vec, - clock: Box, + clock: Arc, ) { const TICK_NOW_BEGIN: u64 = 1; const MAX_COALESCE_COUNT: u32 = 3; @@ -4412,7 +4415,7 @@ async fn build_chain_with_two_blocks_with_one_candidate_each( async fn setup_overseer_with_two_blocks_each_with_one_assignment_triggered( virtual_overseer: &mut VirtualOverseer, store: TestStore, - clock: &Box, + clock: &Arc, sync_oracle_handle: TestSyncOracleHandle, ) { assert_matches!( @@ -4926,7 +4929,7 @@ fn test_gathering_assignments_statements() { let mut state = State { keystore: Arc::new(LocalKeystore::in_memory()), slot_duration_millis: 6_000, - clock: Box::new(MockClock::default()), + clock: Arc::new(MockClock::default()), assignment_criteria: Box::new(MockAssignmentCriteria::check_only(|_| Ok(0))), spans: HashMap::new(), per_block_assignments_gathering_times: LruMap::new(ByLength::new( @@ -5021,7 +5024,7 @@ fn test_observe_assignment_gathering_status() { let mut state = State { keystore: Arc::new(LocalKeystore::in_memory()), slot_duration_millis: 6_000, - clock: Box::new(MockClock::default()), + clock: Arc::new(MockClock::default()), assignment_criteria: Box::new(MockAssignmentCriteria::check_only(|_| Ok(0))), spans: HashMap::new(), per_block_assignments_gathering_times: LruMap::new(ByLength::new( diff --git a/polkadot/node/service/src/lib.rs b/polkadot/node/service/src/lib.rs index a8e7fc16eb4..fe96d29c1ce 100644 --- a/polkadot/node/service/src/lib.rs +++ b/polkadot/node/service/src/lib.rs @@ -63,6 +63,7 @@ use { }; use polkadot_node_subsystem_util::database::Database; +use polkadot_overseer::SpawnGlue; #[cfg(feature = "full-node")] pub use { @@ -83,7 +84,7 @@ use std::{collections::HashMap, path::PathBuf, sync::Arc, time::Duration}; use prometheus_endpoint::Registry; #[cfg(feature = "full-node")] use sc_service::KeystoreContainer; -use sc_service::RpcHandlers; +use sc_service::{RpcHandlers, SpawnTaskHandle}; use sc_telemetry::TelemetryWorker; #[cfg(feature = "full-node")] use sc_telemetry::{Telemetry, TelemetryWorkerHandle}; @@ -1500,6 +1501,7 @@ pub fn revert_backend( backend: Arc, blocks: BlockNumber, config: Configuration, + task_handle: SpawnTaskHandle, ) -> Result<(), Error> { let best_number = client.info().best_number; let finalized = client.info().finalized_number; @@ -1520,7 +1522,7 @@ pub fn revert_backend( let parachains_db = open_database(&config.database) .map_err(|err| sp_blockchain::Error::Backend(err.to_string()))?; - revert_approval_voting(parachains_db.clone(), hash)?; + revert_approval_voting(parachains_db.clone(), hash, task_handle)?; revert_chain_selection(parachains_db, hash)?; // Revert Substrate consensus related components sc_consensus_babe::revert(client.clone(), backend, blocks)?; @@ -1543,7 +1545,11 @@ fn revert_chain_selection(db: Arc, hash: Hash) -> sp_blockchain::R .map_err(|err| sp_blockchain::Error::Backend(err.to_string())) } -fn revert_approval_voting(db: Arc, hash: Hash) -> sp_blockchain::Result<()> { +fn revert_approval_voting( + db: Arc, + hash: Hash, + task_handle: SpawnTaskHandle, +) -> sp_blockchain::Result<()> { let config = approval_voting_subsystem::Config { col_approval_data: parachains_db::REAL_COLUMNS.col_approval_data, slot_duration_millis: Default::default(), @@ -1555,6 +1561,7 @@ fn revert_approval_voting(db: Arc, hash: Hash) -> sp_blockchain::R Arc::new(sc_keystore::LocalKeystore::in_memory()), Box::new(sp_consensus::NoNetwork), approval_voting_subsystem::Metrics::default(), + Arc::new(SpawnGlue(task_handle)), ); approval_voting diff --git a/polkadot/node/service/src/overseer.rs b/polkadot/node/service/src/overseer.rs index 0b57ff6e395..3c071e34fe1 100644 --- a/polkadot/node/service/src/overseer.rs +++ b/polkadot/node/service/src/overseer.rs @@ -320,6 +320,7 @@ where keystore.clone(), Box::new(sync_service.clone()), Metrics::register(registry)?, + Arc::new(spawner.clone()), )) .gossip_support(GossipSupportSubsystem::new( keystore.clone(), diff --git a/polkadot/node/subsystem-bench/src/lib/approval/mod.rs b/polkadot/node/subsystem-bench/src/lib/approval/mod.rs index f05d061f3fd..9d85039b888 100644 --- a/polkadot/node/subsystem-bench/src/lib/approval/mod.rs +++ b/polkadot/node/subsystem-bench/src/lib/approval/mod.rs @@ -814,7 +814,8 @@ fn build_overseer( Arc::new(keystore), Box::new(TestSyncOracle {}), state.approval_voting_metrics.clone(), - Box::new(system_clock.clone()), + Arc::new(system_clock.clone()), + Arc::new(SpawnGlue(spawn_task_handle.clone())), ); let approval_distribution = ApprovalDistribution::new_with_clock( diff --git a/prdoc/pr_4846.prdoc b/prdoc/pr_4846.prdoc new file mode 100644 index 00000000000..eb18301b101 --- /dev/null +++ b/prdoc/pr_4846.prdoc @@ -0,0 +1,13 @@ +title: "Make approval-voting runnable on a worker thread" + +doc: + - audience: Node Dev + description: | + Make approval-voting subsystem runnable on a separate worker thread without having to + to always pass to it an orchestra context. It achieves that by refactoring existing functions + to require only the minimal set of traits needed in the function instead of the general + `Context` + +crates: + - name: polkadot-node-core-approval-voting + bump: major -- GitLab From 5f72a1fe9d2c1e72325eea2e88f84c2dccbe9c5f Mon Sep 17 00:00:00 2001 From: Maksym H <1177472+mordamax@users.noreply.github.com> Date: Thu, 12 Sep 2024 17:31:25 +0100 Subject: [PATCH 050/125] Fix subsystem bench publishing (#5667) Fixes: https://github.com/paritytech/ci_cd/issues/1034 --- .github/workflows/subsystem-benchmarks.yml | 110 +++++++++++++-------- 1 file changed, 71 insertions(+), 39 deletions(-) diff --git a/.github/workflows/subsystem-benchmarks.yml b/.github/workflows/subsystem-benchmarks.yml index c33c782a731..6f9db9da9e6 100644 --- a/.github/workflows/subsystem-benchmarks.yml +++ b/.github/workflows/subsystem-benchmarks.yml @@ -1,9 +1,11 @@ +name: Subsystem Benchmarks + on: push: branches: - master pull_request: - types: [ opened, synchronize, reopened, ready_for_review ] + types: [opened, synchronize, reopened, ready_for_review] merge_group: concurrency: @@ -21,27 +23,49 @@ jobs: runs-on: ubuntu-latest outputs: IMAGE: ${{ steps.set_image.outputs.IMAGE }} + RUNNER: ${{ steps.set_runner.outputs.RUNNER }} steps: - name: Checkout uses: actions/checkout@v4 - id: set_image run: cat .github/env >> $GITHUB_OUTPUT + - id: set_runner + run: | + # Run merge queues on persistent runners + if [[ $GITHUB_REF_NAME == *"gh-readonly-queue"* ]]; then + echo "RUNNER=arc-runners-polkadot-sdk-beefy-persistent" >> $GITHUB_OUTPUT + else + echo "RUNNER=arc-runners-polkadot-sdk-beefy" >> $GITHUB_OUTPUT + fi build: timeout-minutes: 80 - needs: [ set-image ] - runs-on: arc-runners-polkadot-sdk-benchmark + needs: [set-image] + runs-on: ${{ needs.set-image.outputs.RUNNER }} container: image: ${{ needs.set-image.outputs.IMAGE }} strategy: fail-fast: false matrix: - features: [ - { name: "polkadot-availability-recovery", bench: "availability-recovery-regression-bench" }, - { name: "polkadot-availability-distribution", bench: "availability-distribution-regression-bench" }, - { name: "polkadot-node-core-approval-voting", bench: "approval-voting-regression-bench" }, - { name: "polkadot-statement-distribution", bench: "statement-distribution-regression-bench" } - ] + features: + [ + { + name: "polkadot-availability-recovery", + bench: "availability-recovery-regression-bench", + }, + { + name: "polkadot-availability-distribution", + bench: "availability-distribution-regression-bench", + }, + { + name: "polkadot-node-core-approval-voting", + bench: "approval-voting-regression-bench", + }, + { + name: "polkadot-statement-distribution", + bench: "statement-distribution-regression-bench", + }, + ] steps: - name: Checkout uses: actions/checkout@v4 @@ -56,34 +80,38 @@ jobs: run: | forklift cargo bench -p ${{ matrix.features.name }} --bench ${{ matrix.features.bench }} --features subsystem-benchmarks || echo "Benchmarks failed" ls -lsa ./charts - mkdir ./artifacts - cp ./charts/${{ matrix.features.bench }}.json ./artifacts/${{ matrix.features.bench }}.json + - name: Upload artifacts uses: actions/upload-artifact@v4.3.6 with: name: ${{matrix.features.bench}} - path: ./artifacts + path: ./charts publish-benchmarks: timeout-minutes: 60 - needs: [ build ] + needs: [build] if: github.ref == 'refs/heads/master' environment: subsystem-benchmarks runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 + with: + ref: gh-pages + fetch-depth: 0 + + - run: git checkout master -- - name: Download artifacts uses: actions/download-artifact@v4.1.8 with: - path: ./artifacts + path: ./charts - name: Setup git run: | # Fixes "detected dubious ownership" error in the ci git config --global --add safe.directory '*' - ls -lsR ./artifacts + ls -lsR ./charts - uses: actions/create-github-app-token@v1 id: app-token @@ -91,46 +119,50 @@ jobs: app-id: ${{ secrets.POLKADOTSDK_GHPAGES_APP_ID }} private-key: ${{ secrets.POLKADOTSDK_GHPAGES_APP_KEY }} - - name: Publish ${{ env.BENCH_NAME }} - uses: benchmark-action/github-action-benchmark@v1 + - name: Generate ${{ env.BENCH }} env: - BENCH_NAME: availability-recovery-regression-bench + BENCH: availability-recovery-regression-bench + uses: benchmark-action/github-action-benchmark@v1 with: tool: "customSmallerIsBetter" - name: ${{ env.BENCH_NAME }} - output-file-path: ./artifacts/${{ env.BENCH_NAME }}/${{ env.BENCH_NAME }}.json - benchmark-data-dir-path: ./artifacts/${{ env.BENCH_NAME }} + name: ${{ env.BENCH }} + output-file-path: ./charts/${{ env.BENCH }}/${{ env.BENCH }}.json + benchmark-data-dir-path: ./bench/${{ env.BENCH }} github-token: ${{ steps.app-token.outputs.token }} + auto-push: true - - name: Publish ${{ env.BENCH_NAME }} - uses: benchmark-action/github-action-benchmark@v1 + - name: Generate ${{ env.BENCH }} env: - BENCH_NAME: availability-distribution-regression-bench + BENCH: availability-distribution-regression-bench + uses: benchmark-action/github-action-benchmark@v1 with: tool: "customSmallerIsBetter" - name: ${{ env.BENCH_NAME }} - output-file-path: ./artifacts/${{ env.BENCH_NAME }}/${{ env.BENCH_NAME }}.json - benchmark-data-dir-path: ./artifacts/${{ env.BENCH_NAME }} + name: ${{ env.BENCH }} + output-file-path: ./charts/${{ env.BENCH }}/${{ env.BENCH }}.json + benchmark-data-dir-path: ./bench/${{ env.BENCH }} github-token: ${{ steps.app-token.outputs.token }} + auto-push: true - - name: Publish ${{ env.BENCH_NAME }} - uses: benchmark-action/github-action-benchmark@v1 + - name: Generate ${{ env.BENCH }} env: - BENCH_NAME: approval-voting-regression-bench + BENCH: approval-voting-regression-bench + uses: benchmark-action/github-action-benchmark@v1 with: tool: "customSmallerIsBetter" - name: ${{ env.BENCH_NAME }} - output-file-path: ./artifacts/${{ env.BENCH_NAME }}/${{ env.BENCH_NAME }}.json - benchmark-data-dir-path: ./artifacts/${{ env.BENCH_NAME }} + name: ${{ env.BENCH }} + output-file-path: ./charts/${{ env.BENCH }}/${{ env.BENCH }}.json + benchmark-data-dir-path: ./bench/${{ env.BENCH }} github-token: ${{ steps.app-token.outputs.token }} + auto-push: true - - name: Publish ${{ env.BENCH_NAME }} - uses: benchmark-action/github-action-benchmark@v1 + - name: Generate ${{ env.BENCH }} env: - BENCH_NAME: statement-distribution-regression-bench + BENCH: statement-distribution-regression-bench + uses: benchmark-action/github-action-benchmark@v1 with: tool: "customSmallerIsBetter" - name: ${{ env.BENCH_NAME }} - output-file-path: ./artifacts/${{ env.BENCH_NAME }}/${{ env.BENCH_NAME }}.json - benchmark-data-dir-path: ./artifacts/${{ env.BENCH_NAME }} + name: ${{ env.BENCH }} + output-file-path: ./charts/${{ env.BENCH }}/${{ env.BENCH }}.json + benchmark-data-dir-path: ./bench/${{ env.BENCH }} github-token: ${{ steps.app-token.outputs.token }} + auto-push: true -- GitLab From 4653c3791479cec68ad78624a403eaee1c147835 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=B3nal=20Murray?= Date: Thu, 12 Sep 2024 17:42:28 +0100 Subject: [PATCH 051/125] Add `pallet_proxy` to People Chain and Coretime Chain testnet runtimes. (#5509) Proxies are possible in the runtimes for Kusama and Polkadot but this functionality was not previously available on testnets. Closes #5453. Proxies can now be used on `coretime-rococo`, `coretime-westend`, `people-rococo` and `people-westend` in the same way as they can be on Kusama and Polkadot chains. The exact same proxies are configured as the production runtimes for the respective system parachains. --- Cargo.lock | 4 + .../coretime/coretime-rococo/Cargo.toml | 4 + .../coretime/coretime-rococo/src/lib.rs | 143 ++++++++++- .../coretime-rococo/src/weights/mod.rs | 1 + .../src/weights/pallet_proxy.rs | 226 ++++++++++++++++++ .../coretime/coretime-westend/Cargo.toml | 4 + .../coretime/coretime-westend/src/lib.rs | 143 ++++++++++- .../coretime-westend/src/weights/mod.rs | 1 + .../src/weights/pallet_proxy.rs | 226 ++++++++++++++++++ .../runtimes/people/people-rococo/Cargo.toml | 4 + .../runtimes/people/people-rococo/src/lib.rs | 123 +++++++++- .../people/people-rococo/src/weights/mod.rs | 1 + .../people-rococo/src/weights/pallet_proxy.rs | 226 ++++++++++++++++++ .../runtimes/people/people-westend/Cargo.toml | 4 + .../runtimes/people/people-westend/src/lib.rs | 123 +++++++++- .../people/people-westend/src/weights/mod.rs | 1 + .../src/weights/pallet_proxy.rs | 226 ++++++++++++++++++ prdoc/pr_5509.prdoc | 17 ++ 18 files changed, 1465 insertions(+), 12 deletions(-) create mode 100644 cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_proxy.rs create mode 100644 cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_proxy.rs create mode 100644 cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_proxy.rs create mode 100644 cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_proxy.rs create mode 100644 prdoc/pr_5509.prdoc diff --git a/Cargo.lock b/Cargo.lock index 35dc6192817..d30acc0f5b2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3357,6 +3357,7 @@ dependencies = [ "pallet-collator-selection", "pallet-message-queue", "pallet-multisig", + "pallet-proxy", "pallet-session", "pallet-sudo", "pallet-timestamp", @@ -3453,6 +3454,7 @@ dependencies = [ "pallet-collator-selection", "pallet-message-queue", "pallet-multisig", + "pallet-proxy", "pallet-session", "pallet-timestamp", "pallet-transaction-payment", @@ -12683,6 +12685,7 @@ dependencies = [ "pallet-identity", "pallet-message-queue", "pallet-multisig", + "pallet-proxy", "pallet-session", "pallet-timestamp", "pallet-transaction-payment", @@ -12782,6 +12785,7 @@ dependencies = [ "pallet-identity", "pallet-message-queue", "pallet-multisig", + "pallet-proxy", "pallet-session", "pallet-timestamp", "pallet-transaction-payment", diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml b/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml index 07d133c80be..80417ea0036 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml @@ -34,6 +34,7 @@ pallet-balances = { workspace = true } pallet-message-queue = { workspace = true } pallet-broker = { workspace = true } pallet-multisig = { workspace = true } +pallet-proxy = { workspace = true } pallet-session = { workspace = true } pallet-sudo = { workspace = true } pallet-timestamp = { workspace = true } @@ -108,6 +109,7 @@ std = [ "pallet-collator-selection/std", "pallet-message-queue/std", "pallet-multisig/std", + "pallet-proxy/std", "pallet-session/std", "pallet-sudo/std", "pallet-timestamp/std", @@ -158,6 +160,7 @@ runtime-benchmarks = [ "pallet-collator-selection/runtime-benchmarks", "pallet-message-queue/runtime-benchmarks", "pallet-multisig/runtime-benchmarks", + "pallet-proxy/runtime-benchmarks", "pallet-sudo/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", "pallet-utility/runtime-benchmarks", @@ -188,6 +191,7 @@ try-runtime = [ "pallet-collator-selection/try-runtime", "pallet-message-queue/try-runtime", "pallet-multisig/try-runtime", + "pallet-proxy/try-runtime", "pallet-session/try-runtime", "pallet-sudo/try-runtime", "pallet-timestamp/try-runtime", diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs index aea2bf232cb..0c9f9461f7f 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs @@ -36,6 +36,7 @@ pub mod xcm_config; extern crate alloc; use alloc::{vec, vec::Vec}; +use codec::{Decode, Encode, MaxEncodedLen}; use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; use cumulus_primitives_core::{AggregateMessageOrigin, ParaId}; use frame_support::{ @@ -43,7 +44,9 @@ use frame_support::{ dispatch::DispatchClass, genesis_builder_helper::{build_state, get_preset}, parameter_types, - traits::{ConstBool, ConstU32, ConstU64, ConstU8, EitherOfDiverse, TransformOrigin}, + traits::{ + ConstBool, ConstU32, ConstU64, ConstU8, EitherOfDiverse, InstanceFilter, TransformOrigin, + }, weights::{ConstantMultiplier, Weight, WeightToFee as _}, PalletId, }; @@ -65,9 +68,9 @@ use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; pub use sp_runtime::BuildStorage; use sp_runtime::{ create_runtime_str, generic, impl_opaque_keys, - traits::Block as BlockT, + traits::{BlakeTwo256, Block as BlockT}, transaction_validity::{TransactionSource, TransactionValidity}, - ApplyExtrinsicResult, DispatchError, MultiAddress, Perbill, + ApplyExtrinsicResult, DispatchError, MultiAddress, Perbill, RuntimeDebug, }; #[cfg(feature = "std")] use sp_version::NativeVersion; @@ -438,6 +441,138 @@ impl pallet_multisig::Config for Runtime { type WeightInfo = weights::pallet_multisig::WeightInfo; } +/// The type used to represent the kinds of proxying allowed. +#[derive( + Copy, + Clone, + Eq, + PartialEq, + Ord, + PartialOrd, + Encode, + Decode, + RuntimeDebug, + MaxEncodedLen, + scale_info::TypeInfo, +)] +pub enum ProxyType { + /// Fully permissioned proxy. Can execute any call on behalf of _proxied_. + Any, + /// Can execute any call that does not transfer funds or assets. + NonTransfer, + /// Proxy with the ability to reject time-delay proxy announcements. + CancelProxy, + /// Proxy for all Broker pallet calls. + Broker, + /// Proxy for renewing coretime. + CoretimeRenewer, + /// Proxy able to purchase on-demand coretime credits. + OnDemandPurchaser, + /// Collator selection proxy. Can execute calls related to collator selection mechanism. + Collator, +} +impl Default for ProxyType { + fn default() -> Self { + Self::Any + } +} + +impl InstanceFilter for ProxyType { + fn filter(&self, c: &RuntimeCall) -> bool { + match self { + ProxyType::Any => true, + ProxyType::NonTransfer => !matches!( + c, + RuntimeCall::Balances { .. } | + // `purchase`, `renew`, `transfer` and `purchase_credit` are pretty self explanatory. + RuntimeCall::Broker(pallet_broker::Call::purchase { .. }) | + RuntimeCall::Broker(pallet_broker::Call::renew { .. }) | + RuntimeCall::Broker(pallet_broker::Call::transfer { .. }) | + RuntimeCall::Broker(pallet_broker::Call::purchase_credit { .. }) | + // `pool` doesn't transfer, but it defines the account to be paid for contributions + RuntimeCall::Broker(pallet_broker::Call::pool { .. }) | + // `assign` is essentially a transfer of a region NFT + RuntimeCall::Broker(pallet_broker::Call::assign { .. }) + ), + ProxyType::CancelProxy => matches!( + c, + RuntimeCall::Proxy(pallet_proxy::Call::reject_announcement { .. }) | + RuntimeCall::Utility { .. } | + RuntimeCall::Multisig { .. } + ), + ProxyType::Broker => { + matches!( + c, + RuntimeCall::Broker { .. } | + RuntimeCall::Utility { .. } | + RuntimeCall::Multisig { .. } + ) + }, + ProxyType::CoretimeRenewer => { + matches!( + c, + RuntimeCall::Broker(pallet_broker::Call::renew { .. }) | + RuntimeCall::Utility { .. } | + RuntimeCall::Multisig { .. } + ) + }, + ProxyType::OnDemandPurchaser => { + matches!( + c, + RuntimeCall::Broker(pallet_broker::Call::purchase_credit { .. }) | + RuntimeCall::Utility { .. } | + RuntimeCall::Multisig { .. } + ) + }, + ProxyType::Collator => matches!( + c, + RuntimeCall::CollatorSelection { .. } | + RuntimeCall::Utility { .. } | + RuntimeCall::Multisig { .. } + ), + } + } + + fn is_superset(&self, o: &Self) -> bool { + match (self, o) { + (x, y) if x == y => true, + (ProxyType::Any, _) => true, + (_, ProxyType::Any) => false, + (ProxyType::Broker, ProxyType::CoretimeRenewer) => true, + (ProxyType::Broker, ProxyType::OnDemandPurchaser) => true, + (ProxyType::NonTransfer, ProxyType::Collator) => true, + _ => false, + } + } +} + +parameter_types! { + // One storage item; key size 32, value size 8; . + pub const ProxyDepositBase: Balance = deposit(1, 40); + // Additional storage item size of 33 bytes. + pub const ProxyDepositFactor: Balance = deposit(0, 33); + pub const MaxProxies: u16 = 32; + // One storage item; key size 32, value size 16 + pub const AnnouncementDepositBase: Balance = deposit(1, 48); + pub const AnnouncementDepositFactor: Balance = deposit(0, 66); + pub const MaxPending: u16 = 32; +} + +impl pallet_proxy::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type Currency = Balances; + type ProxyType = ProxyType; + type ProxyDepositBase = ProxyDepositBase; + type ProxyDepositFactor = ProxyDepositFactor; + type MaxProxies = MaxProxies; + type WeightInfo = weights::pallet_proxy::WeightInfo; + type MaxPending = MaxPending; + type CallHasher = BlakeTwo256; + type AnnouncementDepositBase = AnnouncementDepositBase; + type AnnouncementDepositFactor = AnnouncementDepositFactor; +} + impl pallet_utility::Config for Runtime { type RuntimeEvent = RuntimeEvent; type RuntimeCall = RuntimeCall; @@ -481,6 +616,7 @@ construct_runtime!( // Handy utilities. Utility: pallet_utility = 40, Multisig: pallet_multisig = 41, + Proxy: pallet_proxy = 42, // The main stage. Broker: pallet_broker = 50, @@ -504,6 +640,7 @@ mod benches { [pallet_xcm, PalletXcmExtrinsicsBenchmark::] [pallet_message_queue, MessageQueue] [pallet_multisig, Multisig] + [pallet_proxy, Proxy] [pallet_utility, Utility] // NOTE: Make sure you point to the individual modules below. [pallet_xcm_benchmarks::fungible, XcmBalances] diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/mod.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/mod.rs index f1050b3ae63..ab3d6704c93 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/mod.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/mod.rs @@ -27,6 +27,7 @@ pub mod pallet_broker; pub mod pallet_collator_selection; pub mod pallet_message_queue; pub mod pallet_multisig; +pub mod pallet_proxy; pub mod pallet_session; pub mod pallet_timestamp; pub mod pallet_utility; diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_proxy.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_proxy.rs new file mode 100644 index 00000000000..5f95906f473 --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_proxy.rs @@ -0,0 +1,226 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus 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. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . + +//! Autogenerated weights for `pallet_proxy` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-07-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-ynta1nyy-project-238-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! EXECUTION: ``, WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-rococo-dev")`, DB CACHE: 1024 + +// Executed Command: +// ./target/production/polkadot-parachain +// benchmark +// pallet +// --chain=coretime-rococo-dev +// --wasm-execution=compiled +// --pallet=pallet_proxy +// --no-storage-info +// --no-median-slopes +// --no-min-squares +// --extrinsic=* +// --steps=50 +// --repeat=20 +// --json +// --header=./file_header.txt +// --output=./parachains/runtimes/coretime/coretime-rococo/src/weights/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weight functions for `pallet_proxy`. +pub struct WeightInfo(PhantomData); +impl pallet_proxy::WeightInfo for WeightInfo { + /// Storage: `Proxy::Proxies` (r:1 w:0) + /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) + /// The range of component `p` is `[1, 31]`. + fn proxy(p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `127 + p * (37 ±0)` + // Estimated: `4706` + // Minimum execution time: 16_417_000 picoseconds. + Weight::from_parts(17_283_443, 0) + .saturating_add(Weight::from_parts(0, 4706)) + // Standard Error: 2_409 + .saturating_add(Weight::from_parts(32_123, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(1)) + } + /// Storage: `Proxy::Proxies` (r:1 w:0) + /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) + /// Storage: `Proxy::Announcements` (r:1 w:1) + /// Proof: `Proxy::Announcements` (`max_values`: None, `max_size`: Some(2233), added: 4708, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// The range of component `a` is `[0, 31]`. + /// The range of component `p` is `[1, 31]`. + fn proxy_announced(a: u32, p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `454 + a * (68 ±0) + p * (37 ±0)` + // Estimated: `5698` + // Minimum execution time: 37_572_000 picoseconds. + Weight::from_parts(37_045_756, 0) + .saturating_add(Weight::from_parts(0, 5698)) + // Standard Error: 2_896 + .saturating_add(Weight::from_parts(139_561, 0).saturating_mul(a.into())) + // Standard Error: 2_993 + .saturating_add(Weight::from_parts(73_270, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `Proxy::Announcements` (r:1 w:1) + /// Proof: `Proxy::Announcements` (`max_values`: None, `max_size`: Some(2233), added: 4708, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// The range of component `a` is `[0, 31]`. + /// The range of component `p` is `[1, 31]`. + fn remove_announcement(a: u32, p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `369 + a * (68 ±0)` + // Estimated: `5698` + // Minimum execution time: 24_066_000 picoseconds. + Weight::from_parts(24_711_403, 0) + .saturating_add(Weight::from_parts(0, 5698)) + // Standard Error: 1_626 + .saturating_add(Weight::from_parts(128_391, 0).saturating_mul(a.into())) + // Standard Error: 1_680 + .saturating_add(Weight::from_parts(23_124, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `Proxy::Announcements` (r:1 w:1) + /// Proof: `Proxy::Announcements` (`max_values`: None, `max_size`: Some(2233), added: 4708, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// The range of component `a` is `[0, 31]`. + /// The range of component `p` is `[1, 31]`. + fn reject_announcement(a: u32, p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `369 + a * (68 ±0)` + // Estimated: `5698` + // Minimum execution time: 24_162_000 picoseconds. + Weight::from_parts(23_928_058, 0) + .saturating_add(Weight::from_parts(0, 5698)) + // Standard Error: 2_072 + .saturating_add(Weight::from_parts(152_299, 0).saturating_mul(a.into())) + // Standard Error: 2_141 + .saturating_add(Weight::from_parts(39_775, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `Proxy::Proxies` (r:1 w:0) + /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) + /// Storage: `Proxy::Announcements` (r:1 w:1) + /// Proof: `Proxy::Announcements` (`max_values`: None, `max_size`: Some(2233), added: 4708, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// The range of component `a` is `[0, 31]`. + /// The range of component `p` is `[1, 31]`. + fn announce(a: u32, p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `386 + a * (68 ±0) + p * (37 ±0)` + // Estimated: `5698` + // Minimum execution time: 33_858_000 picoseconds. + Weight::from_parts(33_568_059, 0) + .saturating_add(Weight::from_parts(0, 5698)) + // Standard Error: 1_816 + .saturating_add(Weight::from_parts(134_400, 0).saturating_mul(a.into())) + // Standard Error: 1_876 + .saturating_add(Weight::from_parts(57_028, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `Proxy::Proxies` (r:1 w:1) + /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) + /// The range of component `p` is `[1, 31]`. + fn add_proxy(p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `127 + p * (37 ±0)` + // Estimated: `4706` + // Minimum execution time: 24_947_000 picoseconds. + Weight::from_parts(26_235_199, 0) + .saturating_add(Weight::from_parts(0, 4706)) + // Standard Error: 1_363 + .saturating_add(Weight::from_parts(41_435, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Proxy::Proxies` (r:1 w:1) + /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) + /// The range of component `p` is `[1, 31]`. + fn remove_proxy(p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `127 + p * (37 ±0)` + // Estimated: `4706` + // Minimum execution time: 25_186_000 picoseconds. + Weight::from_parts(26_823_133, 0) + .saturating_add(Weight::from_parts(0, 4706)) + // Standard Error: 2_259 + .saturating_add(Weight::from_parts(34_224, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Proxy::Proxies` (r:1 w:1) + /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) + /// The range of component `p` is `[1, 31]`. + fn remove_proxies(p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `127 + p * (37 ±0)` + // Estimated: `4706` + // Minimum execution time: 22_156_000 picoseconds. + Weight::from_parts(23_304_060, 0) + .saturating_add(Weight::from_parts(0, 4706)) + // Standard Error: 1_738 + .saturating_add(Weight::from_parts(39_612, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Proxy::Proxies` (r:1 w:1) + /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) + /// The range of component `p` is `[1, 31]`. + fn create_pure(p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `139` + // Estimated: `4706` + // Minimum execution time: 26_914_000 picoseconds. + Weight::from_parts(28_009_062, 0) + .saturating_add(Weight::from_parts(0, 4706)) + // Standard Error: 1_978 + .saturating_add(Weight::from_parts(12_255, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Proxy::Proxies` (r:1 w:1) + /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) + /// The range of component `p` is `[0, 30]`. + fn kill_pure(p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `164 + p * (37 ±0)` + // Estimated: `4706` + // Minimum execution time: 23_281_000 picoseconds. + Weight::from_parts(24_392_989, 0) + .saturating_add(Weight::from_parts(0, 4706)) + // Standard Error: 2_943 + .saturating_add(Weight::from_parts(30_287, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } +} diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml b/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml index 5029c82f971..25bf777047d 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml @@ -34,6 +34,7 @@ pallet-balances = { workspace = true } pallet-message-queue = { workspace = true } pallet-broker = { workspace = true } pallet-multisig = { workspace = true } +pallet-proxy = { workspace = true } pallet-session = { workspace = true } pallet-timestamp = { workspace = true } pallet-transaction-payment = { workspace = true } @@ -108,6 +109,7 @@ std = [ "pallet-collator-selection/std", "pallet-message-queue/std", "pallet-multisig/std", + "pallet-proxy/std", "pallet-session/std", "pallet-timestamp/std", "pallet-transaction-payment-rpc-runtime-api/std", @@ -157,6 +159,7 @@ runtime-benchmarks = [ "pallet-collator-selection/runtime-benchmarks", "pallet-message-queue/runtime-benchmarks", "pallet-multisig/runtime-benchmarks", + "pallet-proxy/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", "pallet-utility/runtime-benchmarks", "pallet-xcm-benchmarks/runtime-benchmarks", @@ -186,6 +189,7 @@ try-runtime = [ "pallet-collator-selection/try-runtime", "pallet-message-queue/try-runtime", "pallet-multisig/try-runtime", + "pallet-proxy/try-runtime", "pallet-session/try-runtime", "pallet-timestamp/try-runtime", "pallet-transaction-payment/try-runtime", diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs index 218afaab924..614eae895a7 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs @@ -36,6 +36,7 @@ pub mod xcm_config; extern crate alloc; use alloc::{vec, vec::Vec}; +use codec::{Decode, Encode, MaxEncodedLen}; use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; use cumulus_primitives_core::{AggregateMessageOrigin, ParaId}; use frame_support::{ @@ -43,7 +44,9 @@ use frame_support::{ dispatch::DispatchClass, genesis_builder_helper::{build_state, get_preset}, parameter_types, - traits::{ConstBool, ConstU32, ConstU64, ConstU8, EitherOfDiverse, TransformOrigin}, + traits::{ + ConstBool, ConstU32, ConstU64, ConstU8, EitherOfDiverse, InstanceFilter, TransformOrigin, + }, weights::{ConstantMultiplier, Weight, WeightToFee as _}, PalletId, }; @@ -65,9 +68,9 @@ use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; pub use sp_runtime::BuildStorage; use sp_runtime::{ create_runtime_str, generic, impl_opaque_keys, - traits::Block as BlockT, + traits::{BlakeTwo256, Block as BlockT}, transaction_validity::{TransactionSource, TransactionValidity}, - ApplyExtrinsicResult, DispatchError, MultiAddress, Perbill, + ApplyExtrinsicResult, DispatchError, MultiAddress, Perbill, RuntimeDebug, }; #[cfg(feature = "std")] use sp_version::NativeVersion; @@ -438,6 +441,138 @@ impl pallet_multisig::Config for Runtime { type WeightInfo = weights::pallet_multisig::WeightInfo; } +/// The type used to represent the kinds of proxying allowed. +#[derive( + Copy, + Clone, + Eq, + PartialEq, + Ord, + PartialOrd, + Encode, + Decode, + RuntimeDebug, + MaxEncodedLen, + scale_info::TypeInfo, +)] +pub enum ProxyType { + /// Fully permissioned proxy. Can execute any call on behalf of _proxied_. + Any, + /// Can execute any call that does not transfer funds or assets. + NonTransfer, + /// Proxy with the ability to reject time-delay proxy announcements. + CancelProxy, + /// Proxy for all Broker pallet calls. + Broker, + /// Proxy for renewing coretime. + CoretimeRenewer, + /// Proxy able to purchase on-demand coretime credits. + OnDemandPurchaser, + /// Collator selection proxy. Can execute calls related to collator selection mechanism. + Collator, +} +impl Default for ProxyType { + fn default() -> Self { + Self::Any + } +} + +impl InstanceFilter for ProxyType { + fn filter(&self, c: &RuntimeCall) -> bool { + match self { + ProxyType::Any => true, + ProxyType::NonTransfer => !matches!( + c, + RuntimeCall::Balances { .. } | + // `purchase`, `renew`, `transfer` and `purchase_credit` are pretty self explanatory. + RuntimeCall::Broker(pallet_broker::Call::purchase { .. }) | + RuntimeCall::Broker(pallet_broker::Call::renew { .. }) | + RuntimeCall::Broker(pallet_broker::Call::transfer { .. }) | + RuntimeCall::Broker(pallet_broker::Call::purchase_credit { .. }) | + // `pool` doesn't transfer, but it defines the account to be paid for contributions + RuntimeCall::Broker(pallet_broker::Call::pool { .. }) | + // `assign` is essentially a transfer of a region NFT + RuntimeCall::Broker(pallet_broker::Call::assign { .. }) + ), + ProxyType::CancelProxy => matches!( + c, + RuntimeCall::Proxy(pallet_proxy::Call::reject_announcement { .. }) | + RuntimeCall::Utility { .. } | + RuntimeCall::Multisig { .. } + ), + ProxyType::Broker => { + matches!( + c, + RuntimeCall::Broker { .. } | + RuntimeCall::Utility { .. } | + RuntimeCall::Multisig { .. } + ) + }, + ProxyType::CoretimeRenewer => { + matches!( + c, + RuntimeCall::Broker(pallet_broker::Call::renew { .. }) | + RuntimeCall::Utility { .. } | + RuntimeCall::Multisig { .. } + ) + }, + ProxyType::OnDemandPurchaser => { + matches!( + c, + RuntimeCall::Broker(pallet_broker::Call::purchase_credit { .. }) | + RuntimeCall::Utility { .. } | + RuntimeCall::Multisig { .. } + ) + }, + ProxyType::Collator => matches!( + c, + RuntimeCall::CollatorSelection { .. } | + RuntimeCall::Utility { .. } | + RuntimeCall::Multisig { .. } + ), + } + } + + fn is_superset(&self, o: &Self) -> bool { + match (self, o) { + (x, y) if x == y => true, + (ProxyType::Any, _) => true, + (_, ProxyType::Any) => false, + (ProxyType::Broker, ProxyType::CoretimeRenewer) => true, + (ProxyType::Broker, ProxyType::OnDemandPurchaser) => true, + (ProxyType::NonTransfer, ProxyType::Collator) => true, + _ => false, + } + } +} + +parameter_types! { + // One storage item; key size 32, value size 8; . + pub const ProxyDepositBase: Balance = deposit(1, 40); + // Additional storage item size of 33 bytes. + pub const ProxyDepositFactor: Balance = deposit(0, 33); + pub const MaxProxies: u16 = 32; + // One storage item; key size 32, value size 16 + pub const AnnouncementDepositBase: Balance = deposit(1, 48); + pub const AnnouncementDepositFactor: Balance = deposit(0, 66); + pub const MaxPending: u16 = 32; +} + +impl pallet_proxy::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type Currency = Balances; + type ProxyType = ProxyType; + type ProxyDepositBase = ProxyDepositBase; + type ProxyDepositFactor = ProxyDepositFactor; + type MaxProxies = MaxProxies; + type WeightInfo = weights::pallet_proxy::WeightInfo; + type MaxPending = MaxPending; + type CallHasher = BlakeTwo256; + type AnnouncementDepositBase = AnnouncementDepositBase; + type AnnouncementDepositFactor = AnnouncementDepositFactor; +} + impl pallet_utility::Config for Runtime { type RuntimeEvent = RuntimeEvent; type RuntimeCall = RuntimeCall; @@ -475,6 +610,7 @@ construct_runtime!( // Handy utilities. Utility: pallet_utility = 40, Multisig: pallet_multisig = 41, + Proxy: pallet_proxy = 42, // The main stage. Broker: pallet_broker = 50, @@ -495,6 +631,7 @@ mod benches { [pallet_xcm, PalletXcmExtrinsicsBenchmark::] [pallet_message_queue, MessageQueue] [pallet_multisig, Multisig] + [pallet_proxy, Proxy] [pallet_utility, Utility] // NOTE: Make sure you point to the individual modules below. [pallet_xcm_benchmarks::fungible, XcmBalances] diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/mod.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/mod.rs index f1050b3ae63..ab3d6704c93 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/mod.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/mod.rs @@ -27,6 +27,7 @@ pub mod pallet_broker; pub mod pallet_collator_selection; pub mod pallet_message_queue; pub mod pallet_multisig; +pub mod pallet_proxy; pub mod pallet_session; pub mod pallet_timestamp; pub mod pallet_utility; diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_proxy.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_proxy.rs new file mode 100644 index 00000000000..d3edc1a8b20 --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_proxy.rs @@ -0,0 +1,226 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus 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. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . + +//! Autogenerated weights for `pallet_proxy` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-07-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-ynta1nyy-project-238-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! EXECUTION: ``, WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-westend-dev")`, DB CACHE: 1024 + +// Executed Command: +// ./target/production/polkadot-parachain +// benchmark +// pallet +// --chain=coretime-westend-dev +// --wasm-execution=compiled +// --pallet=pallet_proxy +// --no-storage-info +// --no-median-slopes +// --no-min-squares +// --extrinsic=* +// --steps=50 +// --repeat=20 +// --json +// --header=./file_header.txt +// --output=./parachains/runtimes/coretime/coretime-westend/src/weights/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weight functions for `pallet_proxy`. +pub struct WeightInfo(PhantomData); +impl pallet_proxy::WeightInfo for WeightInfo { + /// Storage: `Proxy::Proxies` (r:1 w:0) + /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) + /// The range of component `p` is `[1, 31]`. + fn proxy(p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `127 + p * (37 ±0)` + // Estimated: `4706` + // Minimum execution time: 16_417_000 picoseconds. + Weight::from_parts(17_283_443, 0) + .saturating_add(Weight::from_parts(0, 4706)) + // Standard Error: 2_409 + .saturating_add(Weight::from_parts(32_123, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(1)) + } + /// Storage: `Proxy::Proxies` (r:1 w:0) + /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) + /// Storage: `Proxy::Announcements` (r:1 w:1) + /// Proof: `Proxy::Announcements` (`max_values`: None, `max_size`: Some(2233), added: 4708, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// The range of component `a` is `[0, 31]`. + /// The range of component `p` is `[1, 31]`. + fn proxy_announced(a: u32, p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `454 + a * (68 ±0) + p * (37 ±0)` + // Estimated: `5698` + // Minimum execution time: 37_572_000 picoseconds. + Weight::from_parts(37_045_756, 0) + .saturating_add(Weight::from_parts(0, 5698)) + // Standard Error: 2_896 + .saturating_add(Weight::from_parts(139_561, 0).saturating_mul(a.into())) + // Standard Error: 2_993 + .saturating_add(Weight::from_parts(73_270, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `Proxy::Announcements` (r:1 w:1) + /// Proof: `Proxy::Announcements` (`max_values`: None, `max_size`: Some(2233), added: 4708, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// The range of component `a` is `[0, 31]`. + /// The range of component `p` is `[1, 31]`. + fn remove_announcement(a: u32, p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `369 + a * (68 ±0)` + // Estimated: `5698` + // Minimum execution time: 24_066_000 picoseconds. + Weight::from_parts(24_711_403, 0) + .saturating_add(Weight::from_parts(0, 5698)) + // Standard Error: 1_626 + .saturating_add(Weight::from_parts(128_391, 0).saturating_mul(a.into())) + // Standard Error: 1_680 + .saturating_add(Weight::from_parts(23_124, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `Proxy::Announcements` (r:1 w:1) + /// Proof: `Proxy::Announcements` (`max_values`: None, `max_size`: Some(2233), added: 4708, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// The range of component `a` is `[0, 31]`. + /// The range of component `p` is `[1, 31]`. + fn reject_announcement(a: u32, p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `369 + a * (68 ±0)` + // Estimated: `5698` + // Minimum execution time: 24_162_000 picoseconds. + Weight::from_parts(23_928_058, 0) + .saturating_add(Weight::from_parts(0, 5698)) + // Standard Error: 2_072 + .saturating_add(Weight::from_parts(152_299, 0).saturating_mul(a.into())) + // Standard Error: 2_141 + .saturating_add(Weight::from_parts(39_775, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `Proxy::Proxies` (r:1 w:0) + /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) + /// Storage: `Proxy::Announcements` (r:1 w:1) + /// Proof: `Proxy::Announcements` (`max_values`: None, `max_size`: Some(2233), added: 4708, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// The range of component `a` is `[0, 31]`. + /// The range of component `p` is `[1, 31]`. + fn announce(a: u32, p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `386 + a * (68 ±0) + p * (37 ±0)` + // Estimated: `5698` + // Minimum execution time: 33_858_000 picoseconds. + Weight::from_parts(33_568_059, 0) + .saturating_add(Weight::from_parts(0, 5698)) + // Standard Error: 1_816 + .saturating_add(Weight::from_parts(134_400, 0).saturating_mul(a.into())) + // Standard Error: 1_876 + .saturating_add(Weight::from_parts(57_028, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `Proxy::Proxies` (r:1 w:1) + /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) + /// The range of component `p` is `[1, 31]`. + fn add_proxy(p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `127 + p * (37 ±0)` + // Estimated: `4706` + // Minimum execution time: 24_947_000 picoseconds. + Weight::from_parts(26_235_199, 0) + .saturating_add(Weight::from_parts(0, 4706)) + // Standard Error: 1_363 + .saturating_add(Weight::from_parts(41_435, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Proxy::Proxies` (r:1 w:1) + /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) + /// The range of component `p` is `[1, 31]`. + fn remove_proxy(p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `127 + p * (37 ±0)` + // Estimated: `4706` + // Minimum execution time: 25_186_000 picoseconds. + Weight::from_parts(26_823_133, 0) + .saturating_add(Weight::from_parts(0, 4706)) + // Standard Error: 2_259 + .saturating_add(Weight::from_parts(34_224, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Proxy::Proxies` (r:1 w:1) + /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) + /// The range of component `p` is `[1, 31]`. + fn remove_proxies(p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `127 + p * (37 ±0)` + // Estimated: `4706` + // Minimum execution time: 22_156_000 picoseconds. + Weight::from_parts(23_304_060, 0) + .saturating_add(Weight::from_parts(0, 4706)) + // Standard Error: 1_738 + .saturating_add(Weight::from_parts(39_612, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Proxy::Proxies` (r:1 w:1) + /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) + /// The range of component `p` is `[1, 31]`. + fn create_pure(p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `139` + // Estimated: `4706` + // Minimum execution time: 26_914_000 picoseconds. + Weight::from_parts(28_009_062, 0) + .saturating_add(Weight::from_parts(0, 4706)) + // Standard Error: 1_978 + .saturating_add(Weight::from_parts(12_255, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Proxy::Proxies` (r:1 w:1) + /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) + /// The range of component `p` is `[0, 30]`. + fn kill_pure(p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `164 + p * (37 ±0)` + // Estimated: `4706` + // Minimum execution time: 23_281_000 picoseconds. + Weight::from_parts(24_392_989, 0) + .saturating_add(Weight::from_parts(0, 4706)) + // Standard Error: 2_943 + .saturating_add(Weight::from_parts(30_287, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } +} diff --git a/cumulus/parachains/runtimes/people/people-rococo/Cargo.toml b/cumulus/parachains/runtimes/people/people-rococo/Cargo.toml index c676587b1de..c969bb2985b 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/people/people-rococo/Cargo.toml @@ -31,6 +31,7 @@ pallet-balances = { workspace = true } pallet-identity = { workspace = true } pallet-message-queue = { workspace = true } pallet-multisig = { workspace = true } +pallet-proxy = { workspace = true } pallet-session = { workspace = true } pallet-timestamp = { workspace = true } pallet-transaction-payment = { workspace = true } @@ -104,6 +105,7 @@ std = [ "pallet-identity/std", "pallet-message-queue/std", "pallet-multisig/std", + "pallet-proxy/std", "pallet-session/std", "pallet-timestamp/std", "pallet-transaction-payment-rpc-runtime-api/std", @@ -153,6 +155,7 @@ runtime-benchmarks = [ "pallet-identity/runtime-benchmarks", "pallet-message-queue/runtime-benchmarks", "pallet-multisig/runtime-benchmarks", + "pallet-proxy/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", "pallet-utility/runtime-benchmarks", "pallet-xcm-benchmarks/runtime-benchmarks", @@ -182,6 +185,7 @@ try-runtime = [ "pallet-identity/try-runtime", "pallet-message-queue/try-runtime", "pallet-multisig/try-runtime", + "pallet-proxy/try-runtime", "pallet-session/try-runtime", "pallet-timestamp/try-runtime", "pallet-transaction-payment/try-runtime", diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs b/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs index cb9177d0c23..9b251a90d67 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs @@ -25,6 +25,7 @@ pub mod xcm_config; extern crate alloc; use alloc::{vec, vec::Vec}; +use codec::{Decode, Encode, MaxEncodedLen}; use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; use cumulus_primitives_core::{AggregateMessageOrigin, ParaId}; use frame_support::{ @@ -33,7 +34,8 @@ use frame_support::{ genesis_builder_helper::{build_state, get_preset}, parameter_types, traits::{ - ConstBool, ConstU32, ConstU64, ConstU8, EitherOfDiverse, Everything, TransformOrigin, + ConstBool, ConstU32, ConstU64, ConstU8, EitherOfDiverse, Everything, InstanceFilter, + TransformOrigin, }, weights::{ConstantMultiplier, Weight, WeightToFee as _}, PalletId, @@ -57,11 +59,11 @@ use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; pub use sp_runtime::BuildStorage; use sp_runtime::{ create_runtime_str, generic, impl_opaque_keys, - traits::Block as BlockT, + traits::{BlakeTwo256, Block as BlockT}, transaction_validity::{TransactionSource, TransactionValidity}, ApplyExtrinsicResult, }; -pub use sp_runtime::{MultiAddress, Perbill, Permill}; +pub use sp_runtime::{MultiAddress, Perbill, Permill, RuntimeDebug}; #[cfg(feature = "std")] use sp_version::NativeVersion; use sp_version::RuntimeVersion; @@ -401,6 +403,119 @@ impl pallet_multisig::Config for Runtime { type WeightInfo = weights::pallet_multisig::WeightInfo; } +/// The type used to represent the kinds of proxying allowed. +#[derive( + Copy, + Clone, + Eq, + PartialEq, + Ord, + PartialOrd, + Encode, + Decode, + RuntimeDebug, + MaxEncodedLen, + scale_info::TypeInfo, +)] +pub enum ProxyType { + /// Fully permissioned proxy. Can execute any call on behalf of _proxied_. + Any, + /// Can execute any call that does not transfer funds or assets. + NonTransfer, + /// Proxy with the ability to reject time-delay proxy announcements. + CancelProxy, + /// Proxy for all Identity pallet calls. + Identity, + /// Proxy for identity registrars. + IdentityJudgement, + /// Collator selection proxy. Can execute calls related to collator selection mechanism. + Collator, +} +impl Default for ProxyType { + fn default() -> Self { + Self::Any + } +} + +impl InstanceFilter for ProxyType { + fn filter(&self, c: &RuntimeCall) -> bool { + match self { + ProxyType::Any => true, + ProxyType::NonTransfer => !matches!( + c, + RuntimeCall::Balances { .. } | + // `request_judgement` puts up a deposit to transfer to a registrar + RuntimeCall::Identity(pallet_identity::Call::request_judgement { .. }) + ), + ProxyType::CancelProxy => matches!( + c, + RuntimeCall::Proxy(pallet_proxy::Call::reject_announcement { .. }) | + RuntimeCall::Utility { .. } | + RuntimeCall::Multisig { .. } + ), + ProxyType::Identity => { + matches!( + c, + RuntimeCall::Identity { .. } | + RuntimeCall::Utility { .. } | + RuntimeCall::Multisig { .. } + ) + }, + ProxyType::IdentityJudgement => matches!( + c, + RuntimeCall::Identity(pallet_identity::Call::provide_judgement { .. }) | + RuntimeCall::Utility(..) | + RuntimeCall::Multisig { .. } + ), + ProxyType::Collator => matches!( + c, + RuntimeCall::CollatorSelection { .. } | + RuntimeCall::Utility { .. } | + RuntimeCall::Multisig { .. } + ), + } + } + + fn is_superset(&self, o: &Self) -> bool { + match (self, o) { + (x, y) if x == y => true, + (ProxyType::Any, _) => true, + (_, ProxyType::Any) => false, + (ProxyType::Identity, ProxyType::IdentityJudgement) => true, + (ProxyType::NonTransfer, ProxyType::IdentityJudgement) => true, + (ProxyType::NonTransfer, ProxyType::Collator) => true, + _ => false, + } + } +} + +parameter_types! { + // One storage item; key size 32, value size 8. + pub const ProxyDepositBase: Balance = deposit(1, 40); + // Additional storage item size of 33 bytes. + pub const ProxyDepositFactor: Balance = deposit(0, 33); + pub const MaxProxies: u16 = 32; + // One storage item; key size 32, value size 16. + pub const AnnouncementDepositBase: Balance = deposit(1, 48); + pub const AnnouncementDepositFactor: Balance = deposit(0, 66); + pub const MaxPending: u16 = 32; +} + +impl pallet_proxy::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type Currency = Balances; + type ProxyType = ProxyType; + type ProxyDepositBase = ProxyDepositBase; + type ProxyDepositFactor = ProxyDepositFactor; + type MaxProxies = MaxProxies; + type WeightInfo = weights::pallet_proxy::WeightInfo; + type MaxPending = MaxPending; + type CallHasher = BlakeTwo256; + type AnnouncementDepositBase = AnnouncementDepositBase; + type AnnouncementDepositFactor = AnnouncementDepositFactor; +} + impl pallet_utility::Config for Runtime { type RuntimeEvent = RuntimeEvent; type RuntimeCall = RuntimeCall; @@ -446,6 +561,7 @@ construct_runtime!( // Handy utilities. Utility: pallet_utility = 40, Multisig: pallet_multisig = 41, + Proxy: pallet_proxy = 42, // The main stage. Identity: pallet_identity = 50, @@ -464,6 +580,7 @@ mod benches { [pallet_identity, Identity] [pallet_message_queue, MessageQueue] [pallet_multisig, Multisig] + [pallet_proxy, Proxy] [pallet_session, SessionBench::] [pallet_utility, Utility] [pallet_timestamp, Timestamp] diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/weights/mod.rs b/cumulus/parachains/runtimes/people/people-rococo/src/weights/mod.rs index 3396a8caea0..dce959e817b 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/src/weights/mod.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/weights/mod.rs @@ -25,6 +25,7 @@ pub mod pallet_collator_selection; pub mod pallet_identity; pub mod pallet_message_queue; pub mod pallet_multisig; +pub mod pallet_proxy; pub mod pallet_session; pub mod pallet_timestamp; pub mod pallet_utility; diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_proxy.rs b/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_proxy.rs new file mode 100644 index 00000000000..264213c94d4 --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_proxy.rs @@ -0,0 +1,226 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus 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. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . + +//! Autogenerated weights for `pallet_proxy` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-07-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-ynta1nyy-project-238-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! EXECUTION: ``, WASM-EXECUTION: `Compiled`, CHAIN: `Some("people-rococo-dev")`, DB CACHE: 1024 + +// Executed Command: +// ./target/production/polkadot-parachain +// benchmark +// pallet +// --chain=people-rococo-dev +// --wasm-execution=compiled +// --pallet=pallet_proxy +// --no-storage-info +// --no-median-slopes +// --no-min-squares +// --extrinsic=* +// --steps=50 +// --repeat=20 +// --json +// --header=./file_header.txt +// --output=./parachains/runtimes/people/people-rococo/src/weights/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weight functions for `pallet_proxy`. +pub struct WeightInfo(PhantomData); +impl pallet_proxy::WeightInfo for WeightInfo { + /// Storage: `Proxy::Proxies` (r:1 w:0) + /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) + /// The range of component `p` is `[1, 31]`. + fn proxy(p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `127 + p * (37 ±0)` + // Estimated: `4706` + // Minimum execution time: 16_417_000 picoseconds. + Weight::from_parts(17_283_443, 0) + .saturating_add(Weight::from_parts(0, 4706)) + // Standard Error: 2_409 + .saturating_add(Weight::from_parts(32_123, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(1)) + } + /// Storage: `Proxy::Proxies` (r:1 w:0) + /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) + /// Storage: `Proxy::Announcements` (r:1 w:1) + /// Proof: `Proxy::Announcements` (`max_values`: None, `max_size`: Some(2233), added: 4708, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// The range of component `a` is `[0, 31]`. + /// The range of component `p` is `[1, 31]`. + fn proxy_announced(a: u32, p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `454 + a * (68 ±0) + p * (37 ±0)` + // Estimated: `5698` + // Minimum execution time: 37_572_000 picoseconds. + Weight::from_parts(37_045_756, 0) + .saturating_add(Weight::from_parts(0, 5698)) + // Standard Error: 2_896 + .saturating_add(Weight::from_parts(139_561, 0).saturating_mul(a.into())) + // Standard Error: 2_993 + .saturating_add(Weight::from_parts(73_270, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `Proxy::Announcements` (r:1 w:1) + /// Proof: `Proxy::Announcements` (`max_values`: None, `max_size`: Some(2233), added: 4708, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// The range of component `a` is `[0, 31]`. + /// The range of component `p` is `[1, 31]`. + fn remove_announcement(a: u32, p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `369 + a * (68 ±0)` + // Estimated: `5698` + // Minimum execution time: 24_066_000 picoseconds. + Weight::from_parts(24_711_403, 0) + .saturating_add(Weight::from_parts(0, 5698)) + // Standard Error: 1_626 + .saturating_add(Weight::from_parts(128_391, 0).saturating_mul(a.into())) + // Standard Error: 1_680 + .saturating_add(Weight::from_parts(23_124, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `Proxy::Announcements` (r:1 w:1) + /// Proof: `Proxy::Announcements` (`max_values`: None, `max_size`: Some(2233), added: 4708, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// The range of component `a` is `[0, 31]`. + /// The range of component `p` is `[1, 31]`. + fn reject_announcement(a: u32, p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `369 + a * (68 ±0)` + // Estimated: `5698` + // Minimum execution time: 24_162_000 picoseconds. + Weight::from_parts(23_928_058, 0) + .saturating_add(Weight::from_parts(0, 5698)) + // Standard Error: 2_072 + .saturating_add(Weight::from_parts(152_299, 0).saturating_mul(a.into())) + // Standard Error: 2_141 + .saturating_add(Weight::from_parts(39_775, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `Proxy::Proxies` (r:1 w:0) + /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) + /// Storage: `Proxy::Announcements` (r:1 w:1) + /// Proof: `Proxy::Announcements` (`max_values`: None, `max_size`: Some(2233), added: 4708, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// The range of component `a` is `[0, 31]`. + /// The range of component `p` is `[1, 31]`. + fn announce(a: u32, p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `386 + a * (68 ±0) + p * (37 ±0)` + // Estimated: `5698` + // Minimum execution time: 33_858_000 picoseconds. + Weight::from_parts(33_568_059, 0) + .saturating_add(Weight::from_parts(0, 5698)) + // Standard Error: 1_816 + .saturating_add(Weight::from_parts(134_400, 0).saturating_mul(a.into())) + // Standard Error: 1_876 + .saturating_add(Weight::from_parts(57_028, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `Proxy::Proxies` (r:1 w:1) + /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) + /// The range of component `p` is `[1, 31]`. + fn add_proxy(p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `127 + p * (37 ±0)` + // Estimated: `4706` + // Minimum execution time: 24_947_000 picoseconds. + Weight::from_parts(26_235_199, 0) + .saturating_add(Weight::from_parts(0, 4706)) + // Standard Error: 1_363 + .saturating_add(Weight::from_parts(41_435, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Proxy::Proxies` (r:1 w:1) + /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) + /// The range of component `p` is `[1, 31]`. + fn remove_proxy(p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `127 + p * (37 ±0)` + // Estimated: `4706` + // Minimum execution time: 25_186_000 picoseconds. + Weight::from_parts(26_823_133, 0) + .saturating_add(Weight::from_parts(0, 4706)) + // Standard Error: 2_259 + .saturating_add(Weight::from_parts(34_224, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Proxy::Proxies` (r:1 w:1) + /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) + /// The range of component `p` is `[1, 31]`. + fn remove_proxies(p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `127 + p * (37 ±0)` + // Estimated: `4706` + // Minimum execution time: 22_156_000 picoseconds. + Weight::from_parts(23_304_060, 0) + .saturating_add(Weight::from_parts(0, 4706)) + // Standard Error: 1_738 + .saturating_add(Weight::from_parts(39_612, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Proxy::Proxies` (r:1 w:1) + /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) + /// The range of component `p` is `[1, 31]`. + fn create_pure(p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `139` + // Estimated: `4706` + // Minimum execution time: 26_914_000 picoseconds. + Weight::from_parts(28_009_062, 0) + .saturating_add(Weight::from_parts(0, 4706)) + // Standard Error: 1_978 + .saturating_add(Weight::from_parts(12_255, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Proxy::Proxies` (r:1 w:1) + /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) + /// The range of component `p` is `[0, 30]`. + fn kill_pure(p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `164 + p * (37 ±0)` + // Estimated: `4706` + // Minimum execution time: 23_281_000 picoseconds. + Weight::from_parts(24_392_989, 0) + .saturating_add(Weight::from_parts(0, 4706)) + // Standard Error: 2_943 + .saturating_add(Weight::from_parts(30_287, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } +} diff --git a/cumulus/parachains/runtimes/people/people-westend/Cargo.toml b/cumulus/parachains/runtimes/people/people-westend/Cargo.toml index ab7dd04bb78..64e956d8b6b 100644 --- a/cumulus/parachains/runtimes/people/people-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/people/people-westend/Cargo.toml @@ -31,6 +31,7 @@ pallet-balances = { workspace = true } pallet-identity = { workspace = true } pallet-message-queue = { workspace = true } pallet-multisig = { workspace = true } +pallet-proxy = { workspace = true } pallet-session = { workspace = true } pallet-timestamp = { workspace = true } pallet-transaction-payment = { workspace = true } @@ -104,6 +105,7 @@ std = [ "pallet-identity/std", "pallet-message-queue/std", "pallet-multisig/std", + "pallet-proxy/std", "pallet-session/std", "pallet-timestamp/std", "pallet-transaction-payment-rpc-runtime-api/std", @@ -153,6 +155,7 @@ runtime-benchmarks = [ "pallet-identity/runtime-benchmarks", "pallet-message-queue/runtime-benchmarks", "pallet-multisig/runtime-benchmarks", + "pallet-proxy/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", "pallet-utility/runtime-benchmarks", "pallet-xcm-benchmarks/runtime-benchmarks", @@ -182,6 +185,7 @@ try-runtime = [ "pallet-identity/try-runtime", "pallet-message-queue/try-runtime", "pallet-multisig/try-runtime", + "pallet-proxy/try-runtime", "pallet-session/try-runtime", "pallet-timestamp/try-runtime", "pallet-transaction-payment/try-runtime", diff --git a/cumulus/parachains/runtimes/people/people-westend/src/lib.rs b/cumulus/parachains/runtimes/people/people-westend/src/lib.rs index 9813c5cb6ac..07bfba92c93 100644 --- a/cumulus/parachains/runtimes/people/people-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/people/people-westend/src/lib.rs @@ -25,6 +25,7 @@ pub mod xcm_config; extern crate alloc; use alloc::{vec, vec::Vec}; +use codec::{Decode, Encode, MaxEncodedLen}; use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; use cumulus_primitives_core::{AggregateMessageOrigin, ParaId}; use frame_support::{ @@ -33,7 +34,8 @@ use frame_support::{ genesis_builder_helper::{build_state, get_preset}, parameter_types, traits::{ - ConstBool, ConstU32, ConstU64, ConstU8, EitherOfDiverse, Everything, TransformOrigin, + ConstBool, ConstU32, ConstU64, ConstU8, EitherOfDiverse, Everything, InstanceFilter, + TransformOrigin, }, weights::{ConstantMultiplier, Weight, WeightToFee as _}, PalletId, @@ -57,11 +59,11 @@ use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; pub use sp_runtime::BuildStorage; use sp_runtime::{ create_runtime_str, generic, impl_opaque_keys, - traits::Block as BlockT, + traits::{BlakeTwo256, Block as BlockT}, transaction_validity::{TransactionSource, TransactionValidity}, ApplyExtrinsicResult, }; -pub use sp_runtime::{MultiAddress, Perbill, Permill}; +pub use sp_runtime::{MultiAddress, Perbill, Permill, RuntimeDebug}; #[cfg(feature = "std")] use sp_version::NativeVersion; use sp_version::RuntimeVersion; @@ -401,6 +403,119 @@ impl pallet_multisig::Config for Runtime { type WeightInfo = weights::pallet_multisig::WeightInfo; } +/// The type used to represent the kinds of proxying allowed. +#[derive( + Copy, + Clone, + Eq, + PartialEq, + Ord, + PartialOrd, + Encode, + Decode, + RuntimeDebug, + MaxEncodedLen, + scale_info::TypeInfo, +)] +pub enum ProxyType { + /// Fully permissioned proxy. Can execute any call on behalf of _proxied_. + Any, + /// Can execute any call that does not transfer funds or assets. + NonTransfer, + /// Proxy with the ability to reject time-delay proxy announcements. + CancelProxy, + /// Proxy for all Identity pallet calls. + Identity, + /// Proxy for identity registrars. + IdentityJudgement, + /// Collator selection proxy. Can execute calls related to collator selection mechanism. + Collator, +} +impl Default for ProxyType { + fn default() -> Self { + Self::Any + } +} + +impl InstanceFilter for ProxyType { + fn filter(&self, c: &RuntimeCall) -> bool { + match self { + ProxyType::Any => true, + ProxyType::NonTransfer => !matches!( + c, + RuntimeCall::Balances { .. } | + // `request_judgement` puts up a deposit to transfer to a registrar + RuntimeCall::Identity(pallet_identity::Call::request_judgement { .. }) + ), + ProxyType::CancelProxy => matches!( + c, + RuntimeCall::Proxy(pallet_proxy::Call::reject_announcement { .. }) | + RuntimeCall::Utility { .. } | + RuntimeCall::Multisig { .. } + ), + ProxyType::Identity => { + matches!( + c, + RuntimeCall::Identity { .. } | + RuntimeCall::Utility { .. } | + RuntimeCall::Multisig { .. } + ) + }, + ProxyType::IdentityJudgement => matches!( + c, + RuntimeCall::Identity(pallet_identity::Call::provide_judgement { .. }) | + RuntimeCall::Utility(..) | + RuntimeCall::Multisig { .. } + ), + ProxyType::Collator => matches!( + c, + RuntimeCall::CollatorSelection { .. } | + RuntimeCall::Utility { .. } | + RuntimeCall::Multisig { .. } + ), + } + } + + fn is_superset(&self, o: &Self) -> bool { + match (self, o) { + (x, y) if x == y => true, + (ProxyType::Any, _) => true, + (_, ProxyType::Any) => false, + (ProxyType::Identity, ProxyType::IdentityJudgement) => true, + (ProxyType::NonTransfer, ProxyType::IdentityJudgement) => true, + (ProxyType::NonTransfer, ProxyType::Collator) => true, + _ => false, + } + } +} + +parameter_types! { + // One storage item; key size 32, value size 8. + pub const ProxyDepositBase: Balance = deposit(1, 40); + // Additional storage item size of 33 bytes. + pub const ProxyDepositFactor: Balance = deposit(0, 33); + pub const MaxProxies: u16 = 32; + // One storage item; key size 32, value size 16. + pub const AnnouncementDepositBase: Balance = deposit(1, 48); + pub const AnnouncementDepositFactor: Balance = deposit(0, 66); + pub const MaxPending: u16 = 32; +} + +impl pallet_proxy::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type Currency = Balances; + type ProxyType = ProxyType; + type ProxyDepositBase = ProxyDepositBase; + type ProxyDepositFactor = ProxyDepositFactor; + type MaxProxies = MaxProxies; + type WeightInfo = weights::pallet_proxy::WeightInfo; + type MaxPending = MaxPending; + type CallHasher = BlakeTwo256; + type AnnouncementDepositBase = AnnouncementDepositBase; + type AnnouncementDepositFactor = AnnouncementDepositFactor; +} + impl pallet_utility::Config for Runtime { type RuntimeEvent = RuntimeEvent; type RuntimeCall = RuntimeCall; @@ -446,6 +561,7 @@ construct_runtime!( // Handy utilities. Utility: pallet_utility = 40, Multisig: pallet_multisig = 41, + Proxy: pallet_proxy = 42, // The main stage. Identity: pallet_identity = 50, @@ -464,6 +580,7 @@ mod benches { [pallet_identity, Identity] [pallet_message_queue, MessageQueue] [pallet_multisig, Multisig] + [pallet_proxy, Proxy] [pallet_session, SessionBench::] [pallet_utility, Utility] [pallet_timestamp, Timestamp] diff --git a/cumulus/parachains/runtimes/people/people-westend/src/weights/mod.rs b/cumulus/parachains/runtimes/people/people-westend/src/weights/mod.rs index 3396a8caea0..dce959e817b 100644 --- a/cumulus/parachains/runtimes/people/people-westend/src/weights/mod.rs +++ b/cumulus/parachains/runtimes/people/people-westend/src/weights/mod.rs @@ -25,6 +25,7 @@ pub mod pallet_collator_selection; pub mod pallet_identity; pub mod pallet_message_queue; pub mod pallet_multisig; +pub mod pallet_proxy; pub mod pallet_session; pub mod pallet_timestamp; pub mod pallet_utility; diff --git a/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_proxy.rs b/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_proxy.rs new file mode 100644 index 00000000000..e962123f216 --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_proxy.rs @@ -0,0 +1,226 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus 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. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . + +//! Autogenerated weights for `pallet_proxy` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-07-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-ynta1nyy-project-238-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! EXECUTION: ``, WASM-EXECUTION: `Compiled`, CHAIN: `Some("people-westend-dev")`, DB CACHE: 1024 + +// Executed Command: +// ./target/production/polkadot-parachain +// benchmark +// pallet +// --chain=people-westend-dev +// --wasm-execution=compiled +// --pallet=pallet_proxy +// --no-storage-info +// --no-median-slopes +// --no-min-squares +// --extrinsic=* +// --steps=50 +// --repeat=20 +// --json +// --header=./file_header.txt +// --output=./parachains/runtimes/people/people-westend/src/weights/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weight functions for `pallet_proxy`. +pub struct WeightInfo(PhantomData); +impl pallet_proxy::WeightInfo for WeightInfo { + /// Storage: `Proxy::Proxies` (r:1 w:0) + /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) + /// The range of component `p` is `[1, 31]`. + fn proxy(p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `127 + p * (37 ±0)` + // Estimated: `4706` + // Minimum execution time: 16_417_000 picoseconds. + Weight::from_parts(17_283_443, 0) + .saturating_add(Weight::from_parts(0, 4706)) + // Standard Error: 2_409 + .saturating_add(Weight::from_parts(32_123, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(1)) + } + /// Storage: `Proxy::Proxies` (r:1 w:0) + /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) + /// Storage: `Proxy::Announcements` (r:1 w:1) + /// Proof: `Proxy::Announcements` (`max_values`: None, `max_size`: Some(2233), added: 4708, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// The range of component `a` is `[0, 31]`. + /// The range of component `p` is `[1, 31]`. + fn proxy_announced(a: u32, p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `454 + a * (68 ±0) + p * (37 ±0)` + // Estimated: `5698` + // Minimum execution time: 37_572_000 picoseconds. + Weight::from_parts(37_045_756, 0) + .saturating_add(Weight::from_parts(0, 5698)) + // Standard Error: 2_896 + .saturating_add(Weight::from_parts(139_561, 0).saturating_mul(a.into())) + // Standard Error: 2_993 + .saturating_add(Weight::from_parts(73_270, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `Proxy::Announcements` (r:1 w:1) + /// Proof: `Proxy::Announcements` (`max_values`: None, `max_size`: Some(2233), added: 4708, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// The range of component `a` is `[0, 31]`. + /// The range of component `p` is `[1, 31]`. + fn remove_announcement(a: u32, p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `369 + a * (68 ±0)` + // Estimated: `5698` + // Minimum execution time: 24_066_000 picoseconds. + Weight::from_parts(24_711_403, 0) + .saturating_add(Weight::from_parts(0, 5698)) + // Standard Error: 1_626 + .saturating_add(Weight::from_parts(128_391, 0).saturating_mul(a.into())) + // Standard Error: 1_680 + .saturating_add(Weight::from_parts(23_124, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `Proxy::Announcements` (r:1 w:1) + /// Proof: `Proxy::Announcements` (`max_values`: None, `max_size`: Some(2233), added: 4708, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// The range of component `a` is `[0, 31]`. + /// The range of component `p` is `[1, 31]`. + fn reject_announcement(a: u32, p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `369 + a * (68 ±0)` + // Estimated: `5698` + // Minimum execution time: 24_162_000 picoseconds. + Weight::from_parts(23_928_058, 0) + .saturating_add(Weight::from_parts(0, 5698)) + // Standard Error: 2_072 + .saturating_add(Weight::from_parts(152_299, 0).saturating_mul(a.into())) + // Standard Error: 2_141 + .saturating_add(Weight::from_parts(39_775, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `Proxy::Proxies` (r:1 w:0) + /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) + /// Storage: `Proxy::Announcements` (r:1 w:1) + /// Proof: `Proxy::Announcements` (`max_values`: None, `max_size`: Some(2233), added: 4708, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// The range of component `a` is `[0, 31]`. + /// The range of component `p` is `[1, 31]`. + fn announce(a: u32, p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `386 + a * (68 ±0) + p * (37 ±0)` + // Estimated: `5698` + // Minimum execution time: 33_858_000 picoseconds. + Weight::from_parts(33_568_059, 0) + .saturating_add(Weight::from_parts(0, 5698)) + // Standard Error: 1_816 + .saturating_add(Weight::from_parts(134_400, 0).saturating_mul(a.into())) + // Standard Error: 1_876 + .saturating_add(Weight::from_parts(57_028, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `Proxy::Proxies` (r:1 w:1) + /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) + /// The range of component `p` is `[1, 31]`. + fn add_proxy(p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `127 + p * (37 ±0)` + // Estimated: `4706` + // Minimum execution time: 24_947_000 picoseconds. + Weight::from_parts(26_235_199, 0) + .saturating_add(Weight::from_parts(0, 4706)) + // Standard Error: 1_363 + .saturating_add(Weight::from_parts(41_435, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Proxy::Proxies` (r:1 w:1) + /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) + /// The range of component `p` is `[1, 31]`. + fn remove_proxy(p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `127 + p * (37 ±0)` + // Estimated: `4706` + // Minimum execution time: 25_186_000 picoseconds. + Weight::from_parts(26_823_133, 0) + .saturating_add(Weight::from_parts(0, 4706)) + // Standard Error: 2_259 + .saturating_add(Weight::from_parts(34_224, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Proxy::Proxies` (r:1 w:1) + /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) + /// The range of component `p` is `[1, 31]`. + fn remove_proxies(p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `127 + p * (37 ±0)` + // Estimated: `4706` + // Minimum execution time: 22_156_000 picoseconds. + Weight::from_parts(23_304_060, 0) + .saturating_add(Weight::from_parts(0, 4706)) + // Standard Error: 1_738 + .saturating_add(Weight::from_parts(39_612, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Proxy::Proxies` (r:1 w:1) + /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) + /// The range of component `p` is `[1, 31]`. + fn create_pure(p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `139` + // Estimated: `4706` + // Minimum execution time: 26_914_000 picoseconds. + Weight::from_parts(28_009_062, 0) + .saturating_add(Weight::from_parts(0, 4706)) + // Standard Error: 1_978 + .saturating_add(Weight::from_parts(12_255, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Proxy::Proxies` (r:1 w:1) + /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) + /// The range of component `p` is `[0, 30]`. + fn kill_pure(p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `164 + p * (37 ±0)` + // Estimated: `4706` + // Minimum execution time: 23_281_000 picoseconds. + Weight::from_parts(24_392_989, 0) + .saturating_add(Weight::from_parts(0, 4706)) + // Standard Error: 2_943 + .saturating_add(Weight::from_parts(30_287, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } +} diff --git a/prdoc/pr_5509.prdoc b/prdoc/pr_5509.prdoc new file mode 100644 index 00000000000..154146034e6 --- /dev/null +++ b/prdoc/pr_5509.prdoc @@ -0,0 +1,17 @@ +title: Add `pallet_proxy` to People Chain and Coretime Chain testnet runtimes. + +doc: + - audience: Runtime User + description: | + Proxies can now be used on `coretime-rococo`, `coretime-westend`, `people-rococo` and + `people-westend` in the same way as they can be on Kusama and Polkadot chains. + +crates: + - name: coretime-rococo-runtime + bump: major + - name: coretime-westend-runtime + bump: major + - name: people-rococo-runtime + bump: major + - name: people-westend-runtime + bump: major -- GitLab From cd69f209b71c554f06d3061169ad00c5f1c170b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=B3nal=20Murray?= Date: Thu, 12 Sep 2024 17:48:04 +0100 Subject: [PATCH 052/125] Add emulated tests for the Coretime Interface calls to ensure sufficient weights (#2704) Add emulated test cases for the coretime chain. This tests the calls sent across the `CoretimeInterface` and ensures the weights are sufficient. --- Cargo.lock | 14 +- .../coretime/coretime-rococo/Cargo.toml | 2 +- .../coretime/coretime-westend/Cargo.toml | 2 +- .../tests/coretime/coretime-rococo/Cargo.toml | 5 +- .../tests/coretime/coretime-rococo/src/lib.rs | 4 +- .../src/tests/coretime_interface.rs | 235 ++++++++++++++++++ .../coretime/coretime-rococo/src/tests/mod.rs | 1 + .../coretime/coretime-westend/Cargo.toml | 5 +- .../coretime/coretime-westend/src/lib.rs | 4 +- .../src/tests/coretime_interface.rs | 223 +++++++++++++++++ .../coretime-westend/src/tests/mod.rs | 1 + 11 files changed, 484 insertions(+), 12 deletions(-) create mode 100644 cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-rococo/src/tests/coretime_interface.rs create mode 100644 cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-westend/src/tests/coretime_interface.rs diff --git a/Cargo.lock b/Cargo.lock index d30acc0f5b2..e5b12a60da2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3299,7 +3299,7 @@ dependencies = [ [[package]] name = "coretime-rococo-emulated-chain" -version = "0.0.0" +version = "0.1.0" dependencies = [ "coretime-rococo-runtime", "cumulus-primitives-core", @@ -3312,14 +3312,17 @@ dependencies = [ [[package]] name = "coretime-rococo-integration-tests" -version = "0.1.0" +version = "0.0.0" dependencies = [ + "cumulus-pallet-parachain-system", "emulated-integration-tests-common", "frame-support", "pallet-balances", + "pallet-broker", "pallet-identity", "pallet-message-queue", "polkadot-runtime-common", + "polkadot-runtime-parachains", "rococo-runtime-constants", "rococo-system-emulated-network", "sp-runtime", @@ -3396,7 +3399,7 @@ dependencies = [ [[package]] name = "coretime-westend-emulated-chain" -version = "0.0.0" +version = "0.1.0" dependencies = [ "coretime-westend-runtime", "cumulus-primitives-core", @@ -3409,14 +3412,17 @@ dependencies = [ [[package]] name = "coretime-westend-integration-tests" -version = "0.1.0" +version = "0.0.0" dependencies = [ + "cumulus-pallet-parachain-system", "emulated-integration-tests-common", "frame-support", "pallet-balances", + "pallet-broker", "pallet-identity", "pallet-message-queue", "polkadot-runtime-common", + "polkadot-runtime-parachains", "sp-runtime", "staging-xcm", "staging-xcm-executor", diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/coretime/coretime-rococo/Cargo.toml b/cumulus/parachains/integration-tests/emulated/chains/parachains/coretime/coretime-rococo/Cargo.toml index 6af3f270a90..94d43c5eee2 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/coretime/coretime-rococo/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/coretime/coretime-rococo/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "coretime-rococo-emulated-chain" -version = "0.0.0" +version = "0.1.0" authors.workspace = true edition.workspace = true license = "Apache-2.0" diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/coretime/coretime-westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/chains/parachains/coretime/coretime-westend/Cargo.toml index 895a984eccb..2640c27d016 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/coretime/coretime-westend/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/coretime/coretime-westend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "coretime-westend-emulated-chain" -version = "0.0.0" +version = "0.1.0" authors.workspace = true edition.workspace = true license = "Apache-2.0" diff --git a/cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-rococo/Cargo.toml b/cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-rococo/Cargo.toml index 259be790c3e..28d9da0993f 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-rococo/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-rococo/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "coretime-rococo-integration-tests" -version = "0.1.0" +version = "0.0.0" authors.workspace = true edition.workspace = true license = "Apache-2.0" @@ -12,16 +12,19 @@ publish = false # Substrate frame-support = { workspace = true } pallet-balances = { workspace = true } +pallet-broker = { workspace = true, default-features = true } pallet-message-queue = { workspace = true } pallet-identity = { workspace = true } sp-runtime = { workspace = true } # Polkadot polkadot-runtime-common = { workspace = true, default-features = true } +polkadot-runtime-parachains = { workspace = true, default-features = true } rococo-runtime-constants = { workspace = true, default-features = true } xcm = { workspace = true } xcm-executor = { workspace = true } # Cumulus +cumulus-pallet-parachain-system = { workspace = true, default-features = true } emulated-integration-tests-common = { workspace = true } rococo-system-emulated-network = { workspace = true } diff --git a/cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-rococo/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-rococo/src/lib.rs index ad3c4fd58da..055bd50d829 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-rococo/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-rococo/src/lib.rs @@ -24,7 +24,7 @@ mod imports { // Cumulus pub use emulated_integration_tests_common::xcm_emulator::{ - assert_expected_events, bx, TestExt, + assert_expected_events, bx, Chain, Parachain, TestExt, }; pub use rococo_system_emulated_network::{ coretime_rococo_emulated_chain::{ @@ -32,7 +32,7 @@ mod imports { CoretimeRococoParaPallet as CoretimeRococoPallet, }, CoretimeRococoPara as CoretimeRococo, CoretimeRococoParaReceiver as CoretimeRococoReceiver, - CoretimeRococoParaSender as CoretimeRococoSender, + CoretimeRococoParaSender as CoretimeRococoSender, RococoRelay as Rococo, }; } diff --git a/cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-rococo/src/tests/coretime_interface.rs b/cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-rococo/src/tests/coretime_interface.rs new file mode 100644 index 00000000000..584bce8f1df --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-rococo/src/tests/coretime_interface.rs @@ -0,0 +1,235 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::imports::*; +use frame_support::traits::OnInitialize; +use pallet_broker::{ConfigRecord, Configuration, CoreAssignment, CoreMask, ScheduleItem}; +use rococo_runtime_constants::system_parachain::coretime::TIMESLICE_PERIOD; +use sp_runtime::Perbill; + +#[test] +fn transact_hardcoded_weights_are_sane() { + // There are three transacts with hardcoded weights sent from the Coretime Chain to the Relay + // Chain across the CoretimeInterface which are triggered at various points in the sales cycle. + // - Request core count - triggered directly by `start_sales` or `request_core_count` + // extrinsics. + // - Request revenue info - triggered when each timeslice is committed. + // - Assign core - triggered when an entry is encountered in the workplan for the next + // timeslice. + + // RuntimeEvent aliases to avoid warning from usage of qualified paths in assertions due to + // + type CoretimeEvent = ::RuntimeEvent; + type RelayEvent = ::RuntimeEvent; + + // Reserve a workload, configure broker and start sales. + CoretimeRococo::execute_with(|| { + // Hooks don't run in emulated tests - workaround as we need `on_initialize` to tick things + // along and have no concept of time passing otherwise. + ::Broker::on_initialize( + ::System::block_number(), + ); + + let coretime_root_origin = ::RuntimeOrigin::root(); + + // Create and populate schedule with the worst case assignment on this core. + let mut schedule = Vec::new(); + for i in 0..27 { + schedule.push(ScheduleItem { + mask: CoreMask::void().set(i), + assignment: CoreAssignment::Task(2000 + i), + }) + } + + assert_ok!(::Broker::reserve( + coretime_root_origin.clone(), + schedule.try_into().expect("Vector is within bounds."), + )); + + // Configure broker and start sales. + let config = ConfigRecord { + advance_notice: 1, + interlude_length: 1, + leadin_length: 2, + region_length: 1, + ideal_bulk_proportion: Perbill::from_percent(40), + limit_cores_offered: None, + renewal_bump: Perbill::from_percent(2), + contribution_timeout: 1, + }; + assert_ok!(::Broker::configure( + coretime_root_origin.clone(), + config + )); + assert_ok!(::Broker::start_sales( + coretime_root_origin, + 100, + 0 + )); + assert_eq!( + pallet_broker::Status::<::Runtime>::get() + .unwrap() + .core_count, + 1 + ); + + assert_expected_events!( + CoretimeRococo, + vec![ + CoretimeEvent::Broker( + pallet_broker::Event::ReservationMade { .. } + ) => {}, + CoretimeEvent::Broker( + pallet_broker::Event::CoreCountRequested { core_count: 1 } + ) => {}, + CoretimeEvent::ParachainSystem( + cumulus_pallet_parachain_system::Event::UpwardMessageSent { .. } + ) => {}, + ] + ); + }); + + // Check that the request_core_count message was processed successfully. This will fail if the + // weights are misconfigured. + Rococo::execute_with(|| { + Rococo::assert_ump_queue_processed(true, Some(CoretimeRococo::para_id()), None); + + assert_expected_events!( + Rococo, + vec![ + RelayEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + ] + ); + }); + + // Keep track of the relay chain block number so we can fast forward while still checking the + // right block. + let mut block_number_cursor = Rococo::ext_wrapper(::System::block_number); + + let config = CoretimeRococo::ext_wrapper(|| { + Configuration::<::Runtime>::get() + .expect("Pallet was configured earlier.") + }); + + // Now run up to the block before the sale is rotated. + while block_number_cursor < TIMESLICE_PERIOD - config.advance_notice - 1 { + CoretimeRococo::execute_with(|| { + // Hooks don't run in emulated tests - workaround. + ::Broker::on_initialize( + ::System::block_number(), + ); + }); + + Rococo::ext_wrapper(|| { + block_number_cursor = ::System::block_number(); + }); + } + + // In this block we trigger assign core. + CoretimeRococo::execute_with(|| { + // Hooks don't run in emulated tests - workaround. + ::Broker::on_initialize( + ::System::block_number(), + ); + + assert_expected_events!( + CoretimeRococo, + vec![ + CoretimeEvent::Broker( + pallet_broker::Event::SaleInitialized { .. } + ) => {}, + CoretimeEvent::Broker( + pallet_broker::Event::CoreAssigned { .. } + ) => {}, + CoretimeEvent::ParachainSystem( + cumulus_pallet_parachain_system::Event::UpwardMessageSent { .. } + ) => {}, + ] + ); + }); + + // Check that the assign_core message was processed successfully. + // This will fail if the weights are misconfigured. + Rococo::execute_with(|| { + Rococo::assert_ump_queue_processed(true, Some(CoretimeRococo::para_id()), None); + + assert_expected_events!( + Rococo, + vec![ + RelayEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + RelayEvent::Coretime( + polkadot_runtime_parachains::coretime::Event::CoreAssigned { .. } + ) => {}, + ] + ); + }); + + // In this block we trigger request revenue. + CoretimeRococo::execute_with(|| { + // Hooks don't run in emulated tests - workaround. + ::Broker::on_initialize( + ::System::block_number(), + ); + + assert_expected_events!( + CoretimeRococo, + vec![ + CoretimeEvent::ParachainSystem( + cumulus_pallet_parachain_system::Event::UpwardMessageSent { .. } + ) => {}, + ] + ); + }); + + // Check that the request_revenue_info_at message was processed successfully. + // This will fail if the weights are misconfigured. + Rococo::execute_with(|| { + Rococo::assert_ump_queue_processed(true, Some(CoretimeRococo::para_id()), None); + + assert_expected_events!( + Rococo, + vec![ + RelayEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + ] + ); + }); + + // Here we receive and process the notify_revenue XCM with zero revenue. + CoretimeRococo::execute_with(|| { + // Hooks don't run in emulated tests - workaround. + ::Broker::on_initialize( + ::System::block_number(), + ); + + assert_expected_events!( + CoretimeRococo, + vec![ + CoretimeEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + // Zero revenue in first timeslice so history is immediately dropped. + CoretimeEvent::Broker( + pallet_broker::Event::HistoryDropped { when: 0, revenue: 0 } + ) => {}, + ] + ); + }); +} diff --git a/cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-rococo/src/tests/mod.rs b/cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-rococo/src/tests/mod.rs index 0e78351bce0..bb0387a4b35 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-rococo/src/tests/mod.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-rococo/src/tests/mod.rs @@ -14,3 +14,4 @@ // limitations under the License. mod claim_assets; +mod coretime_interface; diff --git a/cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-westend/Cargo.toml index a8fa905d2e5..d57e7926b0e 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-westend/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-westend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "coretime-westend-integration-tests" -version = "0.1.0" +version = "0.0.0" authors.workspace = true edition.workspace = true license = "Apache-2.0" @@ -12,16 +12,19 @@ publish = false # Substrate frame-support = { workspace = true } pallet-balances = { workspace = true } +pallet-broker = { workspace = true, default-features = true } pallet-message-queue = { workspace = true } pallet-identity = { workspace = true } sp-runtime = { workspace = true } # Polkadot polkadot-runtime-common = { workspace = true, default-features = true } +polkadot-runtime-parachains = { workspace = true, default-features = true } westend-runtime-constants = { workspace = true, default-features = true } xcm = { workspace = true } xcm-executor = { workspace = true } # Cumulus +cumulus-pallet-parachain-system = { workspace = true, default-features = true } emulated-integration-tests-common = { workspace = true } westend-system-emulated-network = { workspace = true } diff --git a/cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-westend/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-westend/src/lib.rs index 838ca6eeafb..ac844e0f328 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-westend/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-westend/src/lib.rs @@ -24,7 +24,7 @@ mod imports { // Cumulus pub use emulated_integration_tests_common::xcm_emulator::{ - assert_expected_events, bx, TestExt, + assert_expected_events, bx, Chain, Parachain, TestExt, }; pub use westend_system_emulated_network::{ coretime_westend_emulated_chain::{ @@ -33,7 +33,7 @@ mod imports { }, CoretimeWestendPara as CoretimeWestend, CoretimeWestendParaReceiver as CoretimeWestendReceiver, - CoretimeWestendParaSender as CoretimeWestendSender, + CoretimeWestendParaSender as CoretimeWestendSender, WestendRelay as Westend, }; } diff --git a/cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-westend/src/tests/coretime_interface.rs b/cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-westend/src/tests/coretime_interface.rs new file mode 100644 index 00000000000..f61bc4285a0 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-westend/src/tests/coretime_interface.rs @@ -0,0 +1,223 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::imports::*; +use frame_support::traits::OnInitialize; +use pallet_broker::{ConfigRecord, Configuration, CoreAssignment, CoreMask, ScheduleItem}; +use sp_runtime::Perbill; +use westend_runtime_constants::system_parachain::coretime::TIMESLICE_PERIOD; + +#[test] +fn transact_hardcoded_weights_are_sane() { + // There are three transacts with hardcoded weights sent from the Coretime Chain to the Relay + // Chain across the CoretimeInterface which are triggered at various points in the sales cycle. + // - Request core count - triggered directly by `start_sales` or `request_core_count` + // extrinsics. + // - Request revenue info - triggered when each timeslice is committed. + // - Assign core - triggered when an entry is encountered in the workplan for the next + // timeslice. + + // RuntimeEvent aliases to avoid warning from usage of qualified paths in assertions due to + // + type CoretimeEvent = ::RuntimeEvent; + type RelayEvent = ::RuntimeEvent; + + // Reserve a workload, configure broker and start sales. + CoretimeWestend::execute_with(|| { + // Hooks don't run in emulated tests - workaround as we need `on_initialize` to tick things + // along and have no concept of time passing otherwise. + ::Broker::on_initialize( + ::System::block_number(), + ); + + let coretime_root_origin = ::RuntimeOrigin::root(); + + // Create and populate schedule with the worst case assignment on this core. + let mut schedule = Vec::new(); + for i in 0..27 { + schedule.push(ScheduleItem { + mask: CoreMask::void().set(i), + assignment: CoreAssignment::Task(2000 + i), + }) + } + + assert_ok!(::Broker::reserve( + coretime_root_origin.clone(), + schedule.try_into().expect("Vector is within bounds."), + )); + + // Configure broker and start sales. + let config = ConfigRecord { + advance_notice: 1, + interlude_length: 1, + leadin_length: 2, + region_length: 1, + ideal_bulk_proportion: Perbill::from_percent(40), + limit_cores_offered: None, + renewal_bump: Perbill::from_percent(2), + contribution_timeout: 1, + }; + assert_ok!(::Broker::configure( + coretime_root_origin.clone(), + config + )); + assert_ok!(::Broker::start_sales( + coretime_root_origin, + 100, + 0 + )); + assert_eq!( + pallet_broker::Status::<::Runtime>::get() + .unwrap() + .core_count, + 1 + ); + + assert_expected_events!( + CoretimeWestend, + vec![ + CoretimeEvent::Broker( + pallet_broker::Event::ReservationMade { .. } + ) => {}, + CoretimeEvent::Broker( + pallet_broker::Event::CoreCountRequested { core_count: 1 } + ) => {}, + CoretimeEvent::ParachainSystem( + cumulus_pallet_parachain_system::Event::UpwardMessageSent { .. } + ) => {}, + ] + ); + }); + + // Check that the request_core_count message was processed successfully. This will fail if the + // weights are misconfigured. + Westend::execute_with(|| { + Westend::assert_ump_queue_processed(true, Some(CoretimeWestend::para_id()), None); + + assert_expected_events!( + Westend, + vec![ + RelayEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + ] + ); + }); + + // Keep track of the relay chain block number so we can fast forward while still checking the + // right block. + let mut block_number_cursor = Westend::ext_wrapper(::System::block_number); + + let config = CoretimeWestend::ext_wrapper(|| { + Configuration::<::Runtime>::get() + .expect("Pallet was configured earlier.") + }); + + // Now run up to the block before the sale is rotated. + while block_number_cursor < TIMESLICE_PERIOD - config.advance_notice - 1 { + CoretimeWestend::execute_with(|| { + // Hooks don't run in emulated tests - workaround. + ::Broker::on_initialize( + ::System::block_number(), + ); + }); + + Westend::ext_wrapper(|| { + block_number_cursor = ::System::block_number(); + }); + } + + // In this block we trigger assign core. + CoretimeWestend::execute_with(|| { + // Hooks don't run in emulated tests - workaround. + ::Broker::on_initialize( + ::System::block_number(), + ); + + assert_expected_events!( + CoretimeWestend, + vec![ + CoretimeEvent::Broker( + pallet_broker::Event::SaleInitialized { .. } + ) => {}, + CoretimeEvent::Broker( + pallet_broker::Event::CoreAssigned { .. } + ) => {}, + CoretimeEvent::ParachainSystem( + cumulus_pallet_parachain_system::Event::UpwardMessageSent { .. } + ) => {}, + ] + ); + }); + + // In this block we trigger request revenue. + CoretimeWestend::execute_with(|| { + // Hooks don't run in emulated tests - workaround. + ::Broker::on_initialize( + ::System::block_number(), + ); + + assert_expected_events!( + CoretimeWestend, + vec![ + CoretimeEvent::ParachainSystem( + cumulus_pallet_parachain_system::Event::UpwardMessageSent { .. } + ) => {}, + ] + ); + }); + + // Check that the assign_core and request_revenue_info_at messages were processed successfully. + // This will fail if the weights are misconfigured. + Westend::execute_with(|| { + Westend::assert_ump_queue_processed(true, Some(CoretimeWestend::para_id()), None); + + assert_expected_events!( + Westend, + vec![ + RelayEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + RelayEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + RelayEvent::Coretime( + polkadot_runtime_parachains::coretime::Event::CoreAssigned { .. } + ) => {}, + ] + ); + }); + + // Here we receive and process the notify_revenue XCM with zero revenue. + CoretimeWestend::execute_with(|| { + // Hooks don't run in emulated tests - workaround. + ::Broker::on_initialize( + ::System::block_number(), + ); + + assert_expected_events!( + CoretimeWestend, + vec![ + CoretimeEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + // Zero revenue in first timeslice so history is immediately dropped. + CoretimeEvent::Broker( + pallet_broker::Event::HistoryDropped { when: 0, revenue: 0 } + ) => {}, + ] + ); + }); +} diff --git a/cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-westend/src/tests/mod.rs b/cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-westend/src/tests/mod.rs index 0e78351bce0..bb0387a4b35 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-westend/src/tests/mod.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-westend/src/tests/mod.rs @@ -14,3 +14,4 @@ // limitations under the License. mod claim_assets; +mod coretime_interface; -- GitLab From 8d0aab812ebb70e39b0af893862ba204dc098860 Mon Sep 17 00:00:00 2001 From: PG Herveou Date: Thu, 12 Sep 2024 19:26:59 +0200 Subject: [PATCH 053/125] [pallet-revive] fix xcm tests (#5684) fix https://github.com/paritytech/polkadot-sdk/issues/5683 --- Cargo.lock | 1 + prdoc/pr_5684.prdoc | 19 +++++++ substrate/frame/revive/fixtures/Cargo.toml | 2 + substrate/frame/revive/fixtures/src/lib.rs | 1 + .../frame/revive/mock-network/Cargo.toml | 1 + .../frame/revive/mock-network/src/tests.rs | 55 +++++++++---------- substrate/frame/revive/src/wasm/runtime.rs | 7 ++- .../frame/revive/uapi/src/host/riscv32.rs | 9 ++- umbrella/Cargo.toml | 1 + 9 files changed, 64 insertions(+), 32 deletions(-) create mode 100644 prdoc/pr_5684.prdoc diff --git a/Cargo.lock b/Cargo.lock index e5b12a60da2..5f0fa0f613f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11471,6 +11471,7 @@ version = "0.1.0" dependencies = [ "anyhow", "frame-system", + "log", "parity-wasm", "polkavm-linker 0.10.0", "sp-core", diff --git a/prdoc/pr_5684.prdoc b/prdoc/pr_5684.prdoc new file mode 100644 index 00000000000..a17bacd2fb9 --- /dev/null +++ b/prdoc/pr_5684.prdoc @@ -0,0 +1,19 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "[pallet-revive]" + +doc: + - audience: Runtime Devs + description: | + Update xcm runtime api, and fix pallet-revive xcm tests + +crates: + - name: pallet-revive + bump: patch + - name: pallet-revive-fixtures + bump: patch + - name: pallet-revive-mock-network + bump: patch + - name: polkadot-sdk + bump: patch diff --git a/substrate/frame/revive/fixtures/Cargo.toml b/substrate/frame/revive/fixtures/Cargo.toml index db284c7cc06..903298d2df2 100644 --- a/substrate/frame/revive/fixtures/Cargo.toml +++ b/substrate/frame/revive/fixtures/Cargo.toml @@ -16,6 +16,7 @@ sp-core = { workspace = true, default-features = true, optional = true } sp-io = { workspace = true, default-features = true, optional = true } sp-runtime = { workspace = true, default-features = true, optional = true } anyhow = { workspace = true, default-features = true, optional = true } +log = { workspace = true } [build-dependencies] parity-wasm = { workspace = true } @@ -34,6 +35,7 @@ riscv = [] std = [ "anyhow", "frame-system", + "log/std", "sp-core", "sp-io", "sp-runtime", diff --git a/substrate/frame/revive/fixtures/src/lib.rs b/substrate/frame/revive/fixtures/src/lib.rs index 54e32130635..5548dca66d0 100644 --- a/substrate/frame/revive/fixtures/src/lib.rs +++ b/substrate/frame/revive/fixtures/src/lib.rs @@ -24,6 +24,7 @@ extern crate alloc; pub fn compile_module(fixture_name: &str) -> anyhow::Result<(Vec, sp_core::H256)> { let out_dir: std::path::PathBuf = env!("OUT_DIR").into(); let fixture_path = out_dir.join(format!("{fixture_name}.polkavm")); + log::debug!("Loading fixture from {fixture_path:?}"); let binary = std::fs::read(fixture_path)?; let code_hash = sp_io::hashing::keccak_256(&binary); Ok((binary, sp_core::H256(code_hash))) diff --git a/substrate/frame/revive/mock-network/Cargo.toml b/substrate/frame/revive/mock-network/Cargo.toml index 0d597bbdc22..85656a57b49 100644 --- a/substrate/frame/revive/mock-network/Cargo.toml +++ b/substrate/frame/revive/mock-network/Cargo.toml @@ -48,6 +48,7 @@ pallet-revive-fixtures = { workspace = true } [features] default = ["std"] +riscv = ["pallet-revive-fixtures/riscv"] std = [ "codec/std", "frame-support/std", diff --git a/substrate/frame/revive/mock-network/src/tests.rs b/substrate/frame/revive/mock-network/src/tests.rs index 9259dd6f169..bd05726a1a4 100644 --- a/substrate/frame/revive/mock-network/src/tests.rs +++ b/substrate/frame/revive/mock-network/src/tests.rs @@ -16,10 +16,8 @@ // limitations under the License. use crate::{ - parachain::{self, Runtime}, - parachain_account_sovereign_account_id, - primitives::{AccountId, CENTS}, - relay_chain, MockNet, ParaA, ParachainBalances, Relay, ALICE, BOB, INITIAL_BALANCE, + parachain, parachain_account_sovereign_account_id, primitives::CENTS, relay_chain, MockNet, + ParaA, ParachainBalances, Relay, ALICE, BOB, INITIAL_BALANCE, }; use codec::{Decode, Encode}; use frame_support::traits::{fungibles::Mutate, Currency}; @@ -30,6 +28,7 @@ use pallet_revive::{ }; use pallet_revive_fixtures::compile_module; use pallet_revive_uapi::ReturnErrorCode; +use sp_core::H160; use xcm::{v4::prelude::*, VersionedLocation, VersionedXcm}; use xcm_simulator::TestExt; @@ -39,41 +38,43 @@ macro_rules! assert_return_code { }}; } -fn bare_call(dest: sp_runtime::AccountId32) -> BareCallBuilder { +fn bare_call(dest: H160) -> BareCallBuilder { BareCallBuilder::::bare_call(RawOrigin::Signed(ALICE).into(), dest) } /// Instantiate the tests contract, and fund it with some balance and assets. -fn instantiate_test_contract(name: &str) -> AccountId { - let (wasm, _) = compile_module::(name).unwrap(); +fn instantiate_test_contract(name: &str) -> Contract { + let (wasm, _) = compile_module(name).unwrap(); // Instantiate contract. - let contract_addr = ParaA::execute_with(|| { + let contract = ParaA::execute_with(|| { BareInstantiateBuilder::::bare_instantiate( RawOrigin::Signed(ALICE).into(), Code::Upload(wasm), ) - .build_and_unwrap_account_id() + .storage_deposit_limit(1_000_000_000_000) + .build_and_unwrap_contract() }); // Funds contract account with some balance and assets. ParaA::execute_with(|| { - parachain::Balances::make_free_balance_be(&contract_addr, INITIAL_BALANCE); - parachain::Assets::mint_into(0u32.into(), &contract_addr, INITIAL_BALANCE).unwrap(); + parachain::Balances::make_free_balance_be(&contract.account_id, INITIAL_BALANCE); + parachain::Assets::mint_into(0u32.into(), &contract.account_id, INITIAL_BALANCE).unwrap(); }); Relay::execute_with(|| { - let sovereign_account = parachain_account_sovereign_account_id(1u32, contract_addr.clone()); + let sovereign_account = + parachain_account_sovereign_account_id(1u32, contract.account_id.clone()); relay_chain::Balances::make_free_balance_be(&sovereign_account, INITIAL_BALANCE); }); - contract_addr + contract } #[test] fn test_xcm_execute() { MockNet::reset(); - let contract_addr = instantiate_test_contract("xcm_execute"); + let Contract { addr, account_id } = instantiate_test_contract("xcm_execute"); // Execute XCM instructions through the contract. ParaA::execute_with(|| { @@ -87,9 +88,7 @@ fn test_xcm_execute() { .deposit_asset(assets, beneficiary) .build(); - let result = bare_call(contract_addr.clone()) - .data(VersionedXcm::V4(message).encode()) - .build(); + let result = bare_call(addr).data(VersionedXcm::V4(message).encode()).build(); assert_eq!(result.gas_consumed, result.gas_required); assert_return_code!(&result.result.unwrap(), ReturnErrorCode::Success); @@ -98,7 +97,7 @@ fn test_xcm_execute() { // Bob. let initial = INITIAL_BALANCE; assert_eq!(ParachainBalances::free_balance(BOB), initial + amount); - assert_eq!(ParachainBalances::free_balance(&contract_addr), initial - amount); + assert_eq!(ParachainBalances::free_balance(&account_id), initial - amount); }); } @@ -106,7 +105,7 @@ fn test_xcm_execute() { fn test_xcm_execute_incomplete() { MockNet::reset(); - let contract_addr = instantiate_test_contract("xcm_execute"); + let Contract { addr, account_id } = instantiate_test_contract("xcm_execute"); let amount = 10 * CENTS; // Execute XCM instructions through the contract. @@ -124,15 +123,13 @@ fn test_xcm_execute_incomplete() { .deposit_asset(assets, beneficiary) .build(); - let result = bare_call(contract_addr.clone()) - .data(VersionedXcm::V4(message).encode()) - .build(); + let result = bare_call(addr).data(VersionedXcm::V4(message).encode()).build(); assert_eq!(result.gas_consumed, result.gas_required); assert_return_code!(&result.result.unwrap(), ReturnErrorCode::XcmExecutionFailed); assert_eq!(ParachainBalances::free_balance(BOB), INITIAL_BALANCE); - assert_eq!(ParachainBalances::free_balance(&contract_addr), INITIAL_BALANCE - amount); + assert_eq!(ParachainBalances::free_balance(&account_id), INITIAL_BALANCE - amount); }); } @@ -140,11 +137,11 @@ fn test_xcm_execute_incomplete() { fn test_xcm_execute_reentrant_call() { MockNet::reset(); - let contract_addr = instantiate_test_contract("xcm_execute"); + let Contract { addr, .. } = instantiate_test_contract("xcm_execute"); ParaA::execute_with(|| { let transact_call = parachain::RuntimeCall::Contracts(pallet_revive::Call::call { - dest: contract_addr.clone(), + dest: addr, gas_limit: 1_000_000.into(), storage_deposit_limit: test_utils::deposit_limit::(), data: vec![], @@ -157,7 +154,7 @@ fn test_xcm_execute_reentrant_call() { .expect_transact_status(MaybeErrorCode::Success) .build(); - let result = bare_call(contract_addr.clone()) + let result = bare_call(addr) .data(VersionedXcm::V4(message).encode()) .build_and_unwrap_result(); @@ -171,7 +168,7 @@ fn test_xcm_execute_reentrant_call() { #[test] fn test_xcm_send() { MockNet::reset(); - let contract_addr = instantiate_test_contract("xcm_send"); + let Contract { addr, account_id } = instantiate_test_contract("xcm_send"); let amount = 1_000 * CENTS; let fee = parachain::estimate_message_fee(4); // Accounts for the `DescendOrigin` instruction added by `send_xcm` @@ -188,7 +185,7 @@ fn test_xcm_send() { .deposit_asset(assets, beneficiary) .build(); - let result = bare_call(contract_addr.clone()) + let result = bare_call(addr) .data((dest, VersionedXcm::V4(message)).encode()) .build_and_unwrap_result(); @@ -197,7 +194,7 @@ fn test_xcm_send() { }); Relay::execute_with(|| { - let derived_contract_addr = ¶chain_account_sovereign_account_id(1, contract_addr); + let derived_contract_addr = ¶chain_account_sovereign_account_id(1, account_id); assert_eq!( INITIAL_BALANCE - amount, relay_chain::Balances::free_balance(derived_contract_addr) diff --git a/substrate/frame/revive/src/wasm/runtime.rs b/substrate/frame/revive/src/wasm/runtime.rs index d9257d38b66..02d60ee9e34 100644 --- a/substrate/frame/revive/src/wasm/runtime.rs +++ b/substrate/frame/revive/src/wasm/runtime.rs @@ -1790,6 +1790,7 @@ pub mod env { &mut self, memory: &mut M, dest_ptr: u32, + dest_len: u32, msg_ptr: u32, msg_len: u32, output_ptr: u32, @@ -1797,10 +1798,12 @@ pub mod env { use xcm::{VersionedLocation, VersionedXcm}; use xcm_builder::{SendController, SendControllerWeightInfo}; - self.charge_gas(RuntimeCosts::CopyFromContract(msg_len))?; - let dest: VersionedLocation = memory.read_as(dest_ptr)?; + self.charge_gas(RuntimeCosts::CopyFromContract(dest_len))?; + let dest: VersionedLocation = memory.read_as_unbounded(dest_ptr, dest_len)?; + self.charge_gas(RuntimeCosts::CopyFromContract(msg_len))?; let message: VersionedXcm<()> = memory.read_as_unbounded(msg_ptr, msg_len)?; + let weight = <::Xcm as SendController<_>>::WeightInfo::send(); self.charge_gas(RuntimeCosts::CallRuntime(weight))?; let origin = crate::RawOrigin::Signed(self.ext.account_id().clone()).into(); diff --git a/substrate/frame/revive/uapi/src/host/riscv32.rs b/substrate/frame/revive/uapi/src/host/riscv32.rs index b7b660c4083..89cf9278644 100644 --- a/substrate/frame/revive/uapi/src/host/riscv32.rs +++ b/substrate/frame/revive/uapi/src/host/riscv32.rs @@ -124,6 +124,7 @@ mod sys { pub fn xcm_execute(msg_ptr: *const u8, msg_len: u32) -> ReturnCode; pub fn xcm_send( dest_ptr: *const u8, + dest_len: *const u8, msg_ptr: *const u8, msg_len: u32, out_ptr: *mut u8, @@ -530,7 +531,13 @@ impl HostFn for HostFnImpl { fn xcm_send(dest: &[u8], msg: &[u8], output: &mut [u8; 32]) -> Result { let ret_code = unsafe { - sys::xcm_send(dest.as_ptr(), msg.as_ptr(), msg.len() as _, output.as_mut_ptr()) + sys::xcm_send( + dest.as_ptr(), + dest.len() as _, + msg.as_ptr(), + msg.len() as _, + output.as_mut_ptr(), + ) }; ret_code.into() } diff --git a/umbrella/Cargo.toml b/umbrella/Cargo.toml index 6d380a4bcbb..b7c1c375094 100644 --- a/umbrella/Cargo.toml +++ b/umbrella/Cargo.toml @@ -607,6 +607,7 @@ tuples-96 = [ ] riscv = [ "pallet-revive-fixtures?/riscv", + "pallet-revive-mock-network?/riscv", "pallet-revive?/riscv", ] -- GitLab From 766ab8283ed331b1fc3bbfc8b6737d35b5eb03d4 Mon Sep 17 00:00:00 2001 From: Cyrill Leutwiler Date: Fri, 13 Sep 2024 09:03:40 +0200 Subject: [PATCH 054/125] [pallet-revive] Add balance_of syscyall for fetching foreign balances (#5675) This adds an API method `balance_of`, corresponding to the [BALANCE](https://www.evm.codes/#31?fork=cancun) EVM opcode. In `Ext`, `balance` and `balance_of` are internally routed through the same new `account_balance` method: `balance` is technically the same as `balance_of` with the caller address. This avoids duplicating all the tests and avoids a small inefficiency (in theory, `balance` directly call `balance_of` however this introduces a round trip of converting the target address to a H160 and back). --------- Signed-off-by: Cyrill Leutwiler Signed-off-by: xermicus Co-authored-by: command-bot <> --- prdoc/pr_5675.prdoc | 14 ++++++++ .../revive/fixtures/contracts/balance_of.rs | 36 +++++++++++++++++++ .../frame/revive/src/benchmarking/mod.rs | 20 +++++++++++ substrate/frame/revive/src/exec.rs | 21 +++++++---- substrate/frame/revive/src/tests.rs | 25 +++++++++++++ substrate/frame/revive/src/wasm/runtime.rs | 24 +++++++++++++ substrate/frame/revive/src/weights.rs | 21 +++++++++++ substrate/frame/revive/uapi/src/host.rs | 10 +++++- .../frame/revive/uapi/src/host/riscv32.rs | 5 +++ 9 files changed, 169 insertions(+), 7 deletions(-) create mode 100644 prdoc/pr_5675.prdoc create mode 100644 substrate/frame/revive/fixtures/contracts/balance_of.rs diff --git a/prdoc/pr_5675.prdoc b/prdoc/pr_5675.prdoc new file mode 100644 index 00000000000..ceae446dab1 --- /dev/null +++ b/prdoc/pr_5675.prdoc @@ -0,0 +1,14 @@ +title: "[pallet-revive] Add balance_of syscyall for fetching foreign balances" + +doc: + - audience: Runtime Dev + description: | + This adds an API method balance_of, corresponding to the BALANCE EVM opcode. + +crates: + - name: pallet-revive + bump: minor + - name: pallet-revive-uapi + bump: minor + - name: pallet-revive-fixtures + bump: minor diff --git a/substrate/frame/revive/fixtures/contracts/balance_of.rs b/substrate/frame/revive/fixtures/contracts/balance_of.rs new file mode 100644 index 00000000000..1f94d3e506c --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/balance_of.rs @@ -0,0 +1,36 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_std] +#![no_main] + +use common::{input, u64_output}; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!(address: &[u8; 20],); + + let reported_free_balance = u64_output!(api::balance_of, address); + + assert_ne!(reported_free_balance, 0); +} diff --git a/substrate/frame/revive/src/benchmarking/mod.rs b/substrate/frame/revive/src/benchmarking/mod.rs index 8601f5f5354..ed4f8f362e3 100644 --- a/substrate/frame/revive/src/benchmarking/mod.rs +++ b/substrate/frame/revive/src/benchmarking/mod.rs @@ -612,6 +612,26 @@ mod benchmarks { assert_eq!(U256::from_little_endian(&memory[..]), runtime.ext().balance()); } + #[benchmark(pov_mode = Measured)] + fn seal_balance_of() { + let len = ::max_encoded_len(); + let account = account::("target", 0, 0); + let address = T::AddressMapper::to_address(&account); + let balance = Pallet::::min_balance() * 2u32.into(); + T::Currency::set_balance(&account, balance); + + build_runtime!(runtime, memory: [vec![0u8; len], address.0, ]); + + let result; + #[block] + { + result = runtime.bench_balance_of(memory.as_mut_slice(), len as u32, 0); + } + + assert_ok!(result); + assert_eq!(U256::from_little_endian(&memory[..len]), runtime.ext().balance_of(&address)); + } + #[benchmark(pov_mode = Measured)] fn seal_value_transferred() { build_runtime!(runtime, memory: [[0u8;32], ]); diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index 587d89b3198..b065adb4675 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -305,6 +305,11 @@ pub trait Ext: sealing::Sealed { /// The `value_transferred` is already added. fn balance(&self) -> U256; + /// Returns the balance of the supplied account. + /// + /// The `value_transferred` is already added. + fn balance_of(&self, address: &H160) -> U256; + /// Returns the value transferred along with this call. fn value_transferred(&self) -> U256; @@ -1256,6 +1261,11 @@ where fn allows_reentry(&self, id: &T::AccountId) -> bool { !self.frames().any(|f| &f.account_id == id && !f.allows_reentry) } + + /// Returns the *free* balance of the supplied AccountId. + fn account_balance(&self, who: &T::AccountId) -> U256 { + T::Currency::reducible_balance(who, Preservation::Preserve, Fortitude::Polite).into() + } } impl<'a, T, E> Ext for Stack<'a, T, E> @@ -1496,12 +1506,11 @@ where } fn balance(&self) -> U256 { - T::Currency::reducible_balance( - &self.top_frame().account_id, - Preservation::Preserve, - Fortitude::Polite, - ) - .into() + self.account_balance(&self.top_frame().account_id) + } + + fn balance_of(&self, address: &H160) -> U256 { + self.account_balance(&::AddressMapper::to_account_id(address)) } fn value_transferred(&self) -> U256 { diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index 19d6eabd577..80c49849464 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -4063,6 +4063,31 @@ mod run_tests { }); } + #[test] + fn balance_of_api() { + let (wasm, _code_hash) = compile_module("balance_of").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = Balances::set_balance(&ALICE, 1_000_000); + let _ = Balances::set_balance(Ð_ALICE, 1_000_000); + + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(wasm.to_vec())).build_and_unwrap_contract(); + + // The fixture asserts a non-zero returned free balance of the account; + // The ETH_ALICE account is endowed; + // Hence we should not revert + assert_ok!(builder::call(addr).data(ALICE_ADDR.0.to_vec()).build()); + + // The fixture asserts a non-zero returned free balance of the account; + // The ETH_BOB account is not endowed; + // Hence we should revert + assert_err_ignore_postinfo!( + builder::call(addr).data(BOB_ADDR.0.to_vec()).build(), + >::ContractTrapped + ); + }); + } + #[test] fn balance_api_returns_free_balance() { let (wasm, _code_hash) = compile_module("balance").unwrap(); diff --git a/substrate/frame/revive/src/wasm/runtime.rs b/substrate/frame/revive/src/wasm/runtime.rs index 02d60ee9e34..80daac8f9db 100644 --- a/substrate/frame/revive/src/wasm/runtime.rs +++ b/substrate/frame/revive/src/wasm/runtime.rs @@ -314,6 +314,8 @@ pub enum RuntimeCosts { GasLeft, /// Weight of calling `seal_balance`. Balance, + /// Weight of calling `seal_balance_of`. + BalanceOf, /// Weight of calling `seal_value_transferred`. ValueTransferred, /// Weight of calling `seal_minimum_balance`. @@ -457,6 +459,7 @@ impl Token for RuntimeCosts { Address => T::WeightInfo::seal_address(), GasLeft => T::WeightInfo::seal_gas_left(), Balance => T::WeightInfo::seal_balance(), + BalanceOf => T::WeightInfo::seal_balance_of(), ValueTransferred => T::WeightInfo::seal_value_transferred(), MinimumBalance => T::WeightInfo::seal_minimum_balance(), BlockNumber => T::WeightInfo::seal_block_number(), @@ -1515,6 +1518,27 @@ pub mod env { )?) } + /// Stores the *free* balance of the supplied address into the supplied buffer. + /// See [`pallet_revive_uapi::HostFn::balance`]. + #[api_version(0)] + fn balance_of( + &mut self, + memory: &mut M, + addr_ptr: u32, + out_ptr: u32, + ) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::BalanceOf)?; + let mut address = H160::zero(); + memory.read_into_buf(addr_ptr, address.as_bytes_mut())?; + Ok(self.write_fixed_sandbox_output( + memory, + out_ptr, + &as_bytes(self.ext.balance_of(&address)), + false, + already_charged, + )?) + } + /// Stores the value transferred along with this call/instantiate into the supplied buffer. /// See [`pallet_revive_uapi::HostFn::value_transferred`]. #[api_version(0)] diff --git a/substrate/frame/revive/src/weights.rs b/substrate/frame/revive/src/weights.rs index 8913592c13b..b66c28bdf7d 100644 --- a/substrate/frame/revive/src/weights.rs +++ b/substrate/frame/revive/src/weights.rs @@ -69,6 +69,7 @@ pub trait WeightInfo { fn seal_address() -> Weight; fn seal_gas_left() -> Weight; fn seal_balance() -> Weight; + fn seal_balance_of() -> Weight; fn seal_value_transferred() -> Weight; fn seal_minimum_balance() -> Weight; fn seal_block_number() -> Weight; @@ -409,6 +410,16 @@ impl WeightInfo for SubstrateWeight { // Minimum execution time: 4_361_000 picoseconds. Weight::from_parts(4_577_000, 0) } + /// Storage: `System::Account` (r:1 w:0) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) + fn seal_balance_of() -> Weight { + // Proof Size summary in bytes: + // Measured: `52` + // Estimated: `3517` + // Minimum execution time: 3_751_000 picoseconds. + Weight::from_parts(3_874_000, 3517) + .saturating_add(T::DbWeight::get().reads(1_u64)) + } fn seal_value_transferred() -> Weight { // Proof Size summary in bytes: // Measured: `0` @@ -1223,6 +1234,16 @@ impl WeightInfo for () { // Minimum execution time: 4_361_000 picoseconds. Weight::from_parts(4_577_000, 0) } + /// Storage: `System::Account` (r:1 w:0) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) + fn seal_balance_of() -> Weight { + // Proof Size summary in bytes: + // Measured: `52` + // Estimated: `3517` + // Minimum execution time: 3_751_000 picoseconds. + Weight::from_parts(3_874_000, 3517) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + } fn seal_value_transferred() -> Weight { // Proof Size summary in bytes: // Measured: `0` diff --git a/substrate/frame/revive/uapi/src/host.rs b/substrate/frame/revive/uapi/src/host.rs index 101ae9aca46..0c9dce3fb38 100644 --- a/substrate/frame/revive/uapi/src/host.rs +++ b/substrate/frame/revive/uapi/src/host.rs @@ -56,13 +56,21 @@ pub trait HostFn: private::Sealed { /// otherwise. fn lock_delegate_dependency(code_hash: &[u8; 32]); - /// Stores the *free* balance of the current account into the supplied buffer. + /// Stores the **reducible** balance of the current account into the supplied buffer. /// /// # Parameters /// /// - `output`: A reference to the output data buffer to write the balance. fn balance(output: &mut [u8; 32]); + /// Stores the **reducible** balance of the supplied address into the supplied buffer. + /// + /// # Parameters + /// + /// - `addr`: The target address of which to retreive the free balance. + /// - `output`: A reference to the output data buffer to write the balance. + fn balance_of(addr: &[u8; 20], output: &mut [u8; 32]); + /// Stores the current block number of the current contract into the supplied buffer. /// /// # Parameters diff --git a/substrate/frame/revive/uapi/src/host/riscv32.rs b/substrate/frame/revive/uapi/src/host/riscv32.rs index 89cf9278644..908d4f52520 100644 --- a/substrate/frame/revive/uapi/src/host/riscv32.rs +++ b/substrate/frame/revive/uapi/src/host/riscv32.rs @@ -82,6 +82,7 @@ mod sys { pub fn weight_to_fee(ref_time: u64, proof_size: u64, out_ptr: *mut u8); pub fn weight_left(out_ptr: *mut u8, out_len_ptr: *mut u32); pub fn balance(out_ptr: *mut u8); + pub fn balance_of(addr_ptr: *const u8, out_ptr: *mut u8); pub fn value_transferred(out_ptr: *mut u8); pub fn now(out_ptr: *mut u8); pub fn minimum_balance(out_ptr: *mut u8); @@ -497,6 +498,10 @@ impl HostFn for HostFnImpl { ret_val.into_bool() } + fn balance_of(address: &[u8; 20], output: &mut [u8; 32]) { + unsafe { sys::balance_of(address.as_ptr(), output.as_mut_ptr()) }; + } + fn caller_is_origin() -> bool { let ret_val = unsafe { sys::caller_is_origin() }; ret_val.into_bool() -- GitLab From 5a29cee02390401916aae5c353e4b28d2e3be90a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Fri, 13 Sep 2024 09:22:27 +0200 Subject: [PATCH 055/125] revive: Get rid of no longer needed trait bound (#5696) This was necessary when we needed to compact encode a `Option` which is no longer necessary. The storage deposit limit is now non optional. This is just a left over. Also got rid of the `T::Hash: IsType`. We use `keccak256` instead of the generic hash function everywhere now. --- .../revive/src/benchmarking/call_builder.rs | 11 ++--------- substrate/frame/revive/src/benchmarking/mod.rs | 6 +----- substrate/frame/revive/src/exec.rs | 4 +--- substrate/frame/revive/src/lib.rs | 17 +++-------------- substrate/frame/revive/src/storage.rs | 8 ++------ substrate/frame/revive/src/storage/meter.rs | 15 +++------------ .../frame/revive/src/test_utils/builder.rs | 4 ---- substrate/frame/revive/src/wasm/mod.rs | 5 ++--- 8 files changed, 14 insertions(+), 56 deletions(-) diff --git a/substrate/frame/revive/src/benchmarking/call_builder.rs b/substrate/frame/revive/src/benchmarking/call_builder.rs index 020a578c3a3..8a859a3a508 100644 --- a/substrate/frame/revive/src/benchmarking/call_builder.rs +++ b/substrate/frame/revive/src/benchmarking/call_builder.rs @@ -22,14 +22,11 @@ use crate::{ storage::meter::Meter, transient_storage::MeterEntry, wasm::{ApiVersion, PreparedCall, Runtime}, - BalanceOf, Config, DebugBuffer, Error, GasMeter, MomentOf, Origin, TypeInfo, WasmBlob, Weight, + BalanceOf, Config, DebugBuffer, Error, GasMeter, MomentOf, Origin, WasmBlob, Weight, }; use alloc::{vec, vec::Vec}; -use codec::{Encode, HasCompact}; -use core::fmt::Debug; use frame_benchmarking::benchmarking; -use frame_support::traits::IsType; -use sp_core::{H256, U256}; +use sp_core::U256; type StackExt<'a, T> = Stack<'a, T, WasmBlob>; @@ -49,9 +46,7 @@ pub struct CallSetup { impl Default for CallSetup where T: Config + pallet_balances::Config, - as HasCompact>::Type: Clone + Eq + PartialEq + Debug + TypeInfo + Encode, BalanceOf: Into + TryFrom, - T::Hash: IsType, MomentOf: Into, { fn default() -> Self { @@ -62,8 +57,6 @@ where impl CallSetup where T: Config + pallet_balances::Config, - T::Hash: IsType, - as HasCompact>::Type: Clone + Eq + PartialEq + Debug + TypeInfo + Encode, BalanceOf: Into + TryFrom, MomentOf: Into, { diff --git a/substrate/frame/revive/src/benchmarking/mod.rs b/substrate/frame/revive/src/benchmarking/mod.rs index ed4f8f362e3..332c425d714 100644 --- a/substrate/frame/revive/src/benchmarking/mod.rs +++ b/substrate/frame/revive/src/benchmarking/mod.rs @@ -68,10 +68,8 @@ struct Contract { impl Contract where T: Config + pallet_balances::Config, - as HasCompact>::Type: Clone + Eq + PartialEq + Debug + TypeInfo + Encode, BalanceOf: Into + TryFrom, MomentOf: Into, - T::Hash: IsType, { /// Returns the address of the contract. fn address(&self) -> H160 { @@ -221,12 +219,10 @@ fn default_deposit_limit() -> BalanceOf { #[benchmarks( where - as HasCompact>::Type: Clone + Eq + PartialEq + Debug + TypeInfo + Encode, - BalanceOf: From< as Currency>::Balance> + Into + TryFrom, + BalanceOf: Into + TryFrom, T: Config + pallet_balances::Config, MomentOf: Into, ::RuntimeEvent: From>, - T::Hash: IsType, as Currency>::Balance: From>, )] mod benchmarks { diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index b065adb4675..233658696c8 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -37,7 +37,7 @@ use frame_support::{ traits::{ fungible::{Inspect, Mutate}, tokens::{Fortitude, Preservation}, - Contains, IsType, OriginTrait, Time, + Contains, OriginTrait, Time, }, weights::Weight, Blake2_128Concat, BoundedVec, StorageHasher, @@ -699,7 +699,6 @@ impl CachedContract { impl<'a, T, E> Stack<'a, T, E> where T: Config, - T::Hash: IsType, BalanceOf: Into + TryFrom, MomentOf: Into, E: Executable, @@ -1271,7 +1270,6 @@ where impl<'a, T, E> Ext for Stack<'a, T, E> where T: Config, - T::Hash: IsType, E: Executable, BalanceOf: Into + TryFrom, MomentOf: Into, diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index d1e17fb7b39..1cc77a673b1 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -50,8 +50,7 @@ use crate::{ storage::{meter::Meter as StorageMeter, ContractInfo, DeletionQueueManager}, wasm::{CodeInfo, RuntimeCosts, WasmBlob}, }; -use codec::{Codec, Decode, Encode, HasCompact}; -use core::fmt::Debug; +use codec::{Codec, Decode, Encode}; use environmental::*; use frame_support::{ dispatch::{ @@ -597,10 +596,7 @@ pub mod pallet { } #[pallet::hooks] - impl Hooks> for Pallet - where - T::Hash: IsType, - { + impl Hooks> for Pallet { fn on_idle(_block: BlockNumberFor, limit: Weight) -> Weight { let mut meter = WeightMeter::with_limit(limit); ContractInfo::::process_deletion_queue_batch(&mut meter); @@ -734,8 +730,6 @@ pub mod pallet { #[pallet::call] impl Pallet where - T::Hash: IsType, - as HasCompact>::Type: Clone + Eq + PartialEq + Debug + TypeInfo + Encode, BalanceOf: Into + TryFrom, MomentOf: Into, { @@ -987,7 +981,6 @@ impl Pallet where BalanceOf: Into + TryFrom, MomentOf: Into, - T::Hash: IsType, { /// A generalized version of [`Self::call`]. /// @@ -1175,11 +1168,7 @@ where } } -impl Pallet -where - T: Config, - T::Hash: IsType, -{ +impl Pallet { /// Return the existential deposit of [`Config::Currency`]. fn min_balance() -> BalanceOf { >>::minimum_balance() diff --git a/substrate/frame/revive/src/storage.rs b/substrate/frame/revive/src/storage.rs index 9939de1dfd1..ef7ce2db32c 100644 --- a/substrate/frame/revive/src/storage.rs +++ b/substrate/frame/revive/src/storage.rs @@ -33,12 +33,11 @@ use codec::{Decode, Encode, MaxEncodedLen}; use core::marker::PhantomData; use frame_support::{ storage::child::{self, ChildInfo}, - traits::IsType, weights::{Weight, WeightMeter}, CloneNoBound, DefaultNoBound, }; use scale_info::TypeInfo; -use sp_core::{ConstU32, Get, H160, H256}; +use sp_core::{ConstU32, Get, H160}; use sp_io::KillStorageResult; use sp_runtime::{ traits::{Hash, Saturating, Zero}, @@ -78,10 +77,7 @@ pub struct ContractInfo { delegate_dependencies: DelegateDependencyMap, } -impl ContractInfo -where - T::Hash: IsType, -{ +impl ContractInfo { /// Constructs a new contract info **without** writing it to storage. /// /// This returns an `Err` if an contract with the supplied `account` already exists diff --git a/substrate/frame/revive/src/storage/meter.rs b/substrate/frame/revive/src/storage/meter.rs index 9d70ddf8587..a2ece03f9aa 100644 --- a/substrate/frame/revive/src/storage/meter.rs +++ b/substrate/frame/revive/src/storage/meter.rs @@ -27,11 +27,10 @@ use frame_support::{ traits::{ fungible::{Mutate, MutateHold}, tokens::{Fortitude, Fortitude::Polite, Precision, Preservation, Restriction}, - Get, IsType, + Get, }, DefaultNoBound, RuntimeDebugNoBound, }; -use sp_core::H256; use sp_runtime::{ traits::{Saturating, Zero}, DispatchError, FixedPointNumber, FixedU128, @@ -397,12 +396,7 @@ where } /// Functions that only apply to the nested state. -impl RawMeter -where - T: Config, - T::Hash: IsType, - E: Ext, -{ +impl> RawMeter { /// Charges `diff` from the meter. pub fn charge(&mut self, diff: &Diff) { match &mut self.own_contribution { @@ -504,10 +498,7 @@ where } } -impl Ext for ReservingExt -where - T::Hash: IsType, -{ +impl Ext for ReservingExt { fn check_limit( origin: &T::AccountId, limit: BalanceOf, diff --git a/substrate/frame/revive/src/test_utils/builder.rs b/substrate/frame/revive/src/test_utils/builder.rs index b17d7628fb8..d361590df95 100644 --- a/substrate/frame/revive/src/test_utils/builder.rs +++ b/substrate/frame/revive/src/test_utils/builder.rs @@ -21,11 +21,8 @@ use crate::{ ContractExecResult, ContractInstantiateResult, DebugInfo, EventRecordOf, ExecReturnValue, InstantiateReturnValue, OriginFor, Pallet, Weight, }; -use codec::{Encode, HasCompact}; -use core::fmt::Debug; use frame_support::pallet_prelude::DispatchResultWithPostInfo; use paste::paste; -use scale_info::TypeInfo; use sp_core::H160; /// Helper macro to generate a builder for contract API calls. @@ -53,7 +50,6 @@ macro_rules! builder { #[allow(dead_code)] impl $name where - as HasCompact>::Type: Clone + Eq + PartialEq + Debug + TypeInfo + Encode, BalanceOf: Into + TryFrom, crate::MomentOf: Into, T::Hash: frame_support::traits::IsType, diff --git a/substrate/frame/revive/src/wasm/mod.rs b/substrate/frame/revive/src/wasm/mod.rs index 5813903326b..b8f6eef126b 100644 --- a/substrate/frame/revive/src/wasm/mod.rs +++ b/substrate/frame/revive/src/wasm/mod.rs @@ -45,9 +45,9 @@ use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ dispatch::DispatchResult, ensure, - traits::{fungible::MutateHold, tokens::Precision::BestEffort, IsType}, + traits::{fungible::MutateHold, tokens::Precision::BestEffort}, }; -use sp_core::{Get, H256, U256}; +use sp_core::{Get, U256}; use sp_runtime::DispatchError; /// Validated Wasm module ready for execution. @@ -125,7 +125,6 @@ impl Token for CodeLoadToken { impl WasmBlob where - T::Hash: IsType, BalanceOf: Into + TryFrom, { /// We only check for size and nothing else when the code is uploaded. -- GitLab From d66dee3c3da836bcf41a12ca4e1191faee0b6a5b Mon Sep 17 00:00:00 2001 From: Alexander Samusev <41779041+alvicsam@users.noreply.github.com> Date: Fri, 13 Sep 2024 09:55:15 +0200 Subject: [PATCH 056/125] [ci] Move check each crate osx from gitlab to github (#5690) PR adds `cargo-check-each-crate-macos` job in GitHub actions. It'll work some time in both CI systems until it's moved entirely to GHA. cc https://github.com/paritytech/ci_cd/issues/1021 --- .github/actions/set-up-mac/README.md | 15 -------- .github/actions/set-up-mac/action.yml | 43 --------------------- .github/workflows/tests-misc.yml | 55 +++++++++++++++------------ 3 files changed, 31 insertions(+), 82 deletions(-) delete mode 100644 .github/actions/set-up-mac/README.md delete mode 100644 .github/actions/set-up-mac/action.yml diff --git a/.github/actions/set-up-mac/README.md b/.github/actions/set-up-mac/README.md deleted file mode 100644 index 0bbc7112bd1..00000000000 --- a/.github/actions/set-up-mac/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# How to use - -```yml - set-image: - runs-on: macos-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - id: set_image - run: cat .github/env >> $GITHUB_OUTPUT - - name: Install dependencies - uses: ./.github/actions/set-up-mac - with: - IMAGE: ${{ steps.set-image.outputs.IMAGE }} -``` diff --git a/.github/actions/set-up-mac/action.yml b/.github/actions/set-up-mac/action.yml deleted file mode 100644 index a3b02667940..00000000000 --- a/.github/actions/set-up-mac/action.yml +++ /dev/null @@ -1,43 +0,0 @@ -name: "Set up rust on mac" -description: "Install the required tools for Mac runners" -inputs: - IMAGE: - description: "Rust docker image" - required: true -runs: - using: "composite" - steps: - - name: Install with Hombrew - shell: bash - run: brew install protobuf rustup openssl pkg-config zlib xz zstd llvm jq curl gcc make cmake - - name: Set version - shell: bash - run: | - VERSION=$(echo $IMAGE | sed -E 's/.*:bullseye-([^-]+)-.*/\1/') - echo $VERSION - echo "VERSION=$VERSION" >> $GITHUB_ENV - NIGHTLY=$(echo $IMAGE | sed -E 's/.*([0-9]{4}-[0-9]{2}-[0-9]{2}).*/\1/') - echo $NIGHTLY - echo "NIGHTLY=$NIGHTLY" >> $GITHUB_ENV - env: - IMAGE: ${{ inputs.IMAGE }} - - - name: Install rustup - shell: bash - run: | - rustup-init -y - rustup install $VERSION - rustup default $VERSION - rustup toolchain install "nightly-${NIGHTLY}" - - - name: MacOS Deps - shell: bash - run: | - rustup target add wasm32-unknown-unknown --toolchain $VERSION - rustup component add rust-src rustfmt clippy --toolchain $VERSION - - - name: Check Rust - shell: bash - run: | - rustup show - rustup +nightly show diff --git a/.github/workflows/tests-misc.yml b/.github/workflows/tests-misc.yml index e4f0d6575c5..90685743a41 100644 --- a/.github/workflows/tests-misc.yml +++ b/.github/workflows/tests-misc.yml @@ -346,30 +346,37 @@ jobs: cp .forklift/config.toml /github/home/.forklift/config.toml PYTHONUNBUFFERED=x .github/scripts/check-each-crate.py ${{ matrix.index }} ${{ strategy.job-total }} - # cargo-check-each-crate-macos: - # timeout-minutes: 120 - # needs: [ set-image ] - # runs-on: macOS - # env: - # RUSTFLAGS: "-D warnings" - # CI_JOB_NAME: cargo-check-each-crate - # IMAGE: ${{ needs.set-image.outputs.IMAGE }} - # strategy: - # fail-fast: false - # matrix: - # index: [ 1,2,3,4,5,6,7,8,9,10 ] # 10 parallel jobs - # steps: - # - name: Checkout - # uses: actions/checkout@v4 - - # - name: Install dependencies - # uses: ./.github/actions/set-up-mac - # with: - # IMAGE: ${{ needs.set-image.outputs.IMAGE }} - - # - name: script - # run: | - # PYTHONUNBUFFERED=x .github/scripts/check-each-crate.py ${{ matrix.index }} ${{ strategy.job-total }} True + cargo-check-all-crate-macos: + timeout-minutes: 30 + runs-on: parity-macos + env: + SKIP_WASM_BUILD: 1 + steps: + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - name: Set rust version from env file + run: | + RUST_VERSION=$(cat .github/env | sed -E 's/.*ci-unified:([^-]+)-([^-]+).*/\2/') + echo $RUST_VERSION + echo "RUST_VERSION=${RUST_VERSION}" >> $GITHUB_ENV + - name: Set up Homebrew + uses: Homebrew/actions/setup-homebrew@1ccc07ccd54b6048295516a3eb89b192c35057dc # master from 12.09.2024 + - name: Install rust ${{ env.RUST_VERSION }} + uses: actions-rust-lang/setup-rust-toolchain@1fbea72663f6d4c03efaab13560c8a24cfd2a7cc # v1.9.0 + with: + cache: false + toolchain: ${{ env.RUST_VERSION }} + target: wasm32-unknown-unknown + components: cargo, clippy, rust-docs, rust-src, rustfmt, rustc, rust-std + - name: Install protobuf + run: brew install protobuf + - name: cargo info + run: | + echo "######## rustup show ########" + rustup show + echo "######## cargo --version ########" + cargo --version + - name: Run cargo check + run: cargo check --workspace --locked confirm-required-test-misc-jobs-passed: runs-on: ubuntu-latest -- GitLab From b21da7480899fcfd5f9b8b6f44d54adb118badbb Mon Sep 17 00:00:00 2001 From: Cyrill Leutwiler Date: Fri, 13 Sep 2024 12:33:51 +0200 Subject: [PATCH 057/125] [pallet-revive] uapi: allow create1 equivalent calls (#5701) The salt argument should be optional to allow create1 equivalent calls. --------- Signed-off-by: xermicus --- prdoc/pr_5701.prdoc | 14 +++++++ .../fixtures/contracts/caller_contract.rs | 22 ++++++++--- .../fixtures/contracts/create1_with_value.rs | 39 +++++++++++++++++++ .../create_storage_and_instantiate.rs | 2 +- .../contracts/destroy_and_transfer.rs | 2 +- .../contracts/instantiate_return_code.rs | 2 +- substrate/frame/revive/src/tests.rs | 22 +++++++++++ substrate/frame/revive/uapi/src/host.rs | 2 +- .../frame/revive/uapi/src/host/riscv32.rs | 5 ++- 9 files changed, 98 insertions(+), 12 deletions(-) create mode 100644 prdoc/pr_5701.prdoc create mode 100644 substrate/frame/revive/fixtures/contracts/create1_with_value.rs diff --git a/prdoc/pr_5701.prdoc b/prdoc/pr_5701.prdoc new file mode 100644 index 00000000000..3d237eb34e7 --- /dev/null +++ b/prdoc/pr_5701.prdoc @@ -0,0 +1,14 @@ +title: "[pallet-revive] uapi: allow create1 equivalent calls" + +doc: + - audience: Runtime Dev + description: | + The salt argument should be optional to allow create1 equivalent calls. + +crates: + - name: pallet-revive + bump: minor + - name: pallet-revive-uapi + bump: major + - name: pallet-revive-fixtures + bump: patch diff --git a/substrate/frame/revive/fixtures/contracts/caller_contract.rs b/substrate/frame/revive/fixtures/contracts/caller_contract.rs index eb29fca87c1..f9a30b87df4 100644 --- a/substrate/frame/revive/fixtures/contracts/caller_contract.rs +++ b/substrate/frame/revive/fixtures/contracts/caller_contract.rs @@ -49,25 +49,35 @@ pub extern "C" fn call() { &reverted_input, None, None, - &salt, + Some(&salt), ); assert!(matches!(res, Err(ReturnErrorCode::CalleeReverted))); // Fail to deploy the contract due to insufficient ref_time weight. let res = api::instantiate( - code_hash, 1u64, // too little ref_time weight + code_hash, + 1u64, // too little ref_time weight 0u64, // How much proof_size weight to devote for the execution. 0 = all. None, // No deposit limit. - &value, &input, None, None, &salt, + &value, + &input, + None, + None, + Some(&salt), ); assert!(matches!(res, Err(ReturnErrorCode::CalleeTrapped))); // Fail to deploy the contract due to insufficient proof_size weight. let res = api::instantiate( - code_hash, 0u64, // How much ref_time weight to devote for the execution. 0 = all. + code_hash, + 0u64, // How much ref_time weight to devote for the execution. 0 = all. 1u64, // Too little proof_size weight None, // No deposit limit. - &value, &input, None, None, &salt, + &value, + &input, + None, + None, + Some(&salt), ); assert!(matches!(res, Err(ReturnErrorCode::CalleeTrapped))); @@ -83,7 +93,7 @@ pub extern "C" fn call() { &input, Some(&mut callee), None, - &salt, + Some(&salt), ) .unwrap(); diff --git a/substrate/frame/revive/fixtures/contracts/create1_with_value.rs b/substrate/frame/revive/fixtures/contracts/create1_with_value.rs new file mode 100644 index 00000000000..644777aff99 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/create1_with_value.rs @@ -0,0 +1,39 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_std] +#![no_main] + +use common::{input, u256_bytes}; +use uapi::{HostFn, HostFnImpl as api, ReturnErrorCode}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!(code_hash: &[u8; 32],); + + let mut value = [0; 32]; + api::value_transferred(&mut value); + + // Deploy the contract with no salt (equivalent to create1). + let ret = api::instantiate(code_hash, 0u64, 0u64, None, &value, &[], None, None, None); + assert!(ret.is_ok()); +} diff --git a/substrate/frame/revive/fixtures/contracts/create_storage_and_instantiate.rs b/substrate/frame/revive/fixtures/contracts/create_storage_and_instantiate.rs index e1372e2eb8b..463706457a1 100644 --- a/substrate/frame/revive/fixtures/contracts/create_storage_and_instantiate.rs +++ b/substrate/frame/revive/fixtures/contracts/create_storage_and_instantiate.rs @@ -48,7 +48,7 @@ pub extern "C" fn call() { input, Some(&mut address), None, - &salt, + Some(&salt), ) .unwrap(); diff --git a/substrate/frame/revive/fixtures/contracts/destroy_and_transfer.rs b/substrate/frame/revive/fixtures/contracts/destroy_and_transfer.rs index d381db8e398..8342f4acf95 100644 --- a/substrate/frame/revive/fixtures/contracts/destroy_and_transfer.rs +++ b/substrate/frame/revive/fixtures/contracts/destroy_and_transfer.rs @@ -42,7 +42,7 @@ pub extern "C" fn deploy() { &input, Some(&mut address), None, - &salt, + Some(&salt), ) .unwrap(); diff --git a/substrate/frame/revive/fixtures/contracts/instantiate_return_code.rs b/substrate/frame/revive/fixtures/contracts/instantiate_return_code.rs index c5736850960..9764859c619 100644 --- a/substrate/frame/revive/fixtures/contracts/instantiate_return_code.rs +++ b/substrate/frame/revive/fixtures/contracts/instantiate_return_code.rs @@ -41,7 +41,7 @@ pub extern "C" fn call() { input, None, None, - &[0u8; 32], // Salt. + Some(&[0u8; 32]), // Salt. ) { Ok(_) => 0u32, Err(code) => code as u32, diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index 80c49849464..c7185caf0ef 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -4232,4 +4232,26 @@ mod run_tests { assert_ok!(builder::call(addr_caller).data(addr_callee.encode()).build()); }); } + + #[test] + fn create1_with_value_works() { + let (code, code_hash) = compile_module("create1_with_value").unwrap(); + let value = 42; + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Create the contract: Constructor does nothing. + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + // Call the contract: Deploys itself using create1 and the expected value + assert_ok!(builder::call(addr).value(value).data(code_hash.encode()).build()); + + // We should see the expected balance at the expected account + let address = crate::address::create1(&addr, 0); + let account_id = ::AddressMapper::to_account_id(&address); + let usable_balance = ::Currency::usable_balance(&account_id); + assert_eq!(usable_balance, value); + }); + } } diff --git a/substrate/frame/revive/uapi/src/host.rs b/substrate/frame/revive/uapi/src/host.rs index 0c9dce3fb38..538de7ea251 100644 --- a/substrate/frame/revive/uapi/src/host.rs +++ b/substrate/frame/revive/uapi/src/host.rs @@ -409,7 +409,7 @@ pub trait HostFn: private::Sealed { input: &[u8], address: Option<&mut [u8; 20]>, output: Option<&mut &mut [u8]>, - salt: &[u8; 32], + salt: Option<&[u8; 32]>, ) -> Result; /// Checks whether a specified address belongs to a contract. diff --git a/substrate/frame/revive/uapi/src/host/riscv32.rs b/substrate/frame/revive/uapi/src/host/riscv32.rs index 908d4f52520..0bb0ede4543 100644 --- a/substrate/frame/revive/uapi/src/host/riscv32.rs +++ b/substrate/frame/revive/uapi/src/host/riscv32.rs @@ -198,7 +198,7 @@ impl HostFn for HostFnImpl { input: &[u8], mut address: Option<&mut [u8; 20]>, mut output: Option<&mut &mut [u8]>, - salt: &[u8; 32], + salt: Option<&[u8; 32]>, ) -> Result { let address = match address { Some(ref mut data) => data.as_mut_ptr(), @@ -206,6 +206,7 @@ impl HostFn for HostFnImpl { }; let (output_ptr, mut output_len) = ptr_len_or_sentinel(&mut output); let deposit_limit_ptr = ptr_or_sentinel(&deposit_limit); + let salt_ptr = ptr_or_sentinel(&salt); #[repr(packed)] #[allow(dead_code)] struct Args { @@ -232,7 +233,7 @@ impl HostFn for HostFnImpl { address, output: output_ptr, output_len: &mut output_len as *mut _, - salt: salt.as_ptr(), + salt: salt_ptr, }; let ret_code = { unsafe { sys::instantiate(&args as *const Args as *const _) } }; -- GitLab From 0136463321dcf661740f9bfec2a0f0739487b60d Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Fri, 13 Sep 2024 13:31:17 +0200 Subject: [PATCH 058/125] pallet-migrations: fix index access for singluar migrations (#5695) Discovered a bug in the migrations pallet while debugging https://github.com/paritytech/try-runtime-cli/pull/90. It only occurs when a single MBM is configured - hence it did not happen when Ajuna Network tried it... Changes: - Check len of the tuple before accessing its nth_id - Make nth_id return `None` on unary tuples and n>0 --------- Signed-off-by: Oliver Tale-Yazdi Co-authored-by: ggwpez --- prdoc/pr_5695.prdoc | 15 ++++++++++++++ substrate/frame/migrations/src/lib.rs | 7 ++++--- substrate/frame/migrations/src/tests.rs | 25 +++++++++++++++++++++++ substrate/frame/support/src/migrations.rs | 9 +++++--- 4 files changed, 50 insertions(+), 6 deletions(-) create mode 100644 prdoc/pr_5695.prdoc diff --git a/prdoc/pr_5695.prdoc b/prdoc/pr_5695.prdoc new file mode 100644 index 00000000000..202eafa26eb --- /dev/null +++ b/prdoc/pr_5695.prdoc @@ -0,0 +1,15 @@ +title: 'pallet-migrations: fix index access for singluar migrations' +doc: +- audience: Runtime Dev + description: |- + Discovered a bug in the migrations pallet while debugging https://github.com/paritytech/try-runtime-cli/pull/90. + It only occurs when a single MBM is configured - hence it did not happen when Ajuna Network tried it... + + Changes: + - Check len of the tuple before accessing its nth_id + - Make nth_id return `None` on unary tuples and n>0 +crates: +- name: pallet-migrations + bump: patch +- name: frame-support + bump: patch diff --git a/substrate/frame/migrations/src/lib.rs b/substrate/frame/migrations/src/lib.rs index 68041a57eaa..1823e5a2f95 100644 --- a/substrate/frame/migrations/src/lib.rs +++ b/substrate/frame/migrations/src/lib.rs @@ -678,7 +678,7 @@ impl Pallet { return Some(ControlFlow::Break(cursor)) } - let Some(id) = T::Migrations::nth_id(cursor.index) else { + if cursor.index >= T::Migrations::len() { // No more migrations in the tuple - we are done. defensive_assert!(cursor.index == T::Migrations::len(), "Inconsistent MBMs tuple"); Self::deposit_event(Event::UpgradeCompleted); @@ -687,8 +687,9 @@ impl Pallet { return None; }; - let Ok(bounded_id): Result, _> = id.try_into() else { - defensive!("integrity_test ensures that all identifiers' MEL bounds fit into CursorMaxLen; qed."); + let id = T::Migrations::nth_id(cursor.index).map(TryInto::try_into); + let Some(Ok(bounded_id)): Option, _>> = id else { + defensive!("integrity_test ensures that all identifiers are present and bounde; qed."); Self::upgrade_failed(Some(cursor.index)); return None }; diff --git a/substrate/frame/migrations/src/tests.rs b/substrate/frame/migrations/src/tests.rs index 9c9043d37a6..73ca2a9a09c 100644 --- a/substrate/frame/migrations/src/tests.rs +++ b/substrate/frame/migrations/src/tests.rs @@ -27,6 +27,31 @@ use frame_support::{pallet_prelude::Weight, traits::OnRuntimeUpgrade}; #[docify::export] #[test] fn simple_works() { + use Event::*; + test_closure(|| { + // Add three migrations, each taking one block longer than the previous. + MockedMigrations::set(vec![(SucceedAfter, 2)]); + + System::set_block_number(1); + Migrations::on_runtime_upgrade(); + run_to_block(10); + + // Check that the executed migrations are recorded in `Historical`. + assert_eq!(historic(), vec![mocked_id(SucceedAfter, 2),]); + + // Check that we got all events. + assert_events(vec![ + UpgradeStarted { migrations: 1 }, + MigrationAdvanced { index: 0, took: 1 }, + MigrationAdvanced { index: 0, took: 2 }, + MigrationCompleted { index: 0, took: 3 }, + UpgradeCompleted, + ]); + }); +} + +#[test] +fn simple_multiple_works() { use Event::*; test_closure(|| { // Add three migrations, each taking one block longer than the previous. diff --git a/substrate/frame/support/src/migrations.rs b/substrate/frame/support/src/migrations.rs index 7f746146956..0eabf9d0ee1 100644 --- a/substrate/frame/support/src/migrations.rs +++ b/substrate/frame/support/src/migrations.rs @@ -673,7 +673,8 @@ pub trait SteppedMigrations { /// The `n`th [`SteppedMigration::id`]. /// - /// Is guaranteed to return `Some` if `n < Self::len()`. + /// Is guaranteed to return `Some` if `n < Self::len()`. Calling this with any index larger or + /// equal to `Self::len()` MUST return `None`. fn nth_id(n: u32) -> Option>; /// The [`SteppedMigration::max_steps`] of the `n`th migration. @@ -777,8 +778,10 @@ impl SteppedMigrations for T { 1 } - fn nth_id(_n: u32) -> Option> { - Some(T::id().encode()) + fn nth_id(n: u32) -> Option> { + n.is_zero() + .then_some(T::id().encode()) + .defensive_proof("nth_id should only be called with n==0") } fn nth_max_steps(n: u32) -> Option> { -- GitLab From fb7300ce15086de8333b22de823f5b02f2bb3280 Mon Sep 17 00:00:00 2001 From: Ron Date: Fri, 13 Sep 2024 20:09:47 +0800 Subject: [PATCH 059/125] Transfer Polkadot-native assets to Ethereum (#5546) # Description Adding support for send polkadot native assets(PNA) to Ethereum network through snowbridge. Asset with location in view of AH Including: - Relay token `(1,Here)` - Native asset `(0,[PalletInstance(instance),GenereIndex(index)])` managed by Assets Pallet - Native asset of Parachain `(1,[Parachain(paraId)])` managed by Foreign Assets Pallet The original PR in https://github.com/Snowfork/polkadot-sdk/pull/128 which has been internally reviewed by Snowbridge team. # Notes - This feature depends on the companion solidity change in https://github.com/Snowfork/snowbridge/pull/1155. Currently register PNA is only allowed from [sudo](https://github.com/Snowfork/polkadot-sdk/blob/46cb3528cd8cd1394af2335a6907d7ab8647717a/bridges/snowbridge/pallets/system/src/lib.rs#L621), so it's actually not enabled. Will require another runtime upgrade to make the call permissionless together with upgrading the Gateway contract. - To make things easy multi-hop transfer(i.e. sending PNA from Ethereum through AH to Destination chain) is not support ed in this PR. For this case user can switch to 2-phases transfer instead. --------- Co-authored-by: Clara van Staden Co-authored-by: Alistair Singh Co-authored-by: Vincent Geddes <117534+vgeddes@users.noreply.github.com> Co-authored-by: Francisco Aguirre Co-authored-by: Adrian Catangiu --- Cargo.lock | 1 + .../pallets/inbound-queue/src/lib.rs | 29 +- .../pallets/inbound-queue/src/mock.rs | 20 +- .../pallets/outbound-queue/src/mock.rs | 10 +- .../pallets/system/src/benchmarking.rs | 23 + bridges/snowbridge/pallets/system/src/lib.rs | 116 +++++- bridges/snowbridge/pallets/system/src/mock.rs | 9 +- .../snowbridge/pallets/system/src/tests.rs | 135 +++++- .../snowbridge/pallets/system/src/weights.rs | 11 + bridges/snowbridge/primitives/core/Cargo.toml | 2 + bridges/snowbridge/primitives/core/src/lib.rs | 34 +- .../primitives/core/src/location.rs | 205 +++++++++ .../primitives/core/src/outbound.rs | 57 +++ .../primitives/router/src/inbound/mod.rs | 169 +++++++- .../primitives/router/src/inbound/tests.rs | 31 +- .../primitives/router/src/outbound/mod.rs | 183 ++++++-- .../primitives/router/src/outbound/tests.rs | 393 +++++++++++------- .../assets/asset-hub-westend/src/genesis.rs | 8 + .../bridges/bridge-hub-westend/src/lib.rs | 1 + .../emulated/common/src/lib.rs | 9 + .../bridge-hub-westend/src/tests/mod.rs | 3 +- .../src/tests/snowbridge.rs | 384 ++++++++++++++--- .../src/bridge_to_ethereum_config.rs | 16 +- .../src/weights/snowbridge_pallet_system.rs | 10 + .../src/bridge_to_ethereum_config.rs | 13 +- .../src/weights/snowbridge_pallet_system.rs | 10 + .../runtimes/constants/src/rococo.rs | 3 +- .../runtimes/constants/src/westend.rs | 3 +- prdoc/pr_5546.prdoc | 36 ++ 29 files changed, 1626 insertions(+), 298 deletions(-) create mode 100644 bridges/snowbridge/primitives/core/src/location.rs create mode 100644 prdoc/pr_5546.prdoc diff --git a/Cargo.lock b/Cargo.lock index 5f0fa0f613f..85b0699fff7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19588,6 +19588,7 @@ dependencies = [ "sp-std 14.0.0", "staging-xcm", "staging-xcm-builder", + "staging-xcm-executor", ] [[package]] diff --git a/bridges/snowbridge/pallets/inbound-queue/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue/src/lib.rs index 4a1486204eb..423b92b9fae 100644 --- a/bridges/snowbridge/pallets/inbound-queue/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue/src/lib.rs @@ -48,12 +48,11 @@ use frame_support::{ }; use frame_system::ensure_signed; use scale_info::TypeInfo; -use sp_core::{H160, H256}; +use sp_core::H160; use sp_runtime::traits::Zero; use sp_std::vec; use xcm::prelude::{ - send_xcm, Instruction::SetTopic, Junction::*, Location, SendError as XcmpSendError, SendXcm, - Xcm, XcmContext, XcmHash, + send_xcm, Junction::*, Location, SendError as XcmpSendError, SendXcm, Xcm, XcmContext, XcmHash, }; use xcm_executor::traits::TransactAsset; @@ -62,9 +61,8 @@ use snowbridge_core::{ sibling_sovereign_account, BasicOperatingMode, Channel, ChannelId, ParaId, PricingParameters, StaticLookup, }; -use snowbridge_router_primitives::{ - inbound, - inbound::{ConvertMessage, ConvertMessageError}, +use snowbridge_router_primitives::inbound::{ + ConvertMessage, ConvertMessageError, VersionedMessage, }; use sp_runtime::{traits::Saturating, SaturatedConversion, TokenError}; @@ -86,6 +84,7 @@ pub mod pallet { use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; + use sp_core::H256; #[pallet::pallet] pub struct Pallet(_); @@ -276,12 +275,12 @@ pub mod pallet { T::Token::transfer(&sovereign_account, &who, amount, Preservation::Preserve)?; } + // Decode payload into `VersionedMessage` + let message = VersionedMessage::decode_all(&mut envelope.payload.as_ref()) + .map_err(|_| Error::::InvalidPayload)?; + // Decode message into XCM - let (xcm, fee) = - match inbound::VersionedMessage::decode_all(&mut envelope.payload.as_ref()) { - Ok(message) => Self::do_convert(envelope.message_id, message)?, - Err(_) => return Err(Error::::InvalidPayload.into()), - }; + let (xcm, fee) = Self::do_convert(envelope.message_id, message.clone())?; log::info!( target: LOG_TARGET, @@ -323,12 +322,10 @@ pub mod pallet { impl Pallet { pub fn do_convert( message_id: H256, - message: inbound::VersionedMessage, + message: VersionedMessage, ) -> Result<(Xcm<()>, BalanceOf), Error> { - let (mut xcm, fee) = - T::MessageConverter::convert(message).map_err(|e| Error::::ConvertMessage(e))?; - // Append the message id as an XCM topic - xcm.inner_mut().extend(vec![SetTopic(message_id.into())]); + let (xcm, fee) = T::MessageConverter::convert(message_id, message) + .map_err(|e| Error::::ConvertMessage(e))?; Ok((xcm, fee)) } diff --git a/bridges/snowbridge/pallets/inbound-queue/src/mock.rs b/bridges/snowbridge/pallets/inbound-queue/src/mock.rs index 871df6d1e51..3e67d5ab738 100644 --- a/bridges/snowbridge/pallets/inbound-queue/src/mock.rs +++ b/bridges/snowbridge/pallets/inbound-queue/src/mock.rs @@ -10,12 +10,12 @@ use snowbridge_beacon_primitives::{ use snowbridge_core::{ gwei, inbound::{Log, Proof, VerificationError}, - meth, Channel, ChannelId, PricingParameters, Rewards, StaticLookup, + meth, Channel, ChannelId, PricingParameters, Rewards, StaticLookup, TokenId, }; use snowbridge_router_primitives::inbound::MessageToXcm; use sp_core::{H160, H256}; use sp_runtime::{ - traits::{IdentifyAccount, IdentityLookup, Verify}, + traits::{IdentifyAccount, IdentityLookup, MaybeEquivalence, Verify}, BuildStorage, FixedU128, MultiSignature, }; use sp_std::{convert::From, default::Default}; @@ -112,6 +112,9 @@ parameter_types! { pub const SendTokenExecutionFee: u128 = 1_000_000_000; pub const InitialFund: u128 = 1_000_000_000_000; pub const InboundQueuePalletInstance: u8 = 80; + pub UniversalLocation: InteriorLocation = + [GlobalConsensus(Westend), Parachain(1002)].into(); + pub AssetHubFromEthereum: Location = Location::new(1,[GlobalConsensus(Westend),Parachain(1000)]); } #[cfg(feature = "runtime-benchmarks")] @@ -205,6 +208,16 @@ impl TransactAsset for SuccessfulTransactor { } } +pub struct MockTokenIdConvert; +impl MaybeEquivalence for MockTokenIdConvert { + fn convert(_id: &TokenId) -> Option { + Some(Location::parent()) + } + fn convert_back(_loc: &Location) -> Option { + None + } +} + impl inbound_queue::Config for Test { type RuntimeEvent = RuntimeEvent; type Verifier = MockVerifier; @@ -218,6 +231,9 @@ impl inbound_queue::Config for Test { InboundQueuePalletInstance, AccountId, Balance, + MockTokenIdConvert, + UniversalLocation, + AssetHubFromEthereum, >; type PricingParameters = Parameters; type ChannelLookup = MockChannelLookup; diff --git a/bridges/snowbridge/pallets/outbound-queue/src/mock.rs b/bridges/snowbridge/pallets/outbound-queue/src/mock.rs index d65a96e2702..0b34893333e 100644 --- a/bridges/snowbridge/pallets/outbound-queue/src/mock.rs +++ b/bridges/snowbridge/pallets/outbound-queue/src/mock.rs @@ -164,13 +164,11 @@ pub fn mock_message(sibling_para_id: u32) -> Message { Message { id: None, channel_id: ParaId::from(sibling_para_id).into(), - command: Command::AgentExecute { + command: Command::TransferNativeToken { agent_id: Default::default(), - command: AgentExecuteCommand::TransferToken { - token: Default::default(), - recipient: Default::default(), - amount: 0, - }, + token: Default::default(), + recipient: Default::default(), + amount: 0, }, } } diff --git a/bridges/snowbridge/pallets/system/src/benchmarking.rs b/bridges/snowbridge/pallets/system/src/benchmarking.rs index ef908ad6a3f..20798b7c349 100644 --- a/bridges/snowbridge/pallets/system/src/benchmarking.rs +++ b/bridges/snowbridge/pallets/system/src/benchmarking.rs @@ -159,6 +159,29 @@ mod benchmarks { Ok(()) } + #[benchmark] + fn register_token() -> Result<(), BenchmarkError> { + let caller: T::AccountId = whitelisted_caller(); + + let amount: BalanceOf = + (10_000_000_000_000_u128).saturated_into::().saturated_into(); + + T::Token::mint_into(&caller, amount)?; + + let relay_token_asset_id: Location = Location::parent(); + let asset = Box::new(VersionedLocation::V4(relay_token_asset_id)); + let asset_metadata = AssetMetadata { + name: "wnd".as_bytes().to_vec().try_into().unwrap(), + symbol: "wnd".as_bytes().to_vec().try_into().unwrap(), + decimals: 12, + }; + + #[extrinsic_call] + _(RawOrigin::Root, asset, asset_metadata); + + Ok(()) + } + impl_benchmark_test_suite!( SnowbridgeControl, crate::mock::new_test_ext(true), diff --git a/bridges/snowbridge/pallets/system/src/lib.rs b/bridges/snowbridge/pallets/system/src/lib.rs index 39c73e3630e..1e8a788b7a5 100644 --- a/bridges/snowbridge/pallets/system/src/lib.rs +++ b/bridges/snowbridge/pallets/system/src/lib.rs @@ -35,8 +35,14 @@ //! //! Typically, Polkadot governance will use the `force_transfer_native_from_agent` and //! `force_update_channel` and extrinsics to manage agents and channels for system parachains. +//! +//! ## Polkadot-native tokens on Ethereum +//! +//! Tokens deposited on AssetHub pallet can be bridged to Ethereum as wrapped ERC20 tokens. As a +//! prerequisite, the token should be registered first. +//! +//! * [`Call::register_token`]: Register a token location as a wrapped ERC20 contract on Ethereum. #![cfg_attr(not(feature = "std"), no_std)] - #[cfg(test)] mod mock; @@ -63,13 +69,16 @@ use frame_system::pallet_prelude::*; use snowbridge_core::{ meth, outbound::{Command, Initializer, Message, OperatingMode, SendError, SendMessage}, - sibling_sovereign_account, AgentId, Channel, ChannelId, ParaId, - PricingParameters as PricingParametersRecord, PRIMARY_GOVERNANCE_CHANNEL, + sibling_sovereign_account, AgentId, AssetMetadata, Channel, ChannelId, ParaId, + PricingParameters as PricingParametersRecord, TokenId, TokenIdOf, PRIMARY_GOVERNANCE_CHANNEL, SECONDARY_GOVERNANCE_CHANNEL, }; use sp_core::{RuntimeDebug, H160, H256}; use sp_io::hashing::blake2_256; -use sp_runtime::{traits::BadOrigin, DispatchError, SaturatedConversion}; +use sp_runtime::{ + traits::{BadOrigin, MaybeEquivalence}, + DispatchError, SaturatedConversion, +}; use sp_std::prelude::*; use xcm::prelude::*; use xcm_executor::traits::ConvertLocation; @@ -99,7 +108,7 @@ where } /// Hash the location to produce an agent id -fn agent_id_of(location: &Location) -> Result { +pub fn agent_id_of(location: &Location) -> Result { T::AgentIdOf::convert_location(location).ok_or(Error::::LocationConversionFailed.into()) } @@ -127,6 +136,7 @@ where #[frame_support::pallet] pub mod pallet { + use frame_support::dispatch::PostDispatchInfo; use snowbridge_core::StaticLookup; use sp_core::U256; @@ -164,6 +174,12 @@ pub mod pallet { type WeightInfo: WeightInfo; + /// This chain's Universal Location. + type UniversalLocation: Get; + + // The bridges configured Ethereum location + type EthereumLocation: Get; + #[cfg(feature = "runtime-benchmarks")] type Helper: BenchmarkHelper; } @@ -211,6 +227,13 @@ pub mod pallet { PricingParametersChanged { params: PricingParametersOf, }, + /// Register Polkadot-native token as a wrapped ERC20 token on Ethereum + RegisterToken { + /// Location of Polkadot-native token + location: VersionedLocation, + /// ID of Polkadot-native token on Ethereum + foreign_token_id: H256, + }, } #[pallet::error] @@ -243,6 +266,16 @@ pub mod pallet { pub type PricingParameters = StorageValue<_, PricingParametersOf, ValueQuery, T::DefaultPricingParameters>; + /// Lookup table for foreign token ID to native location relative to ethereum + #[pallet::storage] + pub type ForeignToNativeId = + StorageMap<_, Blake2_128Concat, TokenId, xcm::v4::Location, OptionQuery>; + + /// Lookup table for native location relative to ethereum to foreign token ID + #[pallet::storage] + pub type NativeToForeignId = + StorageMap<_, Blake2_128Concat, xcm::v4::Location, TokenId, OptionQuery>; + #[pallet::genesis_config] #[derive(frame_support::DefaultNoBound)] pub struct GenesisConfig { @@ -574,6 +607,34 @@ pub mod pallet { }); Ok(()) } + + /// Registers a Polkadot-native token as a wrapped ERC20 token on Ethereum. + /// Privileged. Can only be called by root. + /// + /// Fee required: No + /// + /// - `origin`: Must be root + /// - `location`: Location of the asset (relative to this chain) + /// - `metadata`: Metadata to include in the instantiated ERC20 contract on Ethereum + #[pallet::call_index(10)] + #[pallet::weight(T::WeightInfo::register_token())] + pub fn register_token( + origin: OriginFor, + location: Box, + metadata: AssetMetadata, + ) -> DispatchResultWithPostInfo { + ensure_root(origin)?; + + let location: Location = + (*location).try_into().map_err(|_| Error::::UnsupportedLocationVersion)?; + + Self::do_register_token(&location, metadata, PaysFee::::No)?; + + Ok(PostDispatchInfo { + actual_weight: Some(T::WeightInfo::register_token()), + pays_fee: Pays::No, + }) + } } impl Pallet { @@ -663,6 +724,42 @@ pub mod pallet { let secondary_exists = Channels::::contains_key(SECONDARY_GOVERNANCE_CHANNEL); primary_exists && secondary_exists } + + pub(crate) fn do_register_token( + location: &Location, + metadata: AssetMetadata, + pays_fee: PaysFee, + ) -> Result<(), DispatchError> { + let ethereum_location = T::EthereumLocation::get(); + // reanchor to Ethereum context + let location = location + .clone() + .reanchored(ðereum_location, &T::UniversalLocation::get()) + .map_err(|_| Error::::LocationConversionFailed)?; + + let token_id = TokenIdOf::convert_location(&location) + .ok_or(Error::::LocationConversionFailed)?; + + if !ForeignToNativeId::::contains_key(token_id) { + NativeToForeignId::::insert(location.clone(), token_id); + ForeignToNativeId::::insert(token_id, location.clone()); + } + + let command = Command::RegisterForeignToken { + token_id, + name: metadata.name.into_inner(), + symbol: metadata.symbol.into_inner(), + decimals: metadata.decimals, + }; + Self::send(SECONDARY_GOVERNANCE_CHANNEL, command, pays_fee)?; + + Self::deposit_event(Event::::RegisterToken { + location: location.clone().into(), + foreign_token_id: token_id, + }); + + Ok(()) + } } impl StaticLookup for Pallet { @@ -684,4 +781,13 @@ pub mod pallet { PricingParameters::::get() } } + + impl MaybeEquivalence for Pallet { + fn convert(foreign_id: &TokenId) -> Option { + ForeignToNativeId::::get(foreign_id) + } + fn convert_back(location: &Location) -> Option { + NativeToForeignId::::get(location) + } + } } diff --git a/bridges/snowbridge/pallets/system/src/mock.rs b/bridges/snowbridge/pallets/system/src/mock.rs index 98bd3da9ab2..47b089866a5 100644 --- a/bridges/snowbridge/pallets/system/src/mock.rs +++ b/bridges/snowbridge/pallets/system/src/mock.rs @@ -166,10 +166,12 @@ impl snowbridge_pallet_outbound_queue::Config for Test { parameter_types! { pub const SS58Prefix: u8 = 42; pub const AnyNetwork: Option = None; - pub const RelayNetwork: Option = Some(NetworkId::Kusama); + pub const RelayNetwork: Option = Some(NetworkId::Polkadot); pub const RelayLocation: Location = Location::parent(); pub UniversalLocation: InteriorLocation = [GlobalConsensus(RelayNetwork::get().unwrap()), Parachain(1013)].into(); + pub EthereumNetwork: NetworkId = NetworkId::Ethereum { chain_id: 11155111 }; + pub EthereumDestination: Location = Location::new(2,[GlobalConsensus(EthereumNetwork::get())]); } pub const DOT: u128 = 10_000_000_000; @@ -177,8 +179,8 @@ pub const DOT: u128 = 10_000_000_000; parameter_types! { pub TreasuryAccount: AccountId = PalletId(*b"py/trsry").into_account_truncating(); pub Fee: u64 = 1000; - pub const RococoNetwork: NetworkId = NetworkId::Rococo; pub const InitialFunding: u128 = 1_000_000_000_000; + pub BridgeHubParaId: ParaId = ParaId::new(1002); pub AssetHubParaId: ParaId = ParaId::new(1000); pub TestParaId: u32 = 2000; pub Parameters: PricingParameters = PricingParameters { @@ -188,7 +190,6 @@ parameter_types! { multiplier: FixedU128::from_rational(4, 3) }; pub const InboundDeliveryCost: u128 = 1_000_000_000; - } #[cfg(feature = "runtime-benchmarks")] @@ -208,6 +209,8 @@ impl crate::Config for Test { type DefaultPricingParameters = Parameters; type WeightInfo = (); type InboundDeliveryCost = InboundDeliveryCost; + type UniversalLocation = UniversalLocation; + type EthereumLocation = EthereumDestination; #[cfg(feature = "runtime-benchmarks")] type Helper = (); } diff --git a/bridges/snowbridge/pallets/system/src/tests.rs b/bridges/snowbridge/pallets/system/src/tests.rs index 09f24195a30..d0286e04abd 100644 --- a/bridges/snowbridge/pallets/system/src/tests.rs +++ b/bridges/snowbridge/pallets/system/src/tests.rs @@ -248,7 +248,7 @@ fn create_channel() { let _ = Balances::mint_into(&sovereign_account, 10000); assert_ok!(EthereumSystem::create_agent(origin.clone())); - assert_ok!(EthereumSystem::create_channel(origin, OperatingMode::Normal)); + assert_ok!(EthereumSystem::create_channel(origin, OperatingMode::Normal,)); }); } @@ -264,10 +264,10 @@ fn create_channel_fail_already_exists() { let _ = Balances::mint_into(&sovereign_account, 10000); assert_ok!(EthereumSystem::create_agent(origin.clone())); - assert_ok!(EthereumSystem::create_channel(origin.clone(), OperatingMode::Normal)); + assert_ok!(EthereumSystem::create_channel(origin.clone(), OperatingMode::Normal,)); assert_noop!( - EthereumSystem::create_channel(origin, OperatingMode::Normal), + EthereumSystem::create_channel(origin, OperatingMode::Normal,), Error::::ChannelAlreadyCreated ); }); @@ -334,10 +334,10 @@ fn update_channel() { // First create the channel let _ = Balances::mint_into(&sovereign_account, 10000); assert_ok!(EthereumSystem::create_agent(origin.clone())); - assert_ok!(EthereumSystem::create_channel(origin.clone(), OperatingMode::Normal)); + assert_ok!(EthereumSystem::create_channel(origin.clone(), OperatingMode::Normal,)); // Now try to update it - assert_ok!(EthereumSystem::update_channel(origin, OperatingMode::Normal)); + assert_ok!(EthereumSystem::update_channel(origin, OperatingMode::Normal,)); System::assert_last_event(RuntimeEvent::EthereumSystem(crate::Event::UpdateChannel { channel_id: ParaId::from(2000).into(), @@ -383,12 +383,12 @@ fn update_channel_bad_origin() { // Signed origin not allowed assert_noop!( - EthereumSystem::update_channel(RuntimeOrigin::signed([14; 32].into()), mode), + EthereumSystem::update_channel(RuntimeOrigin::signed([14; 32].into()), mode,), BadOrigin ); // None origin not allowed - assert_noop!(EthereumSystem::update_channel(RuntimeOrigin::none(), mode), BadOrigin); + assert_noop!(EthereumSystem::update_channel(RuntimeOrigin::none(), mode,), BadOrigin); }); } @@ -400,7 +400,7 @@ fn update_channel_fails_not_exist() { // Now try to update it assert_noop!( - EthereumSystem::update_channel(origin, OperatingMode::Normal), + EthereumSystem::update_channel(origin, OperatingMode::Normal,), Error::::NoChannel ); }); @@ -419,7 +419,7 @@ fn force_update_channel() { // First create the channel let _ = Balances::mint_into(&sovereign_account, 10000); assert_ok!(EthereumSystem::create_agent(origin.clone())); - assert_ok!(EthereumSystem::create_channel(origin.clone(), OperatingMode::Normal)); + assert_ok!(EthereumSystem::create_channel(origin.clone(), OperatingMode::Normal,)); // Now try to force update it let force_origin = RuntimeOrigin::root(); @@ -463,7 +463,7 @@ fn transfer_native_from_agent() { // First create the agent and channel assert_ok!(EthereumSystem::create_agent(origin.clone())); - assert_ok!(EthereumSystem::create_channel(origin, OperatingMode::Normal)); + assert_ok!(EthereumSystem::create_channel(origin, OperatingMode::Normal,)); let origin = make_xcm_origin(origin_location.clone()); assert_ok!(EthereumSystem::transfer_native_from_agent(origin, recipient, amount),); @@ -584,7 +584,7 @@ fn charge_fee_for_transfer_native_from_agent() { // create_agent & create_channel first assert_ok!(EthereumSystem::create_agent(origin.clone())); - assert_ok!(EthereumSystem::create_channel(origin.clone(), OperatingMode::Normal)); + assert_ok!(EthereumSystem::create_channel(origin.clone(), OperatingMode::Normal,)); // assert sovereign_balance decreased by only the base_fee let sovereign_balance_before = Balances::balance(&sovereign_account); @@ -631,3 +631,116 @@ fn no_genesis_build_is_uninitialized() { assert!(!EthereumSystem::is_initialized(), "Ethereum initialized."); }); } + +#[test] +fn register_token_with_signed_yields_bad_origin() { + new_test_ext(true).execute_with(|| { + let origin = RuntimeOrigin::signed([14; 32].into()); + let location = Location::new(1, [Parachain(2000)]); + let versioned_location: Box = Box::new(location.clone().into()); + assert_noop!( + EthereumSystem::register_token(origin, versioned_location, Default::default()), + BadOrigin + ); + }); +} + +pub struct RegisterTokenTestCase { + /// Input: Location of Polkadot-native token relative to BH + pub native: Location, + /// Output: Reanchored, canonicalized location + pub reanchored: Location, + /// Output: Stable hash of reanchored location + pub foreign: TokenId, +} + +#[test] +fn register_all_tokens_succeeds() { + let test_cases = vec![ + // DOT + RegisterTokenTestCase { + native: Location::parent(), + reanchored: Location::new(1, GlobalConsensus(Polkadot)), + foreign: hex!("4e241583d94b5d48a27a22064cd49b2ed6f5231d2d950e432f9b7c2e0ade52b2") + .into(), + }, + // GLMR (Some Polkadot parachain currency) + RegisterTokenTestCase { + native: Location::new(1, [Parachain(2004)]), + reanchored: Location::new(1, [GlobalConsensus(Polkadot), Parachain(2004)]), + foreign: hex!("34c08fc90409b6924f0e8eabb7c2aaa0c749e23e31adad9f6d217b577737fafb") + .into(), + }, + // USDT + RegisterTokenTestCase { + native: Location::new(1, [Parachain(1000), PalletInstance(50), GeneralIndex(1984)]), + reanchored: Location::new( + 1, + [ + GlobalConsensus(Polkadot), + Parachain(1000), + PalletInstance(50), + GeneralIndex(1984), + ], + ), + foreign: hex!("14b0579be12d7d7f9971f1d4b41f0e88384b9b74799b0150d4aa6cd01afb4444") + .into(), + }, + // KSM + RegisterTokenTestCase { + native: Location::new(2, [GlobalConsensus(Kusama)]), + reanchored: Location::new(1, [GlobalConsensus(Kusama)]), + foreign: hex!("03b6054d0c576dd8391e34e1609cf398f68050c23009d19ce93c000922bcd852") + .into(), + }, + // KAR (Some Kusama parachain currency) + RegisterTokenTestCase { + native: Location::new(2, [GlobalConsensus(Kusama), Parachain(2000)]), + reanchored: Location::new(1, [GlobalConsensus(Kusama), Parachain(2000)]), + foreign: hex!("d3e39ad6ea4cee68c9741181e94098823b2ea34a467577d0875c036f0fce5be0") + .into(), + }, + ]; + for tc in test_cases.iter() { + new_test_ext(true).execute_with(|| { + let origin = RuntimeOrigin::root(); + let versioned_location: VersionedLocation = tc.native.clone().into(); + + assert_ok!(EthereumSystem::register_token( + origin, + Box::new(versioned_location), + Default::default() + )); + + assert_eq!(NativeToForeignId::::get(tc.reanchored.clone()), Some(tc.foreign)); + assert_eq!(ForeignToNativeId::::get(tc.foreign), Some(tc.reanchored.clone())); + + System::assert_last_event(RuntimeEvent::EthereumSystem(Event::::RegisterToken { + location: tc.reanchored.clone().into(), + foreign_token_id: tc.foreign, + })); + }); + } +} + +#[test] +fn register_ethereum_native_token_fails() { + new_test_ext(true).execute_with(|| { + let origin = RuntimeOrigin::root(); + let location = Location::new( + 2, + [ + GlobalConsensus(Ethereum { chain_id: 11155111 }), + AccountKey20 { + network: None, + key: hex!("87d1f7fdfEe7f651FaBc8bFCB6E086C278b77A7d"), + }, + ], + ); + let versioned_location: Box = Box::new(location.clone().into()); + assert_noop!( + EthereumSystem::register_token(origin, versioned_location, Default::default()), + Error::::LocationConversionFailed + ); + }); +} diff --git a/bridges/snowbridge/pallets/system/src/weights.rs b/bridges/snowbridge/pallets/system/src/weights.rs index 6e532a0d8a8..3513097f8b5 100644 --- a/bridges/snowbridge/pallets/system/src/weights.rs +++ b/bridges/snowbridge/pallets/system/src/weights.rs @@ -42,6 +42,7 @@ pub trait WeightInfo { fn force_transfer_native_from_agent() -> Weight; fn set_token_transfer_fees() -> Weight; fn set_pricing_parameters() -> Weight; + fn register_token() -> Weight; } // For backwards compatibility and tests. @@ -246,4 +247,14 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } + + fn register_token() -> Weight { + // Proof Size summary in bytes: + // Measured: `256` + // Estimated: `6044` + // Minimum execution time: 45_000_000 picoseconds. + Weight::from_parts(45_000_000, 6044) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } } diff --git a/bridges/snowbridge/primitives/core/Cargo.toml b/bridges/snowbridge/primitives/core/Cargo.toml index f9bee1ff495..fa37c795b2d 100644 --- a/bridges/snowbridge/primitives/core/Cargo.toml +++ b/bridges/snowbridge/primitives/core/Cargo.toml @@ -35,6 +35,7 @@ ethabi = { workspace = true } [dev-dependencies] hex = { workspace = true, default-features = true } +xcm-executor = { workspace = true, default-features = true } [features] default = ["std"] @@ -62,4 +63,5 @@ runtime-benchmarks = [ "polkadot-parachain-primitives/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", + "xcm-executor/runtime-benchmarks", ] diff --git a/bridges/snowbridge/primitives/core/src/lib.rs b/bridges/snowbridge/primitives/core/src/lib.rs index ed1af4225d2..7ad129a5254 100644 --- a/bridges/snowbridge/primitives/core/src/lib.rs +++ b/bridges/snowbridge/primitives/core/src/lib.rs @@ -9,11 +9,13 @@ mod tests; pub mod inbound; +pub mod location; pub mod operating_mode; pub mod outbound; pub mod pricing; pub mod ringbuffer; +pub use location::{AgentId, AgentIdOf, TokenId, TokenIdOf}; pub use polkadot_parachain_primitives::primitives::{ Id as ParaId, IsSystem, Sibling as SiblingParaId, }; @@ -21,18 +23,16 @@ pub use ringbuffer::{RingBufferMap, RingBufferMapImpl}; pub use sp_core::U256; use codec::{Decode, Encode, MaxEncodedLen}; -use frame_support::traits::Contains; +use frame_support::{traits::Contains, BoundedVec}; use hex_literal::hex; use scale_info::TypeInfo; -use sp_core::H256; +use sp_core::{ConstU32, H256}; use sp_io::hashing::keccak_256; use sp_runtime::{traits::AccountIdConversion, RuntimeDebug}; use sp_std::prelude::*; use xcm::prelude::{Junction::Parachain, Location}; -use xcm_builder::{DescribeAllTerminal, DescribeFamily, DescribeLocation, HashedDescription}; /// The ID of an agent contract -pub type AgentId = H256; pub use operating_mode::BasicOperatingMode; pub use pricing::{PricingParameters, Rewards}; @@ -151,16 +151,24 @@ pub const PRIMARY_GOVERNANCE_CHANNEL: ChannelId = pub const SECONDARY_GOVERNANCE_CHANNEL: ChannelId = ChannelId::new(hex!("0000000000000000000000000000000000000000000000000000000000000002")); -pub struct DescribeHere; -impl DescribeLocation for DescribeHere { - fn describe_location(l: &Location) -> Option> { - match l.unpack() { - (0, []) => Some(Vec::::new().encode()), - _ => None, +/// Metadata to include in the instantiated ERC20 token contract +#[derive(Clone, Encode, Decode, PartialEq, RuntimeDebug, TypeInfo)] +pub struct AssetMetadata { + pub name: BoundedVec>, + pub symbol: BoundedVec>, + pub decimals: u8, +} + +#[cfg(any(test, feature = "std", feature = "runtime-benchmarks"))] +impl Default for AssetMetadata { + fn default() -> Self { + AssetMetadata { + name: BoundedVec::truncate_from(vec![]), + symbol: BoundedVec::truncate_from(vec![]), + decimals: 0, } } } -/// Creates an AgentId from a Location. An AgentId is a unique mapping to a Agent contract on -/// Ethereum which acts as the sovereign account for the Location. -pub type AgentIdOf = HashedDescription)>; +/// Maximum length of a string field in ERC20 token metada +const METADATA_FIELD_MAX_LEN: u32 = 32; diff --git a/bridges/snowbridge/primitives/core/src/location.rs b/bridges/snowbridge/primitives/core/src/location.rs new file mode 100644 index 00000000000..aad1c9ece05 --- /dev/null +++ b/bridges/snowbridge/primitives/core/src/location.rs @@ -0,0 +1,205 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +//! # Location +//! +//! Location helpers for dealing with Tokens and Agents + +pub use polkadot_parachain_primitives::primitives::{ + Id as ParaId, IsSystem, Sibling as SiblingParaId, +}; +pub use sp_core::U256; + +use codec::Encode; +use sp_core::H256; +use sp_std::prelude::*; +use xcm::prelude::{ + AccountId32, AccountKey20, GeneralIndex, GeneralKey, GlobalConsensus, Location, PalletInstance, +}; +use xcm_builder::{ + DescribeAllTerminal, DescribeFamily, DescribeLocation, DescribeTerminus, HashedDescription, +}; + +pub type AgentId = H256; + +/// Creates an AgentId from a Location. An AgentId is a unique mapping to a Agent contract on +/// Ethereum which acts as the sovereign account for the Location. +#[allow(deprecated)] +pub type AgentIdOf = + HashedDescription)>; + +pub type TokenId = H256; + +/// Convert a token location (relative to Ethereum) to a stable ID that can be used on the Ethereum +/// side +pub type TokenIdOf = HashedDescription< + TokenId, + DescribeGlobalPrefix<(DescribeTerminus, DescribeFamily)>, +>; + +/// This looks like DescribeTerminus that was added to xcm-builder. However this does an extra +/// `encode` to the Vector producing a different output to DescribeTerminus. `DescribeHere` +/// should NOT be used for new code. This is left here for backwards compatibility of channels and +/// agents. +#[deprecated(note = "Use DescribeTerminus from xcm-builder instead.")] +pub struct DescribeHere; +#[allow(deprecated)] +impl DescribeLocation for DescribeHere { + fn describe_location(l: &Location) -> Option> { + match l.unpack() { + (0, []) => Some(Vec::::new().encode()), + _ => None, + } + } +} +pub struct DescribeGlobalPrefix(sp_std::marker::PhantomData); +impl DescribeLocation for DescribeGlobalPrefix { + fn describe_location(l: &Location) -> Option> { + match (l.parent_count(), l.first_interior()) { + (1, Some(GlobalConsensus(network))) => { + let mut tail = l.clone().split_first_interior().0; + tail.dec_parent(); + let interior = Suffix::describe_location(&tail)?; + Some((b"GlobalConsensus", network, interior).encode()) + }, + _ => None, + } + } +} + +pub struct DescribeTokenTerminal; +impl DescribeLocation for DescribeTokenTerminal { + fn describe_location(l: &Location) -> Option> { + match l.unpack().1 { + [] => Some(Vec::::new().encode()), + [GeneralIndex(index)] => Some((b"GeneralIndex", *index).encode()), + [GeneralKey { data, .. }] => Some((b"GeneralKey", *data).encode()), + [AccountKey20 { key, .. }] => Some((b"AccountKey20", *key).encode()), + [AccountId32 { id, .. }] => Some((b"AccountId32", *id).encode()), + + // Pallet + [PalletInstance(instance)] => Some((b"PalletInstance", *instance).encode()), + [PalletInstance(instance), GeneralIndex(index)] => + Some((b"PalletInstance", *instance, b"GeneralIndex", *index).encode()), + [PalletInstance(instance), GeneralKey { data, .. }] => + Some((b"PalletInstance", *instance, b"GeneralKey", *data).encode()), + + [PalletInstance(instance), AccountKey20 { key, .. }] => + Some((b"PalletInstance", *instance, b"AccountKey20", *key).encode()), + [PalletInstance(instance), AccountId32 { id, .. }] => + Some((b"PalletInstance", *instance, b"AccountId32", *id).encode()), + + // Reject all other locations + _ => None, + } + } +} + +#[cfg(test)] +mod tests { + use crate::TokenIdOf; + use xcm::prelude::{ + GeneralIndex, GeneralKey, GlobalConsensus, Junction::*, Location, NetworkId::*, + PalletInstance, Parachain, + }; + use xcm_executor::traits::ConvertLocation; + + #[test] + fn test_token_of_id() { + let token_locations = [ + // Relay Chain cases + // Relay Chain relative to Ethereum + Location::new(1, [GlobalConsensus(Westend)]), + // Parachain cases + // Parachain relative to Ethereum + Location::new(1, [GlobalConsensus(Westend), Parachain(2000)]), + // Parachain general index + Location::new(1, [GlobalConsensus(Westend), Parachain(2000), GeneralIndex(1)]), + // Parachain general key + Location::new( + 1, + [ + GlobalConsensus(Westend), + Parachain(2000), + GeneralKey { length: 32, data: [0; 32] }, + ], + ), + // Parachain account key 20 + Location::new( + 1, + [ + GlobalConsensus(Westend), + Parachain(2000), + AccountKey20 { network: None, key: [0; 20] }, + ], + ), + // Parachain account id 32 + Location::new( + 1, + [ + GlobalConsensus(Westend), + Parachain(2000), + AccountId32 { network: None, id: [0; 32] }, + ], + ), + // Parchain Pallet instance cases + // Parachain pallet instance + Location::new(1, [GlobalConsensus(Westend), Parachain(2000), PalletInstance(8)]), + // Parachain Pallet general index + Location::new( + 1, + [GlobalConsensus(Westend), Parachain(2000), PalletInstance(8), GeneralIndex(1)], + ), + // Parachain Pallet general key + Location::new( + 1, + [ + GlobalConsensus(Westend), + Parachain(2000), + PalletInstance(8), + GeneralKey { length: 32, data: [0; 32] }, + ], + ), + // Parachain Pallet account key 20 + Location::new( + 1, + [ + GlobalConsensus(Westend), + Parachain(2000), + PalletInstance(8), + AccountKey20 { network: None, key: [0; 20] }, + ], + ), + // Parachain Pallet account id 32 + Location::new( + 1, + [ + GlobalConsensus(Westend), + Parachain(2000), + PalletInstance(8), + AccountId32 { network: None, id: [0; 32] }, + ], + ), + ]; + + for token in token_locations { + assert!( + TokenIdOf::convert_location(&token).is_some(), + "Valid token = {token:?} yeilds no TokenId." + ); + } + + let non_token_locations = [ + // Relative location for a token should fail. + Location::new(1, []), + // Relative location for a token should fail. + Location::new(1, [Parachain(1000)]), + ]; + + for token in non_token_locations { + assert!( + TokenIdOf::convert_location(&token).is_none(), + "Invalid token = {token:?} yeilds a TokenId." + ); + } + } +} diff --git a/bridges/snowbridge/primitives/core/src/outbound.rs b/bridges/snowbridge/primitives/core/src/outbound.rs index 0ba0fdb6108..77770761822 100644 --- a/bridges/snowbridge/primitives/core/src/outbound.rs +++ b/bridges/snowbridge/primitives/core/src/outbound.rs @@ -139,6 +139,37 @@ mod v1 { // Fee multiplier multiplier: UD60x18, }, + /// Transfer ERC20 tokens + TransferNativeToken { + /// ID of the agent + agent_id: H256, + /// Address of the ERC20 token + token: H160, + /// The recipient of the tokens + recipient: H160, + /// The amount of tokens to transfer + amount: u128, + }, + /// Register foreign token from Polkadot + RegisterForeignToken { + /// ID for the token + token_id: H256, + /// Name of the token + name: Vec, + /// Short symbol for the token + symbol: Vec, + /// Number of decimal places + decimals: u8, + }, + /// Mint foreign token from Polkadot + MintForeignToken { + /// ID for the token + token_id: H256, + /// The recipient of the newly minted tokens + recipient: H160, + /// The amount of tokens to mint + amount: u128, + }, } impl Command { @@ -154,6 +185,9 @@ mod v1 { Command::TransferNativeFromAgent { .. } => 6, Command::SetTokenTransferFees { .. } => 7, Command::SetPricingParameters { .. } => 8, + Command::TransferNativeToken { .. } => 9, + Command::RegisterForeignToken { .. } => 10, + Command::MintForeignToken { .. } => 11, } } @@ -211,6 +245,26 @@ mod v1 { Token::Uint(U256::from(*delivery_cost)), Token::Uint(multiplier.clone().into_inner()), ])]), + Command::TransferNativeToken { agent_id, token, recipient, amount } => + ethabi::encode(&[Token::Tuple(vec![ + Token::FixedBytes(agent_id.as_bytes().to_owned()), + Token::Address(*token), + Token::Address(*recipient), + Token::Uint(U256::from(*amount)), + ])]), + Command::RegisterForeignToken { token_id, name, symbol, decimals } => + ethabi::encode(&[Token::Tuple(vec![ + Token::FixedBytes(token_id.as_bytes().to_owned()), + Token::String(name.to_owned()), + Token::String(symbol.to_owned()), + Token::Uint(U256::from(*decimals)), + ])]), + Command::MintForeignToken { token_id, recipient, amount } => + ethabi::encode(&[Token::Tuple(vec![ + Token::FixedBytes(token_id.as_bytes().to_owned()), + Token::Address(*recipient), + Token::Uint(U256::from(*amount)), + ])]), } } } @@ -403,6 +457,9 @@ impl GasMeter for ConstantGasMeter { }, Command::SetTokenTransferFees { .. } => 60_000, Command::SetPricingParameters { .. } => 60_000, + Command::TransferNativeToken { .. } => 100_000, + Command::RegisterForeignToken { .. } => 1_200_000, + Command::MintForeignToken { .. } => 100_000, } } } diff --git a/bridges/snowbridge/primitives/router/src/inbound/mod.rs b/bridges/snowbridge/primitives/router/src/inbound/mod.rs index 54e47a7a8b6..5cff8413af6 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/mod.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/mod.rs @@ -9,9 +9,10 @@ use codec::{Decode, Encode}; use core::marker::PhantomData; use frame_support::{traits::tokens::Balance as BalanceT, weights::Weight, PalletError}; use scale_info::TypeInfo; -use sp_core::{Get, RuntimeDebug, H160}; +use snowbridge_core::TokenId; +use sp_core::{Get, RuntimeDebug, H160, H256}; use sp_io::hashing::blake2_256; -use sp_runtime::MultiAddress; +use sp_runtime::{traits::MaybeEquivalence, MultiAddress}; use sp_std::prelude::*; use xcm::prelude::{Junction::AccountKey20, *}; use xcm_executor::traits::ConvertLocation; @@ -45,7 +46,7 @@ pub enum Command { /// XCM execution fee on AssetHub fee: u128, }, - /// Send a token to AssetHub or another parachain + /// Send Ethereum token to AssetHub or another parachain SendToken { /// The address of the ERC20 token to be bridged over to AssetHub token: H160, @@ -56,6 +57,17 @@ pub enum Command { /// XCM execution fee on AssetHub fee: u128, }, + /// Send Polkadot token back to the original parachain + SendNativeToken { + /// The Id of the token + token_id: TokenId, + /// The destination for the transfer + destination: Destination, + /// Amount to transfer + amount: u128, + /// XCM execution fee on AssetHub + fee: u128, + }, } /// Destination for bridged tokens @@ -89,10 +101,16 @@ pub struct MessageToXcm< InboundQueuePalletInstance, AccountId, Balance, + ConvertAssetId, + EthereumUniversalLocation, + GlobalAssetHubLocation, > where CreateAssetCall: Get, CreateAssetDeposit: Get, Balance: BalanceT, + ConvertAssetId: MaybeEquivalence, + EthereumUniversalLocation: Get, + GlobalAssetHubLocation: Get, { _phantom: PhantomData<( CreateAssetCall, @@ -100,6 +118,9 @@ pub struct MessageToXcm< InboundQueuePalletInstance, AccountId, Balance, + ConvertAssetId, + EthereumUniversalLocation, + GlobalAssetHubLocation, )>, } @@ -108,6 +129,11 @@ pub struct MessageToXcm< pub enum ConvertMessageError { /// The message version is not supported for conversion. UnsupportedVersion, + InvalidDestination, + InvalidToken, + /// The fee asset is not supported for conversion. + UnsupportedFeeAsset, + CannotReanchor, } /// convert the inbound message to xcm which will be forwarded to the destination chain @@ -115,51 +141,107 @@ pub trait ConvertMessage { type Balance: BalanceT + From; type AccountId; /// Converts a versioned message into an XCM message and an optional topicID - fn convert(message: VersionedMessage) -> Result<(Xcm<()>, Self::Balance), ConvertMessageError>; + fn convert( + message_id: H256, + message: VersionedMessage, + ) -> Result<(Xcm<()>, Self::Balance), ConvertMessageError>; } pub type CallIndex = [u8; 2]; -impl - ConvertMessage +impl< + CreateAssetCall, + CreateAssetDeposit, + InboundQueuePalletInstance, + AccountId, + Balance, + ConvertAssetId, + EthereumUniversalLocation, + GlobalAssetHubLocation, + > ConvertMessage for MessageToXcm< CreateAssetCall, CreateAssetDeposit, InboundQueuePalletInstance, AccountId, Balance, + ConvertAssetId, + EthereumUniversalLocation, + GlobalAssetHubLocation, > where CreateAssetCall: Get, CreateAssetDeposit: Get, InboundQueuePalletInstance: Get, Balance: BalanceT + From, AccountId: Into<[u8; 32]>, + ConvertAssetId: MaybeEquivalence, + EthereumUniversalLocation: Get, + GlobalAssetHubLocation: Get, { type Balance = Balance; type AccountId = AccountId; - fn convert(message: VersionedMessage) -> Result<(Xcm<()>, Self::Balance), ConvertMessageError> { + fn convert( + message_id: H256, + message: VersionedMessage, + ) -> Result<(Xcm<()>, Self::Balance), ConvertMessageError> { use Command::*; use VersionedMessage::*; match message { V1(MessageV1 { chain_id, command: RegisterToken { token, fee } }) => - Ok(Self::convert_register_token(chain_id, token, fee)), + Ok(Self::convert_register_token(message_id, chain_id, token, fee)), V1(MessageV1 { chain_id, command: SendToken { token, destination, amount, fee } }) => - Ok(Self::convert_send_token(chain_id, token, destination, amount, fee)), + Ok(Self::convert_send_token(message_id, chain_id, token, destination, amount, fee)), + V1(MessageV1 { + chain_id, + command: SendNativeToken { token_id, destination, amount, fee }, + }) => Self::convert_send_native_token( + message_id, + chain_id, + token_id, + destination, + amount, + fee, + ), } } } -impl - MessageToXcm -where +impl< + CreateAssetCall, + CreateAssetDeposit, + InboundQueuePalletInstance, + AccountId, + Balance, + ConvertAssetId, + EthereumUniversalLocation, + GlobalAssetHubLocation, + > + MessageToXcm< + CreateAssetCall, + CreateAssetDeposit, + InboundQueuePalletInstance, + AccountId, + Balance, + ConvertAssetId, + EthereumUniversalLocation, + GlobalAssetHubLocation, + > where CreateAssetCall: Get, CreateAssetDeposit: Get, InboundQueuePalletInstance: Get, Balance: BalanceT + From, AccountId: Into<[u8; 32]>, + ConvertAssetId: MaybeEquivalence, + EthereumUniversalLocation: Get, + GlobalAssetHubLocation: Get, { - fn convert_register_token(chain_id: u64, token: H160, fee: u128) -> (Xcm<()>, Balance) { + fn convert_register_token( + message_id: H256, + chain_id: u64, + token: H160, + fee: u128, + ) -> (Xcm<()>, Balance) { let network = Ethereum { chain_id }; let xcm_fee: Asset = (Location::parent(), fee).into(); let deposit: Asset = (Location::parent(), CreateAssetDeposit::get()).into(); @@ -202,6 +284,8 @@ where // Clear the origin so that remaining assets in holding // are claimable by the physical origin (BridgeHub) ClearOrigin, + // Forward message id to Asset Hub + SetTopic(message_id.into()), ] .into(); @@ -209,6 +293,7 @@ where } fn convert_send_token( + message_id: H256, chain_id: u64, token: H160, destination: Destination, @@ -266,6 +351,8 @@ where BuyExecution { fees: dest_para_fee_asset, weight_limit: Unlimited }, // Deposit asset to beneficiary. DepositAsset { assets: Definite(asset.into()), beneficiary }, + // Forward message id to destination parachain. + SetTopic(message_id.into()), ] .into(), }, @@ -281,6 +368,9 @@ where }, } + // Forward message id to Asset Hub. + instructions.push(SetTopic(message_id.into())); + (instructions.into(), total_fees.into()) } @@ -291,6 +381,59 @@ where [GlobalConsensus(network), AccountKey20 { network: None, key: token.into() }], ) } + + /// Constructs an XCM message destined for AssetHub that withdraws assets from the sovereign + /// account of the Gateway contract and either deposits those assets into a recipient account or + /// forwards the assets to another parachain. + fn convert_send_native_token( + message_id: H256, + chain_id: u64, + token_id: TokenId, + destination: Destination, + amount: u128, + asset_hub_fee: u128, + ) -> Result<(Xcm<()>, Balance), ConvertMessageError> { + let network = Ethereum { chain_id }; + let asset_hub_fee_asset: Asset = (Location::parent(), asset_hub_fee).into(); + + let beneficiary = match destination { + // Final destination is a 32-byte account on AssetHub + Destination::AccountId32 { id } => + Ok(Location::new(0, [AccountId32 { network: None, id }])), + _ => Err(ConvertMessageError::InvalidDestination), + }?; + + let total_fee_asset: Asset = (Location::parent(), asset_hub_fee).into(); + + let asset_loc = + ConvertAssetId::convert(&token_id).ok_or(ConvertMessageError::InvalidToken)?; + + let mut reanchored_asset_loc = asset_loc.clone(); + reanchored_asset_loc + .reanchor(&GlobalAssetHubLocation::get(), &EthereumUniversalLocation::get()) + .map_err(|_| ConvertMessageError::CannotReanchor)?; + + let asset: Asset = (reanchored_asset_loc, amount).into(); + + let inbound_queue_pallet_index = InboundQueuePalletInstance::get(); + + let instructions = vec![ + ReceiveTeleportedAsset(total_fee_asset.clone().into()), + BuyExecution { fees: asset_hub_fee_asset, weight_limit: Unlimited }, + DescendOrigin(PalletInstance(inbound_queue_pallet_index).into()), + UniversalOrigin(GlobalConsensus(network)), + WithdrawAsset(asset.clone().into()), + // Deposit both asset and fees to beneficiary so the fees will not get + // trapped. Another benefit is when fees left more than ED on AssetHub could be + // used to create the beneficiary account in case it does not exist. + DepositAsset { assets: Wild(AllCounted(2)), beneficiary }, + SetTopic(message_id.into()), + ]; + + // `total_fees` to burn on this chain when sending `instructions` to run on AH (which also + // teleport fees) + Ok((instructions.into(), asset_hub_fee.into())) + } } pub struct GlobalConsensusEthereumConvertsFor(PhantomData); diff --git a/bridges/snowbridge/primitives/router/src/inbound/tests.rs b/bridges/snowbridge/primitives/router/src/inbound/tests.rs index 75670b05c10..e0e90e516be 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/tests.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/tests.rs @@ -1,6 +1,6 @@ use super::GlobalConsensusEthereumConvertsFor; use crate::inbound::CallIndex; -use frame_support::parameter_types; +use frame_support::{assert_ok, parameter_types}; use hex_literal::hex; use xcm::prelude::*; use xcm_executor::traits::ConvertLocation; @@ -38,3 +38,32 @@ fn test_contract_location_with_incorrect_location_fails_convert() { None, ); } + +#[test] +fn test_reanchor_all_assets() { + let ethereum_context: InteriorLocation = [GlobalConsensus(Ethereum { chain_id: 1 })].into(); + let ethereum = Location::new(2, ethereum_context.clone()); + let ah_context: InteriorLocation = [GlobalConsensus(Polkadot), Parachain(1000)].into(); + let global_ah = Location::new(1, ah_context.clone()); + let assets = vec![ + // DOT + Location::new(1, []), + // GLMR (Some Polkadot parachain currency) + Location::new(1, [Parachain(2004)]), + // AH asset + Location::new(0, [PalletInstance(50), GeneralIndex(42)]), + // KSM + Location::new(2, [GlobalConsensus(Kusama)]), + // KAR (Some Kusama parachain currency) + Location::new(2, [GlobalConsensus(Kusama), Parachain(2000)]), + ]; + for asset in assets.iter() { + // reanchor logic in pallet_xcm on AH + let mut reanchored_asset = asset.clone(); + assert_ok!(reanchored_asset.reanchor(ðereum, &ah_context)); + // reanchor back to original location in context of Ethereum + let mut reanchored_asset_with_ethereum_context = reanchored_asset.clone(); + assert_ok!(reanchored_asset_with_ethereum_context.reanchor(&global_ah, ðereum_context)); + assert_eq!(reanchored_asset_with_ethereum_context, asset.clone()); + } +} diff --git a/bridges/snowbridge/primitives/router/src/outbound/mod.rs b/bridges/snowbridge/primitives/router/src/outbound/mod.rs index ddc36ce8cb6..d3b6c116dd7 100644 --- a/bridges/snowbridge/primitives/router/src/outbound/mod.rs +++ b/bridges/snowbridge/primitives/router/src/outbound/mod.rs @@ -12,9 +12,10 @@ use codec::{Decode, Encode}; use frame_support::{ensure, traits::Get}; use snowbridge_core::{ outbound::{AgentExecuteCommand, Command, Message, SendMessage}, - ChannelId, ParaId, + AgentId, ChannelId, ParaId, TokenId, TokenIdOf, }; use sp_core::{H160, H256}; +use sp_runtime::traits::MaybeEquivalence; use sp_std::{iter::Peekable, marker::PhantomData, prelude::*}; use xcm::prelude::*; use xcm_executor::traits::{ConvertLocation, ExportXcm}; @@ -24,15 +25,31 @@ pub struct EthereumBlobExporter< EthereumNetwork, OutboundQueue, AgentHashedDescription, ->(PhantomData<(UniversalLocation, EthereumNetwork, OutboundQueue, AgentHashedDescription)>); - -impl ExportXcm - for EthereumBlobExporter -where + ConvertAssetId, +>( + PhantomData<( + UniversalLocation, + EthereumNetwork, + OutboundQueue, + AgentHashedDescription, + ConvertAssetId, + )>, +); + +impl + ExportXcm + for EthereumBlobExporter< + UniversalLocation, + EthereumNetwork, + OutboundQueue, + AgentHashedDescription, + ConvertAssetId, + > where UniversalLocation: Get, EthereumNetwork: Get, OutboundQueue: SendMessage, AgentHashedDescription: ConvertLocation, + ConvertAssetId: MaybeEquivalence, { type Ticket = (Vec, XcmHash); @@ -87,13 +104,8 @@ where SendError::MissingArgument })?; - let mut converter = XcmConverter::new(&message, &expected_network); - let (agent_execute_command, message_id) = converter.convert().map_err(|err|{ - log::error!(target: "xcm::ethereum_blob_exporter", "unroutable due to pattern matching error '{err:?}'."); - SendError::Unroutable - })?; - let source_location = Location::new(1, local_sub.clone()); + let agent_id = match AgentHashedDescription::convert_location(&source_location) { Some(id) => id, None => { @@ -102,13 +114,16 @@ where }, }; + let mut converter = + XcmConverter::::new(&message, expected_network, agent_id); + let (command, message_id) = converter.convert().map_err(|err|{ + log::error!(target: "xcm::ethereum_blob_exporter", "unroutable due to pattern matching error '{err:?}'."); + SendError::Unroutable + })?; + let channel_id: ChannelId = ParaId::from(para_id).into(); - let outbound_message = Message { - id: Some(message_id.into()), - channel_id, - command: Command::AgentExecute { agent_id, command: agent_execute_command }, - }; + let outbound_message = Message { id: Some(message_id.into()), channel_id, command }; // validate the message let (ticket, fee) = OutboundQueue::validate(&outbound_message).map_err(|err| { @@ -154,6 +169,9 @@ enum XcmConverterError { AssetResolutionFailed, InvalidFeeAsset, SetTopicExpected, + ReserveAssetDepositedExpected, + InvalidAsset, + UnexpectedInstruction, } macro_rules! match_expression { @@ -165,18 +183,33 @@ macro_rules! match_expression { }; } -struct XcmConverter<'a, Call> { +struct XcmConverter<'a, ConvertAssetId, Call> { iter: Peekable>>, - ethereum_network: &'a NetworkId, + ethereum_network: NetworkId, + agent_id: AgentId, + _marker: PhantomData, } -impl<'a, Call> XcmConverter<'a, Call> { - fn new(message: &'a Xcm, ethereum_network: &'a NetworkId) -> Self { - Self { iter: message.inner().iter().peekable(), ethereum_network } +impl<'a, ConvertAssetId, Call> XcmConverter<'a, ConvertAssetId, Call> +where + ConvertAssetId: MaybeEquivalence, +{ + fn new(message: &'a Xcm, ethereum_network: NetworkId, agent_id: AgentId) -> Self { + Self { + iter: message.inner().iter().peekable(), + ethereum_network, + agent_id, + _marker: Default::default(), + } } - fn convert(&mut self) -> Result<(AgentExecuteCommand, [u8; 32]), XcmConverterError> { - // Get withdraw/deposit and make native tokens create message. - let result = self.native_tokens_unlock_message()?; + fn convert(&mut self) -> Result<(Command, [u8; 32]), XcmConverterError> { + let result = match self.peek() { + Ok(ReserveAssetDeposited { .. }) => self.send_native_tokens_message(), + // Get withdraw/deposit and make native tokens create message. + Ok(WithdrawAsset { .. }) => self.send_tokens_message(), + Err(e) => Err(e), + _ => return Err(XcmConverterError::UnexpectedInstruction), + }?; // All xcm instructions must be consumed before exit. if self.next().is_ok() { @@ -186,9 +219,7 @@ impl<'a, Call> XcmConverter<'a, Call> { Ok(result) } - fn native_tokens_unlock_message( - &mut self, - ) -> Result<(AgentExecuteCommand, [u8; 32]), XcmConverterError> { + fn send_tokens_message(&mut self) -> Result<(Command, [u8; 32]), XcmConverterError> { use XcmConverterError::*; // Get the reserve assets from WithdrawAsset. @@ -262,7 +293,13 @@ impl<'a, Call> XcmConverter<'a, Call> { // Check if there is a SetTopic and skip over it if found. let topic_id = match_expression!(self.next()?, SetTopic(id), id).ok_or(SetTopicExpected)?; - Ok((AgentExecuteCommand::TransferToken { token, recipient, amount }, *topic_id)) + Ok(( + Command::AgentExecute { + agent_id: self.agent_id, + command: AgentExecuteCommand::TransferToken { token, recipient, amount }, + }, + *topic_id, + )) } fn next(&mut self) -> Result<&'a Instruction, XcmConverterError> { @@ -275,9 +312,95 @@ impl<'a, Call> XcmConverter<'a, Call> { fn network_matches(&self, network: &Option) -> bool { if let Some(network) = network { - network == self.ethereum_network + *network == self.ethereum_network } else { true } } + + /// Convert the xcm for Polkadot-native token from AH into the Command + /// To match transfers of Polkadot-native tokens, we expect an input of the form: + /// # ReserveAssetDeposited + /// # ClearOrigin + /// # BuyExecution + /// # DepositAsset + /// # SetTopic + fn send_native_tokens_message(&mut self) -> Result<(Command, [u8; 32]), XcmConverterError> { + use XcmConverterError::*; + + // Get the reserve assets. + let reserve_assets = + match_expression!(self.next()?, ReserveAssetDeposited(reserve_assets), reserve_assets) + .ok_or(ReserveAssetDepositedExpected)?; + + // Check if clear origin exists and skip over it. + if match_expression!(self.peek(), Ok(ClearOrigin), ()).is_some() { + let _ = self.next(); + } + + // Get the fee asset item from BuyExecution or continue parsing. + let fee_asset = match_expression!(self.peek(), Ok(BuyExecution { fees, .. }), fees); + if fee_asset.is_some() { + let _ = self.next(); + } + + let (deposit_assets, beneficiary) = match_expression!( + self.next()?, + DepositAsset { assets, beneficiary }, + (assets, beneficiary) + ) + .ok_or(DepositAssetExpected)?; + + // assert that the beneficiary is AccountKey20. + let recipient = match_expression!( + beneficiary.unpack(), + (0, [AccountKey20 { network, key }]) + if self.network_matches(network), + H160(*key) + ) + .ok_or(BeneficiaryResolutionFailed)?; + + // Make sure there are reserved assets. + if reserve_assets.len() == 0 { + return Err(NoReserveAssets) + } + + // Check the the deposit asset filter matches what was reserved. + if reserve_assets.inner().iter().any(|asset| !deposit_assets.matches(asset)) { + return Err(FilterDoesNotConsumeAllAssets) + } + + // We only support a single asset at a time. + ensure!(reserve_assets.len() == 1, TooManyAssets); + let reserve_asset = reserve_assets.get(0).ok_or(AssetResolutionFailed)?; + + // If there was a fee specified verify it. + if let Some(fee_asset) = fee_asset { + // The fee asset must be the same as the reserve asset. + if fee_asset.id != reserve_asset.id || fee_asset.fun > reserve_asset.fun { + return Err(InvalidFeeAsset) + } + } + + let (asset_id, amount) = match reserve_asset { + Asset { id: AssetId(inner_location), fun: Fungible(amount) } => + Some((inner_location.clone(), *amount)), + _ => None, + } + .ok_or(AssetResolutionFailed)?; + + // transfer amount must be greater than 0. + ensure!(amount > 0, ZeroAssetTransfer); + + let token_id = TokenIdOf::convert_location(&asset_id).ok_or(InvalidAsset)?; + + let expected_asset_id = ConvertAssetId::convert(&token_id).ok_or(InvalidAsset)?; + + ensure!(asset_id == expected_asset_id, InvalidAsset); + + // Check if there is a SetTopic and skip over it if found. + let topic_id = match_expression!(self.next()?, SetTopic(id), id).ok_or(SetTopicExpected)?; + + Ok((Command::MintForeignToken { token_id, recipient, amount }, *topic_id)) + } } diff --git a/bridges/snowbridge/primitives/router/src/outbound/tests.rs b/bridges/snowbridge/primitives/router/src/outbound/tests.rs index 111243bb45a..6e4fd594634 100644 --- a/bridges/snowbridge/primitives/router/src/outbound/tests.rs +++ b/bridges/snowbridge/primitives/router/src/outbound/tests.rs @@ -4,7 +4,8 @@ use snowbridge_core::{ outbound::{Fee, SendError, SendMessageFeeProvider}, AgentIdOf, }; -use xcm::v3::prelude::SendError as XcmSendError; +use sp_std::default::Default; +use xcm::prelude::SendError as XcmSendError; use super::*; @@ -57,6 +58,16 @@ impl SendMessageFeeProvider for MockErrOutboundQueue { } } +pub struct MockTokenIdConvert; +impl MaybeEquivalence for MockTokenIdConvert { + fn convert(_id: &TokenId) -> Option { + Some(Location::new(1, [GlobalConsensus(Westend)])) + } + fn convert_back(_loc: &Location) -> Option { + None + } +} + #[test] fn exporter_validate_with_unknown_network_yields_not_applicable() { let network = Ethereum { chain_id: 1337 }; @@ -65,14 +76,14 @@ fn exporter_validate_with_unknown_network_yields_not_applicable() { let mut destination: Option = None; let mut message: Option> = None; - let result = EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - >::validate( - network, channel, &mut universal_source, &mut destination, &mut message - ); + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert_eq!(result, Err(XcmSendError::NotApplicable)); } @@ -84,14 +95,14 @@ fn exporter_validate_with_invalid_destination_yields_missing_argument() { let mut destination: Option = None; let mut message: Option> = None; - let result = EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - >::validate( - network, channel, &mut universal_source, &mut destination, &mut message - ); + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert_eq!(result, Err(XcmSendError::MissingArgument)); } @@ -106,14 +117,14 @@ fn exporter_validate_with_x8_destination_yields_not_applicable() { ); let mut message: Option> = None; - let result = EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - >::validate( - network, channel, &mut universal_source, &mut destination, &mut message - ); + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert_eq!(result, Err(XcmSendError::NotApplicable)); } @@ -125,14 +136,14 @@ fn exporter_validate_without_universal_source_yields_missing_argument() { let mut destination: Option = Here.into(); let mut message: Option> = None; - let result = EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - >::validate( - network, channel, &mut universal_source, &mut destination, &mut message - ); + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert_eq!(result, Err(XcmSendError::MissingArgument)); } @@ -144,14 +155,14 @@ fn exporter_validate_without_global_universal_location_yields_unroutable() { let mut destination: Option = Here.into(); let mut message: Option> = None; - let result = EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - >::validate( - network, channel, &mut universal_source, &mut destination, &mut message - ); + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert_eq!(result, Err(XcmSendError::Unroutable)); } @@ -163,14 +174,14 @@ fn exporter_validate_without_global_bridge_location_yields_not_applicable() { let mut destination: Option = Here.into(); let mut message: Option> = None; - let result = EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - >::validate( - network, channel, &mut universal_source, &mut destination, &mut message - ); + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert_eq!(result, Err(XcmSendError::NotApplicable)); } @@ -183,14 +194,14 @@ fn exporter_validate_with_remote_universal_source_yields_not_applicable() { let mut destination: Option = Here.into(); let mut message: Option> = None; - let result = EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - >::validate( - network, channel, &mut universal_source, &mut destination, &mut message - ); + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert_eq!(result, Err(XcmSendError::NotApplicable)); } @@ -202,14 +213,14 @@ fn exporter_validate_without_para_id_in_source_yields_missing_argument() { let mut destination: Option = Here.into(); let mut message: Option> = None; - let result = EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - >::validate( - network, channel, &mut universal_source, &mut destination, &mut message - ); + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert_eq!(result, Err(XcmSendError::MissingArgument)); } @@ -222,14 +233,14 @@ fn exporter_validate_complex_para_id_in_source_yields_missing_argument() { let mut destination: Option = Here.into(); let mut message: Option> = None; - let result = EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - >::validate( - network, channel, &mut universal_source, &mut destination, &mut message - ); + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert_eq!(result, Err(XcmSendError::MissingArgument)); } @@ -242,14 +253,14 @@ fn exporter_validate_without_xcm_message_yields_missing_argument() { let mut destination: Option = Here.into(); let mut message: Option> = None; - let result = EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - >::validate( - network, channel, &mut universal_source, &mut destination, &mut message - ); + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert_eq!(result, Err(XcmSendError::MissingArgument)); } @@ -289,14 +300,14 @@ fn exporter_validate_with_max_target_fee_yields_unroutable() { .into(), ); - let result = EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - >::validate( - network, channel, &mut universal_source, &mut destination, &mut message - ); + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert_eq!(result, Err(XcmSendError::Unroutable)); } @@ -316,14 +327,14 @@ fn exporter_validate_with_unparsable_xcm_yields_unroutable() { let mut message: Option> = Some(vec![WithdrawAsset(fees), BuyExecution { fees: fee, weight_limit: Unlimited }].into()); - let result = EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - >::validate( - network, channel, &mut universal_source, &mut destination, &mut message - ); + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert_eq!(result, Err(XcmSendError::Unroutable)); } @@ -362,14 +373,14 @@ fn exporter_validate_xcm_success_case_1() { .into(), ); - let result = EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - >::validate( - network, channel, &mut universal_source, &mut destination, &mut message - ); + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert!(result.is_ok()); } @@ -381,6 +392,7 @@ fn exporter_deliver_with_submit_failure_yields_unroutable() { BridgedNetwork, MockErrOutboundQueue, AgentIdOf, + MockTokenIdConvert, >::deliver((hex!("deadbeef").to_vec(), XcmHash::default())); assert_eq!(result, Err(XcmSendError::Transport("other transport error"))) } @@ -410,11 +422,15 @@ fn xcm_converter_convert_success() { SetTopic([0; 32]), ] .into(); - let mut converter = XcmConverter::new(&message, &network); - let expected_payload = AgentExecuteCommand::TransferToken { - token: token_address.into(), - recipient: beneficiary_address.into(), - amount: 1000, + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + let expected_payload = Command::AgentExecute { + agent_id: Default::default(), + command: AgentExecuteCommand::TransferToken { + token: token_address.into(), + recipient: beneficiary_address.into(), + amount: 1000, + }, }; let result = converter.convert(); assert_eq!(result, Ok((expected_payload, [0; 32]))); @@ -443,11 +459,15 @@ fn xcm_converter_convert_without_buy_execution_yields_success() { SetTopic([0; 32]), ] .into(); - let mut converter = XcmConverter::new(&message, &network); - let expected_payload = AgentExecuteCommand::TransferToken { - token: token_address.into(), - recipient: beneficiary_address.into(), - amount: 1000, + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + let expected_payload = Command::AgentExecute { + agent_id: Default::default(), + command: AgentExecuteCommand::TransferToken { + token: token_address.into(), + recipient: beneficiary_address.into(), + amount: 1000, + }, }; let result = converter.convert(); assert_eq!(result, Ok((expected_payload, [0; 32]))); @@ -478,11 +498,15 @@ fn xcm_converter_convert_with_wildcard_all_asset_filter_succeeds() { SetTopic([0; 32]), ] .into(); - let mut converter = XcmConverter::new(&message, &network); - let expected_payload = AgentExecuteCommand::TransferToken { - token: token_address.into(), - recipient: beneficiary_address.into(), - amount: 1000, + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + let expected_payload = Command::AgentExecute { + agent_id: Default::default(), + command: AgentExecuteCommand::TransferToken { + token: token_address.into(), + recipient: beneficiary_address.into(), + amount: 1000, + }, }; let result = converter.convert(); assert_eq!(result, Ok((expected_payload, [0; 32]))); @@ -513,11 +537,15 @@ fn xcm_converter_convert_with_fees_less_than_reserve_yields_success() { SetTopic([0; 32]), ] .into(); - let mut converter = XcmConverter::new(&message, &network); - let expected_payload = AgentExecuteCommand::TransferToken { - token: token_address.into(), - recipient: beneficiary_address.into(), - amount: 1000, + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + let expected_payload = Command::AgentExecute { + agent_id: Default::default(), + command: AgentExecuteCommand::TransferToken { + token: token_address.into(), + recipient: beneficiary_address.into(), + amount: 1000, + }, }; let result = converter.convert(); assert_eq!(result, Ok((expected_payload, [0; 32]))); @@ -547,7 +575,8 @@ fn xcm_converter_convert_without_set_topic_yields_set_topic_expected() { ClearTopic, ] .into(); - let mut converter = XcmConverter::new(&message, &network); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); let result = converter.convert(); assert_eq!(result.err(), Some(XcmConverterError::SetTopicExpected)); } @@ -564,7 +593,8 @@ fn xcm_converter_convert_with_partial_message_yields_unexpected_end_of_xcm() { .into(); let message: Xcm<()> = vec![WithdrawAsset(assets)].into(); - let mut converter = XcmConverter::new(&message, &network); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); let result = converter.convert(); assert_eq!(result.err(), Some(XcmConverterError::UnexpectedEndOfXcm)); } @@ -595,7 +625,8 @@ fn xcm_converter_with_different_fee_asset_fails() { SetTopic([0; 32]), ] .into(); - let mut converter = XcmConverter::new(&message, &network); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); let result = converter.convert(); assert_eq!(result.err(), Some(XcmConverterError::InvalidFeeAsset)); } @@ -625,7 +656,8 @@ fn xcm_converter_with_fees_greater_than_reserve_fails() { SetTopic([0; 32]), ] .into(); - let mut converter = XcmConverter::new(&message, &network); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); let result = converter.convert(); assert_eq!(result.err(), Some(XcmConverterError::InvalidFeeAsset)); } @@ -636,7 +668,8 @@ fn xcm_converter_convert_with_empty_xcm_yields_unexpected_end_of_xcm() { let message: Xcm<()> = vec![].into(); - let mut converter = XcmConverter::new(&message, &network); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); let result = converter.convert(); assert_eq!(result.err(), Some(XcmConverterError::UnexpectedEndOfXcm)); @@ -668,7 +701,8 @@ fn xcm_converter_convert_with_extra_instructions_yields_end_of_xcm_message_expec ClearError, ] .into(); - let mut converter = XcmConverter::new(&message, &network); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); let result = converter.convert(); assert_eq!(result.err(), Some(XcmConverterError::EndOfXcmMessageExpected)); @@ -698,10 +732,11 @@ fn xcm_converter_convert_without_withdraw_asset_yields_withdraw_expected() { SetTopic([0; 32]), ] .into(); - let mut converter = XcmConverter::new(&message, &network); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); let result = converter.convert(); - assert_eq!(result.err(), Some(XcmConverterError::WithdrawAssetExpected)); + assert_eq!(result.err(), Some(XcmConverterError::UnexpectedInstruction)); } #[test] @@ -723,7 +758,8 @@ fn xcm_converter_convert_without_withdraw_asset_yields_deposit_expected() { SetTopic([0; 32]), ] .into(); - let mut converter = XcmConverter::new(&message, &network); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); let result = converter.convert(); assert_eq!(result.err(), Some(XcmConverterError::DepositAssetExpected)); @@ -756,7 +792,8 @@ fn xcm_converter_convert_without_assets_yields_no_reserve_assets() { SetTopic([0; 32]), ] .into(); - let mut converter = XcmConverter::new(&message, &network); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); let result = converter.convert(); assert_eq!(result.err(), Some(XcmConverterError::NoReserveAssets)); @@ -794,7 +831,8 @@ fn xcm_converter_convert_with_two_assets_yields_too_many_assets() { SetTopic([0; 32]), ] .into(); - let mut converter = XcmConverter::new(&message, &network); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); let result = converter.convert(); assert_eq!(result.err(), Some(XcmConverterError::TooManyAssets)); @@ -825,7 +863,8 @@ fn xcm_converter_convert_without_consuming_filter_yields_filter_does_not_consume SetTopic([0; 32]), ] .into(); - let mut converter = XcmConverter::new(&message, &network); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); let result = converter.convert(); assert_eq!(result.err(), Some(XcmConverterError::FilterDoesNotConsumeAllAssets)); @@ -856,7 +895,8 @@ fn xcm_converter_convert_with_zero_amount_asset_yields_zero_asset_transfer() { SetTopic([0; 32]), ] .into(); - let mut converter = XcmConverter::new(&message, &network); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); let result = converter.convert(); assert_eq!(result.err(), Some(XcmConverterError::ZeroAssetTransfer)); @@ -886,7 +926,8 @@ fn xcm_converter_convert_non_ethereum_asset_yields_asset_resolution_failed() { SetTopic([0; 32]), ] .into(); - let mut converter = XcmConverter::new(&message, &network); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); let result = converter.convert(); assert_eq!(result.err(), Some(XcmConverterError::AssetResolutionFailed)); @@ -919,7 +960,8 @@ fn xcm_converter_convert_non_ethereum_chain_asset_yields_asset_resolution_failed SetTopic([0; 32]), ] .into(); - let mut converter = XcmConverter::new(&message, &network); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); let result = converter.convert(); assert_eq!(result.err(), Some(XcmConverterError::AssetResolutionFailed)); @@ -952,7 +994,8 @@ fn xcm_converter_convert_non_ethereum_chain_yields_asset_resolution_failed() { SetTopic([0; 32]), ] .into(); - let mut converter = XcmConverter::new(&message, &network); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); let result = converter.convert(); assert_eq!(result.err(), Some(XcmConverterError::AssetResolutionFailed)); @@ -989,7 +1032,8 @@ fn xcm_converter_convert_with_non_ethereum_beneficiary_yields_beneficiary_resolu SetTopic([0; 32]), ] .into(); - let mut converter = XcmConverter::new(&message, &network); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); let result = converter.convert(); assert_eq!(result.err(), Some(XcmConverterError::BeneficiaryResolutionFailed)); @@ -1025,7 +1069,8 @@ fn xcm_converter_convert_with_non_ethereum_chain_beneficiary_yields_beneficiary_ SetTopic([0; 32]), ] .into(); - let mut converter = XcmConverter::new(&message, &network); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); let result = converter.convert(); assert_eq!(result.err(), Some(XcmConverterError::BeneficiaryResolutionFailed)); @@ -1056,3 +1101,65 @@ fn test_describe_here() { hex!("03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314").into() ) } + +#[test] +fn xcm_converter_transfer_native_token_success() { + let network = BridgedNetwork::get(); + + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let amount = 1000000; + let asset_location = Location::new(1, [GlobalConsensus(Westend)]); + let token_id = TokenIdOf::convert_location(&asset_location).unwrap(); + + let assets: Assets = vec![Asset { id: AssetId(asset_location), fun: Fungible(amount) }].into(); + let filter: AssetFilter = assets.clone().into(); + + let message: Xcm<()> = vec![ + ReserveAssetDeposited(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + let expected_payload = + Command::MintForeignToken { recipient: beneficiary_address.into(), amount, token_id }; + let result = converter.convert(); + assert_eq!(result, Ok((expected_payload, [0; 32]))); +} + +#[test] +fn xcm_converter_transfer_native_token_with_invalid_location_will_fail() { + let network = BridgedNetwork::get(); + + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let amount = 1000000; + // Invalid asset location from a different consensus + let asset_location = Location { parents: 2, interior: [GlobalConsensus(Rococo)].into() }; + + let assets: Assets = vec![Asset { id: AssetId(asset_location), fun: Fungible(amount) }].into(); + let filter: AssetFilter = assets.clone().into(); + + let message: Xcm<()> = vec![ + ReserveAssetDeposited(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::InvalidAsset)); +} diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/src/genesis.rs index 2876474e094..a9cfcda0dac 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/src/genesis.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/src/genesis.rs @@ -20,6 +20,7 @@ use sp_core::{sr25519, storage::Storage}; // Cumulus use emulated_integration_tests_common::{ accounts, build_genesis_storage, collators, get_account_id_from_seed, + PenpalBSiblingSovereignAccount, PenpalBTeleportableAssetLocation, PenpalSiblingSovereignAccount, PenpalTeleportableAssetLocation, RESERVABLE_ASSET_ID, SAFE_XCM_VERSION, USDT_ID, }; @@ -81,6 +82,13 @@ pub fn genesis() -> Storage { false, ED, ), + // PenpalB's teleportable asset representation + ( + PenpalBTeleportableAssetLocation::get(), + PenpalBSiblingSovereignAccount::get(), + false, + ED, + ), ], ..Default::default() }, diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/src/lib.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/src/lib.rs index feb59c411c8..e7a28ebf4a4 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/src/lib.rs @@ -46,6 +46,7 @@ decl_test_parachains! { pallets = { PolkadotXcm: bridge_hub_westend_runtime::PolkadotXcm, Balances: bridge_hub_westend_runtime::Balances, + EthereumSystem: bridge_hub_westend_runtime::EthereumSystem, } }, } diff --git a/cumulus/parachains/integration-tests/emulated/common/src/lib.rs b/cumulus/parachains/integration-tests/emulated/common/src/lib.rs index 30e66ced1fb..c6b8889730e 100644 --- a/cumulus/parachains/integration-tests/emulated/common/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/common/src/lib.rs @@ -60,6 +60,7 @@ pub const TELEPORTABLE_ASSET_ID: u32 = 2; pub const USDT_ID: u32 = 1984; pub const PENPAL_ID: u32 = 2000; +pub const PENPAL_B_ID: u32 = 2001; pub const ASSETS_PALLET_ID: u8 = 50; parameter_types! { @@ -71,6 +72,14 @@ parameter_types! { ] ); pub PenpalSiblingSovereignAccount: AccountId = Sibling::from(PENPAL_ID).into_account_truncating(); + pub PenpalBTeleportableAssetLocation: xcm::v4::Location + = xcm::v4::Location::new(1, [ + xcm::v4::Junction::Parachain(PENPAL_B_ID), + xcm::v4::Junction::PalletInstance(ASSETS_PALLET_ID), + xcm::v4::Junction::GeneralIndex(TELEPORTABLE_ASSET_ID.into()), + ] + ); + pub PenpalBSiblingSovereignAccount: AccountId = Sibling::from(PENPAL_B_ID).into_account_truncating(); } /// Helper function to generate a crypto pair from seed diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/mod.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/mod.rs index 30cc4de3905..699641d3328 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/mod.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/mod.rs @@ -18,9 +18,10 @@ use crate::imports::*; mod asset_transfers; mod claim_assets; mod send_xcm; -mod snowbridge; mod teleport; +mod snowbridge; + pub(crate) fn asset_hub_rococo_location() -> Location { Location::new(2, [GlobalConsensus(Rococo), Parachain(AssetHubRococo::para_id().into())]) } diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs index b4db9b365f3..4e9dd5a77dd 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs @@ -16,20 +16,24 @@ use crate::imports::*; use asset_hub_westend_runtime::xcm_config::bridging::to_ethereum::DefaultBridgeHubEthereumBaseFee; use bridge_hub_westend_runtime::EthereumInboundQueue; use codec::{Decode, Encode}; +use emulated_integration_tests_common::RESERVABLE_ASSET_ID; use frame_support::pallet_prelude::TypeInfo; use hex_literal::hex; -use snowbridge_core::outbound::OperatingMode; +use rococo_westend_system_emulated_network::asset_hub_westend_emulated_chain::genesis::AssetHubWestendAssetOwner; +use snowbridge_core::{outbound::OperatingMode, AssetMetadata, TokenIdOf}; use snowbridge_router_primitives::inbound::{ - Command, ConvertMessage, Destination, MessageV1, VersionedMessage, + Command, Destination, GlobalConsensusEthereumConvertsFor, MessageV1, VersionedMessage, }; +use sp_core::H256; use testnet_parachains_constants::westend::snowbridge::EthereumNetwork; +use xcm_executor::traits::ConvertLocation; -const INITIAL_FUND: u128 = 5_000_000_000_000_000_000; +const INITIAL_FUND: u128 = 5_000_000_000_000; pub const CHAIN_ID: u64 = 11155111; pub const WETH: [u8; 20] = hex!("87d1f7fdfEe7f651FaBc8bFCB6E086C278b77A7d"); const ETHEREUM_DESTINATION_ADDRESS: [u8; 20] = hex!("44a57ee2f2FCcb85FDa2B0B18EBD0D8D2333700e"); const XCM_FEE: u128 = 100_000_000_000; -const WETH_AMOUNT: u128 = 1_000_000_000; +const TOKEN_AMOUNT: u128 = 100_000_000_000; #[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)] pub enum ControlCall { @@ -55,20 +59,16 @@ fn register_weth_token_from_ethereum_to_asset_hub() { BridgeHubWestend::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; - type Converter = ::MessageConverter; - let message = VersionedMessage::V1(MessageV1 { chain_id: CHAIN_ID, command: Command::RegisterToken { token: WETH.into(), fee: XCM_FEE }, }); - let (xcm, _) = Converter::convert(message).unwrap(); - let _ = EthereumInboundQueue::send_xcm(xcm, AssetHubRococo::para_id().into()).unwrap(); + let (xcm, _) = EthereumInboundQueue::do_convert([0; 32].into(), message).unwrap(); + let _ = EthereumInboundQueue::send_xcm(xcm, AssetHubWestend::para_id().into()).unwrap(); assert_expected_events!( BridgeHubWestend, - vec![ - RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {}, - ] + vec![RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {},] ); }); @@ -77,9 +77,7 @@ fn register_weth_token_from_ethereum_to_asset_hub() { assert_expected_events!( AssetHubWestend, - vec![ - RuntimeEvent::ForeignAssets(pallet_assets::Event::Created { .. }) => {}, - ] + vec![RuntimeEvent::ForeignAssets(pallet_assets::Event::Created { .. }) => {},] ); }); } @@ -120,26 +118,22 @@ fn send_token_from_ethereum_to_asset_hub() { BridgeHubWestend::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; - type Converter = ::MessageConverter; - let message = VersionedMessage::V1(MessageV1 { chain_id: CHAIN_ID, command: Command::SendToken { token: WETH.into(), destination: Destination::AccountId32 { id: AssetHubWestendReceiver::get().into() }, - amount: WETH_AMOUNT, + amount: TOKEN_AMOUNT, fee: XCM_FEE, }, }); - let (xcm, _) = Converter::convert(message).unwrap(); + let (xcm, _) = EthereumInboundQueue::do_convert([0; 32].into(), message).unwrap(); let _ = EthereumInboundQueue::send_xcm(xcm, AssetHubWestend::para_id().into()).unwrap(); // Check that the message was sent assert_expected_events!( BridgeHubWestend, - vec![ - RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {}, - ] + vec![RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {},] ); }); @@ -149,9 +143,7 @@ fn send_token_from_ethereum_to_asset_hub() { // Check that the token was received and issued as a foreign asset on AssetHub assert_expected_events!( AssetHubWestend, - vec![ - RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {}, - ] + vec![RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {},] ); }); } @@ -167,13 +159,6 @@ fn send_weth_asset_from_asset_hub_to_ethereum() { let weth_asset_location: Location = (Parent, Parent, EthereumNetwork::get(), AccountKey20 { network: None, key: WETH }).into(); - AssetHubWestend::force_default_xcm_version(Some(XCM_VERSION)); - BridgeHubWestend::force_default_xcm_version(Some(XCM_VERSION)); - AssetHubWestend::force_xcm_version( - Location::new(2, [GlobalConsensus(Ethereum { chain_id: CHAIN_ID })]), - XCM_VERSION, - ); - BridgeHubWestend::fund_accounts(vec![(assethub_sovereign.clone(), INITIAL_FUND)]); AssetHubWestend::execute_with(|| { @@ -194,26 +179,23 @@ fn send_weth_asset_from_asset_hub_to_ethereum() { BridgeHubWestend::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; - type Converter = ::MessageConverter; let message = VersionedMessage::V1(MessageV1 { chain_id: CHAIN_ID, command: Command::SendToken { token: WETH.into(), destination: Destination::AccountId32 { id: AssetHubWestendReceiver::get().into() }, - amount: WETH_AMOUNT, + amount: TOKEN_AMOUNT, fee: XCM_FEE, }, }); - let (xcm, _) = Converter::convert(message).unwrap(); + let (xcm, _) = EthereumInboundQueue::do_convert([0; 32].into(), message).unwrap(); let _ = EthereumInboundQueue::send_xcm(xcm, AssetHubWestend::para_id().into()).unwrap(); // Check that the send token message was sent using xcm assert_expected_events!( BridgeHubWestend, - vec![ - RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) =>{},] + vec![RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) =>{},] ); }); @@ -224,9 +206,7 @@ fn send_weth_asset_from_asset_hub_to_ethereum() { // Check that AssetHub has issued the foreign asset assert_expected_events!( AssetHubWestend, - vec![ - RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {}, - ] + vec![RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {},] ); let assets = vec![Asset { id: AssetId(Location::new( @@ -236,9 +216,9 @@ fn send_weth_asset_from_asset_hub_to_ethereum() { AccountKey20 { network: None, key: WETH }, ], )), - fun: Fungible(WETH_AMOUNT), + fun: Fungible(TOKEN_AMOUNT), }]; - let multi_assets = VersionedAssets::V4(Assets::from(assets)); + let versioned_assets = VersionedAssets::V4(Assets::from(assets)); let destination = VersionedLocation::V4(Location::new( 2, @@ -259,7 +239,7 @@ fn send_weth_asset_from_asset_hub_to_ethereum() { RuntimeOrigin::signed(AssetHubWestendReceiver::get()), Box::new(destination), Box::new(beneficiary), - Box::new(multi_assets), + Box::new(versioned_assets), 0, Unlimited, ) @@ -279,10 +259,7 @@ fn send_weth_asset_from_asset_hub_to_ethereum() { // Outbound Queue assert_expected_events!( BridgeHubWestend, - vec![ - - RuntimeEvent::EthereumOutboundQueue(snowbridge_pallet_outbound_queue::Event::MessageQueued - {..}) => {}, ] + vec![RuntimeEvent::EthereumOutboundQueue(snowbridge_pallet_outbound_queue::Event::MessageQueued{ .. }) => {},] ); let events = BridgeHubWestend::events(); // Check that the local fee was credited to the Snowbridge sovereign account @@ -305,3 +282,316 @@ fn send_weth_asset_from_asset_hub_to_ethereum() { ); }); } + +#[test] +fn transfer_relay_token() { + let assethub_sovereign = BridgeHubWestend::sovereign_account_id_of( + BridgeHubWestend::sibling_location_of(AssetHubWestend::para_id()), + ); + BridgeHubWestend::fund_accounts(vec![(assethub_sovereign.clone(), INITIAL_FUND)]); + + let asset_id: Location = Location { parents: 1, interior: [].into() }; + let expected_asset_id: Location = + Location { parents: 1, interior: [GlobalConsensus(Westend)].into() }; + + let expected_token_id = TokenIdOf::convert_location(&expected_asset_id).unwrap(); + + let ethereum_sovereign: AccountId = + GlobalConsensusEthereumConvertsFor::<[u8; 32]>::convert_location(&Location::new( + 2, + [GlobalConsensus(EthereumNetwork::get())], + )) + .unwrap() + .into(); + + // Register token + BridgeHubWestend::execute_with(|| { + type RuntimeOrigin = ::RuntimeOrigin; + type RuntimeEvent = ::RuntimeEvent; + + assert_ok!(::Balances::force_set_balance( + RuntimeOrigin::root(), + sp_runtime::MultiAddress::Id(BridgeHubWestendSender::get()), + INITIAL_FUND * 10, + )); + + assert_ok!(::EthereumSystem::register_token( + RuntimeOrigin::root(), + Box::new(VersionedLocation::V4(asset_id.clone())), + AssetMetadata { + name: "wnd".as_bytes().to_vec().try_into().unwrap(), + symbol: "wnd".as_bytes().to_vec().try_into().unwrap(), + decimals: 12, + }, + )); + // Check that a message was sent to Ethereum to create the agent + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::EthereumSystem(snowbridge_pallet_system::Event::RegisterToken { .. }) => {},] + ); + }); + + // Send token to Ethereum + AssetHubWestend::execute_with(|| { + type RuntimeOrigin = ::RuntimeOrigin; + type RuntimeEvent = ::RuntimeEvent; + + let assets = vec![Asset { id: AssetId(Location::parent()), fun: Fungible(TOKEN_AMOUNT) }]; + let versioned_assets = VersionedAssets::V4(Assets::from(assets)); + + let destination = VersionedLocation::V4(Location::new( + 2, + [GlobalConsensus(Ethereum { chain_id: CHAIN_ID })], + )); + + let beneficiary = VersionedLocation::V4(Location::new( + 0, + [AccountKey20 { network: None, key: ETHEREUM_DESTINATION_ADDRESS.into() }], + )); + + assert_ok!(::PolkadotXcm::limited_reserve_transfer_assets( + RuntimeOrigin::signed(AssetHubWestendSender::get()), + Box::new(destination), + Box::new(beneficiary), + Box::new(versioned_assets), + 0, + Unlimited, + )); + + let events = AssetHubWestend::events(); + // Check that the native asset transferred to some reserved account(sovereign of Ethereum) + assert!( + events.iter().any(|event| matches!( + event, + RuntimeEvent::Balances(pallet_balances::Event::Transfer { amount, to, ..}) + if *amount == TOKEN_AMOUNT && *to == ethereum_sovereign.clone(), + )), + "native token reserved to Ethereum sovereign account." + ); + }); + + // Send token back from ethereum + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + // Check that the transfer token back to Ethereum message was queue in the Ethereum + // Outbound Queue + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::EthereumOutboundQueue(snowbridge_pallet_outbound_queue::Event::MessageQueued{ .. }) => {},] + ); + + // Send relay token back to AH + let message_id: H256 = [0; 32].into(); + let message = VersionedMessage::V1(MessageV1 { + chain_id: CHAIN_ID, + command: Command::SendNativeToken { + token_id: expected_token_id, + destination: Destination::AccountId32 { id: AssetHubWestendReceiver::get().into() }, + amount: TOKEN_AMOUNT, + fee: XCM_FEE, + }, + }); + // Convert the message to XCM + let (xcm, _) = EthereumInboundQueue::do_convert(message_id, message).unwrap(); + // Send the XCM + let _ = EthereumInboundQueue::send_xcm(xcm, AssetHubWestend::para_id().into()).unwrap(); + + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {},] + ); + }); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + assert_expected_events!( + AssetHubWestend, + vec![RuntimeEvent::Balances(pallet_balances::Event::Burned{ .. }) => {},] + ); + + let events = AssetHubWestend::events(); + + // Check that the native token burnt from some reserved account + assert!( + events.iter().any(|event| matches!( + event, + RuntimeEvent::Balances(pallet_balances::Event::Burned { who, ..}) + if *who == ethereum_sovereign.clone(), + )), + "native token burnt from Ethereum sovereign account." + ); + + // Check that the token was minted to beneficiary + assert!( + events.iter().any(|event| matches!( + event, + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, amount }) + if *amount >= TOKEN_AMOUNT && *who == AssetHubWestendReceiver::get() + )), + "Token minted to beneficiary." + ); + }); +} + +#[test] +fn transfer_ah_token() { + let assethub_sovereign = BridgeHubWestend::sovereign_account_id_of( + BridgeHubWestend::sibling_location_of(AssetHubWestend::para_id()), + ); + BridgeHubWestend::fund_accounts(vec![(assethub_sovereign.clone(), INITIAL_FUND)]); + + let ethereum_destination = Location::new(2, [GlobalConsensus(Ethereum { chain_id: CHAIN_ID })]); + + let ethereum_sovereign: AccountId = + GlobalConsensusEthereumConvertsFor::<[u8; 32]>::convert_location(ðereum_destination) + .unwrap() + .into(); + AssetHubWestend::fund_accounts(vec![(ethereum_sovereign.clone(), INITIAL_FUND)]); + + let asset_id: Location = + [PalletInstance(ASSETS_PALLET_ID), GeneralIndex(RESERVABLE_ASSET_ID.into())].into(); + + let asset_id_in_bh: Location = Location::new( + 1, + [ + Parachain(AssetHubWestend::para_id().into()), + PalletInstance(ASSETS_PALLET_ID), + GeneralIndex(RESERVABLE_ASSET_ID.into()), + ], + ); + + let asset_id_after_reanchored = + Location::new(1, [GlobalConsensus(Westend), Parachain(AssetHubWestend::para_id().into())]) + .appended_with(asset_id.clone().interior) + .unwrap(); + + let token_id = TokenIdOf::convert_location(&asset_id_after_reanchored).unwrap(); + + // Register token + BridgeHubWestend::execute_with(|| { + type RuntimeOrigin = ::RuntimeOrigin; + + assert_ok!(::EthereumSystem::register_token( + RuntimeOrigin::root(), + Box::new(VersionedLocation::V4(asset_id_in_bh.clone())), + AssetMetadata { + name: "ah_asset".as_bytes().to_vec().try_into().unwrap(), + symbol: "ah_asset".as_bytes().to_vec().try_into().unwrap(), + decimals: 12, + }, + )); + }); + + // Mint some token + AssetHubWestend::mint_asset( + ::RuntimeOrigin::signed(AssetHubWestendAssetOwner::get()), + RESERVABLE_ASSET_ID, + AssetHubWestendSender::get(), + TOKEN_AMOUNT, + ); + + // Send token to Ethereum + AssetHubWestend::execute_with(|| { + type RuntimeOrigin = ::RuntimeOrigin; + type RuntimeEvent = ::RuntimeEvent; + + // Send partial of the token, will fail if send all + let assets = + vec![Asset { id: AssetId(asset_id.clone()), fun: Fungible(TOKEN_AMOUNT / 10) }]; + let versioned_assets = VersionedAssets::V4(Assets::from(assets)); + + let beneficiary = VersionedLocation::V4(Location::new( + 0, + [AccountKey20 { network: None, key: ETHEREUM_DESTINATION_ADDRESS.into() }], + )); + + assert_ok!(::PolkadotXcm::limited_reserve_transfer_assets( + RuntimeOrigin::signed(AssetHubWestendSender::get()), + Box::new(VersionedLocation::from(ethereum_destination)), + Box::new(beneficiary), + Box::new(versioned_assets), + 0, + Unlimited, + )); + + assert_expected_events!( + AssetHubWestend, + vec![RuntimeEvent::Assets(pallet_assets::Event::Transferred{ .. }) => {},] + ); + + let events = AssetHubWestend::events(); + // Check that the native asset transferred to some reserved account(sovereign of Ethereum) + assert!( + events.iter().any(|event| matches!( + event, + RuntimeEvent::Assets(pallet_assets::Event::Transferred { asset_id, to, ..}) + if *asset_id == RESERVABLE_ASSET_ID && *to == ethereum_sovereign.clone() + )), + "native token reserved to Ethereum sovereign account." + ); + }); + + // Send token back from Ethereum + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + // Check that the transfer token back to Ethereum message was queue in the Ethereum + // Outbound Queue + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::EthereumOutboundQueue(snowbridge_pallet_outbound_queue::Event::MessageQueued{ .. }) => {},] + ); + + let message = VersionedMessage::V1(MessageV1 { + chain_id: CHAIN_ID, + command: Command::SendNativeToken { + token_id, + destination: Destination::AccountId32 { id: AssetHubWestendReceiver::get().into() }, + amount: TOKEN_AMOUNT / 10, + fee: XCM_FEE, + }, + }); + // Convert the message to XCM + let (xcm, _) = EthereumInboundQueue::do_convert([0; 32].into(), message).unwrap(); + // Send the XCM + let _ = EthereumInboundQueue::send_xcm(xcm, AssetHubWestend::para_id().into()).unwrap(); + + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {},] + ); + }); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + assert_expected_events!( + AssetHubWestend, + vec![RuntimeEvent::Assets(pallet_assets::Event::Burned{..}) => {},] + ); + + let events = AssetHubWestend::events(); + + // Check that the native token burnt from some reserved account + assert!( + events.iter().any(|event| matches!( + event, + RuntimeEvent::Assets(pallet_assets::Event::Burned { owner, .. }) + if *owner == ethereum_sovereign.clone(), + )), + "token burnt from Ethereum sovereign account." + ); + + // Check that the token was minted to beneficiary + assert!( + events.iter().any(|event| matches!( + event, + RuntimeEvent::Assets(pallet_assets::Event::Issued { owner, .. }) + if *owner == AssetHubWestendReceiver::get() + )), + "Token minted to beneficiary." + ); + }); +} diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_ethereum_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_ethereum_config.rs index 6c0486c62fa..be7005b5379 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_ethereum_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_ethereum_config.rs @@ -29,9 +29,10 @@ use sp_core::H160; use testnet_parachains_constants::rococo::{ currency::*, fee::WeightToFee, - snowbridge::{EthereumNetwork, INBOUND_QUEUE_PALLET_INDEX}, + snowbridge::{EthereumLocation, EthereumNetwork, INBOUND_QUEUE_PALLET_INDEX}, }; +use crate::xcm_config::RelayNetwork; #[cfg(feature = "runtime-benchmarks")] use benchmark_helpers::DoNothingRouter; use frame_support::{parameter_types, weights::ConstantMultiplier}; @@ -40,8 +41,7 @@ use sp_runtime::{ traits::{ConstU32, ConstU8, Keccak256}, FixedU128, }; - -pub const SLOTS_PER_EPOCH: u32 = snowbridge_pallet_ethereum_client::config::SLOTS_PER_EPOCH as u32; +use xcm::prelude::{GlobalConsensus, InteriorLocation, Location, Parachain}; /// Exports message to the Ethereum Gateway contract. pub type SnowbridgeExporter = EthereumBlobExporter< @@ -49,6 +49,7 @@ pub type SnowbridgeExporter = EthereumBlobExporter< EthereumNetwork, snowbridge_pallet_outbound_queue::Pallet, snowbridge_core::AgentIdOf, + EthereumSystem, >; // Ethereum Bridge @@ -65,6 +66,8 @@ parameter_types! { rewards: Rewards { local: 1 * UNITS, remote: meth(1) }, multiplier: FixedU128::from_rational(1, 1), }; + pub AssetHubFromEthereum: Location = Location::new(1,[GlobalConsensus(RelayNetwork::get()),Parachain(rococo_runtime_constants::system_parachain::ASSET_HUB_ID)]); + pub EthereumUniversalLocation: InteriorLocation = [GlobalConsensus(EthereumNetwork::get())].into(); } impl snowbridge_pallet_inbound_queue::Config for Runtime { @@ -85,6 +88,9 @@ impl snowbridge_pallet_inbound_queue::Config for Runtime { ConstU8, AccountId, Balance, + EthereumSystem, + EthereumUniversalLocation, + AssetHubFromEthereum, >; type WeightToFee = WeightToFee; type LengthToFee = ConstantMultiplier; @@ -161,6 +167,8 @@ parameter_types! { }; } +pub const SLOTS_PER_EPOCH: u32 = snowbridge_pallet_ethereum_client::config::SLOTS_PER_EPOCH as u32; + impl snowbridge_pallet_ethereum_client::Config for Runtime { type RuntimeEvent = RuntimeEvent; type ForkVersions = ChainForkVersions; @@ -181,6 +189,8 @@ impl snowbridge_pallet_system::Config for Runtime { type Helper = (); type DefaultPricingParameters = Parameters; type InboundDeliveryCost = EthereumInboundQueue; + type UniversalLocation = UniversalLocation; + type EthereumLocation = EthereumLocation; } #[cfg(feature = "runtime-benchmarks")] diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/snowbridge_pallet_system.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/snowbridge_pallet_system.rs index c6c188e323a..3831111f097 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/snowbridge_pallet_system.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/snowbridge_pallet_system.rs @@ -253,4 +253,14 @@ impl snowbridge_pallet_system::WeightInfo for WeightInf .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } + + fn register_token() -> Weight { + // Proof Size summary in bytes: + // Measured: `256` + // Estimated: `6044` + // Minimum execution time: 45_000_000 picoseconds. + Weight::from_parts(45_000_000, 6044) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs index 47b6006ed6c..dbca4166a13 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs @@ -30,9 +30,10 @@ use sp_core::H160; use testnet_parachains_constants::westend::{ currency::*, fee::WeightToFee, - snowbridge::{EthereumNetwork, INBOUND_QUEUE_PALLET_INDEX}, + snowbridge::{EthereumLocation, EthereumNetwork, INBOUND_QUEUE_PALLET_INDEX}, }; +use crate::xcm_config::RelayNetwork; #[cfg(feature = "runtime-benchmarks")] use benchmark_helpers::DoNothingRouter; use frame_support::{parameter_types, weights::ConstantMultiplier}; @@ -41,6 +42,7 @@ use sp_runtime::{ traits::{ConstU32, ConstU8, Keccak256}, FixedU128, }; +use xcm::prelude::{GlobalConsensus, InteriorLocation, Location, Parachain}; pub const SLOTS_PER_EPOCH: u32 = snowbridge_pallet_ethereum_client::config::SLOTS_PER_EPOCH as u32; @@ -50,6 +52,7 @@ pub type SnowbridgeExporter = EthereumBlobExporter< EthereumNetwork, snowbridge_pallet_outbound_queue::Pallet, snowbridge_core::AgentIdOf, + EthereumSystem, >; // Ethereum Bridge @@ -66,8 +69,9 @@ parameter_types! { rewards: Rewards { local: 1 * UNITS, remote: meth(1) }, multiplier: FixedU128::from_rational(1, 1), }; + pub AssetHubFromEthereum: Location = Location::new(1,[GlobalConsensus(RelayNetwork::get()),Parachain(westend_runtime_constants::system_parachain::ASSET_HUB_ID)]); + pub EthereumUniversalLocation: InteriorLocation = [GlobalConsensus(EthereumNetwork::get())].into(); } - impl snowbridge_pallet_inbound_queue::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Verifier = snowbridge_pallet_ethereum_client::Pallet; @@ -86,6 +90,9 @@ impl snowbridge_pallet_inbound_queue::Config for Runtime { ConstU8, AccountId, Balance, + EthereumSystem, + EthereumUniversalLocation, + AssetHubFromEthereum, >; type WeightToFee = WeightToFee; type LengthToFee = ConstantMultiplier; @@ -181,6 +188,8 @@ impl snowbridge_pallet_system::Config for Runtime { type Helper = (); type DefaultPricingParameters = Parameters; type InboundDeliveryCost = EthereumInboundQueue; + type UniversalLocation = UniversalLocation; + type EthereumLocation = EthereumLocation; } #[cfg(feature = "runtime-benchmarks")] diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/snowbridge_pallet_system.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/snowbridge_pallet_system.rs index c6c188e323a..3831111f097 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/snowbridge_pallet_system.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/snowbridge_pallet_system.rs @@ -253,4 +253,14 @@ impl snowbridge_pallet_system::WeightInfo for WeightInf .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } + + fn register_token() -> Weight { + // Proof Size summary in bytes: + // Measured: `256` + // Estimated: `6044` + // Minimum execution time: 45_000_000 picoseconds. + Weight::from_parts(45_000_000, 6044) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } } diff --git a/cumulus/parachains/runtimes/constants/src/rococo.rs b/cumulus/parachains/runtimes/constants/src/rococo.rs index 56f4868371c..be4b5c9711c 100644 --- a/cumulus/parachains/runtimes/constants/src/rococo.rs +++ b/cumulus/parachains/runtimes/constants/src/rococo.rs @@ -148,7 +148,7 @@ pub mod time { pub mod snowbridge { use frame_support::parameter_types; - use xcm::opaque::lts::NetworkId; + use xcm::prelude::{Location, NetworkId}; /// The pallet index of the Ethereum inbound queue pallet in the bridge hub runtime. pub const INBOUND_QUEUE_PALLET_INDEX: u8 = 80; @@ -159,6 +159,7 @@ pub mod snowbridge { /// /// pub EthereumNetwork: NetworkId = NetworkId::Ethereum { chain_id: 11155111 }; + pub EthereumLocation: Location = Location::new(2, EthereumNetwork::get()); } } diff --git a/cumulus/parachains/runtimes/constants/src/westend.rs b/cumulus/parachains/runtimes/constants/src/westend.rs index fec66cec2eb..47ba8f7e97a 100644 --- a/cumulus/parachains/runtimes/constants/src/westend.rs +++ b/cumulus/parachains/runtimes/constants/src/westend.rs @@ -171,7 +171,7 @@ pub mod time { pub mod snowbridge { use frame_support::parameter_types; - use xcm::opaque::lts::NetworkId; + use xcm::prelude::{Location, NetworkId}; /// The pallet index of the Ethereum inbound queue pallet in the bridge hub runtime. pub const INBOUND_QUEUE_PALLET_INDEX: u8 = 80; @@ -182,5 +182,6 @@ pub mod snowbridge { /// /// pub EthereumNetwork: NetworkId = NetworkId::Ethereum { chain_id: 11155111 }; + pub EthereumLocation: Location = Location::new(2, EthereumNetwork::get()); } } diff --git a/prdoc/pr_5546.prdoc b/prdoc/pr_5546.prdoc new file mode 100644 index 00000000000..95f02dbe13b --- /dev/null +++ b/prdoc/pr_5546.prdoc @@ -0,0 +1,36 @@ +title: "Transfer polkadot native assets to Ethereum through snowbridge" + +doc: + - audience: Runtime Dev + description: | + Transfer polkadot native asset to Ethereum through snowbridge. + +crates: + - name: snowbridge-pallet-inbound-queue + bump: patch + - name: snowbridge-pallet-inbound-queue + bump: patch + - name: snowbridge-pallet-outbound-queue + bump: patch + - name: snowbridge-pallet-system + bump: minor + validate: false + - name: snowbridge-core + bump: minor + validate: false + - name: snowbridge-router-primitives + bump: minor + validate: false + - name: bridge-hub-westend-runtime + bump: patch + - name: bridge-hub-rococo-runtime + bump: patch + - name: bridge-hub-westend-emulated-chain + bump: patch + - name: bridge-hub-westend-integration-tests + bump: minor + - name: asset-hub-westend-emulated-chain + bump: patch + - name: emulated-integration-tests-common + bump: patch + -- GitLab From ac27c5f5e8571ebfc0191f14ccb842dc2d17cba9 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Fri, 13 Sep 2024 16:27:52 +0200 Subject: [PATCH 060/125] =?UTF-8?q?[Bot]=C2=A0Fix=20backport=20bot=20(#568?= =?UTF-8?q?1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Apparently changing the GH Token made it lose permission... error from here: https://github.com/paritytech/polkadot-sdk/actions/runs/10815755374/job/30005387859 From this list it looks like comment creation should be allowed by adding issues to the list: https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/controlling-permissions-for-github_token --- .github/workflows/command-backport.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/command-backport.yml b/.github/workflows/command-backport.yml index 85e7b77801d..5b32f954d0c 100644 --- a/.github/workflows/command-backport.yml +++ b/.github/workflows/command-backport.yml @@ -9,6 +9,7 @@ on: permissions: contents: write # so it can comment pull-requests: write # so it can create pull requests + issues: write jobs: backport: -- GitLab From 51f336711a0391987db69d6281c9b57bfe49d925 Mon Sep 17 00:00:00 2001 From: Benjamin Gallois Date: Fri, 13 Sep 2024 18:10:17 +0200 Subject: [PATCH 061/125] Fix treasury benchmarks when no SpendOrigin (#3049) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Issue It was impossible to benchmark the pallet_treasury when `SpendOrigin = frame_support::traits::NeverEnsureOrigin;` was specified. ### Done - [x] Use `weight = 0` for all extrinsics that are un-callable with no `SpendOrigin`. - [x] Fix benchmarks for extrinsics requiring a Spend even if `SpendOrigin = frame_support::traits::NeverEnsureOrigin;` --------- Signed-off-by: Oliver Tale-Yazdi Co-authored-by: Bastian Köcher Co-authored-by: Bastian Köcher Co-authored-by: Oliver Tale-Yazdi --- prdoc/pr_3049.prdoc | 11 ++ substrate/frame/treasury/src/benchmarking.rs | 178 +++++++++++++------ substrate/frame/treasury/src/tests.rs | 19 +- 3 files changed, 153 insertions(+), 55 deletions(-) create mode 100644 prdoc/pr_3049.prdoc diff --git a/prdoc/pr_3049.prdoc b/prdoc/pr_3049.prdoc new file mode 100644 index 00000000000..9cead8e2a4e --- /dev/null +++ b/prdoc/pr_3049.prdoc @@ -0,0 +1,11 @@ +title: "Fix treasury benchmarks when `SpendOrigin` being `None`" + +doc: + - audience: Runtime Dev + description: | + Fix treasury benchmarks when `SpendOrigin` not returning any succesful origin. + This is for example the case when `SpendOrigin` is set to `NeverOrigin`. + +crates: + - name: pallet-treasury + bump: patch diff --git a/substrate/frame/treasury/src/benchmarking.rs b/substrate/frame/treasury/src/benchmarking.rs index e63febb5798..0bac78503f4 100644 --- a/substrate/frame/treasury/src/benchmarking.rs +++ b/substrate/frame/treasury/src/benchmarking.rs @@ -26,7 +26,7 @@ use frame_benchmarking::{ v2::*, }; use frame_support::{ - ensure, + assert_err, assert_ok, ensure, traits::{ tokens::{ConversionFromAssetBalance, PaymentStatus}, EnsureOrigin, OnInitialize, @@ -73,12 +73,19 @@ fn setup_proposal, I: 'static>( // Create proposals that are approved for use in `on_initialize`. fn create_approved_proposals, I: 'static>(n: u32) -> Result<(), &'static str> { - let origin = T::SpendOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; + let spender = T::SpendOrigin::try_successful_origin(); + for i in 0..n { let (_, value, lookup) = setup_proposal::(i); - Treasury::::spend_local(origin.clone(), value, lookup)?; + + if let Ok(origin) = &spender { + Treasury::::spend_local(origin.clone(), value, lookup)?; + } + } + + if spender.is_ok() { + ensure!(Approvals::::get().len() == n as usize, "Not all approved"); } - ensure!(Approvals::::get().len() == n as usize, "Not all approved"); Ok(()) } @@ -106,8 +113,8 @@ fn create_spend_arguments, I: 'static>( mod benchmarks { use super::*; - // This benchmark is short-circuited if `SpendOrigin` cannot provide - // a successful origin, in which case `spend` is un-callable and can use weight=0. + /// This benchmark is short-circuited if `SpendOrigin` cannot provide + /// a successful origin, in which case `spend` is un-callable and can use weight=0. #[benchmark] fn spend_local() -> Result<(), BenchmarkError> { let (_, value, beneficiary_lookup) = setup_proposal::(SEED); @@ -155,6 +162,8 @@ mod benchmarks { Ok(()) } + /// This benchmark is short-circuited if `SpendOrigin` cannot provide + /// a successful origin, in which case `spend` is un-callable and can use weight=0. #[benchmark] fn spend() -> Result<(), BenchmarkError> { let origin = @@ -190,85 +199,135 @@ mod benchmarks { #[benchmark] fn payout() -> Result<(), BenchmarkError> { - let origin = T::SpendOrigin::try_successful_origin().map_err(|_| "No origin")?; let (asset_kind, amount, beneficiary, beneficiary_lookup) = create_spend_arguments::(SEED); T::BalanceConverter::ensure_successful(asset_kind.clone()); - Treasury::::spend( - origin, - Box::new(asset_kind.clone()), - amount, - Box::new(beneficiary_lookup), - None, - )?; + + let spend_exists = if let Ok(origin) = T::SpendOrigin::try_successful_origin() { + Treasury::::spend( + origin, + Box::new(asset_kind.clone()), + amount, + Box::new(beneficiary_lookup), + None, + )?; + + true + } else { + false + }; + T::Paymaster::ensure_successful(&beneficiary, asset_kind, amount); let caller: T::AccountId = account("caller", 0, SEED); - #[extrinsic_call] - _(RawOrigin::Signed(caller.clone()), 0u32); - - let id = match Spends::::get(0).unwrap().status { - PaymentState::Attempted { id, .. } => { - assert_ne!(T::Paymaster::check_payment(id), PaymentStatus::Failure); - id - }, - _ => panic!("No payout attempt made"), - }; - assert_last_event::(Event::Paid { index: 0, payment_id: id }.into()); - assert!(Treasury::::payout(RawOrigin::Signed(caller).into(), 0u32).is_err()); + #[block] + { + let res = Treasury::::payout(RawOrigin::Signed(caller.clone()).into(), 0u32); + + if spend_exists { + assert_ok!(res); + } else { + assert_err!(res, crate::Error::::InvalidIndex); + } + } + + if spend_exists { + let id = match Spends::::get(0).unwrap().status { + PaymentState::Attempted { id, .. } => { + assert_ne!(T::Paymaster::check_payment(id), PaymentStatus::Failure); + id + }, + _ => panic!("No payout attempt made"), + }; + assert_last_event::(Event::Paid { index: 0, payment_id: id }.into()); + assert!(Treasury::::payout(RawOrigin::Signed(caller).into(), 0u32).is_err()); + } + Ok(()) } #[benchmark] fn check_status() -> Result<(), BenchmarkError> { - let origin = T::SpendOrigin::try_successful_origin().map_err(|_| "No origin")?; let (asset_kind, amount, beneficiary, beneficiary_lookup) = create_spend_arguments::(SEED); + T::BalanceConverter::ensure_successful(asset_kind.clone()); - Treasury::::spend( - origin, - Box::new(asset_kind.clone()), - amount, - Box::new(beneficiary_lookup), - None, - )?; - T::Paymaster::ensure_successful(&beneficiary, asset_kind, amount); + T::Paymaster::ensure_successful(&beneficiary, asset_kind.clone(), amount); let caller: T::AccountId = account("caller", 0, SEED); - Treasury::::payout(RawOrigin::Signed(caller.clone()).into(), 0u32)?; - match Spends::::get(0).unwrap().status { - PaymentState::Attempted { id, .. } => { - T::Paymaster::ensure_concluded(id); - }, - _ => panic!("No payout attempt made"), + + let spend_exists = if let Ok(origin) = T::SpendOrigin::try_successful_origin() { + Treasury::::spend( + origin, + Box::new(asset_kind), + amount, + Box::new(beneficiary_lookup), + None, + )?; + + Treasury::::payout(RawOrigin::Signed(caller.clone()).into(), 0u32)?; + match Spends::::get(0).unwrap().status { + PaymentState::Attempted { id, .. } => { + T::Paymaster::ensure_concluded(id); + }, + _ => panic!("No payout attempt made"), + }; + + true + } else { + false }; - #[extrinsic_call] - _(RawOrigin::Signed(caller.clone()), 0u32); + #[block] + { + let res = + Treasury::::check_status(RawOrigin::Signed(caller.clone()).into(), 0u32); + + if spend_exists { + assert_ok!(res); + } else { + assert_err!(res, crate::Error::::InvalidIndex); + } + } if let Some(s) = Spends::::get(0) { assert!(!matches!(s.status, PaymentState::Attempted { .. })); } + Ok(()) } #[benchmark] fn void_spend() -> Result<(), BenchmarkError> { - let origin = T::SpendOrigin::try_successful_origin().map_err(|_| "No origin")?; let (asset_kind, amount, _, beneficiary_lookup) = create_spend_arguments::(SEED); T::BalanceConverter::ensure_successful(asset_kind.clone()); - Treasury::::spend( - origin, - Box::new(asset_kind.clone()), - amount, - Box::new(beneficiary_lookup), - None, - )?; - assert!(Spends::::get(0).is_some()); + let spend_exists = if let Ok(origin) = T::SpendOrigin::try_successful_origin() { + Treasury::::spend( + origin, + Box::new(asset_kind.clone()), + amount, + Box::new(beneficiary_lookup), + None, + )?; + assert!(Spends::::get(0).is_some()); + + true + } else { + false + }; + let origin = T::RejectOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; - #[extrinsic_call] - _(origin as T::RuntimeOrigin, 0u32); + #[block] + { + let res = Treasury::::void_spend(origin as T::RuntimeOrigin, 0u32); + + if spend_exists { + assert_ok!(res); + } else { + assert_err!(res, crate::Error::::InvalidIndex); + } + } assert!(Spends::::get(0).is_none()); Ok(()) @@ -279,4 +338,15 @@ mod benchmarks { crate::tests::ExtBuilder::default().build(), crate::tests::Test ); + + mod no_spend_origin_tests { + use super::*; + + impl_benchmark_test_suite!( + Treasury, + crate::tests::ExtBuilder::default().spend_origin_succesful_origin_err().build(), + crate::tests::Test, + benchmarks_path = benchmarking + ); + } } diff --git a/substrate/frame/treasury/src/tests.rs b/substrate/frame/treasury/src/tests.rs index a895ea8151a..106bfb530a8 100644 --- a/substrate/frame/treasury/src/tests.rs +++ b/substrate/frame/treasury/src/tests.rs @@ -77,6 +77,9 @@ thread_local! { pub static PAID: RefCell> = RefCell::new(BTreeMap::new()); pub static STATUS: RefCell> = RefCell::new(BTreeMap::new()); pub static LAST_ID: RefCell = RefCell::new(0u64); + + #[cfg(feature = "runtime-benchmarks")] + pub static TEST_SPEND_ORIGIN_TRY_SUCCESFUL_ORIGIN_ERR: RefCell = RefCell::new(false); } /// paid balance for a given account and asset ids @@ -131,6 +134,7 @@ parameter_types! { pub TreasuryAccount: u128 = Treasury::account_id(); pub const SpendPayoutPeriod: u64 = 5; } + pub struct TestSpendOrigin; impl frame_support::traits::EnsureOrigin for TestSpendOrigin { type Success = u64; @@ -147,7 +151,11 @@ impl frame_support::traits::EnsureOrigin for TestSpendOrigin { } #[cfg(feature = "runtime-benchmarks")] fn try_successful_origin() -> Result { - Ok(RuntimeOrigin::root()) + if TEST_SPEND_ORIGIN_TRY_SUCCESFUL_ORIGIN_ERR.with(|i| *i.borrow()) { + Err(()) + } else { + Ok(frame_system::RawOrigin::Root.into()) + } } } @@ -187,11 +195,20 @@ pub struct ExtBuilder {} impl Default for ExtBuilder { fn default() -> Self { + #[cfg(feature = "runtime-benchmarks")] + TEST_SPEND_ORIGIN_TRY_SUCCESFUL_ORIGIN_ERR.with(|i| *i.borrow_mut() = false); + Self {} } } impl ExtBuilder { + #[cfg(feature = "runtime-benchmarks")] + pub fn spend_origin_succesful_origin_err(self) -> Self { + TEST_SPEND_ORIGIN_TRY_SUCCESFUL_ORIGIN_ERR.with(|i| *i.borrow_mut() = true); + self + } + pub fn build(self) -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); pallet_balances::GenesisConfig:: { -- GitLab From 316b7a7ae6ef2b5aafe0f0dac7e3da039546f97a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Mon, 16 Sep 2024 10:50:29 +0200 Subject: [PATCH 062/125] Introduces `VerifyExistenceProof` trait (#5682) Introduces a trait for verifying existence proofs in the runtime. The trait is implemented for the 16 patricia merkle tree and the binary tree. --------- Co-authored-by: Shawn Tabrizi Co-authored-by: command-bot <> --- Cargo.lock | 3 + prdoc/pr_5682.prdoc | 15 ++ substrate/frame/support/Cargo.toml | 4 + substrate/frame/support/src/traits.rs | 3 + substrate/frame/support/src/traits/proving.rs | 135 ++++++++++++++++++ .../primitives/runtime/src/proving_trie.rs | 7 +- substrate/utils/binary-merkle-tree/Cargo.toml | 9 +- substrate/utils/binary-merkle-tree/src/lib.rs | 61 ++++---- 8 files changed, 204 insertions(+), 33 deletions(-) create mode 100644 prdoc/pr_5682.prdoc create mode 100644 substrate/frame/support/src/traits/proving.rs diff --git a/Cargo.lock b/Cargo.lock index 85b0699fff7..1478a543d14 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1476,6 +1476,7 @@ dependencies = [ "array-bytes", "hash-db", "log", + "parity-scale-codec", "sp-core", "sp-runtime", "sp-tracing 16.0.0", @@ -6017,6 +6018,7 @@ dependencies = [ "aquamarine", "array-bytes", "assert_matches", + "binary-merkle-tree", "bitflags 1.3.2", "docify", "environmental", @@ -6050,6 +6052,7 @@ dependencies = [ "sp-std 14.0.0", "sp-timestamp", "sp-tracing 16.0.0", + "sp-trie", "sp-weights", "static_assertions", "tt-call", diff --git a/prdoc/pr_5682.prdoc b/prdoc/pr_5682.prdoc new file mode 100644 index 00000000000..2b05d73ef55 --- /dev/null +++ b/prdoc/pr_5682.prdoc @@ -0,0 +1,15 @@ +title: Introduces `VerifyExistenceProof` trait + +doc: + - audience: Runtime Dev + description: | + Introduces `VerifyExistenceProof` trait for verifying proofs in the runtime. + An implementation of the trait for binary and 16 patricia merkle tree is provided. + +crates: + - name: binary-merkle-tree + bump: major + - name: sp-runtime + bump: patch + - name: frame-support + bump: minor diff --git a/substrate/frame/support/Cargo.toml b/substrate/frame/support/Cargo.toml index 549059e261c..9e9741ee161 100644 --- a/substrate/frame/support/Cargo.toml +++ b/substrate/frame/support/Cargo.toml @@ -17,6 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] array-bytes = { workspace = true } +binary-merkle-tree.workspace = true serde = { features = ["alloc", "derive"], workspace = true } codec = { features = [ "derive", @@ -44,6 +45,7 @@ sp-staking = { workspace = true } sp-weights = { workspace = true } sp-debug-derive = { workspace = true } sp-metadata-ir = { workspace = true } +sp-trie = { workspace = true } tt-call = { workspace = true } macro_magic = { workspace = true } frame-support-procedural = { workspace = true } @@ -73,6 +75,7 @@ sp-crypto-hashing = { workspace = true, default-features = true } [features] default = ["std"] std = [ + "binary-merkle-tree/std", "codec/std", "environmental/std", "frame-metadata/std", @@ -97,6 +100,7 @@ std = [ "sp-std/std", "sp-timestamp/std", "sp-tracing/std", + "sp-trie/std", "sp-weights/std", ] runtime-benchmarks = [ diff --git a/substrate/frame/support/src/traits.rs b/substrate/frame/support/src/traits.rs index f635ed32a12..6e59ff9d030 100644 --- a/substrate/frame/support/src/traits.rs +++ b/substrate/frame/support/src/traits.rs @@ -132,6 +132,9 @@ pub mod dynamic_params; pub mod tasks; pub use tasks::Task; +mod proving; +pub use proving::*; + #[cfg(feature = "try-runtime")] mod try_runtime; #[cfg(feature = "try-runtime")] diff --git a/substrate/frame/support/src/traits/proving.rs b/substrate/frame/support/src/traits/proving.rs new file mode 100644 index 00000000000..dc44f4cd68e --- /dev/null +++ b/substrate/frame/support/src/traits/proving.rs @@ -0,0 +1,135 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Provides functionality for verifying proofs. + +use alloc::vec::Vec; +use codec::{Decode, Encode}; +use sp_core::Hasher; + +/// Something that can verify the existence of some data in a given proof. +pub trait VerifyExistenceProof { + /// The proof type. + type Proof; + /// The hash type. + type Hash; + + /// Verify the given `proof`. + /// + /// Ensures that the `proof` was build for `root` and returns the proved data. + fn verify_proof(proof: Self::Proof, root: &Self::Hash) -> Result, ()>; +} + +/// Implements [`VerifyExistenceProof`] using a binary merkle tree. +pub struct BinaryMerkleTreeProver(core::marker::PhantomData); + +impl VerifyExistenceProof for BinaryMerkleTreeProver +where + H::Out: Decode + Encode, +{ + type Proof = binary_merkle_tree::MerkleProof>; + type Hash = H::Out; + + fn verify_proof(proof: Self::Proof, root: &Self::Hash) -> Result, ()> { + if proof.root != *root { + return Err(()); + } + + if binary_merkle_tree::verify_proof::( + &proof.root, + proof.proof, + proof.number_of_leaves, + proof.leaf_index, + &proof.leaf, + ) { + Ok(proof.leaf) + } else { + Err(()) + } + } +} + +/// Proof used by [`SixteenPatriciaMerkleTreeProver`] for [`VerifyExistenceProof`]. +#[derive(Encode, Decode)] +pub struct SixteenPatriciaMerkleTreeExistenceProof { + /// The key of the value to prove. + pub key: Vec, + /// The value for that the existence is proved. + pub value: Vec, + /// The encoded nodes to prove the existence of the data under `key`. + pub proof: Vec>, +} + +/// Implements [`VerifyExistenceProof`] using a 16-patricia merkle tree. +pub struct SixteenPatriciaMerkleTreeProver(core::marker::PhantomData); + +impl VerifyExistenceProof for SixteenPatriciaMerkleTreeProver { + type Proof = SixteenPatriciaMerkleTreeExistenceProof; + type Hash = H::Out; + + fn verify_proof(proof: Self::Proof, root: &Self::Hash) -> Result, ()> { + sp_trie::verify_trie_proof::, _, _, _>( + &root, + &proof.proof, + [&(&proof.key, Some(&proof.value))], + ) + .map_err(drop) + .map(|_| proof.value) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use sp_runtime::{proving_trie::BasicProvingTrie, traits::BlakeTwo256}; + + #[test] + fn verify_binary_merkle_tree_prover_works() { + let proof = binary_merkle_tree::merkle_proof::( + vec![b"hey".encode(), b"yes".encode()], + 1, + ); + let root = proof.root; + + assert_eq!( + BinaryMerkleTreeProver::::verify_proof(proof, &root).unwrap(), + b"yes".encode() + ); + } + + #[test] + fn verify_sixteen_patricia_merkle_tree_prover_works() { + let trie = BasicProvingTrie::::generate_for(vec![ + (0u32, &b"hey"[..]), + (1u32, &b"yes"[..]), + ]) + .unwrap(); + let proof = trie.create_single_value_proof(1u32).unwrap(); + let root = *trie.root(); + + let proof = SixteenPatriciaMerkleTreeExistenceProof { + key: 1u32.encode(), + value: b"yes"[..].encode(), + proof, + }; + + assert_eq!( + SixteenPatriciaMerkleTreeProver::::verify_proof(proof, &root).unwrap(), + b"yes"[..].encode() + ); + } +} diff --git a/substrate/primitives/runtime/src/proving_trie.rs b/substrate/primitives/runtime/src/proving_trie.rs index 688bf81e0d7..9a423f18284 100644 --- a/substrate/primitives/runtime/src/proving_trie.rs +++ b/substrate/primitives/runtime/src/proving_trie.rs @@ -139,7 +139,7 @@ impl BasicProvingTrie where Hashing: sp_core::Hasher, Key: Encode, - Value: Encode + Decode, + Value: Encode, { /// Create a new instance of a `ProvingTrie` using an iterator of key/value pairs. pub fn generate_for(items: I) -> Result @@ -167,7 +167,10 @@ where /// Query a value contained within the current trie. Returns `None` if the /// nodes within the current `MemoryDB` are insufficient to query the item. - pub fn query(&self, key: Key) -> Option { + pub fn query(&self, key: Key) -> Option + where + Value: Decode, + { let trie = TrieDBBuilder::new(&self.db, &self.root).build(); key.using_encoded(|s| trie.get(s)) .ok()? diff --git a/substrate/utils/binary-merkle-tree/Cargo.toml b/substrate/utils/binary-merkle-tree/Cargo.toml index 087ec5fd6c6..9577d94ef0b 100644 --- a/substrate/utils/binary-merkle-tree/Cargo.toml +++ b/substrate/utils/binary-merkle-tree/Cargo.toml @@ -12,6 +12,7 @@ homepage.workspace = true workspace = true [dependencies] +codec = { workspace = true, features = ["derive"] } array-bytes = { optional = true, workspace = true, default-features = true } log = { optional = true, workspace = true } hash-db = { workspace = true } @@ -25,4 +26,10 @@ sp-runtime = { workspace = true, default-features = true } [features] debug = ["array-bytes", "log"] default = ["debug", "std"] -std = ["hash-db/std", "log/std", "sp-core/std", "sp-runtime/std"] +std = [ + "codec/std", + "hash-db/std", + "log/std", + "sp-core/std", + "sp-runtime/std", +] diff --git a/substrate/utils/binary-merkle-tree/src/lib.rs b/substrate/utils/binary-merkle-tree/src/lib.rs index f2d338cf028..f98ee060901 100644 --- a/substrate/utils/binary-merkle-tree/src/lib.rs +++ b/substrate/utils/binary-merkle-tree/src/lib.rs @@ -37,6 +37,7 @@ use alloc::vec; #[cfg(not(feature = "std"))] use alloc::vec::Vec; +use codec::{Decode, Encode}; use hash_db::Hasher; /// Construct a root hash of a Binary Merkle Tree created from given leaves. @@ -87,7 +88,7 @@ where /// A generated merkle proof. /// /// The structure contains all necessary data to later on verify the proof and the leaf itself. -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Encode, Decode)] pub struct MerkleProof { /// Root hash of generated merkle tree. pub root: H, @@ -100,9 +101,9 @@ pub struct MerkleProof { /// /// This is needed to detect a case where we have an odd number of leaves that "get promoted" /// to upper layers. - pub number_of_leaves: usize, + pub number_of_leaves: u32, /// Index of the leaf the proof is for (0-based). - pub leaf_index: usize, + pub leaf_index: u32, /// Leaf content. pub leaf: L, } @@ -121,13 +122,13 @@ trait Visitor { /// The method will also visit the `root` hash (level 0). /// /// The `index` is an index of `left` item. - fn visit(&mut self, index: usize, left: &Option, right: &Option); + fn visit(&mut self, index: u32, left: &Option, right: &Option); } /// No-op implementation of the visitor. impl Visitor for () { fn move_up(&mut self) {} - fn visit(&mut self, _index: usize, _left: &Option, _right: &Option) {} + fn visit(&mut self, _index: u32, _left: &Option, _right: &Option) {} } /// Construct a Merkle Proof for leaves given by indices. @@ -140,7 +141,7 @@ impl Visitor for () { /// # Panic /// /// The function will panic if given `leaf_index` is greater than the number of leaves. -pub fn merkle_proof(leaves: I, leaf_index: usize) -> MerkleProof +pub fn merkle_proof(leaves: I, leaf_index: u32) -> MerkleProof where H: Hasher, H::Out: Default + Copy + AsRef<[u8]>, @@ -151,7 +152,7 @@ where let mut leaf = None; let iter = leaves.into_iter().enumerate().map(|(idx, l)| { let hash = ::hash(l.as_ref()); - if idx == leaf_index { + if idx as u32 == leaf_index { leaf = Some(l); } hash @@ -160,11 +161,11 @@ where /// The struct collects a proof for single leaf. struct ProofCollection { proof: Vec, - position: usize, + position: u32, } impl ProofCollection { - fn new(position: usize) -> Self { + fn new(position: u32) -> Self { ProofCollection { proof: Default::default(), position } } } @@ -174,7 +175,7 @@ where self.position /= 2; } - fn visit(&mut self, index: usize, left: &Option, right: &Option) { + fn visit(&mut self, index: u32, left: &Option, right: &Option) { // we are at left branch - right goes to the proof. if self.position == index { if let Some(right) = right { @@ -190,7 +191,7 @@ where } } - let number_of_leaves = iter.len(); + let number_of_leaves = iter.len() as u32; let mut collect_proof = ProofCollection::new(leaf_index); let root = merkelize::(iter, &mut collect_proof); @@ -237,8 +238,8 @@ impl<'a, H, T: AsRef<[u8]>> From<&'a T> for Leaf<'a, H> { pub fn verify_proof<'a, H, P, L>( root: &'a H::Out, proof: P, - number_of_leaves: usize, - leaf_index: usize, + number_of_leaves: u32, + leaf_index: u32, leaf: L, ) -> bool where @@ -440,7 +441,7 @@ mod tests { assert!(verify_proof::( &proof0.root, proof0.proof.clone(), - data.len(), + data.len() as _, proof0.leaf_index, &proof0.leaf, )); @@ -449,7 +450,7 @@ mod tests { assert!(verify_proof::( &proof1.root, proof1.proof, - data.len(), + data.len() as _, proof1.leaf_index, &proof1.leaf, )); @@ -458,7 +459,7 @@ mod tests { assert!(verify_proof::( &proof2.root, proof2.proof, - data.len(), + data.len() as _, proof2.leaf_index, &proof2.leaf )); @@ -479,7 +480,7 @@ mod tests { ) .into(), proof0.proof, - data.len(), + data.len() as _, proof0.leaf_index, &proof0.leaf )); @@ -487,7 +488,7 @@ mod tests { assert!(!verify_proof::( &proof0.root.into(), vec![], - data.len(), + data.len() as _, proof0.leaf_index, &proof0.leaf )); @@ -498,14 +499,14 @@ mod tests { // given let data = vec!["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"]; - for l in 0..data.len() { + for l in 0..data.len() as u32 { // when let proof = merkle_proof::(data.clone(), l); // then assert!(verify_proof::( &proof.root, proof.proof, - data.len(), + data.len() as _, proof.leaf_index, &proof.leaf )); @@ -523,14 +524,14 @@ mod tests { } } - for l in 0..data.len() { + for l in 0..data.len() as u32 { // when let proof = merkle_proof::(data.clone(), l); // then assert!(verify_proof::( &proof.root, proof.proof, - data.len(), + data.len() as _, proof.leaf_index, &proof.leaf )); @@ -546,14 +547,14 @@ mod tests { data.push(format!("{}", i)); } - for l in (0..data.len()).step_by(13) { + for l in (0..data.len() as u32).step_by(13) { // when let proof = merkle_proof::(data.clone(), l); // then assert!(verify_proof::( &proof.root, proof.proof, - data.len(), + data.len() as _, proof.leaf_index, &proof.leaf )); @@ -747,24 +748,24 @@ mod tests { .map(|address| array_bytes::hex2bytes_unchecked(&address)) .collect::>(); - for l in 0..data.len() { + for l in 0..data.len() as u32 { // when let proof = merkle_proof::(data.clone(), l); assert_eq!(array_bytes::bytes2hex("", &proof.root), array_bytes::bytes2hex("", &root)); assert_eq!(proof.leaf_index, l); - assert_eq!(&proof.leaf, &data[l]); + assert_eq!(&proof.leaf, &data[l as usize]); // then assert!(verify_proof::( &proof.root, proof.proof, - data.len(), + data.len() as _, proof.leaf_index, &proof.leaf )); } - let proof = merkle_proof::(data.clone(), data.len() - 1); + let proof = merkle_proof::(data.clone(), data.len() as u32 - 1); assert_eq!( proof, @@ -788,8 +789,8 @@ mod tests { ) .into(), ], - number_of_leaves: data.len(), - leaf_index: data.len() - 1, + number_of_leaves: data.len() as _, + leaf_index: data.len() as u32 - 1, leaf: array_bytes::hex2array_unchecked::<_, 20>( "c26B34D375533fFc4c5276282Fa5D660F3d8cbcB" ) -- GitLab From 22bdc3e5d8bcf2f542fcf57cea686f2774cbb626 Mon Sep 17 00:00:00 2001 From: Muharem Date: Mon, 16 Sep 2024 13:05:09 +0200 Subject: [PATCH 063/125] Asset Hub: auto incremented asset id for trust backed assets (#5687) Setup auto incremented asset id to `50_000_000` for trust backed assets. In order to align with Polkadot/Kusama Asset Hub - https://github.com/polkadot-fellows/runtimes/pull/414 The next closes existing assets IDs in Rococo is `69_696_969`, in Westend is `88_228_866`. ### Migration **Stakeholders**: all clients providing asset creation functionality on Westend/Rococo Asset Hub This change does not break the API but introduces a new constraint. It implements an auto-incremented ID strategy for Trust-Backed Assets (50 pallet instance indexes on both networks), starting at ID 50,000,000. Each new asset must be created with an ID that is one greater than the last asset created. The next ID can be fetched from the `NextAssetId` storage item of the assets pallet. An empty `NextAssetId` storage item indicates no constraint on the next asset ID and can serve as a feature flag for this release. --- .../assets/asset-hub-rococo/src/lib.rs | 8 ++++++- .../assets/asset-hub-westend/src/lib.rs | 8 ++++++- prdoc/pr_5687.prdoc | 24 +++++++++++++++++++ 3 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 prdoc/pr_5687.prdoc diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs index a4a2554b7af..efe4a4052a9 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs @@ -266,7 +266,7 @@ impl pallet_assets::Config for Runtime { type Freezer = AssetsFreezer; type Extra = (); type WeightInfo = weights::pallet_assets_local::WeightInfo; - type CallbackHandle = (); + type CallbackHandle = pallet_assets::AutoIncAssetId; type AssetAccountDeposit = AssetAccountDeposit; type RemoveItemsLimit = frame_support::traits::ConstU32<1000>; #[cfg(feature = "runtime-benchmarks")] @@ -1019,6 +1019,12 @@ pub type Migrations = ( cumulus_pallet_xcmp_queue::migration::v5::MigrateV4ToV5, pallet_collator_selection::migration::v2::MigrationToV2, frame_support::migrations::RemovePallet, + // unreleased + pallet_assets::migration::next_asset_id::SetNextAssetId< + ConstU32<50_000_000>, + Runtime, + TrustBackedAssetsInstance, + >, // permanent pallet_xcm::migration::MigrateToLatestXcmVersion, ); diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs index 6da2a0bc7b9..712b5c0ada9 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -263,7 +263,7 @@ impl pallet_assets::Config for Runtime { type Freezer = AssetsFreezer; type Extra = (); type WeightInfo = weights::pallet_assets_local::WeightInfo; - type CallbackHandle = (); + type CallbackHandle = pallet_assets::AutoIncAssetId; type AssetAccountDeposit = AssetAccountDeposit; type RemoveItemsLimit = ConstU32<1000>; #[cfg(feature = "runtime-benchmarks")] @@ -1021,6 +1021,12 @@ pub type Migrations = ( // unreleased cumulus_pallet_xcmp_queue::migration::v4::MigrationToV4, cumulus_pallet_xcmp_queue::migration::v5::MigrateV4ToV5, + // unreleased + pallet_assets::migration::next_asset_id::SetNextAssetId< + ConstU32<50_000_000>, + Runtime, + TrustBackedAssetsInstance, + >, // permanent pallet_xcm::migration::MigrateToLatestXcmVersion, ); diff --git a/prdoc/pr_5687.prdoc b/prdoc/pr_5687.prdoc new file mode 100644 index 00000000000..f84f8e72226 --- /dev/null +++ b/prdoc/pr_5687.prdoc @@ -0,0 +1,24 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "Westend/Rococo Asset Hub: auto incremented asset id for trust backed assets" + +doc: + - audience: Runtime User + description: | + Setup auto incremented asset id to `50_000_000` for trust backed assets. + + ### Migration + This change does not break the API but introduces a new constraint. It implements + an auto-incremented ID strategy for Trust-Backed Assets (50 pallet instance indexes on both + networks), starting at ID 50,000,000. Each new asset must be created with an ID that is one + greater than the last asset created. The next ID can be fetched from the `NextAssetId` + storage item of the assets pallet. An empty `NextAssetId` storage item indicates no + constraint on the next asset ID and can serve as a feature flag for this release. + + +crates: + - name: asset-hub-rococo-runtime + bump: major + - name: asset-hub-westend-runtime + bump: major -- GitLab From 655382fa87cd82f12920e316af63084ae098a4e3 Mon Sep 17 00:00:00 2001 From: ordian Date: Mon, 16 Sep 2024 14:09:16 +0200 Subject: [PATCH 064/125] fix rococo-dev bench (#5688) the range should always contain at least 2 values for the benchmark to work closes #5680 --- .../parachains/src/paras_inherent/benchmarking.rs | 4 ++-- prdoc/pr_5688.prdoc | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 prdoc/pr_5688.prdoc diff --git a/polkadot/runtime/parachains/src/paras_inherent/benchmarking.rs b/polkadot/runtime/parachains/src/paras_inherent/benchmarking.rs index fa466de1198..266860061be 100644 --- a/polkadot/runtime/parachains/src/paras_inherent/benchmarking.rs +++ b/polkadot/runtime/parachains/src/paras_inherent/benchmarking.rs @@ -17,7 +17,7 @@ use super::*; use crate::{inclusion, ParaId}; use alloc::collections::btree_map::BTreeMap; -use core::cmp::min; +use core::cmp::{max, min}; use frame_benchmarking::{benchmarks, impl_benchmark_test_suite}; use frame_system::RawOrigin; @@ -107,7 +107,7 @@ benchmarks! { // of a single backed candidate. enter_backed_candidates_variable { let v in (BenchBuilder::::fallback_min_backing_votes()) - ..(BenchBuilder::::fallback_max_validators_per_core()); + .. max(BenchBuilder::::fallback_min_backing_votes() + 1, BenchBuilder::::fallback_max_validators_per_core()); let cores_with_backed: BTreeMap<_, _> = vec![(0, v)] // The backed candidate will have `v` validity votes. diff --git a/prdoc/pr_5688.prdoc b/prdoc/pr_5688.prdoc new file mode 100644 index 00000000000..88e712fcba6 --- /dev/null +++ b/prdoc/pr_5688.prdoc @@ -0,0 +1,10 @@ +title: "Fix `paras_inherent` benchmark" + +doc: + - audience: Runtime Dev + description: | + Fixes the benchmark for relay chains like rococo-dev with `max_validators_per_core` value lower than 2. + +crates: +- name: polkadot-runtime-parachains + bump: patch -- GitLab From 1e25ada05b51dd81219e1dbc1d32da823b3b66b2 Mon Sep 17 00:00:00 2001 From: Evgeny Snitko Date: Mon, 16 Sep 2024 18:03:53 +0400 Subject: [PATCH 065/125] Preflight workflow (#5692) Preflight reusable workflow - aggregated workflow to perform all preliminary jobs It currently includes: - check changed files - `set-image` (and runner) job - useful variables --- .github/workflows/build-misc.yml | 36 ++--- .github/workflows/build-publish-images.yml | 141 +++++++----------- .../workflows/check-cargo-check-runtimes.yml | 67 ++++----- .../workflows/check-frame-omni-bencher.yml | 45 ++---- .github/workflows/check-runtime-migration.yml | 33 +--- .github/workflows/checks-quick.yml | 37 ++--- .github/workflows/checks.yml | 56 ++----- .github/workflows/command-prdoc.yml | 16 +- .github/workflows/docs.yml | 29 ++-- .../reusable-check-changed-files.yml | 59 -------- .github/workflows/reusable-preflight.yml | 131 ++++++++++++++++ .github/workflows/subsystem-benchmarks.yml | 29 +--- .../workflows/tests-linux-stable-coverage.yml | 36 +---- .github/workflows/tests-linux-stable.yml | 60 ++------ .github/workflows/tests-misc.yml | 96 +++++------- .github/workflows/tests.yml | 54 ++----- 16 files changed, 359 insertions(+), 566 deletions(-) delete mode 100644 .github/workflows/reusable-check-changed-files.yml create mode 100644 .github/workflows/reusable-preflight.yml diff --git a/.github/workflows/build-misc.yml b/.github/workflows/build-misc.yml index a01384dc002..2a8e81b9787 100644 --- a/.github/workflows/build-misc.yml +++ b/.github/workflows/build-misc.yml @@ -16,34 +16,16 @@ permissions: contents: read jobs: - set-image: - # GitHub Actions allows using 'env' in a container context. - # However, env variables don't work for forks: https://github.com/orgs/community/discussions/44322 - # This workaround sets the container image for each job using 'set-image' job output. - runs-on: ubuntu-latest - outputs: - IMAGE: ${{ steps.set_image.outputs.IMAGE }} - RUNNER: ${{ steps.set_runner.outputs.RUNNER }} - steps: - - name: Checkout - uses: actions/checkout@v4 - - id: set_image - run: cat .github/env >> $GITHUB_OUTPUT - - id: set_runner - run: | - # Run merge queues on persistent runners - if [[ $GITHUB_REF_NAME == *"gh-readonly-queue"* ]]; then - echo "RUNNER=arc-runners-polkadot-sdk-beefy-persistent" >> $GITHUB_OUTPUT - else - echo "RUNNER=arc-runners-polkadot-sdk-beefy" >> $GITHUB_OUTPUT - fi + + preflight: + uses: ./.github/workflows/reusable-preflight.yml build-runtimes-polkavm: timeout-minutes: 20 - needs: [set-image] - runs-on: ${{ needs.set-image.outputs.RUNNER }} + needs: [preflight] + runs-on: ${{ needs.preflight.outputs.RUNNER }} container: - image: ${{ needs.set-image.outputs.IMAGE }} + image: ${{ needs.preflight.outputs.IMAGE }} steps: - name: Checkout uses: actions/checkout@v4 @@ -64,10 +46,10 @@ jobs: build-subkey: timeout-minutes: 20 - needs: [set-image] - runs-on: ${{ needs.set-image.outputs.RUNNER }} + needs: [preflight] + runs-on: ${{ needs.preflight.outputs.RUNNER }} container: - image: ${{ needs.set-image.outputs.IMAGE }} + image: ${{ needs.preflight.outputs.IMAGE }} steps: - name: Checkout uses: actions/checkout@v4 diff --git a/.github/workflows/build-publish-images.yml b/.github/workflows/build-publish-images.yml index 735b727e58b..3a9f6076186 100644 --- a/.github/workflows/build-publish-images.yml +++ b/.github/workflows/build-publish-images.yml @@ -19,41 +19,10 @@ jobs: # # # - set-image: - ## TODO: remove when ready - if: contains(github.event.label.name, 'GHA-migration') || contains(github.event.pull_request.labels.*.name, 'GHA-migration') - # GitHub Actions allows using 'env' in a container context. - # However, env variables don't work for forks: https://github.com/orgs/community/discussions/44322 - # This workaround sets the container image for each job using 'set-image' job output. - runs-on: ubuntu-latest - env: - BRANCH_NAME: ${{ github.head_ref || github.ref_name }} - outputs: - IMAGE: ${{ steps.set_image.outputs.IMAGE }} - RUNNER: ${{ steps.set_runner.outputs.RUNNER }} - REF_NAME: ${{ steps.set_vars.outputs.REF_NAME }} - steps: - - name: Checkout - uses: actions/checkout@v4 - - id: set_image - run: cat .github/env >> $GITHUB_OUTPUT - - id: log - run: | - echo ${BRANCH_NAME} - echo ${COMMIT_SHA} - - id: set_vars - run: | - echo "REF_NAME=${BRANCH_NAME//\//-}" >> $GITHUB_OUTPUT - # By default we use spot machines that can be terminated at any time. - # Merge queues use persistent runners to avoid kicking off from queue when the runner is terminated. - - id: set_runner - run: | - # Run merge queues on persistent runners - if [[ $GITHUB_REF_NAME == *"gh-readonly-queue"* ]]; then - echo "RUNNER=arc-runners-polkadot-sdk-beefy-persistent" >> $GITHUB_OUTPUT - else - echo "RUNNER=arc-runners-polkadot-sdk-beefy" >> $GITHUB_OUTPUT - fi + preflight: + ## TODO: remove when ready + if: contains(github.event.label.name, 'GHA-migration') || contains(github.event.pull_request.labels.*.name, 'GHA-migration') + uses: ./.github/workflows/reusable-preflight.yml ### Build ######################## @@ -61,11 +30,11 @@ jobs: # # build-linux-stable: - needs: [set-image] - runs-on: ${{ needs.set-image.outputs.RUNNER }} + needs: [preflight] + runs-on: ${{ needs.preflight.outputs.RUNNER }} timeout-minutes: 60 container: - image: ${{ needs.set-image.outputs.IMAGE }} + image: ${{ needs.preflight.outputs.IMAGE }} env: RUST_TOOLCHAIN: stable # Enable debug assertions since we are running optimized builds for testing @@ -85,7 +54,7 @@ jobs: - name: pack artifacts run: | mkdir -p ./artifacts - VERSION="${{ needs.set-image.outputs.REF_NAME }}" # will be tag or branch name + VERSION="${{ needs.preflight.outputs.SOURCE_REF_NAME }}" # will be tag or branch name mv ./target/testnet/polkadot ./artifacts/. mv ./target/testnet/polkadot-prepare-worker ./artifacts/. mv ./target/testnet/polkadot-execute-worker ./artifacts/. @@ -94,7 +63,7 @@ jobs: sha256sum polkadot | tee polkadot.sha256 shasum -c polkadot.sha256 cd ../ - EXTRATAG="${{ needs.set-image.outputs.REF_NAME }}-${COMMIT_SHA}" + EXTRATAG="${{ needs.preflight.outputs.SOURCE_REF_NAME }}-${COMMIT_SHA}" echo "Polkadot version = ${VERSION} (EXTRATAG = ${EXTRATAG})" echo -n ${VERSION} > ./artifacts/VERSION echo -n ${EXTRATAG} > ./artifacts/EXTRATAG @@ -109,7 +78,7 @@ jobs: - name: upload artifacts uses: actions/upload-artifact@v4 with: - name: ${{ github.job }}-${{ needs.set-image.outputs.REF_NAME }} + name: ${{ github.job }}-${{ needs.preflight.outputs.SOURCE_REF_NAME }} path: artifacts.tar retention-days: 1 @@ -117,11 +86,11 @@ jobs: # # build-linux-stable-cumulus: - needs: [set-image] - runs-on: ${{ needs.set-image.outputs.RUNNER }} + needs: [preflight] + runs-on: ${{ needs.preflight.outputs.RUNNER }} timeout-minutes: 60 container: - image: ${{ needs.set-image.outputs.IMAGE }} + image: ${{ needs.preflight.outputs.IMAGE }} env: RUSTFLAGS: "-Cdebug-assertions=y -Dwarnings" steps: @@ -135,7 +104,7 @@ jobs: mkdir -p ./artifacts mv ./target/release/polkadot-parachain ./artifacts/. echo "___The VERSION is either a tag name or the curent branch if triggered not by a tag___" - echo ${{ needs.set-image.outputs.REF_NAME }} | tee ./artifacts/VERSION + echo ${{ needs.preflight.outputs.SOURCE_REF_NAME }} | tee ./artifacts/VERSION - name: tar run: tar -cvf artifacts.tar artifacts @@ -143,7 +112,7 @@ jobs: - name: upload artifacts uses: actions/upload-artifact@v4 with: - name: ${{ github.job }}-${{ needs.set-image.outputs.REF_NAME }} + name: ${{ github.job }}-${{ needs.preflight.outputs.SOURCE_REF_NAME }} path: artifacts.tar retention-days: 1 @@ -151,11 +120,11 @@ jobs: # # build-test-parachain: - needs: [set-image] - runs-on: ${{ needs.set-image.outputs.RUNNER }} + needs: [preflight] + runs-on: ${{ needs.preflight.outputs.RUNNER }} timeout-minutes: 60 container: - image: ${{ needs.set-image.outputs.IMAGE }} + image: ${{ needs.preflight.outputs.IMAGE }} env: RUSTFLAGS: "-Cdebug-assertions=y -Dwarnings" steps: @@ -179,7 +148,7 @@ jobs: - name: upload artifacts uses: actions/upload-artifact@v4 with: - name: ${{ github.job }}-${{ needs.set-image.outputs.REF_NAME }} + name: ${{ github.job }}-${{ needs.preflight.outputs.SOURCE_REF_NAME }} path: artifacts.tar retention-days: 1 @@ -187,11 +156,11 @@ jobs: # # build-test-collators: - needs: [set-image] - runs-on: ${{ needs.set-image.outputs.RUNNER }} + needs: [preflight] + runs-on: ${{ needs.preflight.outputs.RUNNER }} timeout-minutes: 60 container: - image: ${{ needs.set-image.outputs.IMAGE }} + image: ${{ needs.preflight.outputs.IMAGE }} steps: - name: Checkout uses: actions/checkout@v4 @@ -204,8 +173,8 @@ jobs: mkdir -p ./artifacts mv ./target/testnet/adder-collator ./artifacts/. mv ./target/testnet/undying-collator ./artifacts/. - echo -n "${{ needs.set-image.outputs.REF_NAME }}" > ./artifacts/VERSION - echo -n "${{ needs.set-image.outputs.REF_NAME }}-${COMMIT_SHA}" > ./artifacts/EXTRATAG + echo -n "${{ needs.preflight.outputs.SOURCE_REF_NAME }}" > ./artifacts/VERSION + echo -n "${{ needs.preflight.outputs.SOURCE_REF_NAME }}-${COMMIT_SHA}" > ./artifacts/EXTRATAG echo "adder-collator version = $(cat ./artifacts/VERSION) (EXTRATAG = $(cat ./artifacts/EXTRATAG))" echo "undying-collator version = $(cat ./artifacts/VERSION) (EXTRATAG = $(cat ./artifacts/EXTRATAG))" cp -r ./docker/* ./artifacts @@ -216,7 +185,7 @@ jobs: - name: upload artifacts uses: actions/upload-artifact@v4 with: - name: ${{ github.job }}-${{ needs.set-image.outputs.REF_NAME }} + name: ${{ github.job }}-${{ needs.preflight.outputs.SOURCE_REF_NAME }} path: artifacts.tar retention-days: 1 @@ -224,11 +193,11 @@ jobs: # # build-malus: - needs: [set-image] - runs-on: ${{ needs.set-image.outputs.RUNNER }} + needs: [preflight] + runs-on: ${{ needs.preflight.outputs.RUNNER }} timeout-minutes: 60 container: - image: ${{ needs.set-image.outputs.IMAGE }} + image: ${{ needs.preflight.outputs.IMAGE }} steps: - name: Checkout uses: actions/checkout@v4 @@ -241,8 +210,8 @@ jobs: mv ./target/testnet/malus ./artifacts/. mv ./target/testnet/polkadot-execute-worker ./artifacts/. mv ./target/testnet/polkadot-prepare-worker ./artifacts/. - echo -n "${{ needs.set-image.outputs.REF_NAME }}" > ./artifacts/VERSION - echo -n "${{ needs.set-image.outputs.REF_NAME }}-${COMMIT_SHA}" > ./artifacts/EXTRATAG + echo -n "${{ needs.preflight.outputs.SOURCE_REF_NAME }}" > ./artifacts/VERSION + echo -n "${{ needs.preflight.outputs.SOURCE_REF_NAME }}-${COMMIT_SHA}" > ./artifacts/EXTRATAG echo "polkadot-test-malus = $(cat ./artifacts/VERSION) (EXTRATAG = $(cat ./artifacts/EXTRATAG))" cp -r ./docker/* ./artifacts @@ -252,7 +221,7 @@ jobs: - name: upload artifacts uses: actions/upload-artifact@v4 with: - name: ${{ github.job }}-${{ needs.set-image.outputs.REF_NAME }} + name: ${{ github.job }}-${{ needs.preflight.outputs.SOURCE_REF_NAME }} path: artifacts.tar retention-days: 1 @@ -260,11 +229,11 @@ jobs: # # build-linux-substrate: - needs: [set-image] - runs-on: ${{ needs.set-image.outputs.RUNNER }} + needs: [preflight] + runs-on: ${{ needs.preflight.outputs.RUNNER }} timeout-minutes: 60 container: - image: ${{ needs.set-image.outputs.IMAGE }} + image: ${{ needs.preflight.outputs.IMAGE }} steps: - name: Checkout uses: actions/checkout@v4 @@ -296,7 +265,7 @@ jobs: - name: upload artifacts uses: actions/upload-artifact@v4 with: - name: ${{ github.job }}-${{ needs.set-image.outputs.REF_NAME }} + name: ${{ github.job }}-${{ needs.preflight.outputs.SOURCE_REF_NAME }} path: artifacts.tar retention-days: 1 @@ -304,11 +273,11 @@ jobs: # # prepare-bridges-zombienet-artifacts: - needs: [set-image] - runs-on: ${{ needs.set-image.outputs.RUNNER }} + needs: [preflight] + runs-on: ${{ needs.preflight.outputs.RUNNER }} timeout-minutes: 60 container: - image: ${{ needs.set-image.outputs.IMAGE }} + image: ${{ needs.preflight.outputs.IMAGE }} steps: - name: Checkout uses: actions/checkout@v4 @@ -326,7 +295,7 @@ jobs: - name: upload artifacts uses: actions/upload-artifact@v4 with: - name: ${{ github.job }}-${{ needs.set-image.outputs.REF_NAME }} + name: ${{ github.job }}-${{ needs.preflight.outputs.SOURCE_REF_NAME }} path: artifacts.tar retention-days: 1 @@ -336,7 +305,7 @@ jobs: # # build-push-image-test-parachain: - needs: [set-image, build-test-parachain] + needs: [preflight, build-test-parachain] runs-on: arc-runners-polkadot-sdk timeout-minutes: 60 steps: @@ -345,7 +314,7 @@ jobs: - uses: actions/download-artifact@v4.1.8 with: - name: build-test-parachain-${{ needs.set-image.outputs.REF_NAME }} + name: build-test-parachain-${{ needs.preflight.outputs.SOURCE_REF_NAME }} - name: tar run: tar -xvf artifacts.tar @@ -360,7 +329,7 @@ jobs: # # build-push-image-polkadot-debug: - needs: [set-image, build-linux-stable] + needs: [preflight, build-linux-stable] runs-on: arc-runners-polkadot-sdk timeout-minutes: 60 steps: @@ -369,7 +338,7 @@ jobs: - uses: actions/download-artifact@v4.1.8 with: - name: build-linux-stable-${{ needs.set-image.outputs.REF_NAME }} + name: build-linux-stable-${{ needs.preflight.outputs.SOURCE_REF_NAME }} - name: tar run: tar -xvf artifacts.tar @@ -385,7 +354,7 @@ jobs: # # build-push-image-colander: - needs: [set-image, build-test-collators] + needs: [preflight, build-test-collators] runs-on: arc-runners-polkadot-sdk timeout-minutes: 60 steps: @@ -394,7 +363,7 @@ jobs: - uses: actions/download-artifact@v4.1.8 with: - name: build-test-collators-${{ needs.set-image.outputs.REF_NAME }} + name: build-test-collators-${{ needs.preflight.outputs.SOURCE_REF_NAME }} - name: tar run: tar -xvf artifacts.tar @@ -410,7 +379,7 @@ jobs: # # build-push-image-malus: - needs: [set-image, build-malus] + needs: [preflight, build-malus] runs-on: arc-runners-polkadot-sdk timeout-minutes: 60 steps: @@ -419,7 +388,7 @@ jobs: - uses: actions/download-artifact@v4.1.8 with: - name: build-malus-${{ needs.set-image.outputs.REF_NAME }} + name: build-malus-${{ needs.preflight.outputs.SOURCE_REF_NAME }} - name: tar run: tar -xvf artifacts.tar @@ -435,7 +404,7 @@ jobs: # # build-push-image-substrate-pr: - needs: [set-image, build-linux-substrate] + needs: [preflight, build-linux-substrate] runs-on: arc-runners-polkadot-sdk timeout-minutes: 60 steps: @@ -444,7 +413,7 @@ jobs: - uses: actions/download-artifact@v4.1.8 with: - name: build-linux-substrate-${{ needs.set-image.outputs.REF_NAME }} + name: build-linux-substrate-${{ needs.preflight.outputs.SOURCE_REF_NAME }} - name: tar run: tar -xvf artifacts.tar @@ -462,7 +431,7 @@ jobs: # unlike other images, bridges+zombienet image is based on Zombienet image that pulls required binaries # from other fresh images (polkadot and cumulus) build-push-image-bridges-zombienet-tests: - needs: [set-image, build-linux-stable, build-linux-stable-cumulus, prepare-bridges-zombienet-artifacts] + needs: [preflight, build-linux-stable, build-linux-stable-cumulus, prepare-bridges-zombienet-artifacts] runs-on: arc-runners-polkadot-sdk timeout-minutes: 60 steps: @@ -471,7 +440,7 @@ jobs: - uses: actions/download-artifact@v4.1.8 with: - name: build-linux-stable-${{ needs.set-image.outputs.REF_NAME }} + name: build-linux-stable-${{ needs.preflight.outputs.SOURCE_REF_NAME }} - name: tar run: | tar -xvf artifacts.tar @@ -479,7 +448,7 @@ jobs: - uses: actions/download-artifact@v4.1.8 with: - name: build-linux-stable-cumulus-${{ needs.set-image.outputs.REF_NAME }} + name: build-linux-stable-cumulus-${{ needs.preflight.outputs.SOURCE_REF_NAME }} - name: tar run: | tar -xvf artifacts.tar @@ -487,7 +456,7 @@ jobs: - uses: actions/download-artifact@v4.1.8 with: - name: prepare-bridges-zombienet-artifacts-${{ needs.set-image.outputs.REF_NAME }} + name: prepare-bridges-zombienet-artifacts-${{ needs.preflight.outputs.SOURCE_REF_NAME }} - name: tar run: | tar -xvf artifacts.tar @@ -504,7 +473,7 @@ jobs: # # build-push-image-polkadot-parachain-debug: - needs: [set-image, build-linux-stable-cumulus] + needs: [preflight, build-linux-stable-cumulus] runs-on: arc-runners-polkadot-sdk timeout-minutes: 60 steps: @@ -513,7 +482,7 @@ jobs: - uses: actions/download-artifact@v4.1.8 with: - name: build-linux-stable-cumulus-${{ needs.set-image.outputs.REF_NAME }} + name: build-linux-stable-cumulus-${{ needs.preflight.outputs.SOURCE_REF_NAME }} - name: tar run: tar -xvf artifacts.tar diff --git a/.github/workflows/check-cargo-check-runtimes.yml b/.github/workflows/check-cargo-check-runtimes.yml index bfca4968a3b..16263788b8b 100644 --- a/.github/workflows/check-cargo-check-runtimes.yml +++ b/.github/workflows/check-cargo-check-runtimes.yml @@ -11,33 +11,16 @@ on: # Jobs in this workflow depend on each other, only for limiting peak amount of spawned workers jobs: - set-image: - # GitHub Actions allows using 'env' in a container context. - # However, env variables don't work for forks: https://github.com/orgs/community/discussions/44322 - # This workaround sets the container image for each job using 'set-image' job output. - runs-on: ubuntu-latest - outputs: - IMAGE: ${{ steps.set_image.outputs.IMAGE }} - RUNNER: ${{ steps.set_runner.outputs.RUNNER }} - steps: - - name: Checkout - uses: actions/checkout@v4 - - id: set_image - run: cat .github/env >> $GITHUB_OUTPUT - - id: set_runner - run: | - # Run merge queues on persistent runners - if [[ $GITHUB_REF_NAME == *"gh-readonly-queue"* ]]; then - echo "RUNNER=arc-runners-polkadot-sdk-beefy-persistent" >> $GITHUB_OUTPUT - else - echo "RUNNER=arc-runners-polkadot-sdk-beefy" >> $GITHUB_OUTPUT - fi + + preflight: + uses: ./.github/workflows/reusable-preflight.yml + check-runtime-assets: - runs-on: ${{ needs.set-image.outputs.RUNNER }} - needs: [set-image] + runs-on: ${{ needs.preflight.outputs.RUNNER }} + needs: [preflight] timeout-minutes: 20 container: - image: ${{ needs.set-image.outputs.IMAGE }} + image: ${{ needs.preflight.outputs.IMAGE }} steps: - name: Checkout uses: actions/checkout@v4 @@ -47,11 +30,11 @@ jobs: root: cumulus/parachains/runtimes/assets check-runtime-collectives: - runs-on: ${{ needs.set-image.outputs.RUNNER }} - needs: [check-runtime-assets, set-image] + runs-on: ${{ needs.preflight.outputs.RUNNER }} + needs: [check-runtime-assets, preflight] timeout-minutes: 20 container: - image: ${{ needs.set-image.outputs.IMAGE }} + image: ${{ needs.preflight.outputs.IMAGE }} steps: - name: Checkout uses: actions/checkout@v4 @@ -61,10 +44,10 @@ jobs: root: cumulus/parachains/runtimes/collectives check-runtime-coretime: - runs-on: ${{ needs.set-image.outputs.RUNNER }} + runs-on: ${{ needs.preflight.outputs.RUNNER }} container: - image: ${{ needs.set-image.outputs.IMAGE }} - needs: [check-runtime-assets, set-image] + image: ${{ needs.preflight.outputs.IMAGE }} + needs: [check-runtime-assets, preflight] timeout-minutes: 20 steps: - name: Checkout @@ -75,10 +58,10 @@ jobs: root: cumulus/parachains/runtimes/coretime check-runtime-bridge-hubs: - runs-on: ${{ needs.set-image.outputs.RUNNER }} + runs-on: ${{ needs.preflight.outputs.RUNNER }} container: - image: ${{ needs.set-image.outputs.IMAGE }} - needs: [set-image] + image: ${{ needs.preflight.outputs.IMAGE }} + needs: [preflight] timeout-minutes: 20 steps: - name: Checkout @@ -89,10 +72,10 @@ jobs: root: cumulus/parachains/runtimes/bridge-hubs check-runtime-contracts: - runs-on: ${{ needs.set-image.outputs.RUNNER }} + runs-on: ${{ needs.preflight.outputs.RUNNER }} container: - image: ${{ needs.set-image.outputs.IMAGE }} - needs: [check-runtime-collectives, set-image] + image: ${{ needs.preflight.outputs.IMAGE }} + needs: [check-runtime-collectives, preflight] timeout-minutes: 20 steps: - name: Checkout @@ -103,10 +86,10 @@ jobs: root: cumulus/parachains/runtimes/contracts check-runtime-starters: - runs-on: ${{ needs.set-image.outputs.RUNNER }} + runs-on: ${{ needs.preflight.outputs.RUNNER }} container: - image: ${{ needs.set-image.outputs.IMAGE }} - needs: [check-runtime-assets, set-image] + image: ${{ needs.preflight.outputs.IMAGE }} + needs: [check-runtime-assets, preflight] timeout-minutes: 20 steps: - name: Checkout @@ -117,10 +100,10 @@ jobs: root: cumulus/parachains/runtimes/starters check-runtime-testing: - runs-on: ${{ needs.set-image.outputs.RUNNER }} + runs-on: ${{ needs.preflight.outputs.RUNNER }} container: - image: ${{ needs.set-image.outputs.IMAGE }} - needs: [check-runtime-starters, set-image] + image: ${{ needs.preflight.outputs.IMAGE }} + needs: [check-runtime-starters, preflight] timeout-minutes: 20 steps: - name: Checkout diff --git a/.github/workflows/check-frame-omni-bencher.yml b/.github/workflows/check-frame-omni-bencher.yml index e035a30c7c2..9b01311aa69 100644 --- a/.github/workflows/check-frame-omni-bencher.yml +++ b/.github/workflows/check-frame-omni-bencher.yml @@ -16,39 +16,16 @@ env: ARTIFACTS_NAME: frame-omni-bencher-artifacts jobs: - changes: + + preflight: # TODO: remove once migration is complete or this workflow is fully stable if: contains(github.event.label.name, 'GHA-migration') - permissions: - pull-requests: read - uses: ./.github/workflows/reusable-check-changed-files.yml - - set-image: - # GitHub Actions allows using 'env' in a container context. - # However, env variables don't work for forks: https://github.com/orgs/community/discussions/44322 - # This workaround sets the container image for each job using 'set-image' job output. - runs-on: ubuntu-latest - outputs: - IMAGE: ${{ steps.set_image.outputs.IMAGE }} - RUNNER: ${{ steps.set_runner.outputs.RUNNER }} - steps: - - name: Checkout - uses: actions/checkout@v4 - - id: set_image - run: cat .github/env >> $GITHUB_OUTPUT - - id: set_runner - run: | - # Run merge queues on persistent runners - if [[ $GITHUB_REF_NAME == *"gh-readonly-queue"* ]]; then - echo "RUNNER=arc-runners-polkadot-sdk-beefy-persistent" >> $GITHUB_OUTPUT - else - echo "RUNNER=arc-runners-polkadot-sdk-beefy" >> $GITHUB_OUTPUT - fi + uses: ./.github/workflows/reusable-preflight.yml quick-benchmarks-omni: - runs-on: ${{ needs.set-image.outputs.RUNNER }} - needs: [set-image, changes] - if: ${{ needs.changes.outputs.rust }} + runs-on: ${{ needs.preflight.outputs.RUNNER }} + needs: [preflight] + if: ${{ needs.preflight.outputs.changes_rust }} env: RUSTFLAGS: "-C debug-assertions" RUST_BACKTRACE: "full" @@ -56,7 +33,7 @@ jobs: WASM_BUILD_RUSTFLAGS: "-C debug-assertions" timeout-minutes: 30 container: - image: ${{ needs.set-image.outputs.IMAGE }} + image: ${{ needs.preflight.outputs.IMAGE }} steps: - name: Checkout uses: actions/checkout@v4 @@ -66,9 +43,9 @@ jobs: forklift cargo run --locked --release -p frame-omni-bencher --quiet -- v1 benchmark pallet --runtime target/release/wbuild/asset-hub-westend-runtime/asset_hub_westend_runtime.compact.compressed.wasm --all --steps 2 --repeat 1 --quiet run-frame-omni-bencher: - runs-on: ${{ needs.set-image.outputs.RUNNER }} - needs: [set-image, changes] # , build-frame-omni-bencher ] - if: ${{ needs.changes.outputs.rust }} + runs-on: ${{ needs.preflight.outputs.RUNNER }} + needs: [preflight] # , build-frame-omni-bencher ] + if: ${{ needs.preflight.outputs.changes_rust }} timeout-minutes: 30 strategy: fail-fast: false # keep running other workflows even if one fails, to see the logs of all possible failures @@ -89,7 +66,7 @@ jobs: glutton-westend-runtime, ] container: - image: ${{ needs.set-image.outputs.IMAGE }} + image: ${{ needs.preflight.outputs.IMAGE }} env: PACKAGE_NAME: ${{ matrix.runtime }} steps: diff --git a/.github/workflows/check-runtime-migration.yml b/.github/workflows/check-runtime-migration.yml index 0a1dbc4790c..8185cf171ae 100644 --- a/.github/workflows/check-runtime-migration.yml +++ b/.github/workflows/check-runtime-migration.yml @@ -17,38 +17,19 @@ concurrency: cancel-in-progress: true jobs: - set-image: - # GitHub Actions allows using 'env' in a container context. - # However, env variables don't work for forks: https://github.com/orgs/community/discussions/44322 - # This workaround sets the container image for each job using 'set-image' job output. - runs-on: ubuntu-latest - outputs: - IMAGE: ${{ steps.set_image.outputs.IMAGE }} - RUNNER: ${{ steps.set_runner.outputs.RUNNER }} - steps: - - name: Checkout - uses: actions/checkout@v4 - - id: set_image - run: cat .github/env >> $GITHUB_OUTPUT - # By default we use spot machines that can be terminated at any time. - # Merge queues use persistent runners to avoid kicking off from queue when the runner is terminated. - - id: set_runner - run: | - # Run merge queues on persistent runners - if [[ $GITHUB_REF_NAME == *"gh-readonly-queue"* ]]; then - echo "RUNNER=arc-runners-polkadot-sdk-beefy-persistent" >> $GITHUB_OUTPUT - else - echo "RUNNER=arc-runners-polkadot-sdk-beefy" >> $GITHUB_OUTPUT - fi + + preflight: + uses: ./.github/workflows/reusable-preflight.yml + # More info can be found here: https://github.com/paritytech/polkadot/pull/5865 check-runtime-migration: - runs-on: ${{ needs.set-image.outputs.RUNNER }} + runs-on: ${{ needs.preflight.outputs.RUNNER }} # We need to set this to rather long to allow the snapshot to be created, but the average time # should be much lower. timeout-minutes: 60 - needs: [set-image] + needs: [preflight] container: - image: ${{ needs.set-image.outputs.IMAGE }} + image: ${{ needs.preflight.outputs.IMAGE }} strategy: fail-fast: false matrix: diff --git a/.github/workflows/checks-quick.yml b/.github/workflows/checks-quick.yml index c936e7c8938..9e36d2bcb2e 100644 --- a/.github/workflows/checks-quick.yml +++ b/.github/workflows/checks-quick.yml @@ -15,25 +15,16 @@ concurrency: permissions: {} jobs: - set-image: - # GitHub Actions allows using 'env' in a container context. - # However, env variables don't work for forks: https://github.com/orgs/community/discussions/44322 - # This workaround sets the container image for each job using 'set-image' job output. - runs-on: ubuntu-latest - timeout-minutes: 20 - outputs: - IMAGE: ${{ steps.set_image.outputs.IMAGE }} - steps: - - name: Checkout - uses: actions/checkout@v4 - - id: set_image - run: cat .github/env >> $GITHUB_OUTPUT + + preflight: + uses: ./.github/workflows/reusable-preflight.yml + fmt: runs-on: ubuntu-latest timeout-minutes: 20 - needs: [set-image] + needs: [preflight] container: - image: ${{ needs.set-image.outputs.IMAGE }} + image: ${{ needs.preflight.outputs.IMAGE }} steps: - uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.1.7 - name: Cargo fmt @@ -50,9 +41,9 @@ jobs: check-rust-feature-propagation: runs-on: ubuntu-latest timeout-minutes: 20 - needs: [set-image] + needs: [preflight] container: - image: ${{ needs.set-image.outputs.IMAGE }} + image: ${{ needs.preflight.outputs.IMAGE }} steps: - uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.1.7 - name: fetch deps @@ -66,9 +57,9 @@ jobs: test-rust-features: runs-on: ubuntu-latest timeout-minutes: 20 - needs: [set-image] + needs: [preflight] container: - image: ${{ needs.set-image.outputs.IMAGE }} + image: ${{ needs.preflight.outputs.IMAGE }} steps: - uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.1.7 - name: run rust features @@ -76,9 +67,9 @@ jobs: check-toml-format: runs-on: ubuntu-latest timeout-minutes: 20 - needs: [set-image] + needs: [preflight] container: - image: ${{ needs.set-image.outputs.IMAGE }} + image: ${{ needs.preflight.outputs.IMAGE }} steps: - uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.1.7 - name: check toml format @@ -130,9 +121,9 @@ jobs: check-umbrella: runs-on: ubuntu-latest timeout-minutes: 20 - needs: [set-image] + needs: [preflight] container: - image: ${{ needs.set-image.outputs.IMAGE }} + image: ${{ needs.preflight.outputs.IMAGE }} steps: - uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.1.0 (22. Sep 2023) - name: install python deps diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index cba7df51742..f765d79254c 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -15,41 +15,17 @@ concurrency: permissions: {} jobs: - # temporary disabled because currently doesn't work in merge queue - # changes: - # permissions: - # pull-requests: read - # uses: ./.github/workflows/reusable-check-changed-files.yml - set-image: - # GitHub Actions allows using 'env' in a container context. - # However, env variables don't work for forks: https://github.com/orgs/community/discussions/44322 - # This workaround sets the container image for each job using 'set-image' job output. - runs-on: ubuntu-latest - outputs: - IMAGE: ${{ steps.set_image.outputs.IMAGE }} - RUNNER: ${{ steps.set_runner.outputs.RUNNER }} - steps: - - name: Checkout - uses: actions/checkout@v4 - - id: set_image - run: cat .github/env >> $GITHUB_OUTPUT - # By default we use spot machines that can be terminated at any time. - # Merge queues use persistent runners to avoid kicking off from queue when the runner is terminated. - - id: set_runner - run: | - # Run merge queues on persistent runners - if [[ $GITHUB_REF_NAME == *"gh-readonly-queue"* ]]; then - echo "RUNNER=arc-runners-polkadot-sdk-beefy-persistent" >> $GITHUB_OUTPUT - else - echo "RUNNER=arc-runners-polkadot-sdk-beefy" >> $GITHUB_OUTPUT - fi + + preflight: + uses: ./.github/workflows/reusable-preflight.yml + cargo-clippy: - runs-on: ${{ needs.set-image.outputs.RUNNER }} - needs: [set-image] - # if: ${{ needs.changes.outputs.rust }} + runs-on: ${{ needs.preflight.outputs.RUNNER }} + needs: [preflight] + # if: ${{ needs.preflight.outputs.changes_rust }} timeout-minutes: 40 container: - image: ${{ needs.set-image.outputs.IMAGE }} + image: ${{ needs.preflight.outputs.IMAGE }} env: RUSTFLAGS: "-D warnings" SKIP_WASM_BUILD: 1 @@ -60,12 +36,12 @@ jobs: forklift cargo clippy --all-targets --locked --workspace forklift cargo clippy --all-targets --all-features --locked --workspace check-try-runtime: - runs-on: ${{ needs.set-image.outputs.RUNNER }} - needs: [set-image] - # if: ${{ needs.changes.outputs.rust }} + runs-on: ${{ needs.preflight.outputs.RUNNER }} + needs: [preflight] + # if: ${{ needs.preflight.outputs.changes_rust }} timeout-minutes: 40 container: - image: ${{ needs.set-image.outputs.IMAGE }} + image: ${{ needs.preflight.outputs.IMAGE }} steps: - uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.1.7 - name: script @@ -79,12 +55,12 @@ jobs: forklift cargo check --locked --all --features try-runtime,experimental # check-core-crypto-features works fast without forklift check-core-crypto-features: - runs-on: ${{ needs.set-image.outputs.RUNNER }} - needs: [set-image] - # if: ${{ needs.changes.outputs.rust }} + runs-on: ${{ needs.preflight.outputs.RUNNER }} + needs: [preflight] + # if: ${{ needs.preflight.outputs.changes_rust }} timeout-minutes: 30 container: - image: ${{ needs.set-image.outputs.IMAGE }} + image: ${{ needs.preflight.outputs.IMAGE }} steps: - uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.1.7 - name: script diff --git a/.github/workflows/command-prdoc.yml b/.github/workflows/command-prdoc.yml index 3a08b9a5fb2..aa9de9474a7 100644 --- a/.github/workflows/command-prdoc.yml +++ b/.github/workflows/command-prdoc.yml @@ -43,21 +43,15 @@ concurrency: cancel-in-progress: true jobs: - set-image: - runs-on: ubuntu-latest - outputs: - IMAGE: ${{ steps.set_image.outputs.IMAGE }} - steps: - - name: Checkout - uses: actions/checkout@v4 - - id: set_image - run: cat .github/env >> $GITHUB_OUTPUT + preflight: + uses: ./.github/workflows/reusable-preflight.yml + cmd-prdoc: - needs: [set-image] + needs: [preflight] runs-on: ubuntu-latest timeout-minutes: 20 container: - image: ${{ needs.set-image.outputs.IMAGE }} + image: ${{ needs.preflight.outputs.IMAGE }} permissions: contents: write pull-requests: write diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 523c2b19ba8..514bac3973b 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -13,25 +13,14 @@ concurrency: cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} jobs: - set-image: - # GitHub Actions allows using 'env' in a container context. - # However, env variables don't work for forks: https://github.com/orgs/community/discussions/44322 - # This workaround sets the container image for each job using 'set-image' job output. - # TODO: remove once migration is complete or this workflow is fully stable - if: contains(github.event.label.name, 'GHA-migration') || contains(github.event.pull_request.labels.*.name, 'GHA-migration') - runs-on: ubuntu-latest - outputs: - IMAGE: ${{ steps.set_image.outputs.IMAGE }} - steps: - - name: Checkout - uses: actions/checkout@v4 - - id: set_image - run: cat .github/env >> $GITHUB_OUTPUT + preflight: + uses: ./.github/workflows/reusable-preflight.yml + test-rustdoc: runs-on: arc-runners-polkadot-sdk-beefy - needs: [set-image] + needs: [preflight] container: - image: ${{ needs.set-image.outputs.IMAGE }} + image: ${{ needs.preflight.outputs.IMAGE }} steps: - uses: actions/checkout@v4 - run: forklift cargo doc --workspace --all-features --no-deps @@ -39,9 +28,9 @@ jobs: SKIP_WASM_BUILD: 1 test-doc: runs-on: arc-runners-polkadot-sdk-beefy - needs: [set-image] + needs: [preflight] container: - image: ${{ needs.set-image.outputs.IMAGE }} + image: ${{ needs.preflight.outputs.IMAGE }} steps: - uses: actions/checkout@v4 - run: forklift cargo test --doc --workspace @@ -50,9 +39,9 @@ jobs: build-rustdoc: runs-on: arc-runners-polkadot-sdk-beefy - needs: [set-image, test-rustdoc] + needs: [preflight, test-rustdoc] container: - image: ${{ needs.set-image.outputs.IMAGE }} + image: ${{ needs.preflight.outputs.IMAGE }} steps: - uses: actions/checkout@v4 - run: forklift cargo doc --all-features --workspace --no-deps diff --git a/.github/workflows/reusable-check-changed-files.yml b/.github/workflows/reusable-check-changed-files.yml deleted file mode 100644 index 47f0620439c..00000000000 --- a/.github/workflows/reusable-check-changed-files.yml +++ /dev/null @@ -1,59 +0,0 @@ -# Reusable workflow to perform checks and generate conditions for other workflows. -# Currently it checks if any Rust (build-related) file is changed -# and if the current (caller) workflow file is changed. -# Example: -# -# jobs: -# changes: -# permissions: -# pull-requests: read -# uses: ./.github/workflows/reusable-check-changed-files.yml -# some-job: -# needs: changes -# if: ${{ needs.changes.outputs.rust }} -# ....... - -name: Check changes files - -on: - workflow_call: - # Map the workflow outputs to job outputs - outputs: - rust: - value: ${{ jobs.changes.outputs.rust }} - description: "true if any of the build-related OR current (caller) workflow files have changed" - current-workflow: - value: ${{ jobs.changes.outputs.current-workflow }} - description: "true if current (caller) workflow file has changed" - -jobs: - changes: - runs-on: ubuntu-latest - permissions: - pull-requests: read - outputs: - # true if current workflow (caller) file is changed - rust: ${{ steps.filter.outputs.rust == 'true' || steps.filter.outputs.current-workflow == 'true' }} - current-workflow: ${{ steps.filter.outputs.current-workflow }} - steps: - - id: current-file - run: echo "current-workflow-file=$(echo ${{ github.workflow_ref }} | sed -nE "s/.*(\.github\/workflows\/[a-zA-Z0-9_-]*\.y[a]?ml)@refs.*/\1/p")" >> $GITHUB_OUTPUT - - run: echo "${{ steps.current-file.outputs.current-workflow-file }}" - # For pull requests it's not necessary to checkout the code - - name: Checkout - if: github.event_name != 'pull_request' - uses: actions/checkout@v4 - - id: filter - uses: dorny/paths-filter@v3 - with: - predicate-quantifier: "every" - # current-workflow - check if the current (caller) workflow file is changed - # rust - check if any Rust (build-related) file is changed - filters: | - current-workflow: - - '${{ steps.current-file.outputs.current-workflow-file }}' - rust: - - '**/*' - - '!.github/**/*' - - '!prdoc/**/*' - - '!docs/**/*' diff --git a/.github/workflows/reusable-preflight.yml b/.github/workflows/reusable-preflight.yml new file mode 100644 index 00000000000..71823a97ff2 --- /dev/null +++ b/.github/workflows/reusable-preflight.yml @@ -0,0 +1,131 @@ +# Reusable workflow to set various useful variables +# and to perform checks and generate conditions for other workflows. +# Currently it checks if any Rust (build-related) file is changed +# and if the current (caller) workflow file is changed. +# Example: +# +# jobs: +# preflight: +# uses: ./.github/workflows/reusable-preflight.yml +# some-job: +# needs: changes +# if: ${{ needs.preflight.outputs.changes_rust }} +# ....... + +name: Preflight + +on: + workflow_call: + # Map the workflow outputs to job outputs + outputs: + changes_rust: + value: ${{ jobs.preflight.outputs.changes_rust }} + changes_currentWorkflow: + value: ${{ jobs.preflight.outputs.changes_currentWorkflow }} + + IMAGE: + value: ${{ jobs.preflight.outputs.IMAGE }} + description: "CI image" + RUNNER: + value: ${{ jobs.preflight.outputs.RUNNER }} + description: | + Runner name. + By default we use spot machines that can be terminated at any time. + Merge queues use persistent runners to avoid kicking off from queue when the runner is terminated. + OLDLINUXRUNNER: + value: ${{ jobs.preflight.outputs.OLDLINUXRUNNER }} + + SOURCE_REF_NAME: + value: ${{ jobs.preflight.outputs.SOURCE_REF_NAME }} + description: "Name of the current branch for `push` or source branch for `pull_request`" + COMMIT_SHA: + value: ${{ jobs.preflight.outputs.COMMIT_SHA }} + description: "Sha of the current commit for `push` or head of the source branch for `pull_request`" + +jobs: + preflight: + runs-on: ubuntu-latest + outputs: + changes_rust: ${{ steps.set_changes.outputs.rust_any_changed || steps.set_changes.outputs.currentWorkflow_any_changed }} + changes_currentWorkflow: ${{ steps.set_changes.outputs.currentWorkflow_any_changed }} + + IMAGE: ${{ steps.set_image.outputs.IMAGE }} + RUNNER: ${{ steps.set_runner.outputs.RUNNER }} + OLDLINUXRUNNER: ${{ steps.set_runner.outputs.OLDLINUXRUNNER }} + + SOURCE_REF_NAME: ${{ steps.set_vars.outputs.SOURCE_REF_NAME }} + COMMIT_SHA: ${{ steps.set_vars.outputs.COMMIT_SHA }} + + + steps: + + - uses: actions/checkout@v4 + + # + # Set changes + # + - id: current_file + shell: bash + run: | + echo "currentWorkflowFile=$(echo ${{ github.workflow_ref }} | sed -nE "s/.*(\.github\/workflows\/[a-zA-Z0-9_-]*\.y[a]?ml)@refs.*/\1/p")" >> $GITHUB_OUTPUT + echo "currentActionDir=$(echo ${{ github.action_path }} | sed -nE "s/.*(\.github\/actions\/[a-zA-Z0-9_-]*)/\1/p")" >> $GITHUB_OUTPUT + + - name: Set changes + id: set_changes + uses: tj-actions/changed-files@v45 + with: + files_yaml: | + rust: + - '**/*' + - '!.github/**/*' + - '!prdoc/**/*' + - '!docs/**/*' + currentWorkflow: + - '${{ steps.current_file.outputs.currentWorkflowFile }}' + - '.github/workflows/reusable-preflight.yml' + + # + # Set image + # + - name: Set image + id: set_image + shell: bash + run: cat .github/env >> $GITHUB_OUTPUT + + # + # Set runner + # + # By default we use spot machines that can be terminated at any time. + # Merge queues use persistent runners to avoid kicking off from queue when the runner is terminated. + # + - id: set_runner + shell: bash + run: | + # Run merge queues on persistent runners + if [[ $GITHUB_REF_NAME == *"gh-readonly-queue"* ]]; then + echo "RUNNER=arc-runners-polkadot-sdk-beefy-persistent" >> $GITHUB_OUTPUT + echo "OLDLINUXRUNNER=oldlinux-persistent" >> $GITHUB_OUTPUT + else + echo "RUNNER=arc-runners-polkadot-sdk-beefy" >> $GITHUB_OUTPUT + echo "OLDLINUXRUNNER=oldlinux" >> $GITHUB_OUTPUT + fi + + # + # Set vars + # + - id: set_vars + shell: bash + run: | + export BRANCH_NAME=${{ github.head_ref || github.ref_name }} + echo "SOURCE_REF_NAME=${BRANCH_NAME//\//-}" >> $GITHUB_OUTPUT + echo "COMMIT_SHA=${{ github.event.pull_request.head.sha || github.sha }}" >> $GITHUB_OUTPUT + + + - name: log + shell: bash + run: | + echo "workflow file: ${{ steps.current_file.outputs.currentWorkflowFile }}" + echo "Modified: ${{ steps.set_changes.outputs.modified_keys }}" + echo "github.ref: ${{ github.ref }}" + echo "github.ref_name: ${{ github.ref_name }}" + echo "github.sha: ${{ github.sha }}" diff --git a/.github/workflows/subsystem-benchmarks.yml b/.github/workflows/subsystem-benchmarks.yml index 6f9db9da9e6..210714d847f 100644 --- a/.github/workflows/subsystem-benchmarks.yml +++ b/.github/workflows/subsystem-benchmarks.yml @@ -16,34 +16,15 @@ permissions: contents: read jobs: - set-image: - # GitHub Actions allows using 'env' in a container context. - # However, env variables don't work for forks: https://github.com/orgs/community/discussions/44322 - # This workaround sets the container image for each job using 'set-image' job output. - runs-on: ubuntu-latest - outputs: - IMAGE: ${{ steps.set_image.outputs.IMAGE }} - RUNNER: ${{ steps.set_runner.outputs.RUNNER }} - steps: - - name: Checkout - uses: actions/checkout@v4 - - id: set_image - run: cat .github/env >> $GITHUB_OUTPUT - - id: set_runner - run: | - # Run merge queues on persistent runners - if [[ $GITHUB_REF_NAME == *"gh-readonly-queue"* ]]; then - echo "RUNNER=arc-runners-polkadot-sdk-beefy-persistent" >> $GITHUB_OUTPUT - else - echo "RUNNER=arc-runners-polkadot-sdk-beefy" >> $GITHUB_OUTPUT - fi + preflight: + uses: ./.github/workflows/reusable-preflight.yml build: timeout-minutes: 80 - needs: [set-image] - runs-on: ${{ needs.set-image.outputs.RUNNER }} + needs: [preflight] + runs-on: ${{ needs.preflight.outputs.RUNNER }} container: - image: ${{ needs.set-image.outputs.IMAGE }} + image: ${{ needs.preflight.outputs.IMAGE }} strategy: fail-fast: false matrix: diff --git a/.github/workflows/tests-linux-stable-coverage.yml b/.github/workflows/tests-linux-stable-coverage.yml index 4c0a2629e41..90d7bc34a92 100644 --- a/.github/workflows/tests-linux-stable-coverage.yml +++ b/.github/workflows/tests-linux-stable-coverage.yml @@ -13,39 +13,19 @@ concurrency: cancel-in-progress: true jobs: - - set-image: - # GitHub Actions allows using 'env' in a container context. - # However, env variables don't work for forks: https://github.com/orgs/community/discussions/44322 - # This workaround sets the container image for each job using 'set-image' job output. + preflight: + uses: ./.github/workflows/reusable-preflight.yml if: contains(github.event.label.name, 'GHA-coverage') || contains(github.event.pull_request.labels.*.name, 'GHA-coverage') - runs-on: ubuntu-latest - outputs: - IMAGE: ${{ steps.set_image.outputs.IMAGE }} - RUNNER: ${{ steps.set_runner.outputs.RUNNER }} - steps: - - name: Checkout - uses: actions/checkout@v4 - - id: set_image - run: cat .github/env >> $GITHUB_OUTPUT - - id: set_runner - run: | - # Run merge queues on persistent runners - if [[ $GITHUB_REF_NAME == *"gh-readonly-queue"* ]]; then - echo "RUNNER=arc-runners-polkadot-sdk-beefy-persistent" >> $GITHUB_OUTPUT - else - echo "RUNNER=arc-runners-polkadot-sdk-beefy" >> $GITHUB_OUTPUT - fi # # # test-linux-stable-coverage: - needs: [set-image] - runs-on: ${{ needs.set-image.outputs.RUNNER }} + needs: [preflight] + runs-on: ${{ needs.preflight.outputs.RUNNER }} timeout-minutes: 120 container: - image: ${{ needs.set-image.outputs.IMAGE }} + image: ${{ needs.preflight.outputs.IMAGE }} env: RUST_TOOLCHAIN: stable # Enable debug assertions since we are running optimized builds for testing @@ -128,16 +108,16 @@ jobs: verbose: true directory: reports root_dir: /__w/polkadot-sdk/polkadot-sdk/ - + # # # remove-label: runs-on: ubuntu-latest needs: [upload-reports] - if: github.event_name == 'pull_request' + if: github.event_name == 'pull_request' steps: - uses: actions/checkout@v4 - uses: actions-ecosystem/action-remove-labels@v1 with: - labels: GHA-coverage \ No newline at end of file + labels: GHA-coverage \ No newline at end of file diff --git a/.github/workflows/tests-linux-stable.yml b/.github/workflows/tests-linux-stable.yml index 7ed67703395..6cf71422511 100644 --- a/.github/workflows/tests-linux-stable.yml +++ b/.github/workflows/tests-linux-stable.yml @@ -13,47 +13,17 @@ concurrency: cancel-in-progress: true jobs: - # changes: - # # TODO: remove once migration is complete or this workflow is fully stable - # if: contains(github.event.label.name, 'GHA-migration') - # permissions: - # pull-requests: read - # uses: ./.github/workflows/reusable-check-changed-files.yml - set-image: - # GitHub Actions allows using 'env' in a container context. - # However, env variables don't work for forks: https://github.com/orgs/community/discussions/44322 - # This workaround sets the container image for each job using 'set-image' job output. - runs-on: ubuntu-latest - outputs: - IMAGE: ${{ steps.set_image.outputs.IMAGE }} - RUNNER: ${{ steps.set_runner.outputs.RUNNER }} - OLDLINUXRUNNER: ${{ steps.set_runner.outputs.OLDLINUXRUNNER }} - steps: - - name: Checkout - uses: actions/checkout@v4 - - id: set_image - run: cat .github/env >> $GITHUB_OUTPUT - # By default we use spot machines that can be terminated at any time. - # Merge queues use persistent runners to avoid kicking off from queue when the runner is terminated. - - id: set_runner - run: | - # Run merge queues on persistent runners - if [[ $GITHUB_REF_NAME == *"gh-readonly-queue"* ]]; then - echo "RUNNER=arc-runners-polkadot-sdk-beefy-persistent" >> $GITHUB_OUTPUT - echo "OLDLINUXRUNNER=oldlinux-persistent" >> $GITHUB_OUTPUT - else - echo "RUNNER=arc-runners-polkadot-sdk-beefy" >> $GITHUB_OUTPUT - echo "OLDLINUXRUNNER=oldlinux" >> $GITHUB_OUTPUT - fi + preflight: + uses: ./.github/workflows/reusable-preflight.yml test-linux-stable-int: - needs: [set-image] - # if: ${{ needs.changes.outputs.rust }} - runs-on: ${{ needs.set-image.outputs.RUNNER }} + needs: [preflight] + # if: ${{ needs.preflight.outputs.changes_rust }} + runs-on: ${{ needs.preflight.outputs.RUNNER }} timeout-minutes: 60 container: - image: ${{ needs.set-image.outputs.IMAGE }} + image: ${{ needs.preflight.outputs.IMAGE }} env: RUSTFLAGS: "-C debug-assertions -D warnings" RUST_BACKTRACE: 1 @@ -69,12 +39,12 @@ jobs: # https://github.com/paritytech/ci_cd/issues/864 test-linux-stable-runtime-benchmarks: - needs: [set-image] - # if: ${{ needs.changes.outputs.rust }} - runs-on: ${{ needs.set-image.outputs.RUNNER }} + needs: [preflight] + # if: ${{ needs.preflight.outputs.changes_rust }} + runs-on: ${{ needs.preflight.outputs.RUNNER }} timeout-minutes: 60 container: - image: ${{ needs.set-image.outputs.IMAGE }} + image: ${{ needs.preflight.outputs.IMAGE }} env: RUST_TOOLCHAIN: stable # Enable debug assertions since we are running optimized builds for testing @@ -87,8 +57,8 @@ jobs: run: forklift cargo nextest run --workspace --features runtime-benchmarks benchmark --locked --cargo-profile testnet test-linux-stable: - needs: [set-image] - # if: ${{ needs.changes.outputs.rust }} + needs: [preflight] + # if: ${{ needs.preflight.outputs.changes_rust }} runs-on: ${{ matrix.runners }} timeout-minutes: 60 strategy: @@ -97,11 +67,11 @@ jobs: partition: [1/3, 2/3, 3/3] runners: [ - "${{ needs.set-image.outputs.RUNNER }}", - "${{ needs.set-image.outputs.OLDLINUXRUNNER }}", + "${{ needs.preflight.outputs.RUNNER }}", + "${{ needs.preflight.outputs.OLDLINUXRUNNER }}", ] container: - image: ${{ needs.set-image.outputs.IMAGE }} + image: ${{ needs.preflight.outputs.IMAGE }} # needed for tests that use unshare syscall options: --security-opt seccomp=unconfined env: diff --git a/.github/workflows/tests-misc.yml b/.github/workflows/tests-misc.yml index 90685743a41..0f2b617b847 100644 --- a/.github/workflows/tests-misc.yml +++ b/.github/workflows/tests-misc.yml @@ -14,45 +14,18 @@ concurrency: # Jobs in this workflow depend on each other, only for limiting peak amount of spawned workers jobs: - #changes: - # permissions: - # pull-requests: read - # uses: ./.github/workflows/reusable-check-changed-files.yml - - set-image: - # needs: [ changes ] - # if: needs.changes.outputs.rust || needs.changes.outputs.current-workflow - # GitHub Actions allows using 'env' in a container context. - # However, env variables don't work for forks: https://github.com/orgs/community/discussions/44322 - # This workaround sets the container image for each job using 'set-image' job output. - runs-on: ubuntu-latest - outputs: - IMAGE: ${{ steps.set_image.outputs.IMAGE }} - RUNNER: ${{ steps.set_runner.outputs.RUNNER }} - steps: - - name: Checkout - uses: actions/checkout@v4 - - id: set_image - run: cat .github/env >> $GITHUB_OUTPUT - # By default, we use spot machines that can be terminated at any time. - # Merge queues use persistent runners to avoid kicking off from queue when the runner is terminated. - - id: set_runner - run: | - # Run merge queues on persistent runners - if [[ $GITHUB_REF_NAME == *"gh-readonly-queue"* ]]; then - echo "RUNNER=arc-runners-polkadot-sdk-beefy-persistent" >> $GITHUB_OUTPUT - else - echo "RUNNER=arc-runners-polkadot-sdk-beefy" >> $GITHUB_OUTPUT - fi + + preflight: + uses: ./.github/workflows/reusable-preflight.yml # more information about this job can be found here: # https://github.com/paritytech/substrate/pull/3778 test-full-crypto-feature: - needs: [set-image] - runs-on: ${{ needs.set-image.outputs.RUNNER }} + needs: [preflight] + runs-on: ${{ needs.preflight.outputs.RUNNER }} timeout-minutes: 60 container: - image: ${{ needs.set-image.outputs.IMAGE }} + image: ${{ needs.preflight.outputs.IMAGE }} env: # Enable debug assertions since we are running optimized builds for testing # but still want to have debug assertions. @@ -72,10 +45,10 @@ jobs: test-frame-examples-compile-to-wasm: timeout-minutes: 20 # into one job - needs: [set-image, test-full-crypto-feature] - runs-on: ${{ needs.set-image.outputs.RUNNER }} + needs: [preflight, test-full-crypto-feature] + runs-on: ${{ needs.preflight.outputs.RUNNER }} container: - image: ${{ needs.set-image.outputs.IMAGE }} + image: ${{ needs.preflight.outputs.IMAGE }} env: # Enable debug assertions since we are running optimized builds for testing # but still want to have debug assertions. @@ -93,10 +66,10 @@ jobs: test-frame-ui: timeout-minutes: 60 - needs: [set-image] - runs-on: ${{ needs.set-image.outputs.RUNNER }} + needs: [preflight] + runs-on: ${{ needs.preflight.outputs.RUNNER }} container: - image: ${{ needs.set-image.outputs.IMAGE }} + image: ${{ needs.preflight.outputs.IMAGE }} env: # Enable debug assertions since we are running optimized builds for testing # but still want to have debug assertions. @@ -121,10 +94,10 @@ jobs: test-deterministic-wasm: timeout-minutes: 20 - needs: [set-image, test-frame-examples-compile-to-wasm] - runs-on: ${{ needs.set-image.outputs.RUNNER }} + needs: [preflight, test-frame-examples-compile-to-wasm] + runs-on: ${{ needs.preflight.outputs.RUNNER }} container: - image: ${{ needs.set-image.outputs.IMAGE }} + image: ${{ needs.preflight.outputs.IMAGE }} env: WASM_BUILD_NO_COLOR: 1 steps: @@ -143,15 +116,15 @@ jobs: sha256sum -c checksum.sha256 cargo-check-benches: - needs: [set-image] + needs: [preflight] if: ${{ github.event_name == 'pull_request' || github.event_name == 'merge_group' }} timeout-minutes: 60 strategy: matrix: branch: [master, current] - runs-on: ${{ needs.set-image.outputs.RUNNER }} + runs-on: ${{ needs.preflight.outputs.RUNNER }} container: - image: ${{ needs.set-image.outputs.IMAGE }} + image: ${{ needs.preflight.outputs.IMAGE }} steps: - name: Checkout uses: actions/checkout@v4 @@ -183,7 +156,7 @@ jobs: if: always() && !cancelled() # runs-on: arc-runners-polkadot-sdk runs-on: ubuntu-latest - needs: [set-image, cargo-check-benches] + needs: [preflight, cargo-check-benches] steps: - name: Checkout uses: actions/checkout@v4 @@ -219,11 +192,11 @@ jobs: fi test-node-metrics: - needs: [set-image] + needs: [preflight] timeout-minutes: 30 - runs-on: ${{ needs.set-image.outputs.RUNNER }} + runs-on: ${{ needs.preflight.outputs.RUNNER }} container: - image: ${{ needs.set-image.outputs.IMAGE }} + image: ${{ needs.preflight.outputs.IMAGE }} steps: - name: Checkout uses: actions/checkout@v4 @@ -252,10 +225,10 @@ jobs: # https://github.com/paritytech/substrate/pull/6916 check-tracing: timeout-minutes: 20 - needs: [set-image, test-node-metrics] - runs-on: ${{ needs.set-image.outputs.RUNNER }} + needs: [preflight, test-node-metrics] + runs-on: ${{ needs.preflight.outputs.RUNNER }} container: - image: ${{ needs.set-image.outputs.IMAGE }} + image: ${{ needs.preflight.outputs.IMAGE }} steps: - name: Checkout uses: actions/checkout@v4 @@ -267,10 +240,10 @@ jobs: check-metadata-hash: timeout-minutes: 20 - needs: [set-image, check-tracing] - runs-on: ${{ needs.set-image.outputs.RUNNER }} + needs: [preflight, check-tracing] + runs-on: ${{ needs.preflight.outputs.RUNNER }} container: - image: ${{ needs.set-image.outputs.IMAGE }} + image: ${{ needs.preflight.outputs.IMAGE }} steps: - name: Checkout uses: actions/checkout@v4 @@ -281,10 +254,10 @@ jobs: cargo-hfuzz: timeout-minutes: 20 - needs: [set-image, check-metadata-hash] - runs-on: ${{ needs.set-image.outputs.RUNNER }} + needs: [preflight, check-metadata-hash] + runs-on: ${{ needs.preflight.outputs.RUNNER }} container: - image: ${{ needs.set-image.outputs.IMAGE }} + image: ${{ needs.preflight.outputs.IMAGE }} env: # max 10s per iteration, 60s per file HFUZZ_RUN_ARGS: | @@ -321,10 +294,10 @@ jobs: cargo-check-each-crate: timeout-minutes: 140 - needs: [set-image] - runs-on: ${{ needs.set-image.outputs.RUNNER }} + needs: [preflight] + runs-on: ${{ needs.preflight.outputs.RUNNER }} container: - image: ${{ needs.set-image.outputs.IMAGE }} + image: ${{ needs.preflight.outputs.IMAGE }} env: RUSTFLAGS: "-D warnings" CI_JOB_NAME: cargo-check-each-crate @@ -348,6 +321,7 @@ jobs: cargo-check-all-crate-macos: timeout-minutes: 30 + needs: [ preflight ] runs-on: parity-macos env: SKIP_WASM_BUILD: 1 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ed2ef07736b..1132c2ca4dd 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -12,44 +12,18 @@ concurrency: cancel-in-progress: true jobs: - # disabled because currently doesn't work in merge queue - # changes: - # permissions: - # pull-requests: read - # uses: ./.github/workflows/reusable-check-changed-files.yml - set-image: - # GitHub Actions allows using 'env' in a container context. - # However, env variables don't work for forks: https://github.com/orgs/community/discussions/44322 - # This workaround sets the container image for each job using 'set-image' job output. - runs-on: ubuntu-latest - outputs: - IMAGE: ${{ steps.set_image.outputs.IMAGE }} - RUNNER: ${{ steps.set_runner.outputs.RUNNER }} - steps: - - name: Checkout - uses: actions/checkout@v4 - - id: set_image - run: cat .github/env >> $GITHUB_OUTPUT - # By default we use spot machines that can be terminated at any time. - # Merge queues use persistent runners to avoid kicking off from queue when the runner is terminated. - - id: set_runner - run: | - # Run merge queues on persistent runners - if [[ $GITHUB_REF_NAME == *"gh-readonly-queue"* ]]; then - echo "RUNNER=arc-runners-polkadot-sdk-beefy-persistent" >> $GITHUB_OUTPUT - else - echo "RUNNER=arc-runners-polkadot-sdk-beefy" >> $GITHUB_OUTPUT - fi + preflight: + uses: ./.github/workflows/reusable-preflight.yml # This job runs all benchmarks defined in the `/bin/node/runtime` once to check that there are no errors. quick-benchmarks: - needs: [ set-image ] - # if: ${{ needs.changes.outputs.rust }} - runs-on: ${{ needs.set-image.outputs.RUNNER }} + needs: [ preflight ] + # if: ${{ needs.preflight.outputs.changes_rust }} + runs-on: ${{ needs.preflight.outputs.RUNNER }} timeout-minutes: 60 container: - image: ${{ needs.set-image.outputs.IMAGE }} + image: ${{ needs.preflight.outputs.IMAGE }} env: RUSTFLAGS: "-C debug-assertions -D warnings" RUST_BACKTRACE: "full" @@ -63,12 +37,12 @@ jobs: # cf https://github.com/paritytech/polkadot-sdk/issues/1652 test-syscalls: - needs: [ set-image ] - # if: ${{ needs.changes.outputs.rust }} - runs-on: ${{ needs.set-image.outputs.RUNNER }} + needs: [ preflight ] + # if: ${{ needs.preflight.outputs.changes_rust }} + runs-on: ${{ needs.preflight.outputs.RUNNER }} timeout-minutes: 60 container: - image: ${{ needs.set-image.outputs.IMAGE }} + image: ${{ needs.preflight.outputs.IMAGE }} continue-on-error: true # this rarely triggers in practice env: SKIP_WASM_BUILD: 1 @@ -89,12 +63,12 @@ jobs: cargo-check-all-benches: - needs: [ set-image ] - # if: ${{ needs.changes.outputs.rust }} - runs-on: ${{ needs.set-image.outputs.RUNNER }} + needs: [ preflight ] + # if: ${{ needs.preflight.outputs.changes_rust }} + runs-on: ${{ needs.preflight.outputs.RUNNER }} timeout-minutes: 60 container: - image: ${{ needs.set-image.outputs.IMAGE }} + image: ${{ needs.preflight.outputs.IMAGE }} env: SKIP_WASM_BUILD: 1 steps: -- GitLab From 9064fb4d6dea751c537e33cb7c0a78c2510a1dce Mon Sep 17 00:00:00 2001 From: Przemek Rzad Date: Mon, 16 Sep 2024 18:14:54 +0200 Subject: [PATCH 066/125] Ensure correct product name in license headers (#5702) - This will ensure that a correct product name (Polkadot/Cumulus/Substrate) is referenced in license headers. - Closes https://github.com/paritytech/license-scanner/issues/49 --- .github/workflows/check-licenses.yml | 36 ++++++++++++++++++- cumulus/client/collator/src/lib.rs | 4 +-- cumulus/client/network/src/lib.rs | 8 ++--- cumulus/client/network/src/tests.rs | 8 ++--- cumulus/client/parachain-inherent/src/lib.rs | 4 +-- cumulus/client/parachain-inherent/src/mock.rs | 2 +- .../src/active_candidate_recovery.rs | 8 ++--- cumulus/client/pov-recovery/src/lib.rs | 2 +- cumulus/client/pov-recovery/src/tests.rs | 8 ++--- .../src/collator_overseer.rs | 8 ++--- .../relay-chain-minimal-node/src/lib.rs | 8 ++--- cumulus/client/service/src/lib.rs | 4 +-- .../collator-selection/src/migration.rs | 8 ++--- .../pallets/collator-selection/src/weights.rs | 2 +- cumulus/pallets/dmp-queue/src/migration.rs | 8 ++--- cumulus/pallets/dmp-queue/src/mock.rs | 2 +- cumulus/pallets/dmp-queue/src/tests.rs | 4 +-- .../parachain-system/src/benchmarking.rs | 2 +- .../src/validate_block/implementation.rs | 6 ++-- .../src/validate_block/mod.rs | 6 ++-- .../src/validate_block/tests.rs | 6 ++-- .../src/validate_block/trie_cache.rs | 2 +- .../src/validate_block/trie_recorder.rs | 6 ++-- .../pallets/session-benchmarking/src/lib.rs | 2 +- cumulus/pallets/xcmp-queue/src/lib.rs | 4 +-- cumulus/pallets/xcmp-queue/src/migration.rs | 8 ++--- .../pallets/xcmp-queue/src/migration/v5.rs | 8 ++--- .../parachains/common/src/message_queue.rs | 8 ++--- .../src/weights/block_weights.rs | 2 +- .../src/weights/extrinsic_weights.rs | 2 +- .../src/weights/paritydb_weights.rs | 2 +- .../src/weights/rocksdb_weights.rs | 2 +- .../src/weights/block_weights.rs | 2 +- .../src/weights/extrinsic_weights.rs | 2 +- .../src/weights/paritydb_weights.rs | 2 +- .../src/weights/rocksdb_weights.rs | 2 +- .../src/weights/block_weights.rs | 2 +- .../src/weights/extrinsic_weights.rs | 2 +- .../bridge-hub-rococo/src/weights/mod.rs | 2 +- .../src/weights/paritydb_weights.rs | 2 +- .../src/weights/rocksdb_weights.rs | 2 +- .../src/weights/block_weights.rs | 2 +- .../src/weights/extrinsic_weights.rs | 2 +- .../bridge-hub-westend/src/weights/mod.rs | 2 +- .../src/weights/paritydb_weights.rs | 2 +- .../src/weights/rocksdb_weights.rs | 2 +- .../src/weights/block_weights.rs | 2 +- .../src/weights/extrinsic_weights.rs | 2 +- .../src/weights/paritydb_weights.rs | 2 +- .../src/weights/rocksdb_weights.rs | 2 +- .../src/weights/block_weights.rs | 2 +- .../src/weights/extrinsic_weights.rs | 2 +- .../contracts-rococo/src/weights/mod.rs | 2 +- .../src/weights/paritydb_weights.rs | 2 +- .../src/weights/rocksdb_weights.rs | 2 +- .../src/weights/block_weights.rs | 2 +- .../src/weights/extrinsic_weights.rs | 2 +- .../coretime-rococo/src/weights/mod.rs | 2 +- .../src/weights/paritydb_weights.rs | 2 +- .../src/weights/rocksdb_weights.rs | 2 +- .../src/weights/block_weights.rs | 2 +- .../src/weights/extrinsic_weights.rs | 2 +- .../coretime-westend/src/weights/mod.rs | 2 +- .../src/weights/paritydb_weights.rs | 2 +- .../src/weights/rocksdb_weights.rs | 2 +- .../src/weights/block_weights.rs | 2 +- .../src/weights/extrinsic_weights.rs | 2 +- .../src/weights/paritydb_weights.rs | 2 +- .../src/weights/rocksdb_weights.rs | 2 +- .../src/weights/block_weights.rs | 2 +- .../src/weights/extrinsic_weights.rs | 2 +- .../runtimes/starters/shell/build.rs | 4 +-- .../runtimes/testing/penpal/build.rs | 4 +-- .../penpal/src/weights/block_weights.rs | 2 +- .../penpal/src/weights/extrinsic_weights.rs | 2 +- .../testing/penpal/src/weights/mod.rs | 2 +- .../penpal/src/weights/paritydb_weights.rs | 2 +- .../penpal/src/weights/rocksdb_weights.rs | 2 +- cumulus/polkadot-parachain/build.rs | 4 +-- .../src/tests/benchmark_storage_works.rs | 6 ++-- .../src/tests/common.rs | 6 ++-- .../src/tests/polkadot_argument_parsing.rs | 6 ++-- .../src/tests/polkadot_mdns_issue.rs | 6 ++-- .../src/tests/purge_chain_works.rs | 6 ++-- .../tests/running_the_node_and_interrupt.rs | 6 ++-- cumulus/primitives/aura/src/lib.rs | 4 +-- cumulus/primitives/core/src/lib.rs | 4 +-- .../primitives/parachain-inherent/src/lib.rs | 4 +-- .../proof-size-hostfunction/src/lib.rs | 2 +- cumulus/primitives/utility/src/lib.rs | 4 +-- cumulus/primitives/utility/src/tests/mod.rs | 4 +-- .../utility/src/tests/swap_first.rs | 4 +-- cumulus/test/runtime/build.rs | 4 +-- cumulus/xcm/xcm-emulator/src/lib.rs | 8 ++--- polkadot/node/metrics/src/tests.rs | 8 ++--- polkadot/runtime/common/src/claims.rs | 6 ++-- polkadot/runtime/common/src/purchase.rs | 6 ++-- polkadot/runtime/rococo/build.rs | 6 ++-- .../rococo/constants/src/weights/mod.rs | 2 +- .../constants/src/weights/paritydb_weights.rs | 2 +- .../constants/src/weights/rocksdb_weights.rs | 2 +- .../constants/src/weights/block_weights.rs | 2 +- .../src/weights/extrinsic_weights.rs | 2 +- .../test-runtime/constants/src/weights/mod.rs | 2 +- .../constants/src/weights/paritydb_weights.rs | 2 +- .../constants/src/weights/rocksdb_weights.rs | 2 +- polkadot/runtime/westend/build.rs | 2 +- .../westend/constants/src/weights/mod.rs | 2 +- .../constants/src/weights/paritydb_weights.rs | 2 +- .../constants/src/weights/rocksdb_weights.rs | 2 +- polkadot/tests/common.rs | 8 ++--- polkadot/tests/invalid_order_arguments.rs | 8 ++--- polkadot/tests/purge_chain_works.rs | 8 ++--- .../tests/running_the_node_and_interrupt.rs | 8 ++--- polkadot/xcm/src/lib.rs | 4 +-- polkadot/xcm/src/v2/mod.rs | 8 ++--- polkadot/xcm/src/v2/multiasset.rs | 4 +-- polkadot/xcm/src/v2/traits.rs | 8 ++--- polkadot/xcm/src/v3/mod.rs | 4 +-- polkadot/xcm/src/v3/multiasset.rs | 4 +-- polkadot/xcm/src/v3/traits.rs | 4 +-- polkadot/xcm/src/v4/asset.rs | 4 +-- polkadot/xcm/src/v4/mod.rs | 4 +-- polkadot/xcm/src/v4/traits.rs | 4 +-- polkadot/xcm/xcm-builder/src/matcher.rs | 4 +-- .../xcm/xcm-runtime-apis/src/conversions.rs | 4 +-- polkadot/xcm/xcm-runtime-apis/src/dry_run.rs | 4 +-- polkadot/xcm/xcm-runtime-apis/src/fees.rs | 4 +-- polkadot/xcm/xcm-runtime-apis/src/lib.rs | 4 +-- .../xcm/xcm-runtime-apis/tests/conversions.rs | 4 +-- .../xcm-runtime-apis/tests/fee_estimation.rs | 4 +-- polkadot/xcm/xcm-runtime-apis/tests/mock.rs | 4 +-- .../bags-list/remote-tests/src/migration.rs | 8 ++--- .../bags-list/remote-tests/src/snapshot.rs | 8 ++--- .../bags-list/remote-tests/src/try_state.rs | 8 ++--- substrate/frame/balances/src/migration.rs | 8 ++--- .../frame/contracts/mock-network/src/lib.rs | 8 ++--- .../frame/contracts/mock-network/src/mocks.rs | 8 ++--- .../mock-network/src/mocks/msg_queue.rs | 8 ++--- .../src/mocks/relay_message_queue.rs | 8 ++--- .../contracts/mock-network/src/parachain.rs | 8 ++--- .../src/parachain/contracts_config.rs | 8 ++--- .../contracts/mock-network/src/primitives.rs | 8 ++--- .../contracts/mock-network/src/relay_chain.rs | 8 ++--- .../frame/revive/mock-network/src/lib.rs | 8 ++--- .../frame/revive/mock-network/src/mocks.rs | 8 ++--- .../mock-network/src/mocks/msg_queue.rs | 8 ++--- .../src/mocks/relay_message_queue.rs | 8 ++--- .../revive/mock-network/src/parachain.rs | 8 ++--- .../src/parachain/contracts_config.rs | 8 ++--- .../revive/mock-network/src/primitives.rs | 8 ++--- .../revive/mock-network/src/relay_chain.rs | 8 ++--- 152 files changed, 360 insertions(+), 326 deletions(-) diff --git a/.github/workflows/check-licenses.yml b/.github/workflows/check-licenses.yml index ddb1c452a7b..88bd06c6707 100644 --- a/.github/workflows/check-licenses.yml +++ b/.github/workflows/check-licenses.yml @@ -39,7 +39,6 @@ jobs: shopt -s globstar npx @paritytech/license-scanner scan \ --ensure-licenses ${{ env.LICENSES }} \ - --exclude ./cumulus/parachain-template \ -- ./cumulus/**/*.rs - name: Check the licenses in Substrate @@ -48,3 +47,38 @@ jobs: npx @paritytech/license-scanner scan \ --ensure-licenses ${{ env.LICENSES }} \ -- ./substrate/**/*.rs + + check-product-references: + runs-on: ubuntu-latest + timeout-minutes: 10 + env: + NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - name: Checkout sources + uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.1.7 + - uses: actions/setup-node@v4.0.3 + with: + node-version: "18.x" + registry-url: "https://npm.pkg.github.com" + scope: "@paritytech" + + - name: Check the product references in Polkadot + run: | + shopt -s globstar + npx @paritytech/license-scanner scan \ + --ensure-product 'Polkadot' \ + -- ./polkadot/**/*.rs + + - name: Check the product references in Cumulus + run: | + shopt -s globstar + npx @paritytech/license-scanner scan \ + --ensure-product 'Cumulus' \ + -- ./cumulus/**/*.rs + + - name: Check the product references in Substrate + run: | + shopt -s globstar + npx @paritytech/license-scanner scan \ + --ensure-product 'Substrate' \ + -- ./substrate/**/*.rs diff --git a/cumulus/client/collator/src/lib.rs b/cumulus/client/collator/src/lib.rs index 47da0f6d96f..91ff913f263 100644 --- a/cumulus/client/collator/src/lib.rs +++ b/cumulus/client/collator/src/lib.rs @@ -1,12 +1,12 @@ // Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Cumulus. -// Substrate is free software: you can redistribute it and/or modify +// Cumulus is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// Substrate is distributed in the hope that it will be useful, +// Cumulus 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. diff --git a/cumulus/client/network/src/lib.rs b/cumulus/client/network/src/lib.rs index dab15bba590..01ad15bed4d 100644 --- a/cumulus/client/network/src/lib.rs +++ b/cumulus/client/network/src/lib.rs @@ -1,18 +1,18 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Polkadot. +// This file is part of Cumulus. -// Polkadot is free software: you can redistribute it and/or modify +// Cumulus 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, +// Cumulus 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 . +// along with Cumulus. If not, see . //! Parachain specific networking //! diff --git a/cumulus/client/network/src/tests.rs b/cumulus/client/network/src/tests.rs index 81c2d9f24f2..1c8edd803ed 100644 --- a/cumulus/client/network/src/tests.rs +++ b/cumulus/client/network/src/tests.rs @@ -1,18 +1,18 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Polkadot. +// This file is part of Cumulus. -// Polkadot is free software: you can redistribute it and/or modify +// Cumulus 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, +// Cumulus 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 . +// along with Cumulus. If not, see . use super::*; use async_trait::async_trait; diff --git a/cumulus/client/parachain-inherent/src/lib.rs b/cumulus/client/parachain-inherent/src/lib.rs index 051eb6764c8..0bb436a876b 100644 --- a/cumulus/client/parachain-inherent/src/lib.rs +++ b/cumulus/client/parachain-inherent/src/lib.rs @@ -1,12 +1,12 @@ // Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Cumulus. -// Substrate is free software: you can redistribute it and/or modify +// Cumulus is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// Substrate is distributed in the hope that it will be useful, +// Cumulus 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. diff --git a/cumulus/client/parachain-inherent/src/mock.rs b/cumulus/client/parachain-inherent/src/mock.rs index dfe4a66c3dc..a3f881e6ef9 100644 --- a/cumulus/client/parachain-inherent/src/mock.rs +++ b/cumulus/client/parachain-inherent/src/mock.rs @@ -6,7 +6,7 @@ // 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, +// Cumulus 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. diff --git a/cumulus/client/pov-recovery/src/active_candidate_recovery.rs b/cumulus/client/pov-recovery/src/active_candidate_recovery.rs index 50de98909ea..9badc69fe81 100644 --- a/cumulus/client/pov-recovery/src/active_candidate_recovery.rs +++ b/cumulus/client/pov-recovery/src/active_candidate_recovery.rs @@ -1,18 +1,18 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Polkadot. +// This file is part of Cumulus. -// Polkadot is free software: you can redistribute it and/or modify +// Cumulus 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, +// Cumulus 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 . +// along with Cumulus. If not, see . use sp_runtime::traits::Block as BlockT; diff --git a/cumulus/client/pov-recovery/src/lib.rs b/cumulus/client/pov-recovery/src/lib.rs index 6ace18155e8..043cba12d19 100644 --- a/cumulus/client/pov-recovery/src/lib.rs +++ b/cumulus/client/pov-recovery/src/lib.rs @@ -6,7 +6,7 @@ // 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, +// Cumulus 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. diff --git a/cumulus/client/pov-recovery/src/tests.rs b/cumulus/client/pov-recovery/src/tests.rs index f300bdc5f2b..5935824e173 100644 --- a/cumulus/client/pov-recovery/src/tests.rs +++ b/cumulus/client/pov-recovery/src/tests.rs @@ -1,18 +1,18 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Polkadot. +// This file is part of Cumulus. -// Polkadot is free software: you can redistribute it and/or modify +// Cumulus 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, +// Cumulus 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 . +// along with Cumulus. If not, see . use super::*; use assert_matches::assert_matches; diff --git a/cumulus/client/relay-chain-minimal-node/src/collator_overseer.rs b/cumulus/client/relay-chain-minimal-node/src/collator_overseer.rs index f01ef8b05ec..5acc3053708 100644 --- a/cumulus/client/relay-chain-minimal-node/src/collator_overseer.rs +++ b/cumulus/client/relay-chain-minimal-node/src/collator_overseer.rs @@ -1,18 +1,18 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Polkadot. +// This file is part of Cumulus. -// Polkadot is free software: you can redistribute it and/or modify +// Cumulus 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, +// Cumulus 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 . +// along with Cumulus. If not, see . use futures::{select, StreamExt}; use std::sync::Arc; diff --git a/cumulus/client/relay-chain-minimal-node/src/lib.rs b/cumulus/client/relay-chain-minimal-node/src/lib.rs index e65a78f16d7..cea7e6e4a03 100644 --- a/cumulus/client/relay-chain-minimal-node/src/lib.rs +++ b/cumulus/client/relay-chain-minimal-node/src/lib.rs @@ -1,18 +1,18 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Polkadot. +// This file is part of Cumulus. -// Polkadot is free software: you can redistribute it and/or modify +// Cumulus 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, +// Cumulus 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 . +// along with Cumulus. If not, see . use collator_overseer::NewMinimalNode; diff --git a/cumulus/client/service/src/lib.rs b/cumulus/client/service/src/lib.rs index 7f656aabca7..dd14ca514b3 100644 --- a/cumulus/client/service/src/lib.rs +++ b/cumulus/client/service/src/lib.rs @@ -1,12 +1,12 @@ // Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Cumulus. -// Substrate is free software: you can redistribute it and/or modify +// Cumulus is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// Substrate is distributed in the hope that it will be useful, +// Cumulus 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. diff --git a/cumulus/pallets/collator-selection/src/migration.rs b/cumulus/pallets/collator-selection/src/migration.rs index c5201694806..34f91429708 100644 --- a/cumulus/pallets/collator-selection/src/migration.rs +++ b/cumulus/pallets/collator-selection/src/migration.rs @@ -1,18 +1,18 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Polkadot. +// This file is part of Cumulus. -// Polkadot is free software: you can redistribute it and/or modify +// Cumulus 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, +// Cumulus 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 . +// along with Cumulus. If not, see . //! A module that is responsible for migration of storage for Collator Selection. diff --git a/cumulus/pallets/collator-selection/src/weights.rs b/cumulus/pallets/collator-selection/src/weights.rs index 12e6b755e97..0ac4a085754 100644 --- a/cumulus/pallets/collator-selection/src/weights.rs +++ b/cumulus/pallets/collator-selection/src/weights.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Cumulus. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/cumulus/pallets/dmp-queue/src/migration.rs b/cumulus/pallets/dmp-queue/src/migration.rs index b1945e8eb37..1b83fea710a 100644 --- a/cumulus/pallets/dmp-queue/src/migration.rs +++ b/cumulus/pallets/dmp-queue/src/migration.rs @@ -1,18 +1,18 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Polkadot. +// This file is part of Cumulus. -// Polkadot is free software: you can redistribute it and/or modify +// Cumulus 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, +// Cumulus 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 . +// along with Cumulus. If not, see . //! Migrates the storage from the previously deleted DMP pallet. diff --git a/cumulus/pallets/dmp-queue/src/mock.rs b/cumulus/pallets/dmp-queue/src/mock.rs index ed72ce678e3..a46a6ba6c8b 100644 --- a/cumulus/pallets/dmp-queue/src/mock.rs +++ b/cumulus/pallets/dmp-queue/src/mock.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Cumulus. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/cumulus/pallets/dmp-queue/src/tests.rs b/cumulus/pallets/dmp-queue/src/tests.rs index a157d0584f2..70d542ea2ed 100644 --- a/cumulus/pallets/dmp-queue/src/tests.rs +++ b/cumulus/pallets/dmp-queue/src/tests.rs @@ -1,12 +1,12 @@ // Copyright Parity Technologies (UK) Ltd. // This file is part of Cumulus. -// Substrate is free software: you can redistribute it and/or modify +// Cumulus is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// Substrate is distributed in the hope that it will be useful, +// Cumulus 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. diff --git a/cumulus/pallets/parachain-system/src/benchmarking.rs b/cumulus/pallets/parachain-system/src/benchmarking.rs index 5cde8eb5b78..8f97d12a480 100644 --- a/cumulus/pallets/parachain-system/src/benchmarking.rs +++ b/cumulus/pallets/parachain-system/src/benchmarking.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Cumulus. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/cumulus/pallets/parachain-system/src/validate_block/implementation.rs b/cumulus/pallets/parachain-system/src/validate_block/implementation.rs index 42311ca9d83..c4c8440e518 100644 --- a/cumulus/pallets/parachain-system/src/validate_block/implementation.rs +++ b/cumulus/pallets/parachain-system/src/validate_block/implementation.rs @@ -1,12 +1,12 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Substrate. +// This file is part of Cumulus. -// Substrate is free software: you can redistribute it and/or modify +// Cumulus is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// Substrate is distributed in the hope that it will be useful, +// Cumulus 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. diff --git a/cumulus/pallets/parachain-system/src/validate_block/mod.rs b/cumulus/pallets/parachain-system/src/validate_block/mod.rs index 3a00d4d352a..2d210f4bac2 100644 --- a/cumulus/pallets/parachain-system/src/validate_block/mod.rs +++ b/cumulus/pallets/parachain-system/src/validate_block/mod.rs @@ -1,12 +1,12 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Substrate. +// This file is part of Cumulus. -// Substrate is free software: you can redistribute it and/or modify +// Cumulus is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// Substrate is distributed in the hope that it will be useful, +// Cumulus 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. diff --git a/cumulus/pallets/parachain-system/src/validate_block/tests.rs b/cumulus/pallets/parachain-system/src/validate_block/tests.rs index a44d1750781..871ce5c1710 100644 --- a/cumulus/pallets/parachain-system/src/validate_block/tests.rs +++ b/cumulus/pallets/parachain-system/src/validate_block/tests.rs @@ -1,12 +1,12 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Substrate. +// This file is part of Cumulus. -// Substrate is free software: you can redistribute it and/or modify +// Cumulus is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// Substrate is distributed in the hope that it will be useful, +// Cumulus 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. diff --git a/cumulus/pallets/parachain-system/src/validate_block/trie_cache.rs b/cumulus/pallets/parachain-system/src/validate_block/trie_cache.rs index 5999b3ce87f..035541fb17b 100644 --- a/cumulus/pallets/parachain-system/src/validate_block/trie_cache.rs +++ b/cumulus/pallets/parachain-system/src/validate_block/trie_cache.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Cumulus. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/cumulus/pallets/parachain-system/src/validate_block/trie_recorder.rs b/cumulus/pallets/parachain-system/src/validate_block/trie_recorder.rs index 19801340719..4a478d047f1 100644 --- a/cumulus/pallets/parachain-system/src/validate_block/trie_recorder.rs +++ b/cumulus/pallets/parachain-system/src/validate_block/trie_recorder.rs @@ -1,12 +1,12 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Substrate. +// This file is part of Cumulus. -// Substrate is free software: you can redistribute it and/or modify +// Cumulus is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// Substrate is distributed in the hope that it will be useful, +// Cumulus 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. diff --git a/cumulus/pallets/session-benchmarking/src/lib.rs b/cumulus/pallets/session-benchmarking/src/lib.rs index f5bfef00616..7d3a9ff70e7 100644 --- a/cumulus/pallets/session-benchmarking/src/lib.rs +++ b/cumulus/pallets/session-benchmarking/src/lib.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Cumulus. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/cumulus/pallets/xcmp-queue/src/lib.rs b/cumulus/pallets/xcmp-queue/src/lib.rs index 732ee94f3e1..6bb7395f655 100644 --- a/cumulus/pallets/xcmp-queue/src/lib.rs +++ b/cumulus/pallets/xcmp-queue/src/lib.rs @@ -1,12 +1,12 @@ // Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Cumulus. -// Substrate is free software: you can redistribute it and/or modify +// Cumulus is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// Substrate is distributed in the hope that it will be useful, +// Cumulus 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. diff --git a/cumulus/pallets/xcmp-queue/src/migration.rs b/cumulus/pallets/xcmp-queue/src/migration.rs index d0657aaea9f..6e41c9d9a87 100644 --- a/cumulus/pallets/xcmp-queue/src/migration.rs +++ b/cumulus/pallets/xcmp-queue/src/migration.rs @@ -1,18 +1,18 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Polkadot. +// This file is part of Cumulus. -// Polkadot is free software: you can redistribute it and/or modify +// Cumulus 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, +// Cumulus 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 . +// along with Cumulus. If not, see . //! A module that is responsible for migration of storage. diff --git a/cumulus/pallets/xcmp-queue/src/migration/v5.rs b/cumulus/pallets/xcmp-queue/src/migration/v5.rs index 818365f36f6..0bf3303bd7d 100644 --- a/cumulus/pallets/xcmp-queue/src/migration/v5.rs +++ b/cumulus/pallets/xcmp-queue/src/migration/v5.rs @@ -1,18 +1,18 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Polkadot. +// This file is part of Cumulus. -// Polkadot is free software: you can redistribute it and/or modify +// Cumulus 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, +// Cumulus 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 . +// along with Cumulus. If not, see . //! Migrates the storage to version 5. diff --git a/cumulus/parachains/common/src/message_queue.rs b/cumulus/parachains/common/src/message_queue.rs index 511d6243cb8..d6f2118e454 100644 --- a/cumulus/parachains/common/src/message_queue.rs +++ b/cumulus/parachains/common/src/message_queue.rs @@ -1,18 +1,18 @@ // Copyright 2020 Parity Technologies (UK) Ltd. -// This file is part of Polkadot. +// This file is part of Cumulus. -// Polkadot is free software: you can redistribute it and/or modify +// Cumulus 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, +// Cumulus 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 . +// along with Cumulus. If not, see . //! Helpers to deal with configuring the message queue in the runtime. diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/block_weights.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/block_weights.rs index e7fdb2aae2a..41e30725e75 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/block_weights.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/block_weights.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Cumulus. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/extrinsic_weights.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/extrinsic_weights.rs index 1a4adb968bb..3bd48f061bb 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/extrinsic_weights.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/extrinsic_weights.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Cumulus. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/paritydb_weights.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/paritydb_weights.rs index 25679703831..e0b1985c659 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/paritydb_weights.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/paritydb_weights.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Cumulus. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/rocksdb_weights.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/rocksdb_weights.rs index 3dd817aa6f1..c6e91b2fcff 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/rocksdb_weights.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/rocksdb_weights.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Cumulus. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/block_weights.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/block_weights.rs index e7fdb2aae2a..41e30725e75 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/block_weights.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/block_weights.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Cumulus. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/extrinsic_weights.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/extrinsic_weights.rs index 1a4adb968bb..3bd48f061bb 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/extrinsic_weights.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/extrinsic_weights.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Cumulus. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/paritydb_weights.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/paritydb_weights.rs index 25679703831..e0b1985c659 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/paritydb_weights.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/paritydb_weights.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Cumulus. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/rocksdb_weights.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/rocksdb_weights.rs index 3dd817aa6f1..c6e91b2fcff 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/rocksdb_weights.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/rocksdb_weights.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Cumulus. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/block_weights.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/block_weights.rs index e7fdb2aae2a..41e30725e75 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/block_weights.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/block_weights.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Cumulus. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/extrinsic_weights.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/extrinsic_weights.rs index 1a4adb968bb..3bd48f061bb 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/extrinsic_weights.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/extrinsic_weights.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Cumulus. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/mod.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/mod.rs index 942f243141d..517b3eb69fc 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/mod.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/mod.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Cumulus. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/paritydb_weights.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/paritydb_weights.rs index 25679703831..e0b1985c659 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/paritydb_weights.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/paritydb_weights.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Cumulus. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/rocksdb_weights.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/rocksdb_weights.rs index 3dd817aa6f1..c6e91b2fcff 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/rocksdb_weights.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/rocksdb_weights.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Cumulus. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/block_weights.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/block_weights.rs index e7fdb2aae2a..41e30725e75 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/block_weights.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/block_weights.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Cumulus. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/extrinsic_weights.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/extrinsic_weights.rs index 1a4adb968bb..3bd48f061bb 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/extrinsic_weights.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/extrinsic_weights.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Cumulus. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/mod.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/mod.rs index 9b7f7188782..d60529f9a23 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/mod.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/mod.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Cumulus. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/paritydb_weights.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/paritydb_weights.rs index 25679703831..e0b1985c659 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/paritydb_weights.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/paritydb_weights.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Cumulus. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/rocksdb_weights.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/rocksdb_weights.rs index 3dd817aa6f1..c6e91b2fcff 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/rocksdb_weights.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/rocksdb_weights.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Cumulus. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/block_weights.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/block_weights.rs index e7fdb2aae2a..41e30725e75 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/block_weights.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/block_weights.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Cumulus. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/extrinsic_weights.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/extrinsic_weights.rs index 1a4adb968bb..3bd48f061bb 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/extrinsic_weights.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/extrinsic_weights.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Cumulus. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/paritydb_weights.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/paritydb_weights.rs index 25679703831..e0b1985c659 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/paritydb_weights.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/paritydb_weights.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Cumulus. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/rocksdb_weights.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/rocksdb_weights.rs index 3dd817aa6f1..c6e91b2fcff 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/rocksdb_weights.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/rocksdb_weights.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Cumulus. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/weights/block_weights.rs b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/weights/block_weights.rs index e7fdb2aae2a..41e30725e75 100644 --- a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/weights/block_weights.rs +++ b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/weights/block_weights.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Cumulus. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/weights/extrinsic_weights.rs b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/weights/extrinsic_weights.rs index 1a4adb968bb..3bd48f061bb 100644 --- a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/weights/extrinsic_weights.rs +++ b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/weights/extrinsic_weights.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Cumulus. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/weights/mod.rs b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/weights/mod.rs index b473d49e20e..850dae6fbd0 100644 --- a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/weights/mod.rs +++ b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/weights/mod.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Cumulus. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/weights/paritydb_weights.rs b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/weights/paritydb_weights.rs index 25679703831..e0b1985c659 100644 --- a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/weights/paritydb_weights.rs +++ b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/weights/paritydb_weights.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Cumulus. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/weights/rocksdb_weights.rs b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/weights/rocksdb_weights.rs index 3dd817aa6f1..c6e91b2fcff 100644 --- a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/weights/rocksdb_weights.rs +++ b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/weights/rocksdb_weights.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Cumulus. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/block_weights.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/block_weights.rs index b2092d875c8..3ff2b3550fb 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/block_weights.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/block_weights.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Cumulus. // Copyright (C) 2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/extrinsic_weights.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/extrinsic_weights.rs index 332c3b324bb..ab951aea561 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/extrinsic_weights.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/extrinsic_weights.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Cumulus. // Copyright (C) 2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/mod.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/mod.rs index ab3d6704c93..216f41a5a66 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/mod.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/mod.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Cumulus. // Copyright (C) 2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/paritydb_weights.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/paritydb_weights.rs index 4338d928d80..db09e9de7bd 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/paritydb_weights.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/paritydb_weights.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Cumulus. // Copyright (C) 2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/rocksdb_weights.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/rocksdb_weights.rs index 1d115d963fa..855ec356bca 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/rocksdb_weights.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/rocksdb_weights.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Cumulus. // Copyright (C) 2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/block_weights.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/block_weights.rs index 2bd7975bf98..e5c41941a1c 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/block_weights.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/block_weights.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Cumulus. // Copyright (C) 2023 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/extrinsic_weights.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/extrinsic_weights.rs index 898d72ec5b1..b7201588439 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/extrinsic_weights.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/extrinsic_weights.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Cumulus. // Copyright (C) 2023 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/mod.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/mod.rs index ab3d6704c93..216f41a5a66 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/mod.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/mod.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Cumulus. // Copyright (C) 2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/paritydb_weights.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/paritydb_weights.rs index 1c6d2ebe568..d056c8c46a6 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/paritydb_weights.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/paritydb_weights.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Cumulus. // Copyright (C) 2023 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/rocksdb_weights.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/rocksdb_weights.rs index aa0cb2b4bc3..a32b65565a1 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/rocksdb_weights.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/rocksdb_weights.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Cumulus. // Copyright (C) 2023 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/weights/block_weights.rs b/cumulus/parachains/runtimes/people/people-rococo/src/weights/block_weights.rs index b2092d875c8..3ff2b3550fb 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/src/weights/block_weights.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/weights/block_weights.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Cumulus. // Copyright (C) 2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/weights/extrinsic_weights.rs b/cumulus/parachains/runtimes/people/people-rococo/src/weights/extrinsic_weights.rs index 332c3b324bb..ab951aea561 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/src/weights/extrinsic_weights.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/weights/extrinsic_weights.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Cumulus. // Copyright (C) 2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/weights/paritydb_weights.rs b/cumulus/parachains/runtimes/people/people-rococo/src/weights/paritydb_weights.rs index 4338d928d80..db09e9de7bd 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/src/weights/paritydb_weights.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/weights/paritydb_weights.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Cumulus. // Copyright (C) 2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/weights/rocksdb_weights.rs b/cumulus/parachains/runtimes/people/people-rococo/src/weights/rocksdb_weights.rs index 1d115d963fa..855ec356bca 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/src/weights/rocksdb_weights.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/weights/rocksdb_weights.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Cumulus. // Copyright (C) 2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/cumulus/parachains/runtimes/people/people-westend/src/weights/block_weights.rs b/cumulus/parachains/runtimes/people/people-westend/src/weights/block_weights.rs index 2bd7975bf98..e5c41941a1c 100644 --- a/cumulus/parachains/runtimes/people/people-westend/src/weights/block_weights.rs +++ b/cumulus/parachains/runtimes/people/people-westend/src/weights/block_weights.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Cumulus. // Copyright (C) 2023 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/cumulus/parachains/runtimes/people/people-westend/src/weights/extrinsic_weights.rs b/cumulus/parachains/runtimes/people/people-westend/src/weights/extrinsic_weights.rs index 898d72ec5b1..b7201588439 100644 --- a/cumulus/parachains/runtimes/people/people-westend/src/weights/extrinsic_weights.rs +++ b/cumulus/parachains/runtimes/people/people-westend/src/weights/extrinsic_weights.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Cumulus. // Copyright (C) 2023 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/cumulus/parachains/runtimes/starters/shell/build.rs b/cumulus/parachains/runtimes/starters/shell/build.rs index 9c9cde9a25a..896fc0fecf1 100644 --- a/cumulus/parachains/runtimes/starters/shell/build.rs +++ b/cumulus/parachains/runtimes/starters/shell/build.rs @@ -1,12 +1,12 @@ // Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Cumulus. -// Substrate is free software: you can redistribute it and/or modify +// Cumulus is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// Substrate is distributed in the hope that it will be useful, +// Cumulus 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. diff --git a/cumulus/parachains/runtimes/testing/penpal/build.rs b/cumulus/parachains/runtimes/testing/penpal/build.rs index c2fa89aa702..e47e483bf9c 100644 --- a/cumulus/parachains/runtimes/testing/penpal/build.rs +++ b/cumulus/parachains/runtimes/testing/penpal/build.rs @@ -1,12 +1,12 @@ // Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Cumulus. -// Substrate is free software: you can redistribute it and/or modify +// Cumulus is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// Substrate is distributed in the hope that it will be useful, +// Cumulus 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. diff --git a/cumulus/parachains/runtimes/testing/penpal/src/weights/block_weights.rs b/cumulus/parachains/runtimes/testing/penpal/src/weights/block_weights.rs index e7fdb2aae2a..41e30725e75 100644 --- a/cumulus/parachains/runtimes/testing/penpal/src/weights/block_weights.rs +++ b/cumulus/parachains/runtimes/testing/penpal/src/weights/block_weights.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Cumulus. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/cumulus/parachains/runtimes/testing/penpal/src/weights/extrinsic_weights.rs b/cumulus/parachains/runtimes/testing/penpal/src/weights/extrinsic_weights.rs index 1a4adb968bb..3bd48f061bb 100644 --- a/cumulus/parachains/runtimes/testing/penpal/src/weights/extrinsic_weights.rs +++ b/cumulus/parachains/runtimes/testing/penpal/src/weights/extrinsic_weights.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Cumulus. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/cumulus/parachains/runtimes/testing/penpal/src/weights/mod.rs b/cumulus/parachains/runtimes/testing/penpal/src/weights/mod.rs index b473d49e20e..850dae6fbd0 100644 --- a/cumulus/parachains/runtimes/testing/penpal/src/weights/mod.rs +++ b/cumulus/parachains/runtimes/testing/penpal/src/weights/mod.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Cumulus. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/cumulus/parachains/runtimes/testing/penpal/src/weights/paritydb_weights.rs b/cumulus/parachains/runtimes/testing/penpal/src/weights/paritydb_weights.rs index 25679703831..e0b1985c659 100644 --- a/cumulus/parachains/runtimes/testing/penpal/src/weights/paritydb_weights.rs +++ b/cumulus/parachains/runtimes/testing/penpal/src/weights/paritydb_weights.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Cumulus. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/cumulus/parachains/runtimes/testing/penpal/src/weights/rocksdb_weights.rs b/cumulus/parachains/runtimes/testing/penpal/src/weights/rocksdb_weights.rs index 3dd817aa6f1..c6e91b2fcff 100644 --- a/cumulus/parachains/runtimes/testing/penpal/src/weights/rocksdb_weights.rs +++ b/cumulus/parachains/runtimes/testing/penpal/src/weights/rocksdb_weights.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Cumulus. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/cumulus/polkadot-parachain/build.rs b/cumulus/polkadot-parachain/build.rs index dd0d112bca7..8c498735eae 100644 --- a/cumulus/polkadot-parachain/build.rs +++ b/cumulus/polkadot-parachain/build.rs @@ -1,12 +1,12 @@ // Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Cumulus. -// Substrate is free software: you can redistribute it and/or modify +// Cumulus is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// Substrate is distributed in the hope that it will be useful, +// Cumulus 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. diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/tests/benchmark_storage_works.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/tests/benchmark_storage_works.rs index c554b5b3d6b..8502188af51 100644 --- a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/tests/benchmark_storage_works.rs +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/tests/benchmark_storage_works.rs @@ -1,12 +1,12 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Substrate. +// This file is part of Cumulus. -// Substrate is free software: you can redistribute it and/or modify +// Cumulus is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// Substrate is distributed in the hope that it will be useful, +// Cumulus 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. diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/tests/common.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/tests/common.rs index 20926ddd91d..d3f41fb50bc 100644 --- a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/tests/common.rs +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/tests/common.rs @@ -1,12 +1,12 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Substrate. +// This file is part of Cumulus. -// Substrate is free software: you can redistribute it and/or modify +// Cumulus is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// Substrate is distributed in the hope that it will be useful, +// Cumulus 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. diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/tests/polkadot_argument_parsing.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/tests/polkadot_argument_parsing.rs index 9337da85d74..d1f497c1187 100644 --- a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/tests/polkadot_argument_parsing.rs +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/tests/polkadot_argument_parsing.rs @@ -1,12 +1,12 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Substrate. +// This file is part of Cumulus. -// Substrate is free software: you can redistribute it and/or modify +// Cumulus is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// Substrate is distributed in the hope that it will be useful, +// Cumulus 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. diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/tests/polkadot_mdns_issue.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/tests/polkadot_mdns_issue.rs index e3ccb7fe0fb..3b0b08e57f8 100644 --- a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/tests/polkadot_mdns_issue.rs +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/tests/polkadot_mdns_issue.rs @@ -1,12 +1,12 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Substrate. +// This file is part of Cumulus. -// Substrate is free software: you can redistribute it and/or modify +// Cumulus is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// Substrate is distributed in the hope that it will be useful, +// Cumulus 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. diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/tests/purge_chain_works.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/tests/purge_chain_works.rs index 6415a914c7a..65a946e890b 100644 --- a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/tests/purge_chain_works.rs +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/tests/purge_chain_works.rs @@ -1,12 +1,12 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Substrate. +// This file is part of Cumulus. -// Substrate is free software: you can redistribute it and/or modify +// Cumulus is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// Substrate is distributed in the hope that it will be useful, +// Cumulus 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. diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/tests/running_the_node_and_interrupt.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/tests/running_the_node_and_interrupt.rs index 0f4ae699238..a45fd7f4575 100644 --- a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/tests/running_the_node_and_interrupt.rs +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/tests/running_the_node_and_interrupt.rs @@ -1,12 +1,12 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Substrate. +// This file is part of Cumulus. -// Substrate is free software: you can redistribute it and/or modify +// Cumulus is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// Substrate is distributed in the hope that it will be useful, +// Cumulus 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. diff --git a/cumulus/primitives/aura/src/lib.rs b/cumulus/primitives/aura/src/lib.rs index 826b65fddd2..aeeee5f8baf 100644 --- a/cumulus/primitives/aura/src/lib.rs +++ b/cumulus/primitives/aura/src/lib.rs @@ -1,12 +1,12 @@ // Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Cumulus. -// Substrate is free software: you can redistribute it and/or modify +// Cumulus is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// Substrate is distributed in the hope that it will be useful, +// Cumulus 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. diff --git a/cumulus/primitives/core/src/lib.rs b/cumulus/primitives/core/src/lib.rs index 6eafecfc3ff..60b86af8e94 100644 --- a/cumulus/primitives/core/src/lib.rs +++ b/cumulus/primitives/core/src/lib.rs @@ -1,12 +1,12 @@ // Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Cumulus. -// Substrate is free software: you can redistribute it and/or modify +// Cumulus is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// Substrate is distributed in the hope that it will be useful, +// Cumulus 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. diff --git a/cumulus/primitives/parachain-inherent/src/lib.rs b/cumulus/primitives/parachain-inherent/src/lib.rs index ad4b39b547c..127a03b6525 100644 --- a/cumulus/primitives/parachain-inherent/src/lib.rs +++ b/cumulus/primitives/parachain-inherent/src/lib.rs @@ -1,12 +1,12 @@ // Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Cumulus. -// Substrate is free software: you can redistribute it and/or modify +// Cumulus is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// Substrate is distributed in the hope that it will be useful, +// Cumulus 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. diff --git a/cumulus/primitives/proof-size-hostfunction/src/lib.rs b/cumulus/primitives/proof-size-hostfunction/src/lib.rs index 8ebc58ea450..f17b3d3f33b 100644 --- a/cumulus/primitives/proof-size-hostfunction/src/lib.rs +++ b/cumulus/primitives/proof-size-hostfunction/src/lib.rs @@ -6,7 +6,7 @@ // 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, +// Cumulus 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. diff --git a/cumulus/primitives/utility/src/lib.rs b/cumulus/primitives/utility/src/lib.rs index e568c79bb6a..6bd14d136a6 100644 --- a/cumulus/primitives/utility/src/lib.rs +++ b/cumulus/primitives/utility/src/lib.rs @@ -1,12 +1,12 @@ // Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Cumulus. -// Substrate is free software: you can redistribute it and/or modify +// Cumulus is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// Substrate is distributed in the hope that it will be useful, +// Cumulus 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. diff --git a/cumulus/primitives/utility/src/tests/mod.rs b/cumulus/primitives/utility/src/tests/mod.rs index e0ad8718b89..80e72ef2826 100644 --- a/cumulus/primitives/utility/src/tests/mod.rs +++ b/cumulus/primitives/utility/src/tests/mod.rs @@ -1,12 +1,12 @@ // Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Cumulus. -// Substrate is free software: you can redistribute it and/or modify +// Cumulus is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// Substrate is distributed in the hope that it will be useful, +// Cumulus 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. diff --git a/cumulus/primitives/utility/src/tests/swap_first.rs b/cumulus/primitives/utility/src/tests/swap_first.rs index 2e19db49881..69239c552b8 100644 --- a/cumulus/primitives/utility/src/tests/swap_first.rs +++ b/cumulus/primitives/utility/src/tests/swap_first.rs @@ -1,12 +1,12 @@ // Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Cumulus. -// Substrate is free software: you can redistribute it and/or modify +// Cumulus is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// Substrate is distributed in the hope that it will be useful, +// Cumulus 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. diff --git a/cumulus/test/runtime/build.rs b/cumulus/test/runtime/build.rs index bf579f4121e..7a7fe8ffaa8 100644 --- a/cumulus/test/runtime/build.rs +++ b/cumulus/test/runtime/build.rs @@ -1,12 +1,12 @@ // Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Cumulus. -// Substrate is free software: you can redistribute it and/or modify +// Cumulus is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// Substrate is distributed in the hope that it will be useful, +// Cumulus 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. diff --git a/cumulus/xcm/xcm-emulator/src/lib.rs b/cumulus/xcm/xcm-emulator/src/lib.rs index afed14278d1..76bbad38d5e 100644 --- a/cumulus/xcm/xcm-emulator/src/lib.rs +++ b/cumulus/xcm/xcm-emulator/src/lib.rs @@ -1,18 +1,18 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Polkadot. +// This file is part of Cumulus. -// Polkadot is free software: you can redistribute it and/or modify +// Cumulus 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, +// Cumulus 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 . +// along with Cumulus. If not, see . extern crate alloc; diff --git a/polkadot/node/metrics/src/tests.rs b/polkadot/node/metrics/src/tests.rs index e720924feb6..4760138058e 100644 --- a/polkadot/node/metrics/src/tests.rs +++ b/polkadot/node/metrics/src/tests.rs @@ -1,18 +1,18 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Substrate. +// This file is part of Polkadot. -// Substrate is free software: you can redistribute it and/or modify +// 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. -// Substrate is distributed in the hope that it will be useful, +// 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 Substrate. If not, see . +// along with Polkadot. If not, see . //! Polkadot runtime metrics integration test. diff --git a/polkadot/runtime/common/src/claims.rs b/polkadot/runtime/common/src/claims.rs index 162bf01c384..32686d1a0bf 100644 --- a/polkadot/runtime/common/src/claims.rs +++ b/polkadot/runtime/common/src/claims.rs @@ -1,18 +1,18 @@ // Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Polkadot. -// Substrate is free software: you can redistribute it and/or modify +// 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. -// Substrate is distributed in the hope that it will be useful, +// 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 Substrate. If not, see . +// along with Polkadot. If not, see . //! Pallet to process claims from Ethereum addresses. diff --git a/polkadot/runtime/common/src/purchase.rs b/polkadot/runtime/common/src/purchase.rs index d650548b8ac..9cbb907536d 100644 --- a/polkadot/runtime/common/src/purchase.rs +++ b/polkadot/runtime/common/src/purchase.rs @@ -1,18 +1,18 @@ // Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Polkadot. -// Substrate is free software: you can redistribute it and/or modify +// 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. -// Substrate is distributed in the hope that it will be useful, +// 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 Substrate. If not, see . +// along with Polkadot. If not, see . //! Pallet to process purchase of DOTs. diff --git a/polkadot/runtime/rococo/build.rs b/polkadot/runtime/rococo/build.rs index 7aae84cd5e0..aab666b0f11 100644 --- a/polkadot/runtime/rococo/build.rs +++ b/polkadot/runtime/rococo/build.rs @@ -1,18 +1,18 @@ // Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Polkadot. -// Substrate is free software: you can redistribute it and/or modify +// 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. -// Substrate is distributed in the hope that it will be useful, +// 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 Substrate. If not, see . +// along with Polkadot. If not, see . #[cfg(all(not(feature = "metadata-hash"), feature = "std"))] fn main() { diff --git a/polkadot/runtime/rococo/constants/src/weights/mod.rs b/polkadot/runtime/rococo/constants/src/weights/mod.rs index 23812ce7ed0..2648608a2f8 100644 --- a/polkadot/runtime/rococo/constants/src/weights/mod.rs +++ b/polkadot/runtime/rococo/constants/src/weights/mod.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Polkadot. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/polkadot/runtime/rococo/constants/src/weights/paritydb_weights.rs b/polkadot/runtime/rococo/constants/src/weights/paritydb_weights.rs index 25679703831..67d5286022e 100644 --- a/polkadot/runtime/rococo/constants/src/weights/paritydb_weights.rs +++ b/polkadot/runtime/rococo/constants/src/weights/paritydb_weights.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Polkadot. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/polkadot/runtime/rococo/constants/src/weights/rocksdb_weights.rs b/polkadot/runtime/rococo/constants/src/weights/rocksdb_weights.rs index 3dd817aa6f1..57f49e1202c 100644 --- a/polkadot/runtime/rococo/constants/src/weights/rocksdb_weights.rs +++ b/polkadot/runtime/rococo/constants/src/weights/rocksdb_weights.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Polkadot. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/polkadot/runtime/test-runtime/constants/src/weights/block_weights.rs b/polkadot/runtime/test-runtime/constants/src/weights/block_weights.rs index e7fdb2aae2a..07316759104 100644 --- a/polkadot/runtime/test-runtime/constants/src/weights/block_weights.rs +++ b/polkadot/runtime/test-runtime/constants/src/weights/block_weights.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Polkadot. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/polkadot/runtime/test-runtime/constants/src/weights/extrinsic_weights.rs b/polkadot/runtime/test-runtime/constants/src/weights/extrinsic_weights.rs index 1a4adb968bb..d0af4ec8921 100644 --- a/polkadot/runtime/test-runtime/constants/src/weights/extrinsic_weights.rs +++ b/polkadot/runtime/test-runtime/constants/src/weights/extrinsic_weights.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Polkadot. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/polkadot/runtime/test-runtime/constants/src/weights/mod.rs b/polkadot/runtime/test-runtime/constants/src/weights/mod.rs index 30fa2c40606..d9087d7f057 100644 --- a/polkadot/runtime/test-runtime/constants/src/weights/mod.rs +++ b/polkadot/runtime/test-runtime/constants/src/weights/mod.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Polkadot. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/polkadot/runtime/test-runtime/constants/src/weights/paritydb_weights.rs b/polkadot/runtime/test-runtime/constants/src/weights/paritydb_weights.rs index 25679703831..67d5286022e 100644 --- a/polkadot/runtime/test-runtime/constants/src/weights/paritydb_weights.rs +++ b/polkadot/runtime/test-runtime/constants/src/weights/paritydb_weights.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Polkadot. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/polkadot/runtime/test-runtime/constants/src/weights/rocksdb_weights.rs b/polkadot/runtime/test-runtime/constants/src/weights/rocksdb_weights.rs index 3dd817aa6f1..57f49e1202c 100644 --- a/polkadot/runtime/test-runtime/constants/src/weights/rocksdb_weights.rs +++ b/polkadot/runtime/test-runtime/constants/src/weights/rocksdb_weights.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Polkadot. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/polkadot/runtime/westend/build.rs b/polkadot/runtime/westend/build.rs index 55ccd364012..cf4097a2da6 100644 --- a/polkadot/runtime/westend/build.rs +++ b/polkadot/runtime/westend/build.rs @@ -6,7 +6,7 @@ // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// Substrate is distributed in the hope that it will be useful, +// 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. diff --git a/polkadot/runtime/westend/constants/src/weights/mod.rs b/polkadot/runtime/westend/constants/src/weights/mod.rs index 23812ce7ed0..2648608a2f8 100644 --- a/polkadot/runtime/westend/constants/src/weights/mod.rs +++ b/polkadot/runtime/westend/constants/src/weights/mod.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Polkadot. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/polkadot/runtime/westend/constants/src/weights/paritydb_weights.rs b/polkadot/runtime/westend/constants/src/weights/paritydb_weights.rs index 25679703831..67d5286022e 100644 --- a/polkadot/runtime/westend/constants/src/weights/paritydb_weights.rs +++ b/polkadot/runtime/westend/constants/src/weights/paritydb_weights.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Polkadot. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/polkadot/runtime/westend/constants/src/weights/rocksdb_weights.rs b/polkadot/runtime/westend/constants/src/weights/rocksdb_weights.rs index 3dd817aa6f1..57f49e1202c 100644 --- a/polkadot/runtime/westend/constants/src/weights/rocksdb_weights.rs +++ b/polkadot/runtime/westend/constants/src/weights/rocksdb_weights.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +// This file is part of Polkadot. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/polkadot/tests/common.rs b/polkadot/tests/common.rs index dbee2d36503..c13bb8cd143 100644 --- a/polkadot/tests/common.rs +++ b/polkadot/tests/common.rs @@ -1,18 +1,18 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Substrate. +// This file is part of Polkadot. -// Substrate is free software: you can redistribute it and/or modify +// 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. -// Substrate is distributed in the hope that it will be useful, +// 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 Substrate. If not, see . +// along with Polkadot. If not, see . use polkadot_core_primitives::{Block, Hash, Header}; use std::{ diff --git a/polkadot/tests/invalid_order_arguments.rs b/polkadot/tests/invalid_order_arguments.rs index 8b5f4e31c17..f9213bbdc30 100644 --- a/polkadot/tests/invalid_order_arguments.rs +++ b/polkadot/tests/invalid_order_arguments.rs @@ -1,18 +1,18 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Substrate. +// This file is part of Polkadot. -// Substrate is free software: you can redistribute it and/or modify +// 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. -// Substrate is distributed in the hope that it will be useful, +// 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 Substrate. If not, see . +// along with Polkadot. If not, see . use assert_cmd::cargo::cargo_bin; use std::process::Command; diff --git a/polkadot/tests/purge_chain_works.rs b/polkadot/tests/purge_chain_works.rs index f5a73e232e0..bc36097b8d2 100644 --- a/polkadot/tests/purge_chain_works.rs +++ b/polkadot/tests/purge_chain_works.rs @@ -1,18 +1,18 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Substrate. +// This file is part of Polkadot. -// Substrate is free software: you can redistribute it and/or modify +// 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. -// Substrate is distributed in the hope that it will be useful, +// 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 Substrate. If not, see . +// along with Polkadot. If not, see . #![cfg(unix)] diff --git a/polkadot/tests/running_the_node_and_interrupt.rs b/polkadot/tests/running_the_node_and_interrupt.rs index 85c073d3023..053acc96679 100644 --- a/polkadot/tests/running_the_node_and_interrupt.rs +++ b/polkadot/tests/running_the_node_and_interrupt.rs @@ -1,18 +1,18 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Substrate. +// This file is part of Polkadot. -// Substrate is free software: you can redistribute it and/or modify +// 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. -// Substrate is distributed in the hope that it will be useful, +// 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 Substrate. If not, see . +// along with Polkadot. If not, see . use assert_cmd::cargo::cargo_bin; use std::process::{self, Command}; diff --git a/polkadot/xcm/src/lib.rs b/polkadot/xcm/src/lib.rs index 1f5191c2340..0b916c87f54 100644 --- a/polkadot/xcm/src/lib.rs +++ b/polkadot/xcm/src/lib.rs @@ -1,12 +1,12 @@ // Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Polkadot. -// Substrate is free software: you can redistribute it and/or modify +// 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. -// Substrate is distributed in the hope that it will be useful, +// 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. diff --git a/polkadot/xcm/src/v2/mod.rs b/polkadot/xcm/src/v2/mod.rs index 1afc120f500..e3358f08d41 100644 --- a/polkadot/xcm/src/v2/mod.rs +++ b/polkadot/xcm/src/v2/mod.rs @@ -1,18 +1,18 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Cumulus. +// This file is part of Polkadot. -// Substrate is free software: you can redistribute it and/or modify +// 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. -// Substrate is distributed in the hope that it will be useful, +// 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 Cumulus. If not, see . +// along with Polkadot. If not, see . //! # XCM Version 2 //! diff --git a/polkadot/xcm/src/v2/multiasset.rs b/polkadot/xcm/src/v2/multiasset.rs index 7090ef138ca..218f21b63b0 100644 --- a/polkadot/xcm/src/v2/multiasset.rs +++ b/polkadot/xcm/src/v2/multiasset.rs @@ -1,12 +1,12 @@ // Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Polkadot. -// Substrate is free software: you can redistribute it and/or modify +// 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. -// Substrate is distributed in the hope that it will be useful, +// 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. diff --git a/polkadot/xcm/src/v2/traits.rs b/polkadot/xcm/src/v2/traits.rs index 4dcb4c50c68..815495b8127 100644 --- a/polkadot/xcm/src/v2/traits.rs +++ b/polkadot/xcm/src/v2/traits.rs @@ -1,18 +1,18 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Cumulus. +// This file is part of Polkadot. -// Substrate is free software: you can redistribute it and/or modify +// 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. -// Substrate is distributed in the hope that it will be useful, +// 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 Cumulus. If not, see . +// along with Polkadot. If not, see . //! Cross-Consensus Message format data structures. diff --git a/polkadot/xcm/src/v3/mod.rs b/polkadot/xcm/src/v3/mod.rs index 880520cfedc..ff64c98e15b 100644 --- a/polkadot/xcm/src/v3/mod.rs +++ b/polkadot/xcm/src/v3/mod.rs @@ -1,12 +1,12 @@ // Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Polkadot. -// Substrate is free software: you can redistribute it and/or modify +// 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. -// Substrate is distributed in the hope that it will be useful, +// 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. diff --git a/polkadot/xcm/src/v3/multiasset.rs b/polkadot/xcm/src/v3/multiasset.rs index 7db0fa73690..56b46b1d921 100644 --- a/polkadot/xcm/src/v3/multiasset.rs +++ b/polkadot/xcm/src/v3/multiasset.rs @@ -1,12 +1,12 @@ // Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Polkadot. -// Substrate is free software: you can redistribute it and/or modify +// 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. -// Substrate is distributed in the hope that it will be useful, +// 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. diff --git a/polkadot/xcm/src/v3/traits.rs b/polkadot/xcm/src/v3/traits.rs index 7fa8824c356..34c46453b9a 100644 --- a/polkadot/xcm/src/v3/traits.rs +++ b/polkadot/xcm/src/v3/traits.rs @@ -1,12 +1,12 @@ // Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Polkadot. -// Substrate is free software: you can redistribute it and/or modify +// 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. -// Substrate is distributed in the hope that it will be useful, +// 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. diff --git a/polkadot/xcm/src/v4/asset.rs b/polkadot/xcm/src/v4/asset.rs index a081b595adb..41f1f82f828 100644 --- a/polkadot/xcm/src/v4/asset.rs +++ b/polkadot/xcm/src/v4/asset.rs @@ -1,12 +1,12 @@ // Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Polkadot. -// Substrate is free software: you can redistribute it and/or modify +// 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. -// Substrate is distributed in the hope that it will be useful, +// 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. diff --git a/polkadot/xcm/src/v4/mod.rs b/polkadot/xcm/src/v4/mod.rs index 2a279f989e9..a2b12dcc54c 100644 --- a/polkadot/xcm/src/v4/mod.rs +++ b/polkadot/xcm/src/v4/mod.rs @@ -1,12 +1,12 @@ // Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Polkadot. -// Substrate is free software: you can redistribute it and/or modify +// 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. -// Substrate is distributed in the hope that it will be useful, +// 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. diff --git a/polkadot/xcm/src/v4/traits.rs b/polkadot/xcm/src/v4/traits.rs index 351de92c80e..f32b26fb163 100644 --- a/polkadot/xcm/src/v4/traits.rs +++ b/polkadot/xcm/src/v4/traits.rs @@ -1,12 +1,12 @@ // Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Polkadot. -// Substrate is free software: you can redistribute it and/or modify +// 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. -// Substrate is distributed in the hope that it will be useful, +// 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. diff --git a/polkadot/xcm/xcm-builder/src/matcher.rs b/polkadot/xcm/xcm-builder/src/matcher.rs index eae43b290fb..ab515f18052 100644 --- a/polkadot/xcm/xcm-builder/src/matcher.rs +++ b/polkadot/xcm/xcm-builder/src/matcher.rs @@ -1,12 +1,12 @@ // Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Polkadot. -// Substrate is free software: you can redistribute it and/or modify +// 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. -// Substrate is distributed in the hope that it will be useful, +// 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. diff --git a/polkadot/xcm/xcm-runtime-apis/src/conversions.rs b/polkadot/xcm/xcm-runtime-apis/src/conversions.rs index e5eeac013fe..22f0809ea5f 100644 --- a/polkadot/xcm/xcm-runtime-apis/src/conversions.rs +++ b/polkadot/xcm/xcm-runtime-apis/src/conversions.rs @@ -1,12 +1,12 @@ // Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Polkadot. -// Substrate is free software: you can redistribute it and/or modify +// 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. -// Substrate is distributed in the hope that it will be useful, +// 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. diff --git a/polkadot/xcm/xcm-runtime-apis/src/dry_run.rs b/polkadot/xcm/xcm-runtime-apis/src/dry_run.rs index 2a1a0daf0d5..c51a4a5376a 100644 --- a/polkadot/xcm/xcm-runtime-apis/src/dry_run.rs +++ b/polkadot/xcm/xcm-runtime-apis/src/dry_run.rs @@ -1,12 +1,12 @@ // Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Polkadot. -// Substrate is free software: you can redistribute it and/or modify +// 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. -// Substrate is distributed in the hope that it will be useful, +// 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. diff --git a/polkadot/xcm/xcm-runtime-apis/src/fees.rs b/polkadot/xcm/xcm-runtime-apis/src/fees.rs index 3445d42ecab..9500a7f7281 100644 --- a/polkadot/xcm/xcm-runtime-apis/src/fees.rs +++ b/polkadot/xcm/xcm-runtime-apis/src/fees.rs @@ -1,12 +1,12 @@ // Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Polkadot. -// Substrate is free software: you can redistribute it and/or modify +// 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. -// Substrate is distributed in the hope that it will be useful, +// 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. diff --git a/polkadot/xcm/xcm-runtime-apis/src/lib.rs b/polkadot/xcm/xcm-runtime-apis/src/lib.rs index b106836c113..44e518e8e7a 100644 --- a/polkadot/xcm/xcm-runtime-apis/src/lib.rs +++ b/polkadot/xcm/xcm-runtime-apis/src/lib.rs @@ -1,12 +1,12 @@ // Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Polkadot. -// Substrate is free software: you can redistribute it and/or modify +// 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. -// Substrate is distributed in the hope that it will be useful, +// 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. diff --git a/polkadot/xcm/xcm-runtime-apis/tests/conversions.rs b/polkadot/xcm/xcm-runtime-apis/tests/conversions.rs index 7f0f0923b09..c7a1dda0169 100644 --- a/polkadot/xcm/xcm-runtime-apis/tests/conversions.rs +++ b/polkadot/xcm/xcm-runtime-apis/tests/conversions.rs @@ -1,12 +1,12 @@ // Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Polkadot. -// Substrate is free software: you can redistribute it and/or modify +// 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. -// Substrate is distributed in the hope that it will be useful, +// 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. diff --git a/polkadot/xcm/xcm-runtime-apis/tests/fee_estimation.rs b/polkadot/xcm/xcm-runtime-apis/tests/fee_estimation.rs index e5dac7c7a04..889a50a2bab 100644 --- a/polkadot/xcm/xcm-runtime-apis/tests/fee_estimation.rs +++ b/polkadot/xcm/xcm-runtime-apis/tests/fee_estimation.rs @@ -1,12 +1,12 @@ // Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Polkadot. -// Substrate is free software: you can redistribute it and/or modify +// 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. -// Substrate is distributed in the hope that it will be useful, +// 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. diff --git a/polkadot/xcm/xcm-runtime-apis/tests/mock.rs b/polkadot/xcm/xcm-runtime-apis/tests/mock.rs index c76b26fcd2a..6575feccf8a 100644 --- a/polkadot/xcm/xcm-runtime-apis/tests/mock.rs +++ b/polkadot/xcm/xcm-runtime-apis/tests/mock.rs @@ -1,12 +1,12 @@ // Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Polkadot. -// Substrate is free software: you can redistribute it and/or modify +// 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. -// Substrate is distributed in the hope that it will be useful, +// 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. diff --git a/substrate/frame/bags-list/remote-tests/src/migration.rs b/substrate/frame/bags-list/remote-tests/src/migration.rs index dc133745afe..03475b23585 100644 --- a/substrate/frame/bags-list/remote-tests/src/migration.rs +++ b/substrate/frame/bags-list/remote-tests/src/migration.rs @@ -1,18 +1,18 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Polkadot. +// This file is part of Substrate. -// Polkadot is free software: you can redistribute it and/or modify +// Substrate is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// Polkadot is distributed in the hope that it will be useful, +// Substrate is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License -// along with Polkadot. If not, see . +// along with Substrate. If not, see . //! Test to check the migration of the voter bag. diff --git a/substrate/frame/bags-list/remote-tests/src/snapshot.rs b/substrate/frame/bags-list/remote-tests/src/snapshot.rs index 81a8905e6b4..cb23fc6f581 100644 --- a/substrate/frame/bags-list/remote-tests/src/snapshot.rs +++ b/substrate/frame/bags-list/remote-tests/src/snapshot.rs @@ -1,18 +1,18 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Polkadot. +// This file is part of Substrate. -// Polkadot is free software: you can redistribute it and/or modify +// Substrate is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// Polkadot is distributed in the hope that it will be useful, +// Substrate is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License -// along with Polkadot. If not, see . +// along with Substrate. If not, see . //! Test to execute the snapshot using the voter bag. diff --git a/substrate/frame/bags-list/remote-tests/src/try_state.rs b/substrate/frame/bags-list/remote-tests/src/try_state.rs index 83930024c89..d4ce0d4f736 100644 --- a/substrate/frame/bags-list/remote-tests/src/try_state.rs +++ b/substrate/frame/bags-list/remote-tests/src/try_state.rs @@ -1,18 +1,18 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Polkadot. +// This file is part of Substrate. -// Polkadot is free software: you can redistribute it and/or modify +// Substrate is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// Polkadot is distributed in the hope that it will be useful, +// Substrate is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License -// along with Polkadot. If not, see . +// along with Substrate. If not, see . //! Test to execute the sanity-check of the voter bag. diff --git a/substrate/frame/balances/src/migration.rs b/substrate/frame/balances/src/migration.rs index 568c3fbb7cf..920c6eea562 100644 --- a/substrate/frame/balances/src/migration.rs +++ b/substrate/frame/balances/src/migration.rs @@ -1,18 +1,18 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Polkadot. +// This file is part of Substrate. -// Polkadot is free software: you can redistribute it and/or modify +// Substrate is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// Polkadot is distributed in the hope that it will be useful, +// Substrate is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License -// along with Polkadot. If not, see . +// along with Substrate. If not, see . use super::*; use frame_support::{ diff --git a/substrate/frame/contracts/mock-network/src/lib.rs b/substrate/frame/contracts/mock-network/src/lib.rs index 34cc95f2eae..cb9e22439b7 100644 --- a/substrate/frame/contracts/mock-network/src/lib.rs +++ b/substrate/frame/contracts/mock-network/src/lib.rs @@ -1,18 +1,18 @@ // Copyright Parity Technologies (UK) Ltd. -// This file is part of Polkadot. +// This file is part of Substrate. -// Polkadot is free software: you can redistribute it and/or modify +// Substrate is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// Polkadot is distributed in the hope that it will be useful, +// Substrate is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License -// along with Polkadot. If not, see . +// along with Substrate. If not, see . pub mod mocks; pub mod parachain; diff --git a/substrate/frame/contracts/mock-network/src/mocks.rs b/substrate/frame/contracts/mock-network/src/mocks.rs index bf3baec7a52..6903f01e91a 100644 --- a/substrate/frame/contracts/mock-network/src/mocks.rs +++ b/substrate/frame/contracts/mock-network/src/mocks.rs @@ -1,18 +1,18 @@ // Copyright Parity Technologies (UK) Ltd. -// This file is part of Polkadot. +// This file is part of Substrate. -// Polkadot is free software: you can redistribute it and/or modify +// Substrate is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// Polkadot is distributed in the hope that it will be useful, +// Substrate is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License -// along with Polkadot. If not, see . +// along with Substrate. If not, see . pub mod msg_queue; pub mod relay_message_queue; diff --git a/substrate/frame/contracts/mock-network/src/mocks/msg_queue.rs b/substrate/frame/contracts/mock-network/src/mocks/msg_queue.rs index 6e922c16c29..88ab8135ea1 100644 --- a/substrate/frame/contracts/mock-network/src/mocks/msg_queue.rs +++ b/substrate/frame/contracts/mock-network/src/mocks/msg_queue.rs @@ -1,18 +1,18 @@ // Copyright Parity Technologies (UK) Ltd. -// This file is part of Polkadot. +// This file is part of Substrate. -// Polkadot is free software: you can redistribute it and/or modify +// Substrate is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// Polkadot is distributed in the hope that it will be useful, +// Substrate is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License -// along with Polkadot. If not, see . +// along with Substrate. If not, see . //! Parachain runtime mock. diff --git a/substrate/frame/contracts/mock-network/src/mocks/relay_message_queue.rs b/substrate/frame/contracts/mock-network/src/mocks/relay_message_queue.rs index 14099965e3f..50f0febd2c4 100644 --- a/substrate/frame/contracts/mock-network/src/mocks/relay_message_queue.rs +++ b/substrate/frame/contracts/mock-network/src/mocks/relay_message_queue.rs @@ -1,18 +1,18 @@ // Copyright Parity Technologies (UK) Ltd. -// This file is part of Polkadot. +// This file is part of Substrate. -// Polkadot is free software: you can redistribute it and/or modify +// Substrate is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// Polkadot is distributed in the hope that it will be useful, +// Substrate is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License -// along with Polkadot. If not, see . +// along with Substrate. If not, see . use frame_support::{parameter_types, weights::Weight}; use xcm::latest::prelude::*; diff --git a/substrate/frame/contracts/mock-network/src/parachain.rs b/substrate/frame/contracts/mock-network/src/parachain.rs index 3579b46ea6e..5a06cc6748b 100644 --- a/substrate/frame/contracts/mock-network/src/parachain.rs +++ b/substrate/frame/contracts/mock-network/src/parachain.rs @@ -1,18 +1,18 @@ // Copyright Parity Technologies (UK) Ltd. -// This file is part of Polkadot. +// This file is part of Substrate. -// Polkadot is free software: you can redistribute it and/or modify +// Substrate is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// Polkadot is distributed in the hope that it will be useful, +// Substrate is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License -// along with Polkadot. If not, see . +// along with Substrate. If not, see . //! Parachain runtime mock. diff --git a/substrate/frame/contracts/mock-network/src/parachain/contracts_config.rs b/substrate/frame/contracts/mock-network/src/parachain/contracts_config.rs index bf3c00b3ff1..9ad978bbc03 100644 --- a/substrate/frame/contracts/mock-network/src/parachain/contracts_config.rs +++ b/substrate/frame/contracts/mock-network/src/parachain/contracts_config.rs @@ -1,18 +1,18 @@ // Copyright Parity Technologies (UK) Ltd. -// This file is part of Polkadot. +// This file is part of Substrate. -// Polkadot is free software: you can redistribute it and/or modify +// Substrate is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// Polkadot is distributed in the hope that it will be useful, +// Substrate is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License -// along with Polkadot. If not, see . +// along with Substrate. If not, see . use super::{Balances, Runtime, RuntimeCall, RuntimeEvent}; use crate::parachain::RuntimeHoldReason; diff --git a/substrate/frame/contracts/mock-network/src/primitives.rs b/substrate/frame/contracts/mock-network/src/primitives.rs index efc42772f88..cb9b2f00f9e 100644 --- a/substrate/frame/contracts/mock-network/src/primitives.rs +++ b/substrate/frame/contracts/mock-network/src/primitives.rs @@ -1,18 +1,18 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Polkadot. +// This file is part of Substrate. -// Polkadot is free software: you can redistribute it and/or modify +// Substrate is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// Polkadot is distributed in the hope that it will be useful, +// Substrate is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License -// along with Polkadot. If not, see . +// along with Substrate. If not, see . pub type Balance = u128; diff --git a/substrate/frame/contracts/mock-network/src/relay_chain.rs b/substrate/frame/contracts/mock-network/src/relay_chain.rs index 8829fff3d04..705578cde1d 100644 --- a/substrate/frame/contracts/mock-network/src/relay_chain.rs +++ b/substrate/frame/contracts/mock-network/src/relay_chain.rs @@ -1,18 +1,18 @@ // Copyright Parity Technologies (UK) Ltd. -// This file is part of Polkadot. +// This file is part of Substrate. -// Polkadot is free software: you can redistribute it and/or modify +// Substrate is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// Polkadot is distributed in the hope that it will be useful, +// Substrate is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License -// along with Polkadot. If not, see . +// along with Substrate. If not, see . //! Relay chain runtime mock. diff --git a/substrate/frame/revive/mock-network/src/lib.rs b/substrate/frame/revive/mock-network/src/lib.rs index 2e4f273a010..84899465397 100644 --- a/substrate/frame/revive/mock-network/src/lib.rs +++ b/substrate/frame/revive/mock-network/src/lib.rs @@ -1,18 +1,18 @@ // Copyright Parity Technologies (UK) Ltd. -// This file is part of Polkadot. +// This file is part of Substrate. -// Polkadot is free software: you can redistribute it and/or modify +// Substrate is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// Polkadot is distributed in the hope that it will be useful, +// Substrate is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License -// along with Polkadot. If not, see . +// along with Substrate. If not, see . pub mod mocks; pub mod parachain; diff --git a/substrate/frame/revive/mock-network/src/mocks.rs b/substrate/frame/revive/mock-network/src/mocks.rs index bf3baec7a52..6903f01e91a 100644 --- a/substrate/frame/revive/mock-network/src/mocks.rs +++ b/substrate/frame/revive/mock-network/src/mocks.rs @@ -1,18 +1,18 @@ // Copyright Parity Technologies (UK) Ltd. -// This file is part of Polkadot. +// This file is part of Substrate. -// Polkadot is free software: you can redistribute it and/or modify +// Substrate is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// Polkadot is distributed in the hope that it will be useful, +// Substrate is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License -// along with Polkadot. If not, see . +// along with Substrate. If not, see . pub mod msg_queue; pub mod relay_message_queue; diff --git a/substrate/frame/revive/mock-network/src/mocks/msg_queue.rs b/substrate/frame/revive/mock-network/src/mocks/msg_queue.rs index 6e922c16c29..88ab8135ea1 100644 --- a/substrate/frame/revive/mock-network/src/mocks/msg_queue.rs +++ b/substrate/frame/revive/mock-network/src/mocks/msg_queue.rs @@ -1,18 +1,18 @@ // Copyright Parity Technologies (UK) Ltd. -// This file is part of Polkadot. +// This file is part of Substrate. -// Polkadot is free software: you can redistribute it and/or modify +// Substrate is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// Polkadot is distributed in the hope that it will be useful, +// Substrate is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License -// along with Polkadot. If not, see . +// along with Substrate. If not, see . //! Parachain runtime mock. diff --git a/substrate/frame/revive/mock-network/src/mocks/relay_message_queue.rs b/substrate/frame/revive/mock-network/src/mocks/relay_message_queue.rs index 14099965e3f..50f0febd2c4 100644 --- a/substrate/frame/revive/mock-network/src/mocks/relay_message_queue.rs +++ b/substrate/frame/revive/mock-network/src/mocks/relay_message_queue.rs @@ -1,18 +1,18 @@ // Copyright Parity Technologies (UK) Ltd. -// This file is part of Polkadot. +// This file is part of Substrate. -// Polkadot is free software: you can redistribute it and/or modify +// Substrate is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// Polkadot is distributed in the hope that it will be useful, +// Substrate is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License -// along with Polkadot. If not, see . +// along with Substrate. If not, see . use frame_support::{parameter_types, weights::Weight}; use xcm::latest::prelude::*; diff --git a/substrate/frame/revive/mock-network/src/parachain.rs b/substrate/frame/revive/mock-network/src/parachain.rs index 3def48cca96..0fd2248db57 100644 --- a/substrate/frame/revive/mock-network/src/parachain.rs +++ b/substrate/frame/revive/mock-network/src/parachain.rs @@ -1,18 +1,18 @@ // Copyright Parity Technologies (UK) Ltd. -// This file is part of Polkadot. +// This file is part of Substrate. -// Polkadot is free software: you can redistribute it and/or modify +// Substrate is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// Polkadot is distributed in the hope that it will be useful, +// Substrate is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License -// along with Polkadot. If not, see . +// along with Substrate. If not, see . //! Parachain runtime mock. diff --git a/substrate/frame/revive/mock-network/src/parachain/contracts_config.rs b/substrate/frame/revive/mock-network/src/parachain/contracts_config.rs index 678e7a44490..c13c337d166 100644 --- a/substrate/frame/revive/mock-network/src/parachain/contracts_config.rs +++ b/substrate/frame/revive/mock-network/src/parachain/contracts_config.rs @@ -1,18 +1,18 @@ // Copyright Parity Technologies (UK) Ltd. -// This file is part of Polkadot. +// This file is part of Substrate. -// Polkadot is free software: you can redistribute it and/or modify +// Substrate is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// Polkadot is distributed in the hope that it will be useful, +// Substrate is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License -// along with Polkadot. If not, see . +// along with Substrate. If not, see . use super::{Balances, Runtime, RuntimeCall, RuntimeEvent}; use crate::parachain::RuntimeHoldReason; diff --git a/substrate/frame/revive/mock-network/src/primitives.rs b/substrate/frame/revive/mock-network/src/primitives.rs index efc42772f88..cb9b2f00f9e 100644 --- a/substrate/frame/revive/mock-network/src/primitives.rs +++ b/substrate/frame/revive/mock-network/src/primitives.rs @@ -1,18 +1,18 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Polkadot. +// This file is part of Substrate. -// Polkadot is free software: you can redistribute it and/or modify +// Substrate is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// Polkadot is distributed in the hope that it will be useful, +// Substrate is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License -// along with Polkadot. If not, see . +// along with Substrate. If not, see . pub type Balance = u128; diff --git a/substrate/frame/revive/mock-network/src/relay_chain.rs b/substrate/frame/revive/mock-network/src/relay_chain.rs index 8829fff3d04..705578cde1d 100644 --- a/substrate/frame/revive/mock-network/src/relay_chain.rs +++ b/substrate/frame/revive/mock-network/src/relay_chain.rs @@ -1,18 +1,18 @@ // Copyright Parity Technologies (UK) Ltd. -// This file is part of Polkadot. +// This file is part of Substrate. -// Polkadot is free software: you can redistribute it and/or modify +// Substrate is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// Polkadot is distributed in the hope that it will be useful, +// Substrate is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License -// along with Polkadot. If not, see . +// along with Substrate. If not, see . //! Relay chain runtime mock. -- GitLab From 9307d99ab6959b1416f9a178c601f387a1f0e534 Mon Sep 17 00:00:00 2001 From: Javier Viola <363911+pepoviola@users.noreply.github.com> Date: Mon, 16 Sep 2024 15:58:52 -0300 Subject: [PATCH 067/125] add zombienet-sdk test to parachain-template (#5342) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add new `CI` machinery to smoke test the `parachain-template-node` using zombienet-sdk. Thx! --------- Co-authored-by: Przemek Rzad Co-authored-by: Shawn Tabrizi Co-authored-by: rzadp Co-authored-by: Bastian Köcher Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> --- .gitlab/pipeline/build.yml | 17 + .gitlab/pipeline/zombienet.yml | 2 + .../pipeline/zombienet/parachain-template.yml | 42 + Cargo.lock | 4570 ++++++++++++----- Cargo.toml | 1 + templates/zombienet/Cargo.toml | 20 + templates/zombienet/tests/smoke.rs | 96 + 7 files changed, 3320 insertions(+), 1428 deletions(-) create mode 100644 .gitlab/pipeline/zombienet/parachain-template.yml create mode 100644 templates/zombienet/Cargo.toml create mode 100644 templates/zombienet/tests/smoke.rs diff --git a/.gitlab/pipeline/build.yml b/.gitlab/pipeline/build.yml index a5de2173a71..74b6ccb4998 100644 --- a/.gitlab/pipeline/build.yml +++ b/.gitlab/pipeline/build.yml @@ -83,6 +83,23 @@ build-malus: - echo "polkadot-test-malus = $(cat ./artifacts/VERSION) (EXTRATAG = $(cat ./artifacts/EXTRATAG))" - cp -r ./docker/* ./artifacts +build-templates-node: + stage: build + extends: + - .docker-env + - .common-refs + - .run-immediately + - .collect-artifacts + script: + - time cargo build --locked --package parachain-template-node --release + - time cargo build --locked --package minimal-template-node --release + - time cargo build --locked --package solochain-template-node --release + # pack artifacts + - mkdir -p ./artifacts + - mv ./target/release/parachain-template-node ./artifacts/. + - mv ./target/release/minimal-template-node ./artifacts/. + - mv ./target/release/solochain-template-node ./artifacts/. + build-rustdoc: stage: build extends: diff --git a/.gitlab/pipeline/zombienet.yml b/.gitlab/pipeline/zombienet.yml index 7897e55e291..23521b299b1 100644 --- a/.gitlab/pipeline/zombienet.yml +++ b/.gitlab/pipeline/zombienet.yml @@ -14,3 +14,5 @@ include: - .gitlab/pipeline/zombienet/polkadot.yml # bridges tests - .gitlab/pipeline/zombienet/bridges.yml + # parachain-template-node tests + - .gitlab/pipeline/zombienet/parachain-template.yml diff --git a/.gitlab/pipeline/zombienet/parachain-template.yml b/.gitlab/pipeline/zombienet/parachain-template.yml new file mode 100644 index 00000000000..815a46c60d7 --- /dev/null +++ b/.gitlab/pipeline/zombienet/parachain-template.yml @@ -0,0 +1,42 @@ +# common settings for all zombienet jobs +.zombienet-parachain-template-common: + before_script: + # add `./artifacts` to the PATH + - export PATH=$(pwd)/artifacts:$PATH + stage: zombienet + needs: + - job: build-linux-stable # polkadot binaries + artifacts: true + - job: build-templates-node # templates + artifacts: true + extends: + - .docker-env + - .zombienet-refs + variables: + ZOMBIE_PROVIDER: "native" + RUST_LOG: "info,zombienet_orchestrator=debug" + FF_DISABLE_UMASK_FOR_DOCKER_EXECUTOR: 1 + RUN_IN_CONTAINER: "1" + artifacts: + name: "${CI_JOB_NAME}_${CI_COMMIT_REF_NAME}" + when: always + expire_in: 2 days + paths: + - ./zombienet-logs + after_script: + - mkdir -p ./zombienet-logs + - cp /tmp/zombie*/logs/* ./zombienet-logs/ + retry: 2 + timeout: 15m + tags: + - linux-docker + +zombienet-parachain-template-smoke: + extends: + - .zombienet-parachain-template-common + script: + - echo $PATH + - ls -ltr $(pwd)/artifacts + - cargo test -p template-zombienet-tests --features zombienet --tests minimal_template_block_production_test + - cargo test -p template-zombienet-tests --features zombienet --tests parachain_template_block_production_test + # - cargo test -p template-zombienet-tests --features zombienet --tests solochain_template_block_production_test diff --git a/Cargo.lock b/Cargo.lock index 1478a543d14..7d894af7b7f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -655,7 +655,7 @@ dependencies = [ "ark-serialize 0.4.2", "ark-std 0.4.0", "digest 0.10.7", - "rand_core", + "rand_core 0.6.4", "sha3", ] @@ -796,7 +796,7 @@ dependencies = [ "frame-support", "parachains-common", "rococo-emulated-chain", - "sp-core", + "sp-core 28.0.0", "staging-xcm", "testnet-parachains-constants", ] @@ -822,7 +822,7 @@ dependencies = [ "polkadot-runtime-common", "rococo-runtime-constants", "rococo-system-emulated-network", - "sp-runtime", + "sp-runtime 31.0.1", "staging-xcm", "staging-xcm-executor", "xcm-runtime-apis", @@ -893,16 +893,16 @@ dependencies = [ "sp-api", "sp-block-builder", "sp-consensus-aura", - "sp-core", + "sp-core 28.0.0", "sp-genesis-builder", "sp-inherents", "sp-offchain", - "sp-runtime", + "sp-runtime 31.0.1", "sp-session", "sp-storage 19.0.0", "sp-transaction-pool", "sp-version", - "sp-weights", + "sp-weights 27.0.0", "staging-parachain-info", "staging-xcm", "staging-xcm-builder", @@ -922,7 +922,7 @@ dependencies = [ "emulated-integration-tests-common", "frame-support", "parachains-common", - "sp-core", + "sp-core 28.0.0", "staging-xcm", "testnet-parachains-constants", "westend-emulated-chain", @@ -951,9 +951,9 @@ dependencies = [ "parachains-common", "parity-scale-codec", "polkadot-runtime-common", - "sp-core", + "sp-core 28.0.0", "sp-keyring", - "sp-runtime", + "sp-runtime 31.0.1", "staging-xcm", "staging-xcm-executor", "westend-system-emulated-network", @@ -1024,11 +1024,11 @@ dependencies = [ "sp-api", "sp-block-builder", "sp-consensus-aura", - "sp-core", + "sp-core 28.0.0", "sp-genesis-builder", "sp-inherents", "sp-offchain", - "sp-runtime", + "sp-runtime 31.0.1", "sp-session", "sp-std 14.0.0", "sp-storage 19.0.0", @@ -1064,8 +1064,8 @@ dependencies = [ "parachains-common", "parachains-runtimes-test-utils", "parity-scale-codec", - "sp-io", - "sp-runtime", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "staging-parachain-info", "staging-xcm", "staging-xcm-builder", @@ -1088,7 +1088,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-api", - "sp-runtime", + "sp-runtime 31.0.1", "staging-xcm", "staging-xcm-builder", "staging-xcm-executor", @@ -1116,6 +1116,19 @@ dependencies = [ "futures-core", ] +[[package]] +name = "async-channel" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f2776ead772134d55b62dd45e59a79e21612d85d0af729b8b7d3967d601a62a" +dependencies = [ + "concurrent-queue", + "event-listener 5.2.0", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + [[package]] name = "async-executor" version = "1.5.1" @@ -1142,13 +1155,24 @@ dependencies = [ "futures-lite 1.13.0", ] +[[package]] +name = "async-fs" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebcd09b382f40fcd159c2d695175b2ae620ffa5f3bd6f664131efff4e8b9e04a" +dependencies = [ + "async-lock 3.4.0", + "blocking", + "futures-lite 2.0.0", +] + [[package]] name = "async-global-executor" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1b6f5d7df27bd294849f8eec66ecfc63d11814df7a4f5d74168a2394467b776" dependencies = [ - "async-channel", + "async-channel 1.9.0", "async-executor", "async-io 1.13.0", "async-lock 2.8.0", @@ -1187,7 +1211,7 @@ dependencies = [ "cfg-if", "concurrent-queue", "futures-io", - "futures-lite 2.3.0", + "futures-lite 2.0.0", "parking", "polling 3.4.0", "rustix 0.38.21", @@ -1228,6 +1252,17 @@ dependencies = [ "futures-lite 1.13.0", ] +[[package]] +name = "async-net" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7" +dependencies = [ + "async-io 2.3.3", + "blocking", + "futures-lite 2.0.0", +] + [[package]] name = "async-process" version = "1.7.0" @@ -1246,6 +1281,43 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "async-process" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63255f1dc2381611000436537bbedfe83183faa303a5a0edaf191edef06526bb" +dependencies = [ + "async-channel 2.3.0", + "async-io 2.3.3", + "async-lock 3.4.0", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener 5.2.0", + "futures-lite 2.0.0", + "rustix 0.38.21", + "tracing", +] + +[[package]] +name = "async-signal" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfb3634b73397aa844481f814fad23bbf07fdb0eabec10f2eb95e58944b1ec32" +dependencies = [ + "async-io 2.3.3", + "async-lock 3.4.0", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix 0.38.21", + "signal-hook-registry", + "slab", + "windows-sys 0.52.0", +] + [[package]] name = "async-std" version = "1.12.0" @@ -1253,7 +1325,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" dependencies = [ "async-attributes", - "async-channel", + "async-channel 1.9.0", "async-global-executor", "async-io 1.13.0", "async-lock 2.8.0", @@ -1297,9 +1369,9 @@ dependencies = [ [[package]] name = "async-task" -version = "4.4.0" +version = "4.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc7ab41815b3c653ccd2978ec3255c81349336702dfdf62ee6f7069b12a3aae" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" @@ -1416,7 +1488,7 @@ dependencies = [ "ark-std 0.4.0", "dleq_vrf", "rand_chacha", - "rand_core", + "rand_core 0.6.4", "ring 0.1.0", "sha2 0.10.8", "sp-ark-bls12-381", @@ -1436,6 +1508,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" +[[package]] +name = "base58" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6107fe1be6682a68940da878d9e9f5e90ca5745b3dec9fd1bb393c8777d4f581" + [[package]] name = "base64" version = "0.13.1" @@ -1444,9 +1522,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.2" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "base64" @@ -1469,6 +1547,15 @@ dependencies = [ "serde", ] +[[package]] +name = "beef" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" +dependencies = [ + "serde", +] + [[package]] name = "binary-merkle-tree" version = "13.0.0" @@ -1477,8 +1564,8 @@ dependencies = [ "hash-db", "log", "parity-scale-codec", - "sp-core", - "sp-runtime", + "sp-core 28.0.0", + "sp-runtime 31.0.1", "sp-tracing 16.0.0", ] @@ -1675,7 +1762,7 @@ version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77231a1c8f801696fc0123ec6150ce92cffb8e164a02afb9c8ddee0e9b65ad65" dependencies = [ - "async-channel", + "async-channel 1.9.0", "async-lock 2.8.0", "async-task", "atomic-waker", @@ -1739,7 +1826,7 @@ dependencies = [ "scale-info", "serde", "sp-consensus-beefy", - "sp-runtime", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -1766,7 +1853,7 @@ dependencies = [ "bp-runtime", "frame-support", "sp-api", - "sp-runtime", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -1779,7 +1866,7 @@ dependencies = [ "bp-runtime", "frame-support", "sp-api", - "sp-runtime", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -1794,7 +1881,7 @@ dependencies = [ "frame-support", "parity-scale-codec", "sp-api", - "sp-runtime", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -1809,7 +1896,7 @@ dependencies = [ "frame-support", "parity-scale-codec", "sp-api", - "sp-runtime", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -1827,8 +1914,8 @@ dependencies = [ "scale-info", "serde", "sp-consensus-grandpa", - "sp-core", - "sp-runtime", + "sp-core 28.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -1856,8 +1943,8 @@ dependencies = [ "parity-scale-codec", "scale-info", "serde", - "sp-core", - "sp-io", + "sp-core 28.0.0", + "sp-io 30.0.0", "sp-std 14.0.0", ] @@ -1872,8 +1959,8 @@ dependencies = [ "impl-trait-for-tuples", "parity-scale-codec", "scale-info", - "sp-core", - "sp-runtime", + "sp-core 28.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -1902,7 +1989,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-api", - "sp-runtime", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -1919,8 +2006,8 @@ dependencies = [ "parity-util-mem", "scale-info", "serde", - "sp-core", - "sp-runtime", + "sp-core 28.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -1939,7 +2026,7 @@ dependencies = [ "pallet-utility", "parity-scale-codec", "scale-info", - "sp-runtime", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -1969,13 +2056,13 @@ dependencies = [ "parity-scale-codec", "scale-info", "serde", - "sp-core", - "sp-io", - "sp-runtime", - "sp-state-machine", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", + "sp-state-machine 0.35.0", "sp-std 14.0.0", - "sp-trie", - "trie-db", + "sp-trie 29.0.0", + "trie-db 0.29.1", ] [[package]] @@ -1989,12 +2076,12 @@ dependencies = [ "ed25519-dalek", "finality-grandpa", "parity-scale-codec", - "sp-application-crypto", + "sp-application-crypto 30.0.0", "sp-consensus-grandpa", - "sp-core", - "sp-runtime", + "sp-core 28.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", - "sp-trie", + "sp-trie 29.0.0", ] [[package]] @@ -2019,8 +2106,8 @@ dependencies = [ "parity-scale-codec", "scale-info", "serde", - "sp-core", - "sp-io", + "sp-core 28.0.0", + "sp-io 30.0.0", "sp-std 14.0.0", "staging-xcm", ] @@ -2031,8 +2118,8 @@ version = "0.6.0" dependencies = [ "parity-scale-codec", "scale-info", - "sp-core", - "sp-runtime", + "sp-core 28.0.0", + "sp-runtime 31.0.1", "staging-xcm", ] @@ -2046,8 +2133,8 @@ dependencies = [ "parity-scale-codec", "scale-info", "snowbridge-core", - "sp-core", - "sp-runtime", + "sp-core 28.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", "staging-xcm", ] @@ -2061,7 +2148,7 @@ dependencies = [ "emulated-integration-tests-common", "frame-support", "parachains-common", - "sp-core", + "sp-core 28.0.0", "testnet-parachains-constants", ] @@ -2090,8 +2177,8 @@ dependencies = [ "snowbridge-pallet-outbound-queue", "snowbridge-pallet-system", "snowbridge-router-primitives", - "sp-core", - "sp-runtime", + "sp-core 28.0.0", + "sp-runtime 31.0.1", "staging-xcm", "staging-xcm-executor", "testnet-parachains-constants", @@ -2176,13 +2263,13 @@ dependencies = [ "sp-api", "sp-block-builder", "sp-consensus-aura", - "sp-core", + "sp-core 28.0.0", "sp-genesis-builder", "sp-inherents", - "sp-io", + "sp-io 30.0.0", "sp-keyring", "sp-offchain", - "sp-runtime", + "sp-runtime 31.0.1", "sp-session", "sp-std 14.0.0", "sp-storage 19.0.0", @@ -2228,10 +2315,10 @@ dependencies = [ "parachains-common", "parachains-runtimes-test-utils", "parity-scale-codec", - "sp-core", - "sp-io", + "sp-core 28.0.0", + "sp-io 30.0.0", "sp-keyring", - "sp-runtime", + "sp-runtime 31.0.1", "sp-tracing 16.0.0", "staging-xcm", "staging-xcm-builder", @@ -2247,7 +2334,7 @@ dependencies = [ "emulated-integration-tests-common", "frame-support", "parachains-common", - "sp-core", + "sp-core 28.0.0", "testnet-parachains-constants", ] @@ -2279,8 +2366,8 @@ dependencies = [ "snowbridge-pallet-outbound-queue", "snowbridge-pallet-system", "snowbridge-router-primitives", - "sp-core", - "sp-runtime", + "sp-core 28.0.0", + "sp-runtime 31.0.1", "staging-xcm", "staging-xcm-executor", "testnet-parachains-constants", @@ -2362,13 +2449,13 @@ dependencies = [ "sp-api", "sp-block-builder", "sp-consensus-aura", - "sp-core", + "sp-core 28.0.0", "sp-genesis-builder", "sp-inherents", - "sp-io", + "sp-io 30.0.0", "sp-keyring", "sp-offchain", - "sp-runtime", + "sp-runtime 31.0.1", "sp-session", "sp-std 14.0.0", "sp-storage 19.0.0", @@ -2408,11 +2495,11 @@ dependencies = [ "pallet-utility", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", - "sp-trie", + "sp-trie 29.0.0", "staging-xcm", "static_assertions", "tuplex", @@ -2473,9 +2560,9 @@ checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea" [[package]] name = "byteorder" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" @@ -2594,6 +2681,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "chacha" version = "0.3.0" @@ -2644,11 +2737,11 @@ dependencies = [ "scale-info", "serde", "serde_json", - "sp-application-crypto", - "sp-core", + "sp-application-crypto 30.0.0", + "sp-core 28.0.0", "sp-genesis-builder", "sp-keyring", - "sp-runtime", + "sp-runtime 31.0.1", "staging-chain-spec-builder", "substrate-wasm-builder", ] @@ -2663,6 +2756,7 @@ dependencies = [ "iana-time-zone", "js-sys", "num-traits", + "serde", "wasm-bindgen", "windows-targets 0.48.5", ] @@ -2820,7 +2914,7 @@ dependencies = [ "anstream", "anstyle", "clap_lex 0.7.0", - "strsim 0.11.0", + "strsim 0.11.1", "terminal_size", ] @@ -2904,7 +2998,7 @@ dependencies = [ "emulated-integration-tests-common", "frame-support", "parachains-common", - "sp-core", + "sp-core 28.0.0", "testnet-parachains-constants", ] @@ -2928,7 +3022,7 @@ dependencies = [ "parachains-common", "parity-scale-codec", "polkadot-runtime-common", - "sp-runtime", + "sp-runtime 31.0.1", "staging-xcm", "staging-xcm-executor", "testnet-parachains-constants", @@ -2989,15 +3083,15 @@ dependencies = [ "polkadot-runtime-common", "scale-info", "sp-api", - "sp-arithmetic", + "sp-arithmetic 23.0.0", "sp-block-builder", "sp-consensus-aura", - "sp-core", + "sp-core 28.0.0", "sp-genesis-builder", "sp-inherents", - "sp-io", + "sp-io 30.0.0", "sp-offchain", - "sp-runtime", + "sp-runtime 31.0.1", "sp-session", "sp-storage 19.0.0", "sp-transaction-pool", @@ -3249,11 +3343,11 @@ dependencies = [ "sp-api", "sp-block-builder", "sp-consensus-aura", - "sp-core", + "sp-core 28.0.0", "sp-genesis-builder", "sp-inherents", "sp-offchain", - "sp-runtime", + "sp-runtime 31.0.1", "sp-session", "sp-storage 19.0.0", "sp-transaction-pool", @@ -3307,7 +3401,7 @@ dependencies = [ "emulated-integration-tests-common", "frame-support", "parachains-common", - "sp-core", + "sp-core 28.0.0", "testnet-parachains-constants", ] @@ -3326,7 +3420,7 @@ dependencies = [ "polkadot-runtime-parachains", "rococo-runtime-constants", "rococo-system-emulated-network", - "sp-runtime", + "sp-runtime 31.0.1", "staging-xcm", "staging-xcm-executor", ] @@ -3380,11 +3474,11 @@ dependencies = [ "sp-api", "sp-block-builder", "sp-consensus-aura", - "sp-core", + "sp-core 28.0.0", "sp-genesis-builder", "sp-inherents", "sp-offchain", - "sp-runtime", + "sp-runtime 31.0.1", "sp-session", "sp-storage 19.0.0", "sp-transaction-pool", @@ -3407,7 +3501,7 @@ dependencies = [ "emulated-integration-tests-common", "frame-support", "parachains-common", - "sp-core", + "sp-core 28.0.0", "testnet-parachains-constants", ] @@ -3424,7 +3518,7 @@ dependencies = [ "pallet-message-queue", "polkadot-runtime-common", "polkadot-runtime-parachains", - "sp-runtime", + "sp-runtime 31.0.1", "staging-xcm", "staging-xcm-executor", "westend-runtime-constants", @@ -3478,11 +3572,11 @@ dependencies = [ "sp-api", "sp-block-builder", "sp-consensus-aura", - "sp-core", + "sp-core 28.0.0", "sp-genesis-builder", "sp-inherents", "sp-offchain", - "sp-runtime", + "sp-runtime 31.0.1", "sp-session", "sp-storage 19.0.0", "sp-transaction-pool", @@ -3735,7 +3829,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf4c2f4e1afd912bc40bfd6fed5d9dc1f288e0ba01bfcc835cc5bc3eb13efe15" dependencies = [ "generic-array 0.14.7", - "rand_core", + "rand_core 0.6.4", "subtle 2.5.0", "zeroize", ] @@ -3747,7 +3841,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array 0.14.7", - "rand_core", + "rand_core 0.6.4", "typenum", ] @@ -3791,8 +3885,8 @@ dependencies = [ "sc-client-api", "sc-service", "sp-blockchain", - "sp-core", - "sp-runtime", + "sp-core 28.0.0", + "sp-runtime 31.0.1", "url", ] @@ -3818,10 +3912,10 @@ dependencies = [ "sc-client-api", "sp-api", "sp-consensus", - "sp-core", + "sp-core 28.0.0", "sp-maybe-compressed-blob", - "sp-runtime", - "sp-state-machine", + "sp-runtime 31.0.1", + "sp-state-machine 0.35.0", "sp-tracing 16.0.0", "tracing", ] @@ -3854,16 +3948,16 @@ dependencies = [ "sc-utils", "schnellru", "sp-api", - "sp-application-crypto", + "sp-application-crypto 30.0.0", "sp-block-builder", "sp-blockchain", "sp-consensus", "sp-consensus-aura", - "sp-core", + "sp-core 28.0.0", "sp-inherents", - "sp-keystore", - "sp-runtime", - "sp-state-machine", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", + "sp-state-machine 0.35.0", "sp-timestamp", "substrate-prometheus-endpoint", "tokio", @@ -3893,11 +3987,11 @@ dependencies = [ "sp-blockchain", "sp-consensus", "sp-consensus-slots", - "sp-core", - "sp-runtime", + "sp-core 28.0.0", + "sp-runtime 31.0.1", "sp-timestamp", "sp-tracing 16.0.0", - "sp-trie", + "sp-trie 29.0.0", "sp-version", "substrate-prometheus-endpoint", "tracing", @@ -3912,8 +4006,8 @@ dependencies = [ "cumulus-primitives-parachain-inherent", "sp-consensus", "sp-inherents", - "sp-runtime", - "sp-state-machine", + "sp-runtime 31.0.1", + "sp-state-machine 0.35.0", "thiserror", ] @@ -3932,9 +4026,9 @@ dependencies = [ "sp-block-builder", "sp-blockchain", "sp-consensus", - "sp-core", + "sp-core 28.0.0", "sp-inherents", - "sp-runtime", + "sp-runtime 31.0.1", "substrate-prometheus-endpoint", "tracing", ] @@ -3964,11 +4058,11 @@ dependencies = [ "sp-api", "sp-blockchain", "sp-consensus", - "sp-core", + "sp-core 28.0.0", "sp-keyring", - "sp-keystore", - "sp-runtime", - "sp-state-machine", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", + "sp-state-machine 0.35.0", "sp-version", "substrate-test-utils", "tokio", @@ -3988,12 +4082,12 @@ dependencies = [ "parity-scale-codec", "sc-client-api", "sp-api", - "sp-crypto-hashing", + "sp-crypto-hashing 0.1.0", "sp-inherents", - "sp-runtime", - "sp-state-machine", + "sp-runtime 31.0.1", + "sp-state-machine 0.35.0", "sp-storage 19.0.0", - "sp-trie", + "sp-trie 29.0.0", "tracing", ] @@ -4025,7 +4119,7 @@ dependencies = [ "sp-blockchain", "sp-consensus", "sp-maybe-compressed-blob", - "sp-runtime", + "sp-runtime 31.0.1", "sp-tracing 16.0.0", "sp-version", "substrate-test-utils", @@ -4063,9 +4157,9 @@ dependencies = [ "sp-api", "sp-blockchain", "sp-consensus", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-transaction-pool", ] @@ -4080,9 +4174,9 @@ dependencies = [ "pallet-timestamp", "parity-scale-codec", "scale-info", - "sp-application-crypto", + "sp-application-crypto 30.0.0", "sp-consensus-aura", - "sp-runtime", + "sp-runtime 31.0.1", ] [[package]] @@ -4096,9 +4190,9 @@ dependencies = [ "log", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-tracing 16.0.0", "staging-xcm", ] @@ -4134,21 +4228,21 @@ dependencies = [ "sc-client-api", "scale-info", "sp-consensus-slots", - "sp-core", - "sp-crypto-hashing", + "sp-core 28.0.0", + "sp-crypto-hashing 0.1.0", "sp-externalities 0.25.0", "sp-inherents", - "sp-io", + "sp-io 30.0.0", "sp-keyring", - "sp-runtime", - "sp-state-machine", + "sp-runtime 31.0.1", + "sp-state-machine 0.35.0", "sp-std 14.0.0", "sp-tracing 16.0.0", - "sp-trie", + "sp-trie 29.0.0", "sp-version", "staging-xcm", "staging-xcm-builder", - "trie-db", + "trie-db 0.29.1", "trie-standardmap", ] @@ -4171,7 +4265,7 @@ dependencies = [ "frame-system", "pallet-session", "parity-scale-codec", - "sp-runtime", + "sp-runtime 31.0.1", ] [[package]] @@ -4185,7 +4279,7 @@ dependencies = [ "parity-scale-codec", "polkadot-primitives", "scale-info", - "sp-runtime", + "sp-runtime 31.0.1", ] [[package]] @@ -4197,8 +4291,8 @@ dependencies = [ "frame-system", "parity-scale-codec", "scale-info", - "sp-io", - "sp-runtime", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "staging-xcm", ] @@ -4220,9 +4314,9 @@ dependencies = [ "polkadot-runtime-common", "polkadot-runtime-parachains", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "staging-xcm", "staging-xcm-builder", "staging-xcm-executor", @@ -4238,7 +4332,7 @@ dependencies = [ "frame-system", "parity-scale-codec", "scale-info", - "sp-runtime", + "sp-runtime 31.0.1", "staging-xcm", ] @@ -4253,8 +4347,8 @@ dependencies = [ "polkadot-parachain-primitives", "polkadot-primitives", "sc-executor", - "sp-core", - "sp-io", + "sp-core 28.0.0", + "sp-io 30.0.0", "sp-maybe-compressed-blob", "tracing", "tracing-subscriber 0.3.18", @@ -4278,8 +4372,8 @@ dependencies = [ "polkadot-primitives", "scale-info", "sp-api", - "sp-runtime", - "sp-trie", + "sp-runtime 31.0.1", + "sp-trie 29.0.0", "staging-xcm", ] @@ -4291,21 +4385,21 @@ dependencies = [ "cumulus-primitives-core", "parity-scale-codec", "scale-info", - "sp-core", + "sp-core 28.0.0", "sp-inherents", - "sp-trie", + "sp-trie 29.0.0", ] [[package]] name = "cumulus-primitives-proof-size-hostfunction" version = "0.2.0" dependencies = [ - "sp-core", + "sp-core 28.0.0", "sp-externalities 0.25.0", - "sp-io", + "sp-io 30.0.0", "sp-runtime-interface 24.0.0", - "sp-state-machine", - "sp-trie", + "sp-state-machine 0.35.0", + "sp-trie 29.0.0", ] [[package]] @@ -4321,9 +4415,9 @@ dependencies = [ "log", "parity-scale-codec", "scale-info", - "sp-io", - "sp-runtime", - "sp-trie", + "sp-io 30.0.0", + "sp-runtime 31.0.1", + "sp-trie 29.0.0", ] [[package]] @@ -4345,7 +4439,7 @@ dependencies = [ "pallet-asset-conversion", "parity-scale-codec", "polkadot-runtime-common", - "sp-runtime", + "sp-runtime 31.0.1", "staging-xcm", "staging-xcm-builder", "staging-xcm-executor", @@ -4373,10 +4467,10 @@ dependencies = [ "sc-tracing", "sp-api", "sp-consensus", - "sp-core", + "sp-core 28.0.0", "sp-keyring", - "sp-runtime", - "sp-state-machine", + "sp-runtime 31.0.1", + "sp-state-machine 0.35.0", ] [[package]] @@ -4386,13 +4480,13 @@ dependencies = [ "async-trait", "cumulus-primitives-core", "futures", - "jsonrpsee-core", + "jsonrpsee-core 0.24.3", "parity-scale-codec", "polkadot-overseer", "sc-client-api", "sp-api", "sp-blockchain", - "sp-state-machine", + "sp-state-machine 0.35.0", "sp-version", "thiserror", ] @@ -4425,7 +4519,7 @@ dependencies = [ "sp-blockchain", "sp-consensus", "sp-consensus-babe", - "sp-runtime", + "sp-runtime 31.0.1", "substrate-prometheus-endpoint", "tokio", "tracing", @@ -4441,7 +4535,7 @@ dependencies = [ "either", "futures", "futures-timer", - "jsonrpsee", + "jsonrpsee 0.24.3", "parity-scale-codec", "pin-project", "polkadot-overseer", @@ -4453,14 +4547,14 @@ dependencies = [ "schnellru", "serde", "serde_json", - "smoldot", - "smoldot-light", + "smoldot 0.11.0", + "smoldot-light 0.9.0", "sp-api", "sp-authority-discovery", "sp-consensus-babe", - "sp-core", - "sp-runtime", - "sp-state-machine", + "sp-core 28.0.0", + "sp-runtime 31.0.1", + "sp-state-machine 0.35.0", "sp-storage 19.0.0", "sp-version", "thiserror", @@ -4494,15 +4588,15 @@ dependencies = [ "sc-executor-common", "sc-service", "sp-api", - "sp-application-crypto", + "sp-application-crypto 30.0.0", "sp-blockchain", "sp-consensus-aura", - "sp-core", + "sp-core 28.0.0", "sp-inherents", - "sp-io", + "sp-io 30.0.0", "sp-keyring", - "sp-keystore", - "sp-runtime", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", "sp-timestamp", "substrate-test-client", ] @@ -4514,9 +4608,9 @@ dependencies = [ "cumulus-primitives-core", "parity-scale-codec", "polkadot-primitives", - "sp-runtime", - "sp-state-machine", - "sp-trie", + "sp-runtime 31.0.1", + "sp-state-machine 0.35.0", + "sp-trie 29.0.0", ] [[package]] @@ -4547,12 +4641,12 @@ dependencies = [ "sp-api", "sp-block-builder", "sp-consensus-aura", - "sp-core", + "sp-core 28.0.0", "sp-genesis-builder", "sp-inherents", - "sp-io", + "sp-io 30.0.0", "sp-offchain", - "sp-runtime", + "sp-runtime 31.0.1", "sp-session", "sp-transaction-pool", "sp-version", @@ -4588,7 +4682,7 @@ dependencies = [ "frame-system", "frame-system-rpc-runtime-api", "futures", - "jsonrpsee", + "jsonrpsee 0.24.3", "pallet-timestamp", "pallet-transaction-payment", "parachains-common", @@ -4620,16 +4714,16 @@ dependencies = [ "serde", "serde_json", "sp-api", - "sp-arithmetic", + "sp-arithmetic 23.0.0", "sp-authority-discovery", "sp-blockchain", "sp-consensus", "sp-consensus-aura", - "sp-core", - "sp-io", + "sp-core 28.0.0", + "sp-io 30.0.0", "sp-keyring", - "sp-runtime", - "sp-state-machine", + "sp-runtime 31.0.1", + "sp-state-machine 0.35.0", "sp-timestamp", "sp-tracing 16.0.0", "substrate-test-client", @@ -4671,6 +4765,19 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "curve25519-dalek" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core 0.5.1", + "subtle 2.5.0", + "zeroize", +] + [[package]] name = "curve25519-dalek" version = "4.1.3" @@ -4706,7 +4813,7 @@ checksum = "1c359b7249347e46fb28804470d071c921156ad62b3eef5d34e2ba867533dec8" dependencies = [ "byteorder", "digest 0.9.0", - "rand_core", + "rand_core 0.6.4", "subtle-ng", "zeroize", ] @@ -4755,6 +4862,76 @@ dependencies = [ "syn 2.0.65", ] +[[package]] +name = "darling" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" +dependencies = [ + "darling_core 0.14.4", + "darling_macro 0.14.4", +] + +[[package]] +name = "darling" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +dependencies = [ + "darling_core 0.20.10", + "darling_macro 0.20.10", +] + +[[package]] +name = "darling_core" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2 1.0.86", + "quote 1.0.37", + "strsim 0.10.0", + "syn 1.0.109", +] + +[[package]] +name = "darling_core" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2 1.0.86", + "quote 1.0.37", + "strsim 0.11.1", + "syn 2.0.65", +] + +[[package]] +name = "darling_macro" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" +dependencies = [ + "darling_core 0.14.4", + "quote 1.0.37", + "syn 1.0.109", +] + +[[package]] +name = "darling_macro" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "darling_core 0.20.10", + "quote 1.0.37", + "syn 2.0.65", +] + [[package]] name = "dashmap" version = "5.5.1" @@ -4762,7 +4939,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edd72493923899c6f10c641bdbdeddc7183d6396641d99c1a0d1597f37f92e28" dependencies = [ "cfg-if", - "hashbrown 0.14.3", + "hashbrown 0.14.5", "lock_api", "once_cell", "parking_lot_core 0.9.8", @@ -4872,6 +5049,17 @@ dependencies = [ "syn 2.0.65", ] +[[package]] +name = "derive-where" +version = "1.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62d671cc41a825ebabc75757b62d3d168c577f9149b2d49ece1dad1f72119d25" +dependencies = [ + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.65", +] + [[package]] name = "derive_arbitrary" version = "1.3.2" @@ -5137,35 +5325,49 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" dependencies = [ - "curve25519-dalek", + "curve25519-dalek 4.1.3", "ed25519", - "rand_core", + "rand_core 0.6.4", "serde", "sha2 0.10.8", "subtle 2.5.0", "zeroize", ] +[[package]] +name = "ed25519-zebra" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c24f403d068ad0b359e577a77f92392118be3f3c927538f2bb544a5ecd828c6" +dependencies = [ + "curve25519-dalek 3.2.0", + "hashbrown 0.12.3", + "hex", + "rand_core 0.6.4", + "sha2 0.9.9", + "zeroize", +] + [[package]] name = "ed25519-zebra" version = "4.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d9ce6874da5d4415896cd45ffbc4d1cfc0c4f9c079427bd870742c30f2f65a9" dependencies = [ - "curve25519-dalek", + "curve25519-dalek 4.1.3", "ed25519", - "hashbrown 0.14.3", + "hashbrown 0.14.5", "hex", - "rand_core", + "rand_core 0.6.4", "sha2 0.10.8", "zeroize", ] [[package]] name = "either" -version = "1.9.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "elliptic-curve" @@ -5180,7 +5382,7 @@ dependencies = [ "generic-array 0.14.7", "group", "pkcs8", - "rand_core", + "rand_core 0.6.4", "sec1", "serdect", "subtle 2.5.0", @@ -5215,8 +5417,8 @@ dependencies = [ "sp-authority-discovery", "sp-consensus-babe", "sp-consensus-beefy", - "sp-core", - "sp-runtime", + "sp-core 28.0.0", + "sp-runtime 31.0.1", "staging-xcm", "xcm-emulator", ] @@ -5298,6 +5500,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" dependencies = [ "log", + "regex", ] [[package]] @@ -5332,6 +5535,7 @@ dependencies = [ "anstream", "anstyle", "env_filter", + "humantime", "log", ] @@ -5449,6 +5653,16 @@ version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +[[package]] +name = "event-listener" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e" +dependencies = [ + "concurrent-queue", + "pin-project-lite", +] + [[package]] name = "event-listener" version = "5.2.0" @@ -5598,7 +5812,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" dependencies = [ - "rand_core", + "rand_core 0.6.4", "subtle 2.5.0", ] @@ -5697,6 +5911,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "finito" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2384245d85162258a14b43567a9ee3598f5ae746a1581fb5d3d2cb780f0dbf95" +dependencies = [ + "futures-timer", + "pin-project", +] + [[package]] name = "fixed-hash" version = "0.8.0" @@ -5740,6 +5964,21 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "fork-tree" version = "12.0.0" @@ -5798,11 +6037,11 @@ dependencies = [ "scale-info", "serde", "sp-api", - "sp-application-crypto", - "sp-core", - "sp-io", - "sp-keystore", - "sp-runtime", + "sp-application-crypto 30.0.0", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", "sp-runtime-interface 24.0.0", "sp-storage 19.0.0", "static_assertions", @@ -5841,17 +6080,17 @@ dependencies = [ "serde_json", "sp-api", "sp-blockchain", - "sp-core", + "sp-core 28.0.0", "sp-database", "sp-externalities 0.25.0", "sp-genesis-builder", "sp-inherents", - "sp-io", - "sp-keystore", - "sp-runtime", - "sp-state-machine", + "sp-io 30.0.0", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", + "sp-state-machine 0.35.0", "sp-storage 19.0.0", - "sp-trie", + "sp-trie 29.0.0", "sp-wasm-interface 20.0.0", "thiserror", "thousands", @@ -5866,8 +6105,8 @@ dependencies = [ "frame-system", "parity-scale-codec", "scale-info", - "sp-io", - "sp-runtime", + "sp-io 30.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -5881,7 +6120,7 @@ dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", "scale-info", - "sp-arithmetic", + "sp-arithmetic 23.0.0", "syn 2.0.65", "trybuild", ] @@ -5896,11 +6135,11 @@ dependencies = [ "parity-scale-codec", "rand", "scale-info", - "sp-arithmetic", - "sp-core", - "sp-io", + "sp-arithmetic 23.0.0", + "sp-core 28.0.0", + "sp-io 30.0.0", "sp-npos-elections", - "sp-runtime", + "sp-runtime 31.0.1", ] [[package]] @@ -5915,9 +6154,9 @@ dependencies = [ "parity-scale-codec", "rand", "scale-info", - "sp-arithmetic", + "sp-arithmetic 23.0.0", "sp-npos-elections", - "sp-runtime", + "sp-runtime 31.0.1", ] [[package]] @@ -5934,14 +6173,25 @@ dependencies = [ "pallet-transaction-payment", "parity-scale-codec", "scale-info", - "sp-core", + "sp-core 28.0.0", "sp-inherents", - "sp-io", - "sp-runtime", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-tracing 16.0.0", "sp-version", ] +[[package]] +name = "frame-metadata" +version = "15.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "878babb0b136e731cc77ec2fd883ff02745ff21e6fb662729953d44923df009c" +dependencies = [ + "cfg-if", + "parity-scale-codec", + "scale-info", +] + [[package]] name = "frame-metadata" version = "16.0.0" @@ -5960,7 +6210,7 @@ version = "0.1.0" dependencies = [ "array-bytes", "docify", - "frame-metadata", + "frame-metadata 16.0.0", "frame-support", "frame-system", "log", @@ -5968,7 +6218,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-api", - "sp-runtime", + "sp-runtime 31.0.1", "sp-tracing 16.0.0", "sp-transaction-pool", "substrate-test-runtime-client", @@ -5984,7 +6234,7 @@ dependencies = [ "frame-benchmarking-cli", "log", "sc-cli", - "sp-runtime", + "sp-runtime 31.0.1", "sp-statement-store", "tracing-subscriber 0.3.18", ] @@ -5995,15 +6245,15 @@ version = "0.35.0" dependencies = [ "futures", "indicatif", - "jsonrpsee", + "jsonrpsee 0.24.3", "log", "parity-scale-codec", "serde", - "sp-core", - "sp-crypto-hashing", - "sp-io", - "sp-runtime", - "sp-state-machine", + "sp-core 28.0.0", + "sp-crypto-hashing 0.1.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", + "sp-state-machine 0.35.0", "sp-tracing 16.0.0", "spinners", "substrate-rpc-client", @@ -6022,7 +6272,7 @@ dependencies = [ "bitflags 1.3.2", "docify", "environmental", - "frame-metadata", + "frame-metadata 16.0.0", "frame-support-procedural", "frame-system", "impl-trait-for-tuples", @@ -6037,23 +6287,23 @@ dependencies = [ "serde_json", "smallvec", "sp-api", - "sp-arithmetic", - "sp-core", - "sp-crypto-hashing", + "sp-arithmetic 23.0.0", + "sp-core 28.0.0", + "sp-crypto-hashing 0.1.0", "sp-crypto-hashing-proc-macro", "sp-debug-derive 14.0.0", "sp-genesis-builder", "sp-inherents", - "sp-io", + "sp-io 30.0.0", "sp-metadata-ir", - "sp-runtime", + "sp-runtime 31.0.1", "sp-staking", - "sp-state-machine", + "sp-state-machine 0.35.0", "sp-std 14.0.0", "sp-timestamp", "sp-tracing 16.0.0", - "sp-trie", - "sp-weights", + "sp-trie 29.0.0", + "sp-weights 27.0.0", "static_assertions", "tt-call", ] @@ -6079,11 +6329,11 @@ dependencies = [ "quote 1.0.37", "regex", "scale-info", - "sp-core", - "sp-crypto-hashing", - "sp-io", + "sp-core 28.0.0", + "sp-crypto-hashing 0.1.0", + "sp-io 30.0.0", "sp-metadata-ir", - "sp-runtime", + "sp-runtime 31.0.1", "static_assertions", "syn 2.0.65", ] @@ -6114,7 +6364,7 @@ version = "3.0.0" dependencies = [ "frame-benchmarking", "frame-executive", - "frame-metadata", + "frame-metadata 16.0.0", "frame-support", "frame-support-test-pallet", "frame-system", @@ -6124,12 +6374,12 @@ dependencies = [ "scale-info", "serde", "sp-api", - "sp-arithmetic", - "sp-core", - "sp-io", + "sp-arithmetic 23.0.0", + "sp-core 28.0.0", + "sp-io 30.0.0", "sp-metadata-ir", - "sp-runtime", - "sp-state-machine", + "sp-runtime 31.0.1", + "sp-state-machine 0.35.0", "sp-version", "static_assertions", "trybuild", @@ -6143,8 +6393,8 @@ dependencies = [ "frame-system", "parity-scale-codec", "scale-info", - "sp-core", - "sp-runtime", + "sp-core 28.0.0", + "sp-runtime 31.0.1", "sp-version", ] @@ -6157,7 +6407,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "serde", - "sp-runtime", + "sp-runtime 31.0.1", ] [[package]] @@ -6181,13 +6431,13 @@ dependencies = [ "parity-scale-codec", "scale-info", "serde", - "sp-core", + "sp-core 28.0.0", "sp-externalities 0.25.0", - "sp-io", - "sp-runtime", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", "sp-version", - "sp-weights", + "sp-weights 27.0.0", "substrate-test-runtime-client", ] @@ -6200,10 +6450,10 @@ dependencies = [ "frame-system", "parity-scale-codec", "scale-info", - "sp-core", + "sp-core 28.0.0", "sp-externalities 0.25.0", - "sp-io", - "sp-runtime", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-version", ] @@ -6223,7 +6473,7 @@ dependencies = [ "frame-support", "parity-scale-codec", "sp-api", - "sp-runtime", + "sp-runtime 31.0.1", ] [[package]] @@ -6340,12 +6590,17 @@ dependencies = [ [[package]] name = "futures-lite" -version = "2.3.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" +checksum = "9c1155db57329dca6d018b61e76b1488ce9a2e5e44028cac420a5898f4fcef63" dependencies = [ + "fastrand 2.1.0", "futures-core", + "futures-io", + "memchr", + "parking", "pin-project-lite", + "waker-fn", ] [[package]] @@ -6475,7 +6730,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ea1015b5a70616b688dc230cfe50c8af89d972cb132d5a622814d29773b10b9" dependencies = [ "rand", - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -6515,6 +6770,12 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "glob-match" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9985c9503b412198aa4197559e9a318524ebc4519c229bfa05a535828c950b9d" + [[package]] name = "gloo-timers" version = "0.2.6" @@ -6555,11 +6816,11 @@ dependencies = [ "sp-api", "sp-block-builder", "sp-consensus-aura", - "sp-core", + "sp-core 28.0.0", "sp-genesis-builder", "sp-inherents", "sp-offchain", - "sp-runtime", + "sp-runtime 31.0.1", "sp-session", "sp-storage 19.0.0", "sp-transaction-pool", @@ -6597,7 +6858,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ "ff", - "rand_core", + "rand_core 0.6.4", "subtle 2.5.0", ] @@ -6694,9 +6955,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.3" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ "ahash 0.8.11", "allocator-api2", @@ -6709,7 +6970,7 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" dependencies = [ - "hashbrown 0.14.3", + "hashbrown 0.14.5", ] [[package]] @@ -6850,6 +7111,15 @@ dependencies = [ "hmac 0.8.1", ] +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "honggfuzz" version = "0.5.55" @@ -6929,6 +7199,12 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "http-range-header" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f" + [[package]] name = "httparse" version = "1.8.0" @@ -7026,6 +7302,31 @@ dependencies = [ "tower-service", ] +[[package]] +name = "hyper-timeout" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" +dependencies = [ + "hyper 0.14.29", + "pin-project-lite", + "tokio", + "tokio-io-timeout", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper 0.14.29", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "hyper-util" version = "0.1.5" @@ -7069,6 +7370,12 @@ dependencies = [ "cc", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "0.2.3" @@ -7240,7 +7547,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177" dependencies = [ "equivalent", - "hashbrown 0.14.3", + "hashbrown 0.14.5", ] [[package]] @@ -7273,9 +7580,9 @@ dependencies = [ [[package]] name = "instant" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" dependencies = [ "cfg-if", ] @@ -7356,7 +7663,7 @@ version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "334e04b4d781f436dc315cb1e7515bd96826426345d498149e4bde36b67f8ee9" dependencies = [ - "async-channel", + "async-channel 1.9.0", "castaway", "crossbeam-utils", "curl", @@ -7395,6 +7702,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.9" @@ -7463,30 +7779,121 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" [[package]] -name = "jsonpath_lib" -version = "0.3.0" +name = "json-patch" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaa63191d68230cccb81c5aa23abd53ed64d83337cacbb25a7b8c7979523774f" +checksum = "ec9ad60d674508f3ca8f380a928cfe7b096bc729c4e2dbfe3852bc45da3ab30b" dependencies = [ - "log", "serde", "serde_json", + "thiserror", ] [[package]] -name = "jsonrpsee" -version = "0.24.3" +name = "jsonpath-rust" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ec465b607a36dc5dd45d48b7689bc83f679f66a3ac6b6b21cc787a11e0f8685" +checksum = "06cc127b7c3d270be504572364f9569761a180b981919dd0d87693a7f5fb7829" dependencies = [ - "jsonrpsee-core", - "jsonrpsee-http-client", + "pest", + "pest_derive", + "regex", + "serde_json", + "thiserror", +] + +[[package]] +name = "jsonpath_lib" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaa63191d68230cccb81c5aa23abd53ed64d83337cacbb25a7b8c7979523774f" +dependencies = [ + "log", + "serde", + "serde_json", +] + +[[package]] +name = "jsonrpsee" +version = "0.22.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfdb12a2381ea5b2e68c3469ec604a007b367778cdb14d09612c8069ebd616ad" +dependencies = [ + "jsonrpsee-client-transport 0.22.5", + "jsonrpsee-core 0.22.5", + "jsonrpsee-http-client 0.22.5", + "jsonrpsee-types 0.22.5", +] + +[[package]] +name = "jsonrpsee" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b089779ad7f80768693755a031cc14a7766aba707cbe886674e3f79e9b7e47" +dependencies = [ + "jsonrpsee-core 0.23.2", + "jsonrpsee-types 0.23.2", + "jsonrpsee-ws-client 0.23.2", +] + +[[package]] +name = "jsonrpsee" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ec465b607a36dc5dd45d48b7689bc83f679f66a3ac6b6b21cc787a11e0f8685" +dependencies = [ + "jsonrpsee-core 0.24.3", + "jsonrpsee-http-client 0.24.3", "jsonrpsee-proc-macros", "jsonrpsee-server", - "jsonrpsee-types", - "jsonrpsee-ws-client", + "jsonrpsee-types 0.24.3", + "jsonrpsee-ws-client 0.24.3", + "tokio", + "tracing", +] + +[[package]] +name = "jsonrpsee-client-transport" +version = "0.22.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4978087a58c3ab02efc5b07c5e5e2803024536106fd5506f558db172c889b3aa" +dependencies = [ + "futures-util", + "http 0.2.9", + "jsonrpsee-core 0.22.5", + "pin-project", + "rustls-native-certs 0.7.0", + "rustls-pki-types", + "soketto 0.7.1", + "thiserror", + "tokio", + "tokio-rustls 0.25.0", + "tokio-util", + "tracing", + "url", +] + +[[package]] +name = "jsonrpsee-client-transport" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08163edd8bcc466c33d79e10f695cdc98c00d1e6ddfb95cec41b6b0279dd5432" +dependencies = [ + "base64 0.22.1", + "futures-util", + "http 1.1.0", + "jsonrpsee-core 0.23.2", + "pin-project", + "rustls 0.23.10", + "rustls-pki-types", + "rustls-platform-verifier", + "soketto 0.8.0", + "thiserror", "tokio", + "tokio-rustls 0.26.0", + "tokio-util", "tracing", + "url", ] [[package]] @@ -7498,7 +7905,7 @@ dependencies = [ "base64 0.22.1", "futures-util", "http 1.1.0", - "jsonrpsee-core", + "jsonrpsee-core 0.24.3", "pin-project", "rustls 0.23.10", "rustls-pki-types", @@ -7512,6 +7919,51 @@ dependencies = [ "url", ] +[[package]] +name = "jsonrpsee-core" +version = "0.22.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4b257e1ec385e07b0255dde0b933f948b5c8b8c28d42afda9587c3a967b896d" +dependencies = [ + "anyhow", + "async-trait", + "beef", + "futures-timer", + "futures-util", + "hyper 0.14.29", + "jsonrpsee-types 0.22.5", + "pin-project", + "rustc-hash 1.1.0", + "serde", + "serde_json", + "thiserror", + "tokio", + "tokio-stream", + "tracing", +] + +[[package]] +name = "jsonrpsee-core" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79712302e737d23ca0daa178e752c9334846b08321d439fd89af9a384f8c830b" +dependencies = [ + "anyhow", + "async-trait", + "beef", + "futures-timer", + "futures-util", + "jsonrpsee-types 0.23.2", + "pin-project", + "rustc-hash 1.1.0", + "serde", + "serde_json", + "thiserror", + "tokio", + "tokio-stream", + "tracing", +] + [[package]] name = "jsonrpsee-core" version = "0.24.3" @@ -7525,7 +7977,7 @@ dependencies = [ "http 1.1.0", "http-body 1.0.0", "http-body-util", - "jsonrpsee-types", + "jsonrpsee-types 0.24.3", "parking_lot 0.12.3", "pin-project", "rand", @@ -7538,6 +7990,26 @@ dependencies = [ "tracing", ] +[[package]] +name = "jsonrpsee-http-client" +version = "0.22.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ccf93fc4a0bfe05d851d37d7c32b7f370fe94336b52a2f0efc5f1981895c2e5" +dependencies = [ + "async-trait", + "hyper 0.14.29", + "hyper-rustls 0.24.2", + "jsonrpsee-core 0.22.5", + "jsonrpsee-types 0.22.5", + "serde", + "serde_json", + "thiserror", + "tokio", + "tower", + "tracing", + "url", +] + [[package]] name = "jsonrpsee-http-client" version = "0.24.3" @@ -7550,8 +8022,8 @@ dependencies = [ "hyper 1.3.1", "hyper-rustls 0.27.2", "hyper-util", - "jsonrpsee-core", - "jsonrpsee-types", + "jsonrpsee-core 0.24.3", + "jsonrpsee-types 0.24.3", "rustls 0.23.10", "rustls-platform-verifier", "serde", @@ -7588,8 +8060,8 @@ dependencies = [ "http-body-util", "hyper 1.3.1", "hyper-util", - "jsonrpsee-core", - "jsonrpsee-types", + "jsonrpsee-core 0.24.3", + "jsonrpsee-types 0.24.3", "pin-project", "route-recognizer", "serde", @@ -7603,6 +8075,32 @@ dependencies = [ "tracing", ] +[[package]] +name = "jsonrpsee-types" +version = "0.22.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "150d6168405890a7a3231a3c74843f58b8959471f6df76078db2619ddee1d07d" +dependencies = [ + "anyhow", + "beef", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "jsonrpsee-types" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c465fbe385238e861fdc4d1c85e04ada6c1fd246161d26385c1b311724d2af" +dependencies = [ + "beef", + "http 1.1.0", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "jsonrpsee-types" version = "0.24.3" @@ -7615,6 +8113,19 @@ dependencies = [ "thiserror", ] +[[package]] +name = "jsonrpsee-ws-client" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c28759775f5cb2f1ea9667672d3fe2b0e701d1f4b7b67954e60afe7fd058b5e" +dependencies = [ + "http 1.1.0", + "jsonrpsee-client-transport 0.23.2", + "jsonrpsee-core 0.23.2", + "jsonrpsee-types 0.23.2", + "url", +] + [[package]] name = "jsonrpsee-ws-client" version = "0.24.3" @@ -7622,9 +8133,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "992bf67d1132f88edf4a4f8cff474cf01abb2be203004a2b8e11c2b20795b99e" dependencies = [ "http 1.1.0", - "jsonrpsee-client-transport", - "jsonrpsee-core", - "jsonrpsee-types", + "jsonrpsee-client-transport 0.24.3", + "jsonrpsee-core 0.24.3", + "jsonrpsee-types 0.24.3", "url", ] @@ -7642,6 +8153,20 @@ dependencies = [ "sha2 0.10.8", ] +[[package]] +name = "k8s-openapi" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edc3606fd16aca7989db2f84bb25684d0270c6d6fa1dbcd0025af7b4130523a6" +dependencies = [ + "base64 0.21.7", + "bytes", + "chrono", + "serde", + "serde-value", + "serde_json", +] + [[package]] name = "keccak" version = "0.1.4" @@ -7685,6 +8210,99 @@ dependencies = [ "substrate-wasm-builder", ] +[[package]] +name = "kube" +version = "0.87.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3499c8d60c763246c7a213f51caac1e9033f46026904cb89bc8951ae8601f26e" +dependencies = [ + "k8s-openapi", + "kube-client", + "kube-core", + "kube-runtime", +] + +[[package]] +name = "kube-client" +version = "0.87.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "033450dfa0762130565890dadf2f8835faedf749376ca13345bcd8ecd6b5f29f" +dependencies = [ + "base64 0.21.7", + "bytes", + "chrono", + "either", + "futures", + "home", + "http 0.2.9", + "http-body 0.4.5", + "hyper 0.14.29", + "hyper-rustls 0.24.2", + "hyper-timeout", + "jsonpath-rust", + "k8s-openapi", + "kube-core", + "pem 3.0.4", + "pin-project", + "rand", + "rustls 0.21.7", + "rustls-pemfile 1.0.3", + "secrecy", + "serde", + "serde_json", + "serde_yaml", + "thiserror", + "tokio", + "tokio-tungstenite", + "tokio-util", + "tower", + "tower-http 0.4.4", + "tracing", +] + +[[package]] +name = "kube-core" +version = "0.87.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5bba93d054786eba7994d03ce522f368ef7d48c88a1826faa28478d85fb63ae" +dependencies = [ + "chrono", + "form_urlencoded", + "http 0.2.9", + "json-patch", + "k8s-openapi", + "once_cell", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "kube-runtime" +version = "0.87.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d8893eb18fbf6bb6c80ef6ee7dd11ec32b1dc3c034c988ac1b3a84d46a230ae" +dependencies = [ + "ahash 0.8.11", + "async-trait", + "backoff", + "derivative", + "futures", + "hashbrown 0.14.5", + "json-patch", + "k8s-openapi", + "kube-client", + "parking_lot 0.12.3", + "pin-project", + "serde", + "serde_json", + "smallvec", + "thiserror", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "kv-log-macro" version = "1.0.7" @@ -8048,7 +8666,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2eeec39ad3ad0677551907dd304b2f13f17208ccebe333bef194076cd2e8921" dependencies = [ "bytes", - "curve25519-dalek", + "curve25519-dalek 4.1.3", "futures", "libp2p-core", "libp2p-identity", @@ -8511,7 +9129,7 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3262e75e648fce39813cb56ac41f3c3e3f65217ebf3844d818d1f9398cfb0dc" dependencies = [ - "hashbrown 0.14.3", + "hashbrown 0.14.5", ] [[package]] @@ -8736,7 +9354,7 @@ checksum = "f313fcff1d2a4bcaa2deeaa00bf7530d77d5f7bd0467a117dde2e29a75a7a17a" dependencies = [ "array-bytes", "blake3", - "frame-metadata", + "frame-metadata 16.0.0", "parity-scale-codec", "scale-decode", "scale-info", @@ -8750,7 +9368,7 @@ checksum = "58c38e2799fc0978b65dfff8023ec7843e2330bb462f19198840b34b6582397d" dependencies = [ "byteorder", "keccak", - "rand_core", + "rand_core 0.6.4", "zeroize", ] @@ -8768,7 +9386,7 @@ dependencies = [ "num-traits", "parking_lot 0.12.3", "relay-utils", - "sp-arithmetic", + "sp-arithmetic 23.0.0", ] [[package]] @@ -8802,7 +9420,7 @@ dependencies = [ "docify", "futures", "futures-timer", - "jsonrpsee", + "jsonrpsee 0.24.3", "minimal-template-runtime", "polkadot-sdk", "serde_json", @@ -8849,7 +9467,7 @@ dependencies = [ "bitflags 1.3.2", "blake2 0.10.6", "c2-chacha", - "curve25519-dalek", + "curve25519-dalek 4.1.3", "either", "hashlink", "lioness", @@ -8878,9 +9496,9 @@ dependencies = [ "sp-blockchain", "sp-consensus", "sp-consensus-beefy", - "sp-core", + "sp-core 28.0.0", "sp-mmr-primitives", - "sp-runtime", + "sp-runtime 31.0.1", "sp-tracing 16.0.0", "substrate-test-runtime-client", "tokio", @@ -8890,15 +9508,15 @@ dependencies = [ name = "mmr-rpc" version = "28.0.0" dependencies = [ - "jsonrpsee", + "jsonrpsee 0.24.3", "parity-scale-codec", "serde", "serde_json", "sp-api", "sp-blockchain", - "sp-core", + "sp-core 28.0.0", "sp-mmr-primitives", - "sp-runtime", + "sp-runtime 31.0.1", ] [[package]] @@ -9130,6 +9748,23 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" +[[package]] +name = "native-tls" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "netlink-packet-core" version = "0.4.2" @@ -9231,6 +9866,17 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "nix" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +dependencies = [ + "bitflags 2.6.0", + "cfg-if", + "libc", +] + [[package]] name = "nix" version = "0.28.0" @@ -9239,7 +9885,7 @@ checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" dependencies = [ "bitflags 2.6.0", "cfg-if", - "cfg_aliases", + "cfg_aliases 0.1.1", "libc", ] @@ -9281,13 +9927,13 @@ dependencies = [ "serde", "serde_json", "sp-consensus", - "sp-core", + "sp-core 28.0.0", "sp-inherents", - "sp-runtime", - "sp-state-machine", + "sp-runtime 31.0.1", + "sp-state-machine 0.35.0", "sp-timestamp", "sp-tracing 16.0.0", - "sp-trie", + "sp-trie 29.0.0", "tempfile", ] @@ -9295,15 +9941,15 @@ dependencies = [ name = "node-primitives" version = "2.0.0" dependencies = [ - "sp-core", - "sp-runtime", + "sp-core 28.0.0", + "sp-runtime 31.0.1", ] [[package]] name = "node-rpc" version = "3.0.0-dev" dependencies = [ - "jsonrpsee", + "jsonrpsee 0.24.3", "mmr-rpc", "node-primitives", "pallet-transaction-payment-rpc", @@ -9320,14 +9966,14 @@ dependencies = [ "sc-sync-state-rpc", "sc-transaction-pool-api", "sp-api", - "sp-application-crypto", + "sp-application-crypto 30.0.0", "sp-block-builder", "sp-blockchain", "sp-consensus", "sp-consensus-babe", "sp-consensus-beefy", - "sp-keystore", - "sp-runtime", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", "sp-statement-store", "substrate-frame-rpc-system", "substrate-state-trie-migration-rpc", @@ -9383,12 +10029,12 @@ dependencies = [ "sp-block-builder", "sp-blockchain", "sp-consensus", - "sp-core", - "sp-crypto-hashing", + "sp-core 28.0.0", + "sp-crypto-hashing 0.1.0", "sp-inherents", - "sp-io", + "sp-io 30.0.0", "sp-keyring", - "sp-runtime", + "sp-runtime 31.0.1", "sp-timestamp", "staging-node-cli", "substrate-test-client", @@ -9656,16 +10302,42 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] -name = "openssl-probe" -version = "0.1.5" +name = "openssl" +version = "0.10.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" +dependencies = [ + "bitflags 2.6.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.65", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.102" +version = "0.9.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" +checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" dependencies = [ "cc", "libc", @@ -9721,6 +10393,15 @@ dependencies = [ "num-traits", ] +[[package]] +name = "ordered-float" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" +dependencies = [ + "num-traits", +] + [[package]] name = "os_str_bytes" version = "6.5.1" @@ -9753,10 +10434,10 @@ dependencies = [ "pallet-identity", "parity-scale-codec", "scale-info", - "sp-core", - "sp-crypto-hashing", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-crypto-hashing 0.1.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -9773,10 +10454,10 @@ dependencies = [ "primitive-types", "scale-info", "sp-api", - "sp-arithmetic", - "sp-core", - "sp-io", - "sp-runtime", + "sp-arithmetic 23.0.0", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -9793,10 +10474,10 @@ dependencies = [ "parity-scale-codec", "primitive-types", "scale-info", - "sp-arithmetic", - "sp-core", - "sp-io", - "sp-runtime", + "sp-arithmetic 23.0.0", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -9811,9 +10492,9 @@ dependencies = [ "pallet-transaction-payment", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-storage 19.0.0", ] @@ -9827,9 +10508,9 @@ dependencies = [ "pallet-balances", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -9847,9 +10528,9 @@ dependencies = [ "scale-info", "serde", "serde_json", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-storage 19.0.0", ] @@ -9865,9 +10546,9 @@ dependencies = [ "pallet-balances", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -9882,9 +10563,9 @@ dependencies = [ "pallet-balances", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -9896,9 +10577,9 @@ dependencies = [ "pallet-balances", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -9911,11 +10592,11 @@ dependencies = [ "pallet-timestamp", "parity-scale-codec", "scale-info", - "sp-application-crypto", + "sp-application-crypto 30.0.0", "sp-consensus-aura", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -9927,11 +10608,11 @@ dependencies = [ "pallet-session", "parity-scale-codec", "scale-info", - "sp-application-crypto", + "sp-application-crypto 30.0.0", "sp-authority-discovery", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -9943,9 +10624,9 @@ dependencies = [ "impl-trait-for-tuples", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -9966,11 +10647,11 @@ dependencies = [ "pallet-timestamp", "parity-scale-codec", "scale-info", - "sp-application-crypto", + "sp-application-crypto 30.0.0", "sp-consensus-babe", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-session", "sp-staking", ] @@ -9989,9 +10670,9 @@ dependencies = [ "pallet-balances", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-tracing 16.0.0", ] @@ -10016,8 +10697,8 @@ dependencies = [ "log", "pallet-bags-list", "pallet-staking", - "sp-core", - "sp-runtime", + "sp-core 28.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", "sp-storage 19.0.0", "sp-tracing 16.0.0", @@ -10036,9 +10717,9 @@ dependencies = [ "parity-scale-codec", "paste", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -10060,12 +10741,12 @@ dependencies = [ "scale-info", "serde", "sp-consensus-beefy", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-session", "sp-staking", - "sp-state-machine", + "sp-state-machine 0.35.0", ] [[package]] @@ -10086,11 +10767,11 @@ dependencies = [ "serde", "sp-api", "sp-consensus-beefy", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-staking", - "sp-state-machine", + "sp-state-machine 0.35.0", ] [[package]] @@ -10105,9 +10786,9 @@ dependencies = [ "pallet-treasury", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -10128,9 +10809,9 @@ dependencies = [ "scale-info", "serde", "sp-consensus-beefy", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -10148,9 +10829,9 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-consensus-grandpa", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -10170,11 +10851,11 @@ dependencies = [ "pallet-bridge-grandpa", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", - "sp-trie", + "sp-trie 29.0.0", ] [[package]] @@ -10193,9 +10874,9 @@ dependencies = [ "pallet-bridge-grandpa", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -10222,10 +10903,10 @@ dependencies = [ "pallet-utility", "parity-scale-codec", "scale-info", - "sp-arithmetic", - "sp-core", - "sp-io", - "sp-runtime", + "sp-arithmetic 23.0.0", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -10242,10 +10923,10 @@ dependencies = [ "pretty_assertions", "scale-info", "sp-api", - "sp-arithmetic", - "sp-core", - "sp-io", - "sp-runtime", + "sp-arithmetic 23.0.0", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-tracing 16.0.0", ] @@ -10262,9 +10943,9 @@ dependencies = [ "pallet-treasury", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -10284,9 +10965,9 @@ dependencies = [ "rand", "scale-info", "sp-consensus-aura", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-staking", "sp-tracing 16.0.0", ] @@ -10303,9 +10984,9 @@ dependencies = [ "pallet-balances", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -10317,9 +10998,9 @@ dependencies = [ "frame-system", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -10354,10 +11035,10 @@ dependencies = [ "serde", "smallvec", "sp-api", - "sp-core", - "sp-io", - "sp-keystore", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", "sp-tracing 16.0.0", "staging-xcm", @@ -10374,7 +11055,7 @@ dependencies = [ "anyhow", "frame-system", "parity-wasm", - "sp-runtime", + "sp-runtime 31.0.1", "tempfile", "toml 0.8.12", "twox-hash", @@ -10406,10 +11087,10 @@ dependencies = [ "pretty_assertions", "scale-info", "sp-api", - "sp-core", - "sp-io", - "sp-keystore", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", "sp-tracing 16.0.0", "staging-xcm", "staging-xcm-builder", @@ -10449,9 +11130,9 @@ dependencies = [ "parity-scale-codec", "scale-info", "serde", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -10465,10 +11146,10 @@ dependencies = [ "pallet-ranked-collective", "parity-scale-codec", "scale-info", - "sp-arithmetic", - "sp-core", - "sp-io", - "sp-runtime", + "sp-arithmetic 23.0.0", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -10480,8 +11161,8 @@ dependencies = [ "log", "parity-scale-codec", "scale-info", - "sp-io", - "sp-runtime", + "sp-io 30.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -10499,9 +11180,9 @@ dependencies = [ "pallet-timestamp", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-staking", "sp-tracing 16.0.0", "substrate-test-utils", @@ -10521,9 +11202,9 @@ dependencies = [ "parity-scale-codec", "scale-info", "serde", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -10536,9 +11217,9 @@ dependencies = [ "pallet-balances", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -10559,10 +11240,10 @@ dependencies = [ "parity-scale-codec", "parking_lot 0.12.3", "scale-info", - "sp-core", - "sp-io", + "sp-core 28.0.0", + "sp-io 30.0.0", "sp-npos-elections", - "sp-runtime", + "sp-runtime 31.0.1", "sp-staking", "sp-std 14.0.0", "sp-tracing 16.0.0", @@ -10583,11 +11264,11 @@ dependencies = [ "parking_lot 0.12.3", "rand", "scale-info", - "sp-arithmetic", - "sp-core", - "sp-io", + "sp-arithmetic 23.0.0", + "sp-core 28.0.0", + "sp-io 30.0.0", "sp-npos-elections", - "sp-runtime", + "sp-runtime 31.0.1", "sp-tracing 16.0.0", "strum 0.26.2", ] @@ -10601,7 +11282,7 @@ dependencies = [ "frame-system", "parity-scale-codec", "sp-npos-elections", - "sp-runtime", + "sp-runtime 31.0.1", ] [[package]] @@ -10615,10 +11296,10 @@ dependencies = [ "pallet-balances", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", + "sp-core 28.0.0", + "sp-io 30.0.0", "sp-npos-elections", - "sp-runtime", + "sp-runtime 31.0.1", "sp-staking", "sp-tracing 16.0.0", "substrate-test-utils", @@ -10635,9 +11316,9 @@ dependencies = [ "pallet-balances", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -10660,9 +11341,9 @@ dependencies = [ "pallet-balances", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -10676,7 +11357,7 @@ dependencies = [ "pallet-migrations", "parity-scale-codec", "scale-info", - "sp-io", + "sp-io 30.0.0", ] [[package]] @@ -10689,10 +11370,10 @@ dependencies = [ "log", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-keystore", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", ] [[package]] @@ -10708,9 +11389,9 @@ dependencies = [ "pallet-balances", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-version", ] @@ -10724,8 +11405,8 @@ dependencies = [ "log", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", + "sp-core 28.0.0", + "sp-io 30.0.0", ] [[package]] @@ -10738,9 +11419,9 @@ dependencies = [ "log", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -10774,9 +11455,9 @@ dependencies = [ "pallet-timestamp", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-staking", "sp-tracing 16.0.0", "substrate-test-utils", @@ -10794,10 +11475,10 @@ dependencies = [ "pallet-balances", "parity-scale-codec", "scale-info", - "sp-core", + "sp-core 28.0.0", "sp-inherents", - "sp-io", - "sp-runtime", + "sp-io 30.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -10819,12 +11500,12 @@ dependencies = [ "pallet-timestamp", "parity-scale-codec", "scale-info", - "sp-application-crypto", + "sp-application-crypto 30.0.0", "sp-consensus-grandpa", - "sp-core", - "sp-io", + "sp-core 28.0.0", + "sp-io 30.0.0", "sp-keyring", - "sp-runtime", + "sp-runtime 31.0.1", "sp-session", "sp-staking", ] @@ -10841,10 +11522,10 @@ dependencies = [ "pallet-balances", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-keystore", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", ] [[package]] @@ -10859,10 +11540,10 @@ dependencies = [ "pallet-session", "parity-scale-codec", "scale-info", - "sp-application-crypto", - "sp-core", - "sp-io", - "sp-runtime", + "sp-application-crypto 30.0.0", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-staking", ] @@ -10876,10 +11557,10 @@ dependencies = [ "pallet-balances", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", + "sp-core 28.0.0", + "sp-io 30.0.0", "sp-keyring", - "sp-runtime", + "sp-runtime 31.0.1", ] [[package]] @@ -10891,9 +11572,9 @@ dependencies = [ "parity-scale-codec", "safe-mix", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -10907,9 +11588,9 @@ dependencies = [ "pallet-balances", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -10922,9 +11603,9 @@ dependencies = [ "log", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -10941,13 +11622,13 @@ dependencies = [ "rand_distr", "scale-info", "serde", - "sp-arithmetic", - "sp-core", - "sp-crypto-hashing", - "sp-io", - "sp-runtime", + "sp-arithmetic 23.0.0", + "sp-core 28.0.0", + "sp-crypto-hashing 0.1.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-tracing 16.0.0", - "sp-weights", + "sp-weights 27.0.0", ] [[package]] @@ -10966,9 +11647,9 @@ dependencies = [ "scale-info", "sp-api", "sp-block-builder", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-tracing 16.0.0", "sp-version", ] @@ -10993,11 +11674,11 @@ dependencies = [ "parity-scale-codec", "scale-info", "serde", - "sp-application-crypto", - "sp-arithmetic", - "sp-io", + "sp-application-crypto 30.0.0", + "sp-arithmetic 23.0.0", + "sp-io 30.0.0", "sp-mixnet", - "sp-runtime", + "sp-runtime 31.0.1", ] [[package]] @@ -11012,10 +11693,10 @@ dependencies = [ "log", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", + "sp-core 28.0.0", + "sp-io 30.0.0", "sp-mmr-primitives", - "sp-runtime", + "sp-runtime 31.0.1", "sp-tracing 16.0.0", ] @@ -11030,8 +11711,8 @@ dependencies = [ "pallet-balances", "parity-scale-codec", "scale-info", - "sp-io", - "sp-runtime", + "sp-io 30.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -11047,9 +11728,9 @@ dependencies = [ "pallet-nfts", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -11065,10 +11746,10 @@ dependencies = [ "pallet-balances", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-keystore", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", ] [[package]] @@ -11090,10 +11771,10 @@ dependencies = [ "pallet-balances", "parity-scale-codec", "scale-info", - "sp-arithmetic", - "sp-core", - "sp-io", - "sp-runtime", + "sp-arithmetic 23.0.0", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -11105,9 +11786,9 @@ dependencies = [ "log", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -11120,9 +11801,9 @@ dependencies = [ "pallet-balances", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-staking", "sp-tracing 16.0.0", ] @@ -11144,9 +11825,9 @@ dependencies = [ "pallet-timestamp", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-runtime-interface 24.0.0", "sp-staking", ] @@ -11161,8 +11842,8 @@ dependencies = [ "log", "pallet-nomination-pools", "rand", - "sp-io", - "sp-runtime", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-tracing 16.0.0", ] @@ -11192,9 +11873,9 @@ dependencies = [ "pallet-timestamp", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-staking", "sp-std 14.0.0", "sp-tracing 16.0.0", @@ -11216,9 +11897,9 @@ dependencies = [ "pallet-timestamp", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-staking", "sp-std 14.0.0", "sp-tracing 16.0.0", @@ -11235,9 +11916,9 @@ dependencies = [ "parity-scale-codec", "scale-info", "serde", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-staking", ] @@ -11261,9 +11942,9 @@ dependencies = [ "pallet-timestamp", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-staking", ] @@ -11277,10 +11958,10 @@ dependencies = [ "frame-system", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", + "sp-core 28.0.0", + "sp-io 30.0.0", "sp-metadata-ir", - "sp-runtime", + "sp-runtime 31.0.1", ] [[package]] @@ -11291,7 +11972,7 @@ dependencies = [ "frame-support", "honggfuzz", "pallet-paged-list", - "sp-io", + "sp-io 30.0.0", ] [[package]] @@ -11303,9 +11984,9 @@ dependencies = [ "frame-system", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -11322,9 +12003,9 @@ dependencies = [ "paste", "scale-info", "serde", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -11338,9 +12019,9 @@ dependencies = [ "pallet-balances", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -11354,9 +12035,9 @@ dependencies = [ "pallet-utility", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -11370,10 +12051,10 @@ dependencies = [ "log", "parity-scale-codec", "scale-info", - "sp-arithmetic", - "sp-core", - "sp-io", - "sp-runtime", + "sp-arithmetic 23.0.0", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -11386,9 +12067,9 @@ dependencies = [ "pallet-balances", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -11406,10 +12087,10 @@ dependencies = [ "parity-scale-codec", "scale-info", "serde", - "sp-arithmetic", - "sp-core", - "sp-io", - "sp-runtime", + "sp-arithmetic 23.0.0", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -11422,9 +12103,9 @@ dependencies = [ "parity-scale-codec", "scale-info", "serde", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -11457,10 +12138,10 @@ dependencies = [ "scale-info", "serde", "sp-api", - "sp-core", - "sp-io", - "sp-keystore", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", "sp-tracing 16.0.0", "staging-xcm", @@ -11477,9 +12158,9 @@ dependencies = [ "log", "parity-wasm", "polkavm-linker 0.10.0", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "tempfile", "toml 0.8.12", ] @@ -11509,10 +12190,10 @@ dependencies = [ "pretty_assertions", "scale-info", "sp-api", - "sp-core", - "sp-io", - "sp-keystore", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", "sp-tracing 16.0.0", "staging-xcm", "staging-xcm-builder", @@ -11554,9 +12235,9 @@ dependencies = [ "pallet-timestamp", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-staking", "sp-std 14.0.0", ] @@ -11569,9 +12250,9 @@ dependencies = [ "frame-system", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -11587,10 +12268,10 @@ dependencies = [ "pallet-utility", "parity-scale-codec", "scale-info", - "sp-arithmetic", - "sp-core", - "sp-io", - "sp-runtime", + "sp-arithmetic 23.0.0", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -11604,10 +12285,10 @@ dependencies = [ "pallet-ranked-collective", "parity-scale-codec", "scale-info", - "sp-arithmetic", - "sp-core", - "sp-io", - "sp-runtime", + "sp-arithmetic 23.0.0", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -11622,10 +12303,10 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-consensus-sassafras", - "sp-core", - "sp-crypto-hashing", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-crypto-hashing 0.1.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -11640,10 +12321,10 @@ dependencies = [ "pallet-preimage", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", - "sp-weights", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", + "sp-weights 27.0.0", "substrate-test-utils", ] @@ -11656,9 +12337,9 @@ dependencies = [ "pallet-balances", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -11672,13 +12353,13 @@ dependencies = [ "pallet-timestamp", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-session", "sp-staking", - "sp-state-machine", - "sp-trie", + "sp-state-machine 0.35.0", + "sp-trie 29.0.0", ] [[package]] @@ -11697,9 +12378,9 @@ dependencies = [ "parity-scale-codec", "rand", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-session", ] @@ -11711,7 +12392,7 @@ dependencies = [ "frame-system", "parity-scale-codec", "scale-info", - "sp-runtime", + "sp-runtime 31.0.1", ] [[package]] @@ -11727,11 +12408,11 @@ dependencies = [ "parity-scale-codec", "rand_chacha", "scale-info", - "sp-arithmetic", - "sp-core", - "sp-crypto-hashing", - "sp-io", - "sp-runtime", + "sp-arithmetic 23.0.0", + "sp-core 28.0.0", + "sp-crypto-hashing 0.1.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -11753,11 +12434,11 @@ dependencies = [ "rand_chacha", "scale-info", "serde", - "sp-application-crypto", - "sp-core", - "sp-io", + "sp-application-crypto 30.0.0", + "sp-core 28.0.0", + "sp-io 30.0.0", "sp-npos-elections", - "sp-runtime", + "sp-runtime 31.0.1", "sp-staking", "sp-tracing 16.0.0", "substrate-test-utils", @@ -11770,7 +12451,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2 1.0.86", "quote 1.0.37", - "sp-runtime", + "sp-runtime 31.0.1", "syn 2.0.65", ] @@ -11779,7 +12460,7 @@ name = "pallet-staking-reward-fn" version = "19.0.0" dependencies = [ "log", - "sp-arithmetic", + "sp-arithmetic 23.0.0", ] [[package]] @@ -11805,9 +12486,9 @@ dependencies = [ "parking_lot 0.12.3", "scale-info", "serde", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-tracing 16.0.0", "substrate-state-trie-migration-rpc", "thousands", @@ -11826,9 +12507,9 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-api", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-statement-store", ] @@ -11842,9 +12523,9 @@ dependencies = [ "frame-system", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -11856,9 +12537,9 @@ dependencies = [ "frame-system", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -11872,10 +12553,10 @@ dependencies = [ "log", "parity-scale-codec", "scale-info", - "sp-core", + "sp-core 28.0.0", "sp-inherents", - "sp-io", - "sp-runtime", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-storage 19.0.0", "sp-timestamp", ] @@ -11893,9 +12574,9 @@ dependencies = [ "parity-scale-codec", "scale-info", "serde", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-storage 19.0.0", ] @@ -11910,24 +12591,24 @@ dependencies = [ "scale-info", "serde", "serde_json", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", ] [[package]] name = "pallet-transaction-payment-rpc" version = "30.0.0" dependencies = [ - "jsonrpsee", + "jsonrpsee 0.24.3", "pallet-transaction-payment-rpc-runtime-api", "parity-scale-codec", "sp-api", "sp-blockchain", - "sp-core", + "sp-core 28.0.0", "sp-rpc", - "sp-runtime", - "sp-weights", + "sp-runtime 31.0.1", + "sp-weights 27.0.0", ] [[package]] @@ -11937,8 +12618,8 @@ dependencies = [ "pallet-transaction-payment", "parity-scale-codec", "sp-api", - "sp-runtime", - "sp-weights", + "sp-runtime 31.0.1", + "sp-weights 27.0.0", ] [[package]] @@ -11954,10 +12635,10 @@ dependencies = [ "parity-scale-codec", "scale-info", "serde", - "sp-core", + "sp-core 28.0.0", "sp-inherents", - "sp-io", - "sp-runtime", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-transaction-storage-proof", ] @@ -11975,9 +12656,9 @@ dependencies = [ "parity-scale-codec", "scale-info", "serde", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -11993,9 +12674,9 @@ dependencies = [ "pallet-utility", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -12009,9 +12690,9 @@ dependencies = [ "pallet-balances", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -12028,9 +12709,9 @@ dependencies = [ "pallet-timestamp", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -12044,9 +12725,9 @@ dependencies = [ "pallet-balances", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -12061,9 +12742,9 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-api", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -12082,9 +12763,9 @@ dependencies = [ "polkadot-runtime-parachains", "scale-info", "serde", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "staging-xcm", "staging-xcm-builder", "staging-xcm-executor", @@ -12106,8 +12787,8 @@ dependencies = [ "polkadot-primitives", "polkadot-runtime-common", "scale-info", - "sp-io", - "sp-runtime", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-tracing 16.0.0", "staging-xcm", "staging-xcm-builder", @@ -12131,9 +12812,9 @@ dependencies = [ "parity-scale-codec", "polkadot-parachain-primitives", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", "staging-xcm", "staging-xcm-builder", @@ -12151,9 +12832,9 @@ dependencies = [ "log", "parity-scale-codec", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", "staging-xcm", "staging-xcm-builder", @@ -12178,7 +12859,7 @@ dependencies = [ "frame-benchmarking", "frame-benchmarking-cli", "futures", - "jsonrpsee", + "jsonrpsee 0.24.3", "log", "pallet-transaction-payment-rpc", "parachain-template-runtime", @@ -12207,10 +12888,10 @@ dependencies = [ "sp-block-builder", "sp-blockchain", "sp-consensus-aura", - "sp-core", - "sp-io", - "sp-keystore", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", "sp-timestamp", "staging-xcm", "substrate-build-script-utils", @@ -12264,11 +12945,11 @@ dependencies = [ "sp-api", "sp-block-builder", "sp-consensus-aura", - "sp-core", + "sp-core 28.0.0", "sp-genesis-builder", "sp-inherents", "sp-offchain", - "sp-runtime", + "sp-runtime 31.0.1", "sp-session", "sp-transaction-pool", "sp-version", @@ -12299,9 +12980,9 @@ dependencies = [ "polkadot-primitives", "scale-info", "sp-consensus-aura", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "staging-parachain-info", "staging-xcm", "staging-xcm-executor", @@ -12320,7 +13001,7 @@ dependencies = [ "parity-scale-codec", "relay-substrate-client", "relay-utils", - "sp-core", + "sp-core 28.0.0", ] [[package]] @@ -12343,9 +13024,9 @@ dependencies = [ "parity-scale-codec", "polkadot-parachain-primitives", "sp-consensus-aura", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-tracing 16.0.0", "staging-parachain-info", "staging-xcm", @@ -12361,7 +13042,7 @@ checksum = "4e69bf016dc406eff7d53a7d3f7cf1c2e72c82b9088aac1118591e36dd2cd3e9" dependencies = [ "bitcoin_hashes 0.13.0", "rand", - "rand_core", + "rand_core 0.6.4", "serde", "unicode-normalization", ] @@ -12388,7 +13069,7 @@ dependencies = [ "memmap2 0.5.10", "parking_lot 0.12.3", "rand", - "siphasher", + "siphasher 0.3.11", "snap", ] @@ -12521,7 +13202,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" dependencies = [ "base64ct", - "rand_core", + "rand_core 0.6.4", "subtle 2.5.0", ] @@ -12556,6 +13237,16 @@ dependencies = [ "base64 0.13.1", ] +[[package]] +name = "pem" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" +dependencies = [ + "base64 0.22.1", + "serde", +] + [[package]] name = "penpal-emulated-chain" version = "0.0.0" @@ -12565,7 +13256,7 @@ dependencies = [ "frame-support", "parachains-common", "penpal-runtime", - "sp-core", + "sp-core 28.0.0", "staging-xcm", ] @@ -12615,11 +13306,11 @@ dependencies = [ "sp-api", "sp-block-builder", "sp-consensus-aura", - "sp-core", + "sp-core 28.0.0", "sp-genesis-builder", "sp-inherents", "sp-offchain", - "sp-runtime", + "sp-runtime 31.0.1", "sp-session", "sp-storage 19.0.0", "sp-transaction-pool", @@ -12641,7 +13332,7 @@ dependencies = [ "frame-support", "parachains-common", "people-rococo-runtime", - "sp-core", + "sp-core 28.0.0", "testnet-parachains-constants", ] @@ -12660,7 +13351,7 @@ dependencies = [ "polkadot-runtime-common", "rococo-runtime-constants", "rococo-system-emulated-network", - "sp-runtime", + "sp-runtime 31.0.1", "staging-xcm", "staging-xcm-executor", ] @@ -12713,11 +13404,11 @@ dependencies = [ "sp-api", "sp-block-builder", "sp-consensus-aura", - "sp-core", + "sp-core 28.0.0", "sp-genesis-builder", "sp-inherents", "sp-offchain", - "sp-runtime", + "sp-runtime 31.0.1", "sp-session", "sp-storage 19.0.0", "sp-transaction-pool", @@ -12740,7 +13431,7 @@ dependencies = [ "frame-support", "parachains-common", "people-westend-runtime", - "sp-core", + "sp-core 28.0.0", "testnet-parachains-constants", ] @@ -12758,7 +13449,7 @@ dependencies = [ "parachains-common", "parity-scale-codec", "polkadot-runtime-common", - "sp-runtime", + "sp-runtime 31.0.1", "staging-xcm", "staging-xcm-executor", "westend-runtime-constants", @@ -12812,11 +13503,11 @@ dependencies = [ "sp-api", "sp-block-builder", "sp-consensus-aura", - "sp-core", + "sp-core 28.0.0", "sp-genesis-builder", "sp-inherents", "sp-offchain", - "sp-runtime", + "sp-runtime 31.0.1", "sp-session", "sp-storage 19.0.0", "sp-transaction-pool", @@ -13015,12 +13706,12 @@ dependencies = [ "polkadot-primitives-test-helpers", "rand", "rand_chacha", - "rand_core", + "rand_core 0.6.4", "sc-keystore", "schnorrkel 0.11.4", - "sp-application-crypto", + "sp-application-crypto 30.0.0", "sp-authority-discovery", - "sp-core", + "sp-core 28.0.0", "sp-tracing 16.0.0", "tracing-gum", ] @@ -13042,11 +13733,11 @@ dependencies = [ "polkadot-primitives", "rand", "rand_chacha", - "sp-application-crypto", + "sp-application-crypto 30.0.0", "sp-authority-discovery", - "sp-core", + "sp-core 28.0.0", "sp-keyring", - "sp-keystore", + "sp-keystore 0.34.0", "sp-tracing 16.0.0", "tracing-gum", ] @@ -13074,9 +13765,9 @@ dependencies = [ "rstest", "sc-network", "schnellru", - "sp-core", + "sp-core 28.0.0", "sp-keyring", - "sp-keystore", + "sp-keystore 0.34.0", "sp-tracing 16.0.0", "thiserror", "tracing-gum", @@ -13106,8 +13797,8 @@ dependencies = [ "rstest", "sc-network", "schnellru", - "sp-application-crypto", - "sp-core", + "sp-application-crypto 30.0.0", + "sp-core 28.0.0", "sp-keyring", "sp-tracing 16.0.0", "thiserror", @@ -13145,11 +13836,11 @@ dependencies = [ "sc-storage-monitor", "sc-sysinfo", "sc-tracing", - "sp-core", - "sp-io", + "sp-core 28.0.0", + "sp-io 30.0.0", "sp-keyring", "sp-maybe-compressed-blob", - "sp-runtime", + "sp-runtime 31.0.1", "substrate-build-script-utils", "thiserror", ] @@ -13175,10 +13866,10 @@ dependencies = [ "sc-keystore", "sc-network", "schnellru", - "sp-core", + "sp-core 28.0.0", "sp-keyring", - "sp-keystore", - "sp-runtime", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", "sp-tracing 16.0.0", "thiserror", "tokio-util", @@ -13191,8 +13882,8 @@ version = "7.0.0" dependencies = [ "parity-scale-codec", "scale-info", - "sp-core", - "sp-runtime", + "sp-core 28.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -13200,7 +13891,7 @@ name = "polkadot-dispute-distribution" version = "7.0.0" dependencies = [ "assert_matches", - "async-channel", + "async-channel 1.9.0", "async-trait", "derive_more", "fatality", @@ -13220,9 +13911,9 @@ dependencies = [ "sc-keystore", "sc-network", "schnellru", - "sp-application-crypto", + "sp-application-crypto 30.0.0", "sp-keyring", - "sp-keystore", + "sp-keystore 0.34.0", "sp-tracing 16.0.0", "thiserror", "tracing-gum", @@ -13238,8 +13929,8 @@ dependencies = [ "polkadot-primitives", "quickcheck", "reed-solomon-novelpoly", - "sp-core", - "sp-trie", + "sp-core 28.0.0", + "sp-trie 29.0.0", "thiserror", ] @@ -13263,13 +13954,13 @@ dependencies = [ "rand_chacha", "sc-network", "sc-network-common", - "sp-application-crypto", + "sp-application-crypto 30.0.0", "sp-authority-discovery", "sp-consensus-babe", - "sp-core", - "sp-crypto-hashing", + "sp-core 28.0.0", + "sp-crypto-hashing 0.1.0", "sp-keyring", - "sp-keystore", + "sp-keystore 0.34.0", "sp-tracing 16.0.0", "tracing-gum", ] @@ -13297,7 +13988,7 @@ dependencies = [ "polkadot-primitives-test-helpers", "sc-network", "sp-consensus", - "sp-core", + "sp-core 28.0.0", "sp-keyring", "thiserror", "tracing-gum", @@ -13318,7 +14009,7 @@ dependencies = [ "polkadot-primitives", "polkadot-primitives-test-helpers", "rstest", - "sp-core", + "sp-core 28.0.0", "sp-keyring", "sp-maybe-compressed-blob", "thiserror", @@ -13353,18 +14044,18 @@ dependencies = [ "polkadot-subsystem-bench", "rand", "rand_chacha", - "rand_core", + "rand_core 0.6.4", "sc-keystore", "schnellru", "schnorrkel 0.11.4", - "sp-application-crypto", + "sp-application-crypto 30.0.0", "sp-consensus", "sp-consensus-babe", "sp-consensus-slots", - "sp-core", + "sp-core 28.0.0", "sp-keyring", - "sp-keystore", - "sp-runtime", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", "sp-tracing 16.0.0", "thiserror", "tracing-gum", @@ -13393,7 +14084,7 @@ dependencies = [ "polkadot-primitives", "polkadot-primitives-test-helpers", "sp-consensus", - "sp-core", + "sp-core 28.0.0", "sp-keyring", "sp-tracing 16.0.0", "thiserror", @@ -13419,10 +14110,10 @@ dependencies = [ "rstest", "sc-keystore", "schnellru", - "sp-application-crypto", - "sp-core", + "sp-application-crypto 30.0.0", + "sp-core 28.0.0", "sp-keyring", - "sp-keystore", + "sp-keystore 0.34.0", "sp-tracing 16.0.0", "thiserror", "tracing-gum", @@ -13438,7 +14129,7 @@ dependencies = [ "polkadot-node-subsystem-util", "polkadot-primitives", "polkadot-primitives-test-helpers", - "sp-keystore", + "sp-keystore 0.34.0", "thiserror", "tracing-gum", "wasm-timer", @@ -13463,10 +14154,10 @@ dependencies = [ "polkadot-parachain-primitives", "polkadot-primitives", "polkadot-primitives-test-helpers", - "sp-application-crypto", - "sp-core", + "sp-application-crypto 30.0.0", + "sp-core 28.0.0", "sp-keyring", - "sp-keystore", + "sp-keystore 0.34.0", "sp-maybe-compressed-blob", "tracing-gum", ] @@ -13487,7 +14178,7 @@ dependencies = [ "sc-client-api", "sc-consensus-babe", "sp-blockchain", - "sp-core", + "sp-core 28.0.0", "tracing-gum", ] @@ -13507,7 +14198,7 @@ dependencies = [ "polkadot-node-subsystem-test-helpers", "polkadot-node-subsystem-util", "polkadot-primitives", - "sp-core", + "sp-core 28.0.0", "thiserror", "tracing-gum", ] @@ -13531,10 +14222,10 @@ dependencies = [ "polkadot-primitives-test-helpers", "sc-keystore", "schnellru", - "sp-application-crypto", - "sp-core", + "sp-application-crypto 30.0.0", + "sp-core 28.0.0", "sp-keyring", - "sp-keystore", + "sp-keystore 0.34.0", "sp-tracing 16.0.0", "thiserror", "tracing-gum", @@ -13570,7 +14261,7 @@ dependencies = [ "polkadot-primitives-test-helpers", "rand", "rstest", - "sp-core", + "sp-core 28.0.0", "sp-tracing 16.0.0", "thiserror", "tracing-gum", @@ -13592,8 +14283,8 @@ dependencies = [ "polkadot-primitives-test-helpers", "rstest", "schnellru", - "sp-application-crypto", - "sp-keystore", + "sp-application-crypto 30.0.0", + "sp-keystore 0.34.0", "thiserror", "tracing-gum", ] @@ -13631,7 +14322,7 @@ dependencies = [ "rusty-fork", "sc-sysinfo", "slotmap", - "sp-core", + "sp-core 28.0.0", "sp-maybe-compressed-blob", "tempfile", "test-parachain-adder", @@ -13655,11 +14346,11 @@ dependencies = [ "polkadot-primitives", "polkadot-primitives-test-helpers", "sc-keystore", - "sp-application-crypto", - "sp-core", + "sp-application-crypto 30.0.0", + "sp-core 28.0.0", "sp-keyring", - "sp-keystore", - "sp-runtime", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", "thiserror", "tracing-gum", ] @@ -13681,10 +14372,10 @@ dependencies = [ "sc-executor-common", "sc-executor-wasmtime", "seccompiler", - "sp-core", - "sp-crypto-hashing", + "sp-core 28.0.0", + "sp-crypto-hashing 0.1.0", "sp-externalities 0.25.0", - "sp-io", + "sp-io 30.0.0", "sp-tracing 16.0.0", "tempfile", "thiserror", @@ -13748,7 +14439,7 @@ dependencies = [ "schnellru", "sp-api", "sp-consensus-babe", - "sp-core", + "sp-core 28.0.0", "sp-keyring", "tracing-gum", ] @@ -13766,7 +14457,7 @@ dependencies = [ "polkadot-primitives", "sc-network", "sc-network-types", - "sp-core", + "sp-core 28.0.0", "thiserror", "tokio", ] @@ -13803,7 +14494,7 @@ dependencies = [ name = "polkadot-node-network-protocol" version = "7.0.0" dependencies = [ - "async-channel", + "async-channel 1.9.0", "async-trait", "bitvec", "derive_more", @@ -13819,7 +14510,7 @@ dependencies = [ "sc-authority-discovery", "sc-network", "sc-network-types", - "sp-runtime", + "sp-runtime 31.0.1", "strum 0.26.2", "thiserror", "tracing-gum", @@ -13840,13 +14531,13 @@ dependencies = [ "sc-keystore", "schnorrkel 0.11.4", "serde", - "sp-application-crypto", + "sp-application-crypto 30.0.0", "sp-consensus-babe", "sp-consensus-slots", - "sp-core", - "sp-keystore", + "sp-core 28.0.0", + "sp-keystore 0.34.0", "sp-maybe-compressed-blob", - "sp-runtime", + "sp-runtime 31.0.1", "thiserror", "zstd 0.12.4", ] @@ -13875,10 +14566,10 @@ dependencies = [ "sc-client-api", "sc-keystore", "sc-utils", - "sp-application-crypto", - "sp-core", + "sp-application-crypto 30.0.0", + "sp-core 28.0.0", "sp-keyring", - "sp-keystore", + "sp-keystore 0.34.0", ] [[package]] @@ -13905,7 +14596,7 @@ dependencies = [ "sp-authority-discovery", "sp-blockchain", "sp-consensus-babe", - "sp-runtime", + "sp-runtime 31.0.1", "substrate-prometheus-endpoint", "thiserror", ] @@ -13945,9 +14636,9 @@ dependencies = [ "rand", "sc-client-api", "schnellru", - "sp-application-crypto", - "sp-core", - "sp-keystore", + "sp-application-crypto 30.0.0", + "sp-core 28.0.0", + "sp-keystore 0.34.0", "tempfile", "thiserror", "tracing-gum", @@ -13974,7 +14665,7 @@ dependencies = [ "prioritized-metered-channel", "sc-client-api", "sp-api", - "sp-core", + "sp-core 28.0.0", "tikv-jemalloc-ctl", "tracing-gum", ] @@ -14010,8 +14701,8 @@ dependencies = [ "serde", "serde_json", "shell-runtime", - "sp-core", - "sp-runtime", + "sp-core 28.0.0", + "sp-runtime 31.0.1", "staging-xcm", "substrate-build-script-utils", "testnet-parachains-constants", @@ -14043,7 +14734,7 @@ dependencies = [ "frame-system-rpc-runtime-api", "frame-try-runtime", "futures", - "jsonrpsee", + "jsonrpsee 0.24.3", "log", "nix 0.28.0", "pallet-transaction-payment", @@ -14072,16 +14763,16 @@ dependencies = [ "sp-api", "sp-block-builder", "sp-consensus-aura", - "sp-core", + "sp-core 28.0.0", "sp-genesis-builder", "sp-inherents", - "sp-keystore", - "sp-runtime", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", "sp-session", "sp-timestamp", "sp-transaction-pool", "sp-version", - "sp-weights", + "sp-weights 27.0.0", "substrate-frame-rpc-system", "substrate-prometheus-endpoint", "substrate-state-trie-migration-rpc", @@ -14099,9 +14790,9 @@ dependencies = [ "polkadot-core-primitives", "scale-info", "serde", - "sp-core", - "sp-runtime", - "sp-weights", + "sp-core 28.0.0", + "sp-runtime 31.0.1", + "sp-weights 27.0.0", ] [[package]] @@ -14118,15 +14809,15 @@ dependencies = [ "scale-info", "serde", "sp-api", - "sp-application-crypto", - "sp-arithmetic", + "sp-application-crypto 30.0.0", + "sp-arithmetic 23.0.0", "sp-authority-discovery", "sp-consensus-slots", - "sp-core", + "sp-core 28.0.0", "sp-inherents", - "sp-io", - "sp-keystore", - "sp-runtime", + "sp-io 30.0.0", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", "sp-staking", "sp-std 14.0.0", ] @@ -14137,17 +14828,17 @@ version = "1.0.0" dependencies = [ "polkadot-primitives", "rand", - "sp-application-crypto", - "sp-core", + "sp-application-crypto 30.0.0", + "sp-core 28.0.0", "sp-keyring", - "sp-runtime", + "sp-runtime 31.0.1", ] [[package]] name = "polkadot-rpc" version = "7.0.0" dependencies = [ - "jsonrpsee", + "jsonrpsee 0.24.3", "mmr-rpc", "pallet-transaction-payment-rpc", "polkadot-primitives", @@ -14165,14 +14856,14 @@ dependencies = [ "sc-sync-state-rpc", "sc-transaction-pool-api", "sp-api", - "sp-application-crypto", + "sp-application-crypto 30.0.0", "sp-block-builder", "sp-blockchain", "sp-consensus", "sp-consensus-babe", "sp-consensus-beefy", - "sp-keystore", - "sp-runtime", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", "substrate-frame-rpc-system", "substrate-state-trie-migration-rpc", ] @@ -14217,13 +14908,13 @@ dependencies = [ "serde_json", "slot-range-helper", "sp-api", - "sp-core", + "sp-core 28.0.0", "sp-inherents", - "sp-io", + "sp-io 30.0.0", "sp-keyring", - "sp-keystore", + "sp-keystore 0.34.0", "sp-npos-elections", - "sp-runtime", + "sp-runtime 31.0.1", "sp-session", "sp-staking", "staging-xcm", @@ -14284,15 +14975,15 @@ dependencies = [ "serde", "serde_json", "sp-api", - "sp-application-crypto", - "sp-arithmetic", - "sp-core", - "sp-crypto-hashing", + "sp-application-crypto 30.0.0", + "sp-arithmetic 23.0.0", + "sp-core 28.0.0", + "sp-crypto-hashing 0.1.0", "sp-inherents", - "sp-io", + "sp-io 30.0.0", "sp-keyring", - "sp-keystore", - "sp-runtime", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", "sp-session", "sp-staking", "sp-std 14.0.0", @@ -14606,8 +15297,8 @@ dependencies = [ "snowbridge-system-runtime-api", "sp-api", "sp-api-proc-macro", - "sp-application-crypto", - "sp-arithmetic", + "sp-application-crypto 30.0.0", + "sp-arithmetic 23.0.0", "sp-authority-discovery", "sp-block-builder", "sp-blockchain", @@ -14618,34 +15309,34 @@ dependencies = [ "sp-consensus-grandpa", "sp-consensus-pow", "sp-consensus-slots", - "sp-core", + "sp-core 28.0.0", "sp-core-hashing", "sp-core-hashing-proc-macro", "sp-crypto-ec-utils 0.10.0", - "sp-crypto-hashing", + "sp-crypto-hashing 0.1.0", "sp-crypto-hashing-proc-macro", "sp-database", "sp-debug-derive 14.0.0", "sp-externalities 0.25.0", "sp-genesis-builder", "sp-inherents", - "sp-io", + "sp-io 30.0.0", "sp-keyring", - "sp-keystore", + "sp-keystore 0.34.0", "sp-maybe-compressed-blob", "sp-metadata-ir", "sp-mixnet", "sp-mmr-primitives", "sp-npos-elections", "sp-offchain", - "sp-panic-handler", + "sp-panic-handler 13.0.0", "sp-rpc", - "sp-runtime", + "sp-runtime 31.0.1", "sp-runtime-interface 24.0.0", "sp-runtime-interface-proc-macro 17.0.0", "sp-session", "sp-staking", - "sp-state-machine", + "sp-state-machine 0.35.0", "sp-statement-store", "sp-std 14.0.0", "sp-storage 19.0.0", @@ -14653,11 +15344,11 @@ dependencies = [ "sp-tracing 16.0.0", "sp-transaction-pool", "sp-transaction-storage-proof", - "sp-trie", + "sp-trie 29.0.0", "sp-version", "sp-version-proc-macro", "sp-wasm-interface 20.0.0", - "sp-weights", + "sp-weights 27.0.0", "staging-chain-spec-builder", "staging-node-inspect", "staging-parachain-info", @@ -14666,7 +15357,7 @@ dependencies = [ "staging-xcm-builder", "staging-xcm-executor", "subkey", - "substrate-bip39", + "substrate-bip39 0.4.7", "substrate-build-script-utils", "substrate-frame-rpc-support", "substrate-frame-rpc-system", @@ -14750,13 +15441,13 @@ dependencies = [ "simple-mermaid 0.1.1", "solochain-template-runtime", "sp-api", - "sp-arithmetic", - "sp-core", + "sp-arithmetic 23.0.0", + "sp-core 28.0.0", "sp-genesis-builder", - "sp-io", + "sp-io 30.0.0", "sp-keyring", "sp-offchain", - "sp-runtime", + "sp-runtime 31.0.1", "sp-runtime-interface 24.0.0", "sp-std 14.0.0", "sp-tracing 16.0.0", @@ -14790,15 +15481,15 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-api", - "sp-arithmetic", + "sp-arithmetic 23.0.0", "sp-block-builder", "sp-consensus-aura", "sp-consensus-grandpa", - "sp-core", + "sp-core 28.0.0", "sp-inherents", - "sp-io", + "sp-io 30.0.0", "sp-offchain", - "sp-runtime", + "sp-runtime 31.0.1", "sp-session", "sp-storage 19.0.0", "sp-transaction-pool", @@ -14907,22 +15598,22 @@ dependencies = [ "sp-consensus-babe", "sp-consensus-beefy", "sp-consensus-grandpa", - "sp-core", + "sp-core 28.0.0", "sp-inherents", - "sp-io", + "sp-io 30.0.0", "sp-keyring", - "sp-keystore", + "sp-keystore 0.34.0", "sp-mmr-primitives", "sp-offchain", - "sp-runtime", + "sp-runtime 31.0.1", "sp-session", - "sp-state-machine", + "sp-state-machine 0.35.0", "sp-storage 19.0.0", "sp-timestamp", "sp-tracing 16.0.0", "sp-transaction-pool", "sp-version", - "sp-weights", + "sp-weights 27.0.0", "staging-xcm", "substrate-prometheus-endpoint", "tempfile", @@ -14939,7 +15630,7 @@ version = "7.0.0" dependencies = [ "arrayvec 0.7.4", "assert_matches", - "async-channel", + "async-channel 1.9.0", "bitvec", "fatality", "futures", @@ -14957,11 +15648,11 @@ dependencies = [ "rand_chacha", "sc-keystore", "sc-network", - "sp-application-crypto", + "sp-application-crypto 30.0.0", "sp-authority-discovery", - "sp-core", + "sp-core 28.0.0", "sp-keyring", - "sp-keystore", + "sp-keystore 0.34.0", "sp-staking", "sp-tracing 16.0.0", "thiserror", @@ -14974,7 +15665,7 @@ version = "7.0.0" dependencies = [ "parity-scale-codec", "polkadot-primitives", - "sp-core", + "sp-core 28.0.0", "tracing-gum", ] @@ -15025,7 +15716,7 @@ dependencies = [ "pyroscope_pprofrs", "rand", "rand_chacha", - "rand_core", + "rand_core 0.6.4", "rand_distr", "sc-keystore", "sc-network", @@ -15036,13 +15727,13 @@ dependencies = [ "serde_json", "serde_yaml", "sha1", - "sp-application-crypto", + "sp-application-crypto 30.0.0", "sp-consensus", "sp-consensus-babe", - "sp-core", + "sp-core 28.0.0", "sp-keyring", - "sp-keystore", - "sp-runtime", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", "sp-timestamp", "sp-tracing 16.0.0", "strum 0.26.2", @@ -15071,12 +15762,12 @@ dependencies = [ "sp-blockchain", "sp-consensus", "sp-consensus-babe", - "sp-core", + "sp-core 28.0.0", "sp-inherents", - "sp-io", + "sp-io 30.0.0", "sp-keyring", - "sp-runtime", - "sp-state-machine", + "sp-runtime 31.0.1", + "sp-state-machine 0.35.0", "sp-timestamp", "substrate-test-client", ] @@ -15107,8 +15798,8 @@ dependencies = [ "polkadot-node-subsystem-util", "polkadot-primitives", "rand", - "sp-core", - "sp-keystore", + "sp-core 28.0.0", + "sp-keystore 0.34.0", "substrate-build-script-utils", "tracing-gum", ] @@ -15152,18 +15843,18 @@ dependencies = [ "sp-block-builder", "sp-consensus-babe", "sp-consensus-beefy", - "sp-core", + "sp-core 28.0.0", "sp-genesis-builder", "sp-inherents", - "sp-io", + "sp-io 30.0.0", "sp-keyring", "sp-mmr-primitives", "sp-offchain", - "sp-runtime", + "sp-runtime 31.0.1", "sp-session", "sp-staking", "sp-transaction-pool", - "sp-trie", + "sp-trie 29.0.0", "sp-version", "staging-xcm", "staging-xcm-builder", @@ -15206,17 +15897,17 @@ dependencies = [ "sc-tracing", "sc-transaction-pool", "serde_json", - "sp-arithmetic", + "sp-arithmetic 23.0.0", "sp-authority-discovery", "sp-blockchain", "sp-consensus", "sp-consensus-babe", "sp-consensus-grandpa", - "sp-core", + "sp-core 28.0.0", "sp-inherents", "sp-keyring", - "sp-runtime", - "sp-state-machine", + "sp-runtime 31.0.1", + "sp-state-machine 0.35.0", "substrate-test-client", "substrate-test-utils", "tempfile", @@ -15231,7 +15922,7 @@ version = "7.0.0" dependencies = [ "clap 4.5.11", "generate-bags", - "sp-io", + "sp-io 30.0.0", "westend-runtime", ] @@ -15279,6 +15970,12 @@ dependencies = [ "log", ] +[[package]] +name = "polkavm-common" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92c99f7eee94e7be43ba37eef65ad0ee8cbaf89b7c00001c3f6d2be985cb1817" + [[package]] name = "polkavm-common" version = "0.9.0" @@ -15298,6 +15995,15 @@ dependencies = [ "polkavm-assembler 0.10.0", ] +[[package]] +name = "polkavm-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79fa916f7962348bd1bb1a65a83401675e6fc86c51a0fdbcf92a3108e58e6125" +dependencies = [ + "polkavm-derive-impl-macro 0.8.0", +] + [[package]] name = "polkavm-derive" version = "0.9.1" @@ -15316,6 +16022,18 @@ dependencies = [ "polkavm-derive-impl-macro 0.10.0", ] +[[package]] +name = "polkavm-derive-impl" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c10b2654a8a10a83c260bfb93e97b262cf0017494ab94a65d389e0eda6de6c9c" +dependencies = [ + "polkavm-common 0.8.0", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.65", +] + [[package]] name = "polkavm-derive-impl" version = "0.9.0" @@ -15340,6 +16058,16 @@ dependencies = [ "syn 2.0.65", ] +[[package]] +name = "polkavm-derive-impl-macro" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15e85319a0d5129dc9f021c62607e0804f5fb777a05cdda44d750ac0732def66" +dependencies = [ + "polkavm-derive-impl 0.8.0", + "syn 2.0.65", +] + [[package]] name = "polkavm-derive-impl-macro" version = "0.9.0" @@ -15367,7 +16095,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c7be503e60cf56c0eb785f90aaba4b583b36bff00e93997d93fef97f9553c39" dependencies = [ "gimli 0.28.0", - "hashbrown 0.14.3", + "hashbrown 0.14.5", "log", "object 0.32.2", "polkavm-common 0.9.0", @@ -15382,7 +16110,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d704edfe7bdcc876784f19436d53d515b65eb07bc9a0fae77085d552c2dbbb5" dependencies = [ "gimli 0.28.0", - "hashbrown 0.14.3", + "hashbrown 0.14.5", "log", "object 0.36.1", "polkavm-common 0.10.0", @@ -15579,9 +16307,9 @@ dependencies = [ [[package]] name = "primitive-types" -version = "0.12.1" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f3486ccba82358b11a77516035647c34ba167dfa53312630de83b12bd4f3d66" +checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" dependencies = [ "fixed-hash", "impl-codec", @@ -16106,7 +16834,7 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -16116,9 +16844,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", ] +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" + [[package]] name = "rand_core" version = "0.6.4" @@ -16144,7 +16878,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59cad018caf63deb318e5a4586d99a24424a364f40f1e5778c29aca23f4fc73e" dependencies = [ - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -16153,7 +16887,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" dependencies = [ - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -16217,12 +16951,28 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffbe84efe2f38dea12e9bfc1f65377fdf03e53a18cb3b995faedf7934c7e785b" dependencies = [ - "pem", + "pem 1.1.1", "ring 0.16.20", "time", "yasna", ] +[[package]] +name = "reconnecting-jsonrpsee-ws-client" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06fa4f17e09edfc3131636082faaec633c7baa269396b4004040bc6c52f49f65" +dependencies = [ + "cfg_aliases 0.2.1", + "finito", + "futures", + "jsonrpsee 0.23.2", + "serde_json", + "thiserror", + "tokio", + "tracing", +] + [[package]] name = "redox_syscall" version = "0.2.16" @@ -16320,13 +17070,13 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.2" +version = "1.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.3", + "regex-automata 0.4.7", "regex-syntax 0.8.2", ] @@ -16347,9 +17097,9 @@ checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" [[package]] name = "regex-automata" -version = "0.4.3" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" dependencies = [ "aho-corasick", "memchr", @@ -16387,7 +17137,7 @@ dependencies = [ "finality-relay", "frame-support", "futures", - "jsonrpsee", + "jsonrpsee 0.24.3", "log", "num-traits", "pallet-transaction-payment", @@ -16403,11 +17153,11 @@ dependencies = [ "scale-info", "serde_json", "sp-consensus-grandpa", - "sp-core", + "sp-core 28.0.0", "sp-rpc", - "sp-runtime", + "sp-runtime 31.0.1", "sp-std 14.0.0", - "sp-trie", + "sp-trie 29.0.0", "sp-version", "staging-xcm", "thiserror", @@ -16431,7 +17181,7 @@ dependencies = [ "num-traits", "parking_lot 0.12.3", "serde_json", - "sp-runtime", + "sp-runtime 31.0.1", "sp-tracing 16.0.0", "substrate-prometheus-endpoint", "sysinfo", @@ -16448,7 +17198,7 @@ dependencies = [ "frame-system", "log", "pallet-bags-list-remote-tests", - "sp-core", + "sp-core 28.0.0", "sp-tracing 16.0.0", "tokio", "westend-runtime", @@ -16461,7 +17211,7 @@ version = "0.11.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1" dependencies = [ - "base64 0.21.2", + "base64 0.21.7", "bytes", "encoding_rs", "futures-core", @@ -16471,10 +17221,12 @@ dependencies = [ "http-body 0.4.5", "hyper 0.14.29", "hyper-rustls 0.24.2", + "hyper-tls", "ipnet", "js-sys", "log", "mime", + "native-tls", "once_cell", "percent-encoding", "pin-project-lite", @@ -16484,6 +17236,7 @@ dependencies = [ "serde_json", "serde_urlencoded", "tokio", + "tokio-native-tls", "tokio-rustls 0.24.1", "tower-service", "url", @@ -16599,7 +17352,7 @@ dependencies = [ "sp-authority-discovery", "sp-consensus-babe", "sp-consensus-beefy", - "sp-core", + "sp-core 28.0.0", ] [[package]] @@ -16637,11 +17390,11 @@ dependencies = [ "sp-api", "sp-block-builder", "sp-consensus-aura", - "sp-core", + "sp-core 28.0.0", "sp-genesis-builder", "sp-inherents", "sp-offchain", - "sp-runtime", + "sp-runtime 31.0.1", "sp-session", "sp-transaction-pool", "sp-version", @@ -16728,26 +17481,26 @@ dependencies = [ "serde_json", "smallvec", "sp-api", - "sp-arithmetic", + "sp-arithmetic 23.0.0", "sp-authority-discovery", "sp-block-builder", "sp-consensus-babe", "sp-consensus-beefy", "sp-consensus-grandpa", - "sp-core", + "sp-core 28.0.0", "sp-genesis-builder", "sp-inherents", - "sp-io", + "sp-io 30.0.0", "sp-keyring", "sp-mmr-primitives", "sp-offchain", - "sp-runtime", + "sp-runtime 31.0.1", "sp-session", "sp-staking", "sp-storage 19.0.0", "sp-tracing 16.0.0", "sp-transaction-pool", - "sp-trie", + "sp-trie 29.0.0", "sp-version", "staging-xcm", "staging-xcm-builder", @@ -16767,9 +17520,9 @@ dependencies = [ "polkadot-primitives", "polkadot-runtime-common", "smallvec", - "sp-core", - "sp-runtime", - "sp-weights", + "sp-core 28.0.0", + "sp-runtime 31.0.1", + "sp-weights 27.0.0", "staging-xcm", "staging-xcm-builder", ] @@ -17026,6 +17779,20 @@ dependencies = [ "sct", ] +[[package]] +name = "rustls" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" +dependencies = [ + "log", + "ring 0.17.7", + "rustls-pki-types", + "rustls-webpki 0.102.4", + "subtle 2.5.0", + "zeroize", +] + [[package]] name = "rustls" version = "0.23.10" @@ -17072,7 +17839,7 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" dependencies = [ - "base64 0.21.2", + "base64 0.21.7", ] [[package]] @@ -17081,7 +17848,7 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35e4980fa29e4c4b212ffb3db068a564cbf560e51d3944b7c88bd8bf5bec64f4" dependencies = [ - "base64 0.21.2", + "base64 0.21.7", "rustls-pki-types", ] @@ -17168,6 +17935,17 @@ dependencies = [ "twox-hash", ] +[[package]] +name = "ruzstd" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58c4eb8a81997cf040a091d1f7e1938aeab6749d3a0dfa73af43cdc32393483d" +dependencies = [ + "byteorder", + "derive_more", + "twox-hash", +] + [[package]] name = "rw-stream-sink" version = "0.4.0" @@ -17217,7 +17995,7 @@ name = "sc-allocator" version = "23.0.0" dependencies = [ "log", - "sp-core", + "sp-core 28.0.0", "sp-wasm-interface 20.0.0", "thiserror", ] @@ -17245,9 +18023,9 @@ dependencies = [ "sp-api", "sp-authority-discovery", "sp-blockchain", - "sp-core", - "sp-keystore", - "sp-runtime", + "sp-core 28.0.0", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", "sp-tracing 16.0.0", "substrate-prometheus-endpoint", "substrate-test-runtime-client", @@ -17272,9 +18050,9 @@ dependencies = [ "sp-api", "sp-blockchain", "sp-consensus", - "sp-core", + "sp-core 28.0.0", "sp-inherents", - "sp-runtime", + "sp-runtime 31.0.1", "substrate-prometheus-endpoint", "substrate-test-runtime-client", ] @@ -17287,11 +18065,11 @@ dependencies = [ "sp-api", "sp-block-builder", "sp-blockchain", - "sp-core", + "sp-core 28.0.0", "sp-inherents", - "sp-runtime", - "sp-state-machine", - "sp-trie", + "sp-runtime 31.0.1", + "sp-state-machine 0.35.0", + "sp-trie 29.0.0", "substrate-test-runtime-client", ] @@ -17313,16 +18091,16 @@ dependencies = [ "sc-telemetry", "serde", "serde_json", - "sp-application-crypto", + "sp-application-crypto 30.0.0", "sp-blockchain", "sp-consensus-babe", - "sp-core", - "sp-crypto-hashing", + "sp-core 28.0.0", + "sp-crypto-hashing 0.1.0", "sp-genesis-builder", - "sp-io", + "sp-io 30.0.0", "sp-keyring", - "sp-runtime", - "sp-state-machine", + "sp-runtime 31.0.1", + "sp-state-machine 0.35.0", "sp-tracing 16.0.0", "substrate-test-runtime", ] @@ -17368,11 +18146,11 @@ dependencies = [ "serde", "serde_json", "sp-blockchain", - "sp-core", + "sp-core 28.0.0", "sp-keyring", - "sp-keystore", - "sp-panic-handler", - "sp-runtime", + "sp-keystore 0.34.0", + "sp-panic-handler 13.0.0", + "sp-runtime 31.0.1", "sp-tracing 16.0.0", "sp-version", "tempfile", @@ -17395,15 +18173,15 @@ dependencies = [ "sp-api", "sp-blockchain", "sp-consensus", - "sp-core", + "sp-core 28.0.0", "sp-database", "sp-externalities 0.25.0", - "sp-runtime", - "sp-state-machine", + "sp-runtime 31.0.1", + "sp-state-machine 0.35.0", "sp-statement-store", "sp-storage 19.0.0", "sp-test-primitives", - "sp-trie", + "sp-trie 29.0.0", "substrate-prometheus-endpoint", "substrate-test-runtime", "thiserror", @@ -17430,14 +18208,14 @@ dependencies = [ "sc-client-api", "sc-state-db", "schnellru", - "sp-arithmetic", + "sp-arithmetic 23.0.0", "sp-blockchain", - "sp-core", + "sp-core 28.0.0", "sp-database", - "sp-runtime", - "sp-state-machine", + "sp-runtime 31.0.1", + "sp-state-machine 0.35.0", "sp-tracing 16.0.0", - "sp-trie", + "sp-trie 29.0.0", "substrate-test-runtime-client", "tempfile", ] @@ -17458,9 +18236,9 @@ dependencies = [ "sp-api", "sp-blockchain", "sp-consensus", - "sp-core", - "sp-runtime", - "sp-state-machine", + "sp-core 28.0.0", + "sp-runtime 31.0.1", + "sp-state-machine 0.35.0", "sp-test-primitives", "substrate-prometheus-endpoint", "thiserror", @@ -17484,17 +18262,17 @@ dependencies = [ "sc-network-test", "sc-telemetry", "sp-api", - "sp-application-crypto", + "sp-application-crypto 30.0.0", "sp-block-builder", "sp-blockchain", "sp-consensus", "sp-consensus-aura", "sp-consensus-slots", - "sp-core", + "sp-core 28.0.0", "sp-inherents", "sp-keyring", - "sp-keystore", - "sp-runtime", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", "sp-timestamp", "sp-tracing 16.0.0", "substrate-prometheus-endpoint", @@ -17526,18 +18304,18 @@ dependencies = [ "sc-telemetry", "sc-transaction-pool-api", "sp-api", - "sp-application-crypto", + "sp-application-crypto 30.0.0", "sp-block-builder", "sp-blockchain", "sp-consensus", "sp-consensus-babe", "sp-consensus-slots", - "sp-core", - "sp-crypto-hashing", + "sp-core 28.0.0", + "sp-crypto-hashing 0.1.0", "sp-inherents", "sp-keyring", - "sp-keystore", - "sp-runtime", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", "sp-timestamp", "sp-tracing 16.0.0", "substrate-prometheus-endpoint", @@ -17551,7 +18329,7 @@ name = "sc-consensus-babe-rpc" version = "0.34.0" dependencies = [ "futures", - "jsonrpsee", + "jsonrpsee 0.24.3", "sc-consensus", "sc-consensus-babe", "sc-consensus-epochs", @@ -17561,14 +18339,14 @@ dependencies = [ "serde", "serde_json", "sp-api", - "sp-application-crypto", + "sp-application-crypto 30.0.0", "sp-blockchain", "sp-consensus", "sp-consensus-babe", - "sp-core", + "sp-core 28.0.0", "sp-keyring", - "sp-keystore", - "sp-runtime", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", "substrate-test-runtime-client", "thiserror", "tokio", @@ -17579,7 +18357,7 @@ name = "sc-consensus-beefy" version = "13.0.0" dependencies = [ "array-bytes", - "async-channel", + "async-channel 1.9.0", "async-trait", "fnv", "futures", @@ -17597,18 +18375,18 @@ dependencies = [ "sc-utils", "serde", "sp-api", - "sp-application-crypto", - "sp-arithmetic", + "sp-application-crypto 30.0.0", + "sp-arithmetic 23.0.0", "sp-blockchain", "sp-consensus", "sp-consensus-beefy", "sp-consensus-grandpa", - "sp-core", - "sp-crypto-hashing", + "sp-core 28.0.0", + "sp-crypto-hashing 0.1.0", "sp-keyring", - "sp-keystore", + "sp-keystore 0.34.0", "sp-mmr-primitives", - "sp-runtime", + "sp-runtime 31.0.1", "sp-tracing 16.0.0", "substrate-prometheus-endpoint", "substrate-test-runtime-client", @@ -17623,7 +18401,7 @@ name = "sc-consensus-beefy-rpc" version = "13.0.0" dependencies = [ "futures", - "jsonrpsee", + "jsonrpsee 0.24.3", "log", "parity-scale-codec", "parking_lot 0.12.3", @@ -17631,10 +18409,10 @@ dependencies = [ "sc-rpc", "serde", "serde_json", - "sp-application-crypto", + "sp-application-crypto 30.0.0", "sp-consensus-beefy", - "sp-core", - "sp-runtime", + "sp-core 28.0.0", + "sp-runtime 31.0.1", "substrate-test-runtime-client", "thiserror", "tokio", @@ -17649,7 +18427,7 @@ dependencies = [ "sc-client-api", "sc-consensus", "sp-blockchain", - "sp-runtime", + "sp-runtime 31.0.1", ] [[package]] @@ -17685,16 +18463,16 @@ dependencies = [ "serde", "serde_json", "sp-api", - "sp-application-crypto", - "sp-arithmetic", + "sp-application-crypto 30.0.0", + "sp-arithmetic 23.0.0", "sp-blockchain", "sp-consensus", "sp-consensus-grandpa", - "sp-core", - "sp-crypto-hashing", + "sp-core 28.0.0", + "sp-crypto-hashing 0.1.0", "sp-keyring", - "sp-keystore", - "sp-runtime", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", "sp-tracing 16.0.0", "substrate-prometheus-endpoint", "substrate-test-runtime-client", @@ -17708,7 +18486,7 @@ version = "0.19.0" dependencies = [ "finality-grandpa", "futures", - "jsonrpsee", + "jsonrpsee 0.24.3", "log", "parity-scale-codec", "sc-block-builder", @@ -17718,9 +18496,9 @@ dependencies = [ "serde", "sp-blockchain", "sp-consensus-grandpa", - "sp-core", + "sp-core 28.0.0", "sp-keyring", - "sp-runtime", + "sp-runtime 31.0.1", "substrate-test-runtime-client", "thiserror", "tokio", @@ -17734,7 +18512,7 @@ dependencies = [ "async-trait", "futures", "futures-timer", - "jsonrpsee", + "jsonrpsee 0.24.3", "log", "parity-scale-codec", "sc-basic-authorship", @@ -17752,10 +18530,10 @@ dependencies = [ "sp-consensus-aura", "sp-consensus-babe", "sp-consensus-slots", - "sp-core", + "sp-core 28.0.0", "sp-inherents", - "sp-keystore", - "sp-runtime", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", "sp-timestamp", "substrate-prometheus-endpoint", "substrate-test-runtime-client", @@ -17781,9 +18559,9 @@ dependencies = [ "sp-blockchain", "sp-consensus", "sp-consensus-pow", - "sp-core", + "sp-core 28.0.0", "sp-inherents", - "sp-runtime", + "sp-runtime 31.0.1", "substrate-prometheus-endpoint", "thiserror", ] @@ -17800,14 +18578,14 @@ dependencies = [ "sc-client-api", "sc-consensus", "sc-telemetry", - "sp-arithmetic", + "sp-arithmetic 23.0.0", "sp-blockchain", "sp-consensus", "sp-consensus-slots", - "sp-core", + "sp-core 28.0.0", "sp-inherents", - "sp-runtime", - "sp-state-machine", + "sp-runtime 31.0.1", + "sp-state-machine 0.35.0", "substrate-test-runtime-client", ] @@ -17830,17 +18608,17 @@ dependencies = [ "sc-tracing", "schnellru", "sp-api", - "sp-core", - "sp-crypto-hashing", + "sp-core 28.0.0", + "sp-crypto-hashing 0.1.0", "sp-externalities 0.25.0", - "sp-io", + "sp-io 30.0.0", "sp-maybe-compressed-blob", - "sp-panic-handler", - "sp-runtime", + "sp-panic-handler 13.0.0", + "sp-runtime 31.0.1", "sp-runtime-interface 24.0.0", - "sp-state-machine", + "sp-state-machine 0.35.0", "sp-tracing 16.0.0", - "sp-trie", + "sp-trie 29.0.0", "sp-version", "sp-wasm-interface 20.0.0", "substrate-test-runtime", @@ -17888,7 +18666,7 @@ dependencies = [ "sc-allocator", "sc-executor-common", "sc-runtime-test", - "sp-io", + "sp-io 30.0.0", "sp-runtime-interface 24.0.0", "sp-wasm-interface 20.0.0", "tempfile", @@ -17909,7 +18687,7 @@ dependencies = [ "sc-network-common", "sc-network-sync", "sp-blockchain", - "sp-runtime", + "sp-runtime 31.0.1", ] [[package]] @@ -17919,9 +18697,9 @@ dependencies = [ "array-bytes", "parking_lot 0.12.3", "serde_json", - "sp-application-crypto", - "sp-core", - "sp-keystore", + "sp-application-crypto 30.0.0", + "sp-core 28.0.0", + "sp-keystore 0.34.0", "tempfile", "thiserror", ] @@ -17947,10 +18725,10 @@ dependencies = [ "sc-transaction-pool-api", "sp-api", "sp-consensus", - "sp-core", - "sp-keystore", + "sp-core 28.0.0", + "sp-keystore 0.34.0", "sp-mixnet", - "sp-runtime", + "sp-runtime 31.0.1", "thiserror", ] @@ -17960,7 +18738,7 @@ version = "0.34.0" dependencies = [ "array-bytes", "assert_matches", - "async-channel", + "async-channel 1.9.0", "async-trait", "asynchronous-codec", "bytes", @@ -17995,12 +18773,12 @@ dependencies = [ "serde", "serde_json", "smallvec", - "sp-arithmetic", + "sp-arithmetic 23.0.0", "sp-blockchain", "sp-consensus", - "sp-core", - "sp-crypto-hashing", - "sp-runtime", + "sp-core 28.0.0", + "sp-crypto-hashing 0.1.0", + "sp-runtime 31.0.1", "sp-test-primitives", "sp-tracing 16.0.0", "substrate-prometheus-endpoint", @@ -18032,7 +18810,7 @@ dependencies = [ "sc-network-types", "sp-consensus", "sp-consensus-grandpa", - "sp-runtime", + "sp-runtime 31.0.1", "tempfile", ] @@ -18052,7 +18830,7 @@ dependencies = [ "sc-network-sync", "sc-network-types", "schnellru", - "sp-runtime", + "sp-runtime 31.0.1", "substrate-prometheus-endpoint", "substrate-test-runtime-client", "tokio", @@ -18064,7 +18842,7 @@ name = "sc-network-light" version = "0.33.0" dependencies = [ "array-bytes", - "async-channel", + "async-channel 1.9.0", "futures", "log", "parity-scale-codec", @@ -18074,8 +18852,8 @@ dependencies = [ "sc-network", "sc-network-types", "sp-blockchain", - "sp-core", - "sp-runtime", + "sp-core 28.0.0", + "sp-runtime 31.0.1", "thiserror", ] @@ -18084,7 +18862,7 @@ name = "sc-network-statement" version = "0.16.0" dependencies = [ "array-bytes", - "async-channel", + "async-channel 1.9.0", "futures", "log", "parity-scale-codec", @@ -18093,7 +18871,7 @@ dependencies = [ "sc-network-sync", "sc-network-types", "sp-consensus", - "sp-runtime", + "sp-runtime 31.0.1", "sp-statement-store", "substrate-prometheus-endpoint", ] @@ -18103,7 +18881,7 @@ name = "sc-network-sync" version = "0.33.0" dependencies = [ "array-bytes", - "async-channel", + "async-channel 1.9.0", "async-trait", "fork-tree", "futures", @@ -18124,12 +18902,12 @@ dependencies = [ "sc-utils", "schnellru", "smallvec", - "sp-arithmetic", + "sp-arithmetic 23.0.0", "sp-blockchain", "sp-consensus", "sp-consensus-grandpa", - "sp-core", - "sp-runtime", + "sp-core 28.0.0", + "sp-runtime 31.0.1", "sp-test-primitives", "sp-tracing 16.0.0", "substrate-prometheus-endpoint", @@ -18162,8 +18940,8 @@ dependencies = [ "sc-utils", "sp-blockchain", "sp-consensus", - "sp-core", - "sp-runtime", + "sp-core 28.0.0", + "sp-runtime 31.0.1", "sp-tracing 16.0.0", "substrate-test-runtime", "substrate-test-runtime-client", @@ -18184,7 +18962,7 @@ dependencies = [ "sc-network-types", "sc-utils", "sp-consensus", - "sp-runtime", + "sp-runtime 31.0.1", "substrate-prometheus-endpoint", ] @@ -18235,11 +19013,11 @@ dependencies = [ "sc-utils", "sp-api", "sp-consensus", - "sp-core", + "sp-core 28.0.0", "sp-externalities 0.25.0", - "sp-keystore", + "sp-keystore 0.34.0", "sp-offchain", - "sp-runtime", + "sp-runtime 31.0.1", "sp-tracing 16.0.0", "substrate-test-runtime-client", "threadpool", @@ -18261,7 +19039,7 @@ version = "29.0.0" dependencies = [ "assert_matches", "futures", - "jsonrpsee", + "jsonrpsee 0.24.3", "log", "parity-scale-codec", "parking_lot 0.12.3", @@ -18281,13 +19059,13 @@ dependencies = [ "sp-api", "sp-blockchain", "sp-consensus", - "sp-core", - "sp-crypto-hashing", - "sp-io", - "sp-keystore", + "sp-core 28.0.0", + "sp-crypto-hashing 0.1.0", + "sp-io 30.0.0", + "sp-keystore 0.34.0", "sp-offchain", "sp-rpc", - "sp-runtime", + "sp-runtime 31.0.1", "sp-session", "sp-statement-store", "sp-version", @@ -18299,7 +19077,7 @@ dependencies = [ name = "sc-rpc-api" version = "0.33.0" dependencies = [ - "jsonrpsee", + "jsonrpsee 0.24.3", "parity-scale-codec", "sc-chain-spec", "sc-mixnet", @@ -18307,9 +19085,9 @@ dependencies = [ "scale-info", "serde", "serde_json", - "sp-core", + "sp-core 28.0.0", "sp-rpc", - "sp-runtime", + "sp-runtime 31.0.1", "sp-version", "thiserror", ] @@ -18326,7 +19104,7 @@ dependencies = [ "http-body-util", "hyper 1.3.1", "ip_network", - "jsonrpsee", + "jsonrpsee 0.24.3", "log", "sc-rpc-api", "serde", @@ -18334,7 +19112,7 @@ dependencies = [ "substrate-prometheus-endpoint", "tokio", "tower", - "tower-http", + "tower-http 0.5.2", ] [[package]] @@ -18346,7 +19124,7 @@ dependencies = [ "futures", "futures-util", "hex", - "jsonrpsee", + "jsonrpsee 0.24.3", "log", "parity-scale-codec", "parking_lot 0.12.3", @@ -18366,11 +19144,11 @@ dependencies = [ "sp-api", "sp-blockchain", "sp-consensus", - "sp-core", + "sp-core 28.0.0", "sp-externalities 0.25.0", "sp-maybe-compressed-blob", "sp-rpc", - "sp-runtime", + "sp-runtime 31.0.1", "sp-version", "substrate-test-runtime", "substrate-test-runtime-client", @@ -18384,9 +19162,9 @@ dependencies = [ name = "sc-runtime-test" version = "2.0.0" dependencies = [ - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-runtime-interface 24.0.0", "substrate-wasm-builder", ] @@ -18400,7 +19178,7 @@ dependencies = [ "exit-future", "futures", "futures-timer", - "jsonrpsee", + "jsonrpsee 0.24.3", "log", "parity-scale-codec", "parking_lot 0.12.3", @@ -18434,16 +19212,16 @@ dependencies = [ "sp-api", "sp-blockchain", "sp-consensus", - "sp-core", + "sp-core 28.0.0", "sp-externalities 0.25.0", - "sp-keystore", - "sp-runtime", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", "sp-session", - "sp-state-machine", + "sp-state-machine 0.35.0", "sp-storage 19.0.0", "sp-transaction-pool", "sp-transaction-storage-proof", - "sp-trie", + "sp-trie 29.0.0", "sp-version", "static_init", "substrate-prometheus-endpoint", @@ -18461,7 +19239,7 @@ name = "sc-service-test" version = "2.0.0" dependencies = [ "array-bytes", - "async-channel", + "async-channel 1.9.0", "fdlimit", "futures", "log", @@ -18479,13 +19257,13 @@ dependencies = [ "sp-api", "sp-blockchain", "sp-consensus", - "sp-core", - "sp-io", - "sp-runtime", - "sp-state-machine", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", + "sp-state-machine 0.35.0", "sp-storage 19.0.0", "sp-tracing 16.0.0", - "sp-trie", + "sp-trie 29.0.0", "substrate-test-runtime", "substrate-test-runtime-client", "tempfile", @@ -18499,7 +19277,7 @@ dependencies = [ "log", "parity-scale-codec", "parking_lot 0.12.3", - "sp-core", + "sp-core 28.0.0", ] [[package]] @@ -18513,8 +19291,8 @@ dependencies = [ "sc-keystore", "sp-api", "sp-blockchain", - "sp-core", - "sp-runtime", + "sp-core 28.0.0", + "sp-runtime 31.0.1", "sp-statement-store", "sp-tracing 16.0.0", "substrate-prometheus-endpoint", @@ -18529,7 +19307,7 @@ dependencies = [ "clap 4.5.11", "fs4", "log", - "sp-core", + "sp-core 28.0.0", "thiserror", "tokio", ] @@ -18538,7 +19316,7 @@ dependencies = [ name = "sc-sync-state-rpc" version = "0.34.0" dependencies = [ - "jsonrpsee", + "jsonrpsee 0.24.3", "parity-scale-codec", "sc-chain-spec", "sc-client-api", @@ -18548,7 +19326,7 @@ dependencies = [ "serde", "serde_json", "sp-blockchain", - "sp-runtime", + "sp-runtime 31.0.1", "thiserror", ] @@ -18566,10 +19344,10 @@ dependencies = [ "sc-telemetry", "serde", "serde_json", - "sp-core", - "sp-crypto-hashing", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-crypto-hashing 0.1.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -18612,9 +19390,9 @@ dependencies = [ "serde", "sp-api", "sp-blockchain", - "sp-core", + "sp-core 28.0.0", "sp-rpc", - "sp-runtime", + "sp-runtime 31.0.1", "sp-tracing 16.0.0", "thiserror", "tracing", @@ -18654,9 +19432,9 @@ dependencies = [ "sp-api", "sp-blockchain", "sp-consensus", - "sp-core", - "sp-crypto-hashing", - "sp-runtime", + "sp-core 28.0.0", + "sp-crypto-hashing 0.1.0", + "sp-runtime 31.0.1", "sp-tracing 16.0.0", "sp-transaction-pool", "substrate-prometheus-endpoint", @@ -18677,8 +19455,8 @@ dependencies = [ "serde", "serde_json", "sp-blockchain", - "sp-core", - "sp-runtime", + "sp-core 28.0.0", + "sp-runtime 31.0.1", "thiserror", ] @@ -18686,14 +19464,14 @@ dependencies = [ name = "sc-utils" version = "14.0.0" dependencies = [ - "async-channel", + "async-channel 1.9.0", "futures", "futures-timer", "lazy_static", "log", "parking_lot 0.12.3", "prometheus", - "sp-arithmetic", + "sp-arithmetic 23.0.0", "tokio-test", ] @@ -18704,7 +19482,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e57b1e7f6b65ed1f04e79a85a57d755ad56d76fdf1e9bddcc9ae14f71fcdcf54" dependencies = [ "parity-scale-codec", + "scale-info", "scale-type-resolver", + "serde", ] [[package]] @@ -18715,11 +19495,53 @@ checksum = "e98f3262c250d90e700bb802eb704e1f841e03331c2eb815e46516c4edbf5b27" dependencies = [ "derive_more", "parity-scale-codec", + "primitive-types", + "scale-bits", + "scale-decode-derive", + "scale-type-resolver", + "smallvec", +] + +[[package]] +name = "scale-decode-derive" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb22f574168103cdd3133b19281639ca65ad985e24612728f727339dcaf4021" +dependencies = [ + "darling 0.14.4", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 1.0.109", +] + +[[package]] +name = "scale-encode" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ba0b9c48dc0eb20c60b083c29447c0c4617cb7c4a4c9fef72aa5c5bc539e15e" +dependencies = [ + "derive_more", + "parity-scale-codec", + "primitive-types", "scale-bits", + "scale-encode-derive", "scale-type-resolver", "smallvec", ] +[[package]] +name = "scale-encode-derive" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82ab7e60e2d9c8d47105f44527b26f04418e5e624ffc034f6b4a86c0ba19c5bf" +dependencies = [ + "darling 0.14.4", + "proc-macro-crate 1.3.1", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 1.0.109", +] + [[package]] name = "scale-info" version = "2.11.3" @@ -18751,6 +19573,44 @@ name = "scale-type-resolver" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0cded6518aa0bd6c1be2b88ac81bf7044992f0f154bfbabd5ad34f43512abcb" +dependencies = [ + "scale-info", + "smallvec", +] + +[[package]] +name = "scale-typegen" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "498d1aecf2ea61325d4511787c115791639c0fd21ef4f8e11e49dd09eff2bbac" +dependencies = [ + "proc-macro2 1.0.86", + "quote 1.0.37", + "scale-info", + "syn 2.0.65", + "thiserror", +] + +[[package]] +name = "scale-value" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba4d772cfb7569e03868400344a1695d16560bf62b86b918604773607d39ec84" +dependencies = [ + "base58", + "blake2 0.10.6", + "derive_more", + "either", + "frame-metadata 15.1.0", + "parity-scale-codec", + "scale-bits", + "scale-decode", + "scale-encode", + "scale-info", + "scale-type-resolver", + "serde", + "yap", +] [[package]] name = "schannel" @@ -18806,7 +19666,7 @@ dependencies = [ "arrayvec 0.7.4", "curve25519-dalek-ng", "merlin", - "rand_core", + "rand_core 0.6.4", "sha2 0.9.9", "subtle-ng", "zeroize", @@ -18821,10 +19681,10 @@ dependencies = [ "aead", "arrayref", "arrayvec 0.7.4", - "curve25519-dalek", + "curve25519-dalek 4.1.3", "getrandom_or_panic", "merlin", - "rand_core", + "rand_core 0.6.4", "serde_bytes", "sha2 0.10.8", "subtle 2.5.0", @@ -18907,6 +19767,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" dependencies = [ + "serde", "zeroize", ] @@ -18956,11 +19817,11 @@ dependencies = [ "sp-api", "sp-block-builder", "sp-consensus-aura", - "sp-core", + "sp-core 28.0.0", "sp-genesis-builder", "sp-inherents", "sp-offchain", - "sp-runtime", + "sp-runtime 31.0.1", "sp-session", "sp-transaction-pool", "sp-version", @@ -19049,6 +19910,16 @@ dependencies = [ "serde", ] +[[package]] +name = "serde-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" +dependencies = [ + "ordered-float 2.10.1", + "serde", +] + [[package]] name = "serde_bytes" version = "0.11.12" @@ -19259,11 +20130,11 @@ dependencies = [ "sp-api", "sp-block-builder", "sp-consensus-aura", - "sp-core", + "sp-core 28.0.0", "sp-genesis-builder", "sp-inherents", "sp-offchain", - "sp-runtime", + "sp-runtime 31.0.1", "sp-session", "sp-transaction-pool", "sp-version", @@ -19306,7 +20177,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" dependencies = [ "digest 0.10.7", - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -19348,6 +20219,12 @@ version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + [[package]] name = "slab" version = "0.4.9" @@ -19370,7 +20247,7 @@ dependencies = [ "enumn", "parity-scale-codec", "paste", - "sp-runtime", + "sp-runtime 31.0.1", ] [[package]] @@ -19388,7 +20265,7 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d7400c0eff44aa2fcb5e31a5f24ba9716ed90138769e4977a2ba6014ae63eb5" dependencies = [ - "async-channel", + "async-channel 1.9.0", "futures-core", "futures-io", ] @@ -19405,17 +20282,34 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13f2b548cd8447f8de0fdf1c592929f70f4fc7039a05e47404b0d096ec6987a1" dependencies = [ - "async-channel", + "async-channel 1.9.0", "async-executor", - "async-fs", + "async-fs 1.6.0", "async-io 1.13.0", "async-lock 2.8.0", - "async-net", - "async-process", + "async-net 1.7.0", + "async-process 1.7.0", "blocking", "futures-lite 1.13.0", ] +[[package]] +name = "smol" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a33bd3e260892199c3ccfc487c88b2da2265080acb316cd920da72fdfd7c599f" +dependencies = [ + "async-channel 2.3.0", + "async-executor", + "async-fs 2.1.2", + "async-io 2.3.3", + "async-lock 3.4.0", + "async-net 2.0.0", + "async-process 2.3.0", + "blocking", + "futures-lite 2.0.0", +] + [[package]] name = "smol_str" version = "0.2.0" @@ -19434,20 +20328,20 @@ dependencies = [ "arrayvec 0.7.4", "async-lock 2.8.0", "atomic-take", - "base64 0.21.2", + "base64 0.21.7", "bip39", "blake2-rfc", "bs58", "chacha20", "crossbeam-queue", "derive_more", - "ed25519-zebra", + "ed25519-zebra 4.0.3", "either", "event-listener 2.5.3", "fnv", "futures-lite 1.13.0", "futures-util", - "hashbrown 0.14.3", + "hashbrown 0.14.5", "hex", "hmac 0.12.1", "itertools 0.11.0", @@ -19463,13 +20357,68 @@ dependencies = [ "poly1305", "rand", "rand_chacha", - "ruzstd", + "ruzstd 0.4.0", "schnorrkel 0.10.2", "serde", "serde_json", "sha2 0.10.8", "sha3", - "siphasher", + "siphasher 0.3.11", + "slab", + "smallvec", + "soketto 0.7.1", + "twox-hash", + "wasmi 0.31.2", + "x25519-dalek", + "zeroize", +] + +[[package]] +name = "smoldot" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d1eaa97d77be4d026a1e7ffad1bb3b78448763b357ea6f8188d3e6f736a9b9" +dependencies = [ + "arrayvec 0.7.4", + "async-lock 3.4.0", + "atomic-take", + "base64 0.21.7", + "bip39", + "blake2-rfc", + "bs58", + "chacha20", + "crossbeam-queue", + "derive_more", + "ed25519-zebra 4.0.3", + "either", + "event-listener 4.0.3", + "fnv", + "futures-lite 2.0.0", + "futures-util", + "hashbrown 0.14.5", + "hex", + "hmac 0.12.1", + "itertools 0.12.1", + "libm", + "libsecp256k1", + "merlin", + "no-std-net", + "nom", + "num-bigint", + "num-rational", + "num-traits", + "pbkdf2", + "pin-project", + "poly1305", + "rand", + "rand_chacha", + "ruzstd 0.5.0", + "schnorrkel 0.11.4", + "serde", + "serde_json", + "sha2 0.10.8", + "sha3", + "siphasher 1.0.1", "slab", "smallvec", "soketto 0.7.1", @@ -19485,9 +20434,9 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "256b5bad1d6b49045e95fe87492ce73d5af81545d8b4d8318a872d2007024c33" dependencies = [ - "async-channel", + "async-channel 1.9.0", "async-lock 2.8.0", - "base64 0.21.2", + "base64 0.21.7", "blake2-rfc", "derive_more", "either", @@ -19496,7 +20445,7 @@ dependencies = [ "futures-channel", "futures-lite 1.13.0", "futures-util", - "hashbrown 0.14.3", + "hashbrown 0.14.5", "hex", "itertools 0.11.0", "log", @@ -19508,30 +20457,66 @@ dependencies = [ "rand_chacha", "serde", "serde_json", - "siphasher", + "siphasher 0.3.11", "slab", - "smol", - "smoldot", + "smol 1.3.0", + "smoldot 0.11.0", "zeroize", ] [[package]] -name = "snap" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e9f0ab6ef7eb7353d9119c170a436d1bf248eea575ac42d19d12f4e34130831" - -[[package]] -name = "snow" -version = "0.9.6" +name = "smoldot-light" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "850948bee068e713b8ab860fe1adc4d109676ab4c3b621fd8147f06b261f2f85" +checksum = "5496f2d116b7019a526b1039ec2247dd172b8670633b1a64a614c9ea12c9d8c7" dependencies = [ - "aes-gcm", + "async-channel 2.3.0", + "async-lock 3.4.0", + "base64 0.21.7", + "blake2-rfc", + "derive_more", + "either", + "event-listener 4.0.3", + "fnv", + "futures-channel", + "futures-lite 2.0.0", + "futures-util", + "hashbrown 0.14.5", + "hex", + "itertools 0.12.1", + "log", + "lru 0.12.3", + "no-std-net", + "parking_lot 0.12.3", + "pin-project", + "rand", + "rand_chacha", + "serde", + "serde_json", + "siphasher 1.0.1", + "slab", + "smol 2.0.2", + "smoldot 0.16.0", + "zeroize", +] + +[[package]] +name = "snap" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e9f0ab6ef7eb7353d9119c170a436d1bf248eea575ac42d19d12f4e34130831" + +[[package]] +name = "snow" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "850948bee068e713b8ab860fe1adc4d109676ab4c3b621fd8147f06b261f2f85" +dependencies = [ + "aes-gcm", "blake2 0.10.6", "chacha20poly1305", - "curve25519-dalek", - "rand_core", + "curve25519-dalek 4.1.3", + "rand_core 0.6.4", "ring 0.17.7", "rustc_version 0.4.0", "sha2 0.10.8", @@ -19562,9 +20547,9 @@ dependencies = [ "serde", "snowbridge-ethereum", "snowbridge-milagro-bls", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", "ssz_rs", "ssz_rs_derive", @@ -19584,10 +20569,10 @@ dependencies = [ "scale-info", "serde", "snowbridge-beacon-primitives", - "sp-arithmetic", - "sp-core", - "sp-io", - "sp-runtime", + "sp-arithmetic 23.0.0", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", "staging-xcm", "staging-xcm-builder", @@ -19610,8 +20595,8 @@ dependencies = [ "serde", "serde-big-array", "serde_json", - "sp-io", - "sp-runtime", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", "wasm-bindgen-test", ] @@ -19640,9 +20625,9 @@ dependencies = [ "hex-literal", "parity-scale-codec", "scale-info", - "sp-core", - "sp-crypto-hashing", - "sp-runtime", + "sp-core 28.0.0", + "sp-crypto-hashing 0.1.0", + "sp-runtime 31.0.1", "sp-tracing 16.0.0", ] @@ -19677,10 +20662,10 @@ dependencies = [ "snowbridge-core", "snowbridge-ethereum", "snowbridge-pallet-ethereum-client-fixtures", - "sp-core", - "sp-io", + "sp-core 28.0.0", + "sp-io 30.0.0", "sp-keyring", - "sp-runtime", + "sp-runtime 31.0.1", "sp-std 14.0.0", "static_assertions", ] @@ -19692,7 +20677,7 @@ dependencies = [ "hex-literal", "snowbridge-beacon-primitives", "snowbridge-core", - "sp-core", + "sp-core 28.0.0", "sp-std 14.0.0", ] @@ -19716,10 +20701,10 @@ dependencies = [ "snowbridge-pallet-ethereum-client", "snowbridge-pallet-inbound-queue-fixtures", "snowbridge-router-primitives", - "sp-core", - "sp-io", + "sp-core 28.0.0", + "sp-io 30.0.0", "sp-keyring", - "sp-runtime", + "sp-runtime 31.0.1", "sp-std 14.0.0", "staging-xcm", "staging-xcm-executor", @@ -19732,7 +20717,7 @@ dependencies = [ "hex-literal", "snowbridge-beacon-primitives", "snowbridge-core", - "sp-core", + "sp-core 28.0.0", "sp-std 14.0.0", ] @@ -19751,11 +20736,11 @@ dependencies = [ "serde", "snowbridge-core", "snowbridge-outbound-queue-merkle-tree", - "sp-arithmetic", - "sp-core", - "sp-io", + "sp-arithmetic 23.0.0", + "sp-core 28.0.0", + "sp-io 30.0.0", "sp-keyring", - "sp-runtime", + "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -19776,10 +20761,10 @@ dependencies = [ "scale-info", "snowbridge-core", "snowbridge-pallet-outbound-queue", - "sp-core", - "sp-io", + "sp-core 28.0.0", + "sp-io 30.0.0", "sp-keyring", - "sp-runtime", + "sp-runtime 31.0.1", "sp-std 14.0.0", "staging-xcm", "staging-xcm-executor", @@ -19795,9 +20780,9 @@ dependencies = [ "parity-scale-codec", "scale-info", "snowbridge-core", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", "staging-xcm", "staging-xcm-executor", @@ -19811,7 +20796,7 @@ dependencies = [ "log", "parity-scale-codec", "snowbridge-core", - "sp-arithmetic", + "sp-arithmetic 23.0.0", "sp-std 14.0.0", "staging-xcm", "staging-xcm-builder", @@ -19839,10 +20824,10 @@ dependencies = [ "snowbridge-pallet-ethereum-client-fixtures", "snowbridge-pallet-outbound-queue", "snowbridge-pallet-system", - "sp-core", - "sp-io", + "sp-core 28.0.0", + "sp-io 30.0.0", "sp-keyring", - "sp-runtime", + "sp-runtime 31.0.1", "staging-parachain-info", "staging-xcm", "staging-xcm-executor", @@ -19919,7 +20904,7 @@ dependencies = [ "frame-metadata-hash-extension", "frame-system", "futures", - "jsonrpsee", + "jsonrpsee 0.24.3", "pallet-transaction-payment", "pallet-transaction-payment-rpc", "sc-basic-authorship", @@ -19942,11 +20927,11 @@ dependencies = [ "sp-blockchain", "sp-consensus-aura", "sp-consensus-grandpa", - "sp-core", + "sp-core 28.0.0", "sp-inherents", - "sp-io", + "sp-io 30.0.0", "sp-keyring", - "sp-runtime", + "sp-runtime 31.0.1", "sp-timestamp", "substrate-build-script-utils", "substrate-frame-rpc-system", @@ -19978,11 +20963,11 @@ dependencies = [ "sp-block-builder", "sp-consensus-aura", "sp-consensus-grandpa", - "sp-core", + "sp-core 28.0.0", "sp-genesis-builder", "sp-inherents", "sp-offchain", - "sp-runtime", + "sp-runtime 31.0.1", "sp-session", "sp-storage 19.0.0", "sp-transaction-pool", @@ -20000,14 +20985,14 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-api-proc-macro", - "sp-core", + "sp-core 28.0.0", "sp-externalities 0.25.0", "sp-metadata-ir", - "sp-runtime", + "sp-runtime 31.0.1", "sp-runtime-interface 24.0.0", - "sp-state-machine", + "sp-state-machine 0.35.0", "sp-test-primitives", - "sp-trie", + "sp-trie 29.0.0", "sp-version", "thiserror", ] @@ -20039,9 +21024,9 @@ dependencies = [ "scale-info", "sp-api", "sp-consensus", - "sp-core", - "sp-runtime", - "sp-state-machine", + "sp-core 28.0.0", + "sp-runtime 31.0.1", + "sp-state-machine 0.35.0", "sp-tracing 16.0.0", "sp-version", "static_assertions", @@ -20056,8 +21041,22 @@ dependencies = [ "parity-scale-codec", "scale-info", "serde", - "sp-core", - "sp-io", + "sp-core 28.0.0", + "sp-io 30.0.0", +] + +[[package]] +name = "sp-application-crypto" +version = "33.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13ca6121c22c8bd3d1dce1f05c479101fd0d7b159bef2a3e8c834138d839c75c" +dependencies = [ + "parity-scale-codec", + "scale-info", + "serde", + "sp-core 31.0.0", + "sp-io 33.0.0", + "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -20065,9 +21064,9 @@ name = "sp-application-crypto-test" version = "2.0.0" dependencies = [ "sp-api", - "sp-application-crypto", - "sp-core", - "sp-keystore", + "sp-application-crypto 30.0.0", + "sp-core 28.0.0", + "sp-keystore 0.34.0", "substrate-test-runtime-client", ] @@ -20084,7 +21083,22 @@ dependencies = [ "rand", "scale-info", "serde", - "sp-crypto-hashing", + "sp-crypto-hashing 0.1.0", + "static_assertions", +] + +[[package]] +name = "sp-arithmetic" +version = "25.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "910c07fa263b20bf7271fdd4adcb5d3217dfdac14270592e0780223542e7e114" +dependencies = [ + "integer-sqrt", + "num-traits", + "parity-scale-codec", + "scale-info", + "serde", + "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "static_assertions", ] @@ -20096,7 +21110,7 @@ dependencies = [ "fraction", "honggfuzz", "num-bigint", - "sp-arithmetic", + "sp-arithmetic 23.0.0", ] [[package]] @@ -20124,8 +21138,8 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-api", - "sp-application-crypto", - "sp-runtime", + "sp-application-crypto 30.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -20134,7 +21148,7 @@ version = "26.0.0" dependencies = [ "sp-api", "sp-inherents", - "sp-runtime", + "sp-runtime 31.0.1", ] [[package]] @@ -20147,10 +21161,10 @@ dependencies = [ "schnellru", "sp-api", "sp-consensus", - "sp-core", + "sp-core 28.0.0", "sp-database", - "sp-runtime", - "sp-state-machine", + "sp-runtime 31.0.1", + "sp-state-machine 0.35.0", "thiserror", "tracing", ] @@ -20162,10 +21176,10 @@ dependencies = [ "async-trait", "futures", "log", - "sp-core", + "sp-core 28.0.0", "sp-inherents", - "sp-runtime", - "sp-state-machine", + "sp-runtime 31.0.1", + "sp-state-machine 0.35.0", "sp-test-primitives", "thiserror", ] @@ -20178,10 +21192,10 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-api", - "sp-application-crypto", + "sp-application-crypto 30.0.0", "sp-consensus-slots", "sp-inherents", - "sp-runtime", + "sp-runtime 31.0.1", "sp-timestamp", ] @@ -20194,11 +21208,11 @@ dependencies = [ "scale-info", "serde", "sp-api", - "sp-application-crypto", + "sp-application-crypto 30.0.0", "sp-consensus-slots", - "sp-core", + "sp-core 28.0.0", "sp-inherents", - "sp-runtime", + "sp-runtime 31.0.1", "sp-timestamp", ] @@ -20212,14 +21226,14 @@ dependencies = [ "scale-info", "serde", "sp-api", - "sp-application-crypto", - "sp-core", - "sp-crypto-hashing", - "sp-io", - "sp-keystore", + "sp-application-crypto 30.0.0", + "sp-core 28.0.0", + "sp-crypto-hashing 0.1.0", + "sp-io 30.0.0", + "sp-keystore 0.34.0", "sp-mmr-primitives", - "sp-runtime", - "sp-weights", + "sp-runtime 31.0.1", + "sp-weights 27.0.0", "strum 0.26.2", "w3f-bls", ] @@ -20234,10 +21248,10 @@ dependencies = [ "scale-info", "serde", "sp-api", - "sp-application-crypto", - "sp-core", - "sp-keystore", - "sp-runtime", + "sp-application-crypto 30.0.0", + "sp-core 28.0.0", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", ] [[package]] @@ -20246,8 +21260,8 @@ version = "0.32.0" dependencies = [ "parity-scale-codec", "sp-api", - "sp-core", - "sp-runtime", + "sp-core 28.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -20258,10 +21272,10 @@ dependencies = [ "scale-info", "serde", "sp-api", - "sp-application-crypto", + "sp-application-crypto 30.0.0", "sp-consensus-slots", - "sp-core", - "sp-runtime", + "sp-core 28.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -20286,7 +21300,7 @@ dependencies = [ "bs58", "criterion", "dyn-clonable", - "ed25519-zebra", + "ed25519-zebra 4.0.3", "futures", "hash-db", "hash256-std-hasher", @@ -20310,14 +21324,61 @@ dependencies = [ "secrecy", "serde", "serde_json", - "sp-crypto-hashing", + "sp-crypto-hashing 0.1.0", "sp-debug-derive 14.0.0", "sp-externalities 0.25.0", "sp-runtime-interface 24.0.0", "sp-std 14.0.0", "sp-storage 19.0.0", "ss58-registry", - "substrate-bip39", + "substrate-bip39 0.4.7", + "thiserror", + "tracing", + "w3f-bls", + "zeroize", +] + +[[package]] +name = "sp-core" +version = "31.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d7a0fd8f16dcc3761198fc83be12872f823b37b749bc72a3a6a1f702509366" +dependencies = [ + "array-bytes", + "bitflags 1.3.2", + "blake2 0.10.6", + "bounded-collections", + "bs58", + "dyn-clonable", + "ed25519-zebra 3.1.0", + "futures", + "hash-db", + "hash256-std-hasher", + "impl-serde", + "itertools 0.10.5", + "k256", + "libsecp256k1", + "log", + "merlin", + "parity-bip39", + "parity-scale-codec", + "parking_lot 0.12.3", + "paste", + "primitive-types", + "rand", + "scale-info", + "schnorrkel 0.11.4", + "secp256k1", + "secrecy", + "serde", + "sp-crypto-hashing 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "sp-debug-derive 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "sp-externalities 0.27.0", + "sp-runtime-interface 26.0.0", + "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "sp-storage 20.0.0", + "ss58-registry", + "substrate-bip39 0.5.0", "thiserror", "tracing", "w3f-bls", @@ -20331,14 +21392,14 @@ dependencies = [ "lazy_static", "libfuzzer-sys", "regex", - "sp-core", + "sp-core 28.0.0", ] [[package]] name = "sp-core-hashing" version = "15.0.0" dependencies = [ - "sp-crypto-hashing", + "sp-crypto-hashing 0.1.0", ] [[package]] @@ -20402,12 +21463,26 @@ dependencies = [ "twox-hash", ] +[[package]] +name = "sp-crypto-hashing" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc9927a7f81334ed5b8a98a4a978c81324d12bd9713ec76b5c68fd410174c5eb" +dependencies = [ + "blake2b_simd", + "byteorder", + "digest 0.10.7", + "sha2 0.10.8", + "sha3", + "twox-hash", +] + [[package]] name = "sp-crypto-hashing-proc-macro" version = "0.1.0" dependencies = [ "quote 1.0.37", - "sp-crypto-hashing", + "sp-crypto-hashing 0.1.0", "syn 2.0.65", ] @@ -20438,6 +21513,17 @@ dependencies = [ "syn 2.0.65", ] +[[package]] +name = "sp-debug-derive" +version = "14.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48d09fa0a5f7299fb81ee25ae3853d26200f7a348148aed6de76be905c007dbe" +dependencies = [ + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.65", +] + [[package]] name = "sp-externalities" version = "0.19.0" @@ -20458,6 +21544,18 @@ dependencies = [ "sp-storage 19.0.0", ] +[[package]] +name = "sp-externalities" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d6a4572eadd4a63cff92509a210bf425501a0c5e76574b30a366ac77653787" +dependencies = [ + "environmental", + "parity-scale-codec", + "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "sp-storage 20.0.0", +] + [[package]] name = "sp-genesis-builder" version = "0.8.0" @@ -20466,7 +21564,7 @@ dependencies = [ "scale-info", "serde_json", "sp-api", - "sp-runtime", + "sp-runtime 31.0.1", ] [[package]] @@ -20478,7 +21576,7 @@ dependencies = [ "impl-trait-for-tuples", "parity-scale-codec", "scale-info", - "sp-runtime", + "sp-runtime 31.0.1", "thiserror", ] @@ -20495,14 +21593,41 @@ dependencies = [ "polkavm-derive 0.9.1", "rustversion", "secp256k1", - "sp-core", - "sp-crypto-hashing", + "sp-core 28.0.0", + "sp-crypto-hashing 0.1.0", "sp-externalities 0.25.0", - "sp-keystore", + "sp-keystore 0.34.0", "sp-runtime-interface 24.0.0", - "sp-state-machine", + "sp-state-machine 0.35.0", "sp-tracing 16.0.0", - "sp-trie", + "sp-trie 29.0.0", + "tracing", + "tracing-core", +] + +[[package]] +name = "sp-io" +version = "33.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e09bba780b55bd9e67979cd8f654a31e4a6cf45426ff371394a65953d2177f2" +dependencies = [ + "bytes", + "ed25519-dalek", + "libsecp256k1", + "log", + "parity-scale-codec", + "polkavm-derive 0.9.1", + "rustversion", + "secp256k1", + "sp-core 31.0.0", + "sp-crypto-hashing 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "sp-externalities 0.27.0", + "sp-keystore 0.37.0", + "sp-runtime-interface 26.0.0", + "sp-state-machine 0.38.0", + "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "sp-tracing 16.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "sp-trie 32.0.0", "tracing", "tracing-core", ] @@ -20511,8 +21636,8 @@ dependencies = [ name = "sp-keyring" version = "31.0.0" dependencies = [ - "sp-core", - "sp-runtime", + "sp-core 28.0.0", + "sp-runtime 31.0.1", "strum 0.26.2", ] @@ -20524,10 +21649,22 @@ dependencies = [ "parking_lot 0.12.3", "rand", "rand_chacha", - "sp-core", + "sp-core 28.0.0", "sp-externalities 0.25.0", ] +[[package]] +name = "sp-keystore" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdbab8b61bd61d5f8625a0c75753b5d5a23be55d3445419acd42caf59cf6236b" +dependencies = [ + "parity-scale-codec", + "parking_lot 0.12.3", + "sp-core 31.0.0", + "sp-externalities 0.27.0", +] + [[package]] name = "sp-maybe-compressed-blob" version = "11.0.0" @@ -20540,7 +21677,7 @@ dependencies = [ name = "sp-metadata-ir" version = "0.6.0" dependencies = [ - "frame-metadata", + "frame-metadata 16.0.0", "parity-scale-codec", "scale-info", ] @@ -20552,7 +21689,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-api", - "sp-application-crypto", + "sp-application-crypto 30.0.0", ] [[package]] @@ -20566,9 +21703,9 @@ dependencies = [ "scale-info", "serde", "sp-api", - "sp-core", + "sp-core 28.0.0", "sp-debug-derive 14.0.0", - "sp-runtime", + "sp-runtime 31.0.1", "thiserror", ] @@ -20580,9 +21717,9 @@ dependencies = [ "rand", "scale-info", "serde", - "sp-arithmetic", - "sp-core", - "sp-runtime", + "sp-arithmetic 23.0.0", + "sp-core 28.0.0", + "sp-runtime 31.0.1", "substrate-test-utils", ] @@ -20594,7 +21731,7 @@ dependencies = [ "honggfuzz", "rand", "sp-npos-elections", - "sp-runtime", + "sp-runtime 31.0.1", ] [[package]] @@ -20602,8 +21739,8 @@ name = "sp-offchain" version = "26.0.0" dependencies = [ "sp-api", - "sp-core", - "sp-runtime", + "sp-core 28.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -20615,6 +21752,17 @@ dependencies = [ "regex", ] +[[package]] +name = "sp-panic-handler" +version = "13.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8f5a17a0a11de029a8b811cb6e8b32ce7e02183cc04a3e965c383246798c416" +dependencies = [ + "backtrace", + "lazy_static", + "regex", +] + [[package]] name = "sp-rpc" version = "26.0.0" @@ -20622,7 +21770,7 @@ dependencies = [ "rustc-hash 1.1.0", "serde", "serde_json", - "sp-core", + "sp-core 28.0.0", ] [[package]] @@ -20643,20 +21791,45 @@ dependencies = [ "serde_json", "simple-mermaid 0.1.1", "sp-api", - "sp-application-crypto", - "sp-arithmetic", - "sp-core", - "sp-io", - "sp-state-machine", + "sp-application-crypto 30.0.0", + "sp-arithmetic 23.0.0", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-state-machine 0.35.0", "sp-std 14.0.0", "sp-tracing 16.0.0", - "sp-trie", - "sp-weights", + "sp-trie 29.0.0", + "sp-weights 27.0.0", "substrate-test-runtime-client", "tracing", "zstd 0.12.4", ] +[[package]] +name = "sp-runtime" +version = "34.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3cb126971e7db2f0fcf8053dce740684c438c7180cfca1959598230f342c58" +dependencies = [ + "docify", + "either", + "hash256-std-hasher", + "impl-trait-for-tuples", + "log", + "parity-scale-codec", + "paste", + "rand", + "scale-info", + "serde", + "simple-mermaid 0.1.1", + "sp-application-crypto 33.0.0", + "sp-arithmetic 25.0.0", + "sp-core 31.0.0", + "sp-io 33.0.0", + "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "sp-weights 30.0.0", +] + [[package]] name = "sp-runtime-interface" version = "17.0.0" @@ -20685,12 +21858,12 @@ dependencies = [ "polkavm-derive 0.9.1", "primitive-types", "rustversion", - "sp-core", + "sp-core 28.0.0", "sp-externalities 0.25.0", - "sp-io", + "sp-io 30.0.0", "sp-runtime-interface-proc-macro 17.0.0", "sp-runtime-interface-test-wasm", - "sp-state-machine", + "sp-state-machine 0.35.0", "sp-std 14.0.0", "sp-storage 19.0.0", "sp-tracing 16.0.0", @@ -20699,6 +21872,26 @@ dependencies = [ "trybuild", ] +[[package]] +name = "sp-runtime-interface" +version = "26.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48a675ea4858333d4d755899ed5ed780174aa34fec15953428d516af5452295" +dependencies = [ + "bytes", + "impl-trait-for-tuples", + "parity-scale-codec", + "polkavm-derive 0.8.0", + "primitive-types", + "sp-externalities 0.27.0", + "sp-runtime-interface-proc-macro 18.0.0", + "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "sp-storage 20.0.0", + "sp-tracing 16.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "sp-wasm-interface 20.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "static_assertions", +] + [[package]] name = "sp-runtime-interface-proc-macro" version = "11.0.0" @@ -20723,18 +21916,32 @@ dependencies = [ "syn 2.0.65", ] +[[package]] +name = "sp-runtime-interface-proc-macro" +version = "18.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0195f32c628fee3ce1dfbbf2e7e52a30ea85f3589da9fe62a8b816d70fc06294" +dependencies = [ + "Inflector", + "expander", + "proc-macro-crate 3.1.0", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.65", +] + [[package]] name = "sp-runtime-interface-test" version = "2.0.0" dependencies = [ "sc-executor", "sc-executor-common", - "sp-io", - "sp-runtime", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-runtime-interface 24.0.0", "sp-runtime-interface-test-wasm", "sp-runtime-interface-test-wasm-deprecated", - "sp-state-machine", + "sp-state-machine 0.35.0", "tracing", "tracing-core", ] @@ -20744,8 +21951,8 @@ name = "sp-runtime-interface-test-wasm" version = "2.0.0" dependencies = [ "bytes", - "sp-core", - "sp-io", + "sp-core 28.0.0", + "sp-io 30.0.0", "sp-runtime-interface 24.0.0", "substrate-wasm-builder", ] @@ -20754,8 +21961,8 @@ dependencies = [ name = "sp-runtime-interface-test-wasm-deprecated" version = "2.0.0" dependencies = [ - "sp-core", - "sp-io", + "sp-core 28.0.0", + "sp-io 30.0.0", "sp-runtime-interface 24.0.0", "substrate-wasm-builder", ] @@ -20767,9 +21974,9 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-api", - "sp-core", - "sp-keystore", - "sp-runtime", + "sp-core 28.0.0", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", "sp-staking", ] @@ -20781,8 +21988,8 @@ dependencies = [ "parity-scale-codec", "scale-info", "serde", - "sp-core", - "sp-runtime", + "sp-core 28.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -20799,14 +22006,36 @@ dependencies = [ "pretty_assertions", "rand", "smallvec", - "sp-core", + "sp-core 28.0.0", "sp-externalities 0.25.0", - "sp-panic-handler", - "sp-runtime", - "sp-trie", + "sp-panic-handler 13.0.0", + "sp-runtime 31.0.1", + "sp-trie 29.0.0", "thiserror", "tracing", - "trie-db", + "trie-db 0.29.1", +] + +[[package]] +name = "sp-state-machine" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1eae0eac8034ba14437e772366336f579398a46d101de13dbb781ab1e35e67c5" +dependencies = [ + "hash-db", + "log", + "parity-scale-codec", + "parking_lot 0.12.3", + "rand", + "smallvec", + "sp-core 31.0.0", + "sp-externalities 0.27.0", + "sp-panic-handler 13.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "sp-trie 32.0.0", + "thiserror", + "tracing", + "trie-db 0.28.0", ] [[package]] @@ -20814,7 +22043,7 @@ name = "sp-statement-store" version = "10.0.0" dependencies = [ "aes-gcm", - "curve25519-dalek", + "curve25519-dalek 4.1.3", "ed25519-dalek", "hkdf", "parity-scale-codec", @@ -20822,11 +22051,11 @@ dependencies = [ "scale-info", "sha2 0.10.8", "sp-api", - "sp-application-crypto", - "sp-core", - "sp-crypto-hashing", + "sp-application-crypto 30.0.0", + "sp-core 28.0.0", + "sp-crypto-hashing 0.1.0", "sp-externalities 0.25.0", - "sp-runtime", + "sp-runtime 31.0.1", "sp-runtime-interface 24.0.0", "thiserror", "x25519-dalek", @@ -20841,6 +22070,12 @@ source = "git+https://github.com/paritytech/polkadot-sdk#82912acb33a9030c0ef3bf5 name = "sp-std" version = "14.0.0" +[[package]] +name = "sp-std" +version = "14.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f8ee986414b0a9ad741776762f4083cd3a5128449b982a3919c4df36874834" + [[package]] name = "sp-storage" version = "13.0.0" @@ -20865,6 +22100,20 @@ dependencies = [ "sp-debug-derive 14.0.0", ] +[[package]] +name = "sp-storage" +version = "20.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dba5791cb3978e95daf99dad919ecb3ec35565604e88cd38d805d9d4981e8bd" +dependencies = [ + "impl-serde", + "parity-scale-codec", + "ref-cast", + "serde", + "sp-debug-derive 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "sp-test-primitives" version = "2.0.0" @@ -20872,9 +22121,9 @@ dependencies = [ "parity-scale-codec", "scale-info", "serde", - "sp-application-crypto", - "sp-core", - "sp-runtime", + "sp-application-crypto 30.0.0", + "sp-core 28.0.0", + "sp-runtime 31.0.1", ] [[package]] @@ -20884,7 +22133,7 @@ dependencies = [ "async-trait", "parity-scale-codec", "sp-inherents", - "sp-runtime", + "sp-runtime 31.0.1", "thiserror", ] @@ -20910,12 +22159,25 @@ dependencies = [ "tracing-subscriber 0.3.18", ] +[[package]] +name = "sp-tracing" +version = "16.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0351810b9d074df71c4514c5228ed05c250607cba131c1c9d1526760ab69c05c" +dependencies = [ + "parity-scale-codec", + "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "tracing", + "tracing-core", + "tracing-subscriber 0.2.25", +] + [[package]] name = "sp-transaction-pool" version = "26.0.0" dependencies = [ "sp-api", - "sp-runtime", + "sp-runtime 31.0.1", ] [[package]] @@ -20925,10 +22187,10 @@ dependencies = [ "async-trait", "parity-scale-codec", "scale-info", - "sp-core", + "sp-core 28.0.0", "sp-inherents", - "sp-runtime", - "sp-trie", + "sp-runtime 31.0.1", + "sp-trie 29.0.0", ] [[package]] @@ -20947,17 +22209,42 @@ dependencies = [ "rand", "scale-info", "schnellru", - "sp-core", + "sp-core 28.0.0", "sp-externalities 0.25.0", - "sp-runtime", + "sp-runtime 31.0.1", "thiserror", "tracing", "trie-bench", - "trie-db", + "trie-db 0.29.1", "trie-root", "trie-standardmap", ] +[[package]] +name = "sp-trie" +version = "32.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1aa91ad26c62b93d73e65f9ce7ebd04459c4bad086599348846a81988d6faa4" +dependencies = [ + "ahash 0.8.11", + "hash-db", + "lazy_static", + "memory-db", + "nohash-hasher", + "parity-scale-codec", + "parking_lot 0.12.3", + "rand", + "scale-info", + "schnellru", + "sp-core 31.0.0", + "sp-externalities 0.27.0", + "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "thiserror", + "tracing", + "trie-db 0.28.0", + "trie-root", +] + [[package]] name = "sp-version" version = "29.0.0" @@ -20968,7 +22255,7 @@ dependencies = [ "scale-info", "serde", "sp-crypto-hashing-proc-macro", - "sp-runtime", + "sp-runtime 31.0.1", "sp-std 14.0.0", "sp-version-proc-macro", "thiserror", @@ -21010,6 +22297,20 @@ dependencies = [ "wasmtime", ] +[[package]] +name = "sp-wasm-interface" +version = "20.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ef97172c42eb4c6c26506f325f48463e9bc29b2034a587f1b9e48c751229bee" +dependencies = [ + "anyhow", + "impl-trait-for-tuples", + "log", + "parity-scale-codec", + "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "wasmtime", +] + [[package]] name = "sp-weights" version = "27.0.0" @@ -21020,10 +22321,26 @@ dependencies = [ "schemars", "serde", "smallvec", - "sp-arithmetic", + "sp-arithmetic 23.0.0", "sp-debug-derive 14.0.0", ] +[[package]] +name = "sp-weights" +version = "30.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9af6c661fe3066b29f9e1d258000f402ff5cc2529a9191972d214e5871d0ba87" +dependencies = [ + "bounded-collections", + "parity-scale-codec", + "scale-info", + "serde", + "smallvec", + "sp-arithmetic 25.0.0", + "sp-debug-derive 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "spin" version = "0.5.2" @@ -21124,7 +22441,7 @@ dependencies = [ "clap_complete", "criterion", "futures", - "jsonrpsee", + "jsonrpsee 0.24.3", "kitchensink-runtime", "log", "nix 0.28.0", @@ -21161,9 +22478,9 @@ dependencies = [ "sc-client-api", "sc-service", "sp-blockchain", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-statement-store", "thiserror", ] @@ -21177,7 +22494,7 @@ dependencies = [ "frame-system", "parity-scale-codec", "scale-info", - "sp-runtime", + "sp-runtime 31.0.1", ] [[package]] @@ -21200,9 +22517,9 @@ dependencies = [ "scale-info", "schemars", "serde", - "sp-io", - "sp-runtime", - "sp-weights", + "sp-io 30.0.0", + "sp-runtime 31.0.1", + "sp-weights 27.0.0", "xcm-procedural", ] @@ -21228,11 +22545,11 @@ dependencies = [ "polkadot-test-runtime", "primitive-types", "scale-info", - "sp-arithmetic", - "sp-core", - "sp-io", - "sp-runtime", - "sp-weights", + "sp-arithmetic 23.0.0", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", + "sp-weights 27.0.0", "staging-xcm", "staging-xcm-executor", ] @@ -21247,11 +22564,11 @@ dependencies = [ "impl-trait-for-tuples", "parity-scale-codec", "scale-info", - "sp-arithmetic", - "sp-core", - "sp-io", - "sp-runtime", - "sp-weights", + "sp-arithmetic 23.0.0", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", + "sp-weights 27.0.0", "staging-xcm", "tracing", ] @@ -21269,7 +22586,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a2a1c578e98c1c16fc3b8ec1328f7659a500737d7a0c6d625e73e830ff9c1f6" dependencies = [ "bitflags 1.3.2", - "cfg_aliases", + "cfg_aliases 0.1.1", "libc", "parking_lot 0.11.2", "parking_lot_core 0.8.6", @@ -21283,7 +22600,7 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70a2595fc3aa78f2d0e45dd425b22282dd863273761cc77780914b2cf3003acf" dependencies = [ - "cfg_aliases", + "cfg_aliases 0.1.1", "memchr", "proc-macro2 1.0.86", "quote 1.0.37", @@ -21297,7 +22614,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c6a0d765f5807e98a091107bae0a56ea3799f66a5de47b2c84c94a39c09974e" dependencies = [ "cfg-if", - "hashbrown 0.14.3", + "hashbrown 0.14.5", "serde", ] @@ -21315,9 +22632,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "strsim" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "structopt" @@ -21427,6 +22744,19 @@ dependencies = [ "zeroize", ] +[[package]] +name = "substrate-bip39" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2b564c293e6194e8b222e52436bcb99f60de72043c7f845cf6c4406db4df121" +dependencies = [ + "hmac 0.12.1", + "pbkdf2", + "schnorrkel 0.11.4", + "sha2 0.10.8", + "zeroize", +] + [[package]] name = "substrate-build-script-utils" version = "11.0.0" @@ -21454,13 +22784,13 @@ version = "29.0.0" dependencies = [ "frame-support", "frame-system", - "jsonrpsee", + "jsonrpsee 0.24.3", "parity-scale-codec", "sc-rpc-api", "scale-info", "serde", - "sp-core", - "sp-runtime", + "sp-core 28.0.0", + "sp-runtime 31.0.1", "sp-storage 19.0.0", "tokio", ] @@ -21473,7 +22803,7 @@ dependencies = [ "docify", "frame-system-rpc-runtime-api", "futures", - "jsonrpsee", + "jsonrpsee 0.24.3", "log", "parity-scale-codec", "sc-rpc-api", @@ -21482,8 +22812,8 @@ dependencies = [ "sp-api", "sp-block-builder", "sp-blockchain", - "sp-core", - "sp-runtime", + "sp-core 28.0.0", + "sp-runtime 31.0.1", "sp-tracing 16.0.0", "substrate-test-runtime-client", "tokio", @@ -21538,9 +22868,9 @@ dependencies = [ "rustc-hex", "scale-info", "sp-consensus-grandpa", - "sp-core", - "sp-runtime", - "sp-trie", + "sp-core 28.0.0", + "sp-runtime 31.0.1", + "sp-trie 29.0.0", "structopt", "strum 0.26.2", "thiserror", @@ -21551,12 +22881,12 @@ name = "substrate-rpc-client" version = "0.33.0" dependencies = [ "async-trait", - "jsonrpsee", + "jsonrpsee 0.24.3", "log", "sc-rpc-api", "serde", - "sp-core", - "sp-runtime", + "sp-core 28.0.0", + "sp-runtime 31.0.1", "tokio", ] @@ -21564,17 +22894,17 @@ dependencies = [ name = "substrate-state-trie-migration-rpc" version = "27.0.0" dependencies = [ - "jsonrpsee", + "jsonrpsee 0.24.3", "parity-scale-codec", "sc-client-api", "sc-rpc-api", "serde", "serde_json", - "sp-core", - "sp-runtime", - "sp-state-machine", - "sp-trie", - "trie-db", + "sp-core 28.0.0", + "sp-runtime 31.0.1", + "sp-state-machine 0.35.0", + "sp-trie 29.0.0", + "trie-db 0.29.1", ] [[package]] @@ -21595,11 +22925,11 @@ dependencies = [ "serde_json", "sp-blockchain", "sp-consensus", - "sp-core", + "sp-core 28.0.0", "sp-keyring", - "sp-keystore", - "sp-runtime", - "sp-state-machine", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", + "sp-state-machine 0.35.0", "tokio", ] @@ -21628,31 +22958,31 @@ dependencies = [ "serde", "serde_json", "sp-api", - "sp-application-crypto", + "sp-application-crypto 30.0.0", "sp-block-builder", "sp-consensus", "sp-consensus-aura", "sp-consensus-babe", "sp-consensus-grandpa", - "sp-core", - "sp-crypto-hashing", + "sp-core 28.0.0", + "sp-crypto-hashing 0.1.0", "sp-externalities 0.25.0", "sp-genesis-builder", "sp-inherents", - "sp-io", + "sp-io 30.0.0", "sp-keyring", "sp-offchain", - "sp-runtime", + "sp-runtime 31.0.1", "sp-session", - "sp-state-machine", + "sp-state-machine 0.35.0", "sp-tracing 16.0.0", "sp-transaction-pool", - "sp-trie", + "sp-trie 29.0.0", "sp-version", "substrate-test-runtime-client", "substrate-wasm-builder", "tracing", - "trie-db", + "trie-db 0.29.1", ] [[package]] @@ -21666,8 +22996,8 @@ dependencies = [ "sp-api", "sp-blockchain", "sp-consensus", - "sp-core", - "sp-runtime", + "sp-core 28.0.0", + "sp-runtime 31.0.1", "substrate-test-client", "substrate-test-runtime", ] @@ -21682,66 +23012,219 @@ dependencies = [ "sc-transaction-pool", "sc-transaction-pool-api", "sp-blockchain", - "sp-runtime", + "sp-runtime 31.0.1", "substrate-test-runtime-client", "thiserror", ] [[package]] -name = "substrate-test-utils" -version = "4.0.0-dev" +name = "substrate-test-utils" +version = "4.0.0-dev" +dependencies = [ + "futures", + "sc-service", + "tokio", + "trybuild", +] + +[[package]] +name = "substrate-wasm-builder" +version = "17.0.0" +dependencies = [ + "array-bytes", + "build-helper", + "cargo_metadata", + "console", + "filetime", + "frame-metadata 16.0.0", + "jobserver", + "merkleized-metadata", + "parity-scale-codec", + "parity-wasm", + "polkavm-linker 0.9.2", + "sc-executor", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-maybe-compressed-blob", + "sp-tracing 16.0.0", + "sp-version", + "strum 0.26.2", + "tempfile", + "toml 0.8.12", + "walkdir", + "wasm-opt", +] + +[[package]] +name = "subtle" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "subtle-ng" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" + +[[package]] +name = "subxt" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a160cba1edbf3ec4fbbeaea3f1a185f70448116a6bccc8276bb39adb3b3053bd" +dependencies = [ + "async-trait", + "derive-where", + "either", + "frame-metadata 16.0.0", + "futures", + "hex", + "impl-serde", + "instant", + "jsonrpsee 0.22.5", + "parity-scale-codec", + "primitive-types", + "reconnecting-jsonrpsee-ws-client", + "scale-bits", + "scale-decode", + "scale-encode", + "scale-info", + "scale-value", + "serde", + "serde_json", + "sp-crypto-hashing 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "subxt-core", + "subxt-lightclient", + "subxt-macro", + "subxt-metadata", + "thiserror", + "tokio-util", + "tracing", + "url", +] + +[[package]] +name = "subxt-codegen" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d703dca0905cc5272d7cc27a4ac5f37dcaae7671acc7fef0200057cc8c317786" +dependencies = [ + "frame-metadata 16.0.0", + "heck 0.5.0", + "hex", + "jsonrpsee 0.22.5", + "parity-scale-codec", + "proc-macro2 1.0.86", + "quote 1.0.37", + "scale-info", + "scale-typegen", + "subxt-metadata", + "syn 2.0.65", + "thiserror", + "tokio", +] + +[[package]] +name = "subxt-core" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59f41eb2e2eea6ed45649508cc735f92c27f1fcfb15229e75f8270ea73177345" +dependencies = [ + "base58", + "blake2 0.10.6", + "derive-where", + "frame-metadata 16.0.0", + "hashbrown 0.14.5", + "hex", + "impl-serde", + "parity-scale-codec", + "primitive-types", + "scale-bits", + "scale-decode", + "scale-encode", + "scale-info", + "scale-value", + "serde", + "serde_json", + "sp-core 31.0.0", + "sp-crypto-hashing 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "sp-runtime 34.0.0", + "subxt-metadata", + "tracing", +] + +[[package]] +name = "subxt-lightclient" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d9406fbdb9548c110803cb8afa750f8b911d51eefdf95474b11319591d225d9" dependencies = [ "futures", - "sc-service", + "futures-util", + "serde", + "serde_json", + "smoldot-light 0.14.0", + "thiserror", "tokio", - "trybuild", + "tokio-stream", + "tracing", ] [[package]] -name = "substrate-wasm-builder" -version = "17.0.0" +name = "subxt-macro" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c195f803d70687e409aba9be6c87115b5da8952cd83c4d13f2e043239818fcd" dependencies = [ - "array-bytes", - "build-helper", - "cargo_metadata", - "console", - "filetime", - "frame-metadata", - "jobserver", - "merkleized-metadata", + "darling 0.20.10", "parity-scale-codec", - "parity-wasm", - "polkavm-linker 0.9.2", - "sc-executor", - "sp-core", - "sp-io", - "sp-maybe-compressed-blob", - "sp-tracing 16.0.0", - "sp-version", - "strum 0.26.2", - "tempfile", - "toml 0.8.12", - "walkdir", - "wasm-opt", + "proc-macro-error", + "quote 1.0.37", + "scale-typegen", + "subxt-codegen", + "syn 2.0.65", ] [[package]] -name = "subtle" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" - -[[package]] -name = "subtle" -version = "2.5.0" +name = "subxt-metadata" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +checksum = "738be5890fdeff899bbffff4d9c0f244fe2a952fb861301b937e3aa40ebb55da" +dependencies = [ + "frame-metadata 16.0.0", + "hashbrown 0.14.5", + "parity-scale-codec", + "scale-info", + "sp-crypto-hashing 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] -name = "subtle-ng" -version = "2.5.0" +name = "subxt-signer" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" +checksum = "f49888ae6ae90fe01b471193528eea5bd4ed52d8eecd2d13f4a2333b87388850" +dependencies = [ + "bip39", + "cfg-if", + "hex", + "hmac 0.12.1", + "parity-scale-codec", + "pbkdf2", + "regex", + "schnorrkel 0.11.4", + "secp256k1", + "secrecy", + "sha2 0.10.8", + "sp-crypto-hashing 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "subxt-core", + "zeroize", +] [[package]] name = "sval" @@ -21974,6 +23457,17 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "template-zombienet-tests" +version = "0.0.0" +dependencies = [ + "anyhow", + "env_logger 0.11.3", + "log", + "tokio", + "zombienet-sdk", +] + [[package]] name = "termcolor" version = "1.2.0" @@ -22028,7 +23522,7 @@ dependencies = [ "dlmalloc", "parity-scale-codec", "polkadot-parachain-primitives", - "sp-io", + "sp-io 30.0.0", "substrate-wasm-builder", "tiny-keccak", ] @@ -22052,7 +23546,7 @@ dependencies = [ "polkadot-test-service", "sc-cli", "sc-service", - "sp-core", + "sp-core 28.0.0", "sp-keyring", "substrate-test-utils", "test-parachain-adder", @@ -22075,7 +23569,7 @@ dependencies = [ "log", "parity-scale-codec", "polkadot-parachain-primitives", - "sp-io", + "sp-io 30.0.0", "substrate-wasm-builder", "tiny-keccak", ] @@ -22099,7 +23593,7 @@ dependencies = [ "polkadot-test-service", "sc-cli", "sc-service", - "sp-core", + "sp-core 28.0.0", "sp-keyring", "substrate-test-utils", "test-parachain-undying", @@ -22111,7 +23605,7 @@ name = "test-parachains" version = "1.0.0" dependencies = [ "parity-scale-codec", - "sp-core", + "sp-core 28.0.0", "test-parachain-adder", "test-parachain-halt", "tiny-keccak", @@ -22124,7 +23618,7 @@ dependencies = [ "frame-support", "polkadot-primitives", "smallvec", - "sp-runtime", + "sp-runtime 31.0.1", ] [[package]] @@ -22136,7 +23630,7 @@ dependencies = [ "polkadot-core-primitives", "rococo-runtime-constants", "smallvec", - "sp-runtime", + "sp-runtime 31.0.1", "staging-xcm", "westend-runtime-constants", ] @@ -22230,7 +23724,7 @@ dependencies = [ "byteorder", "integer-encoding", "log", - "ordered-float", + "ordered-float 1.1.1", "threadpool", ] @@ -22351,6 +23845,16 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "tokio-io-timeout" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" +dependencies = [ + "pin-project-lite", + "tokio", +] + [[package]] name = "tokio-macros" version = "2.2.0" @@ -22362,6 +23866,16 @@ dependencies = [ "syn 2.0.65", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-retry" version = "0.3.0" @@ -22383,6 +23897,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +dependencies = [ + "rustls 0.22.4", + "rustls-pki-types", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.26.0" @@ -22396,9 +23921,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.14" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" dependencies = [ "futures-core", "pin-project-lite", @@ -22445,6 +23970,7 @@ dependencies = [ "futures-io", "futures-sink", "pin-project-lite", + "slab", "tokio", ] @@ -22457,6 +23983,18 @@ dependencies = [ "serde", ] +[[package]] +name = "toml" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.19.15", +] + [[package]] name = "toml" version = "0.8.12" @@ -22485,6 +24023,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ "indexmap 2.2.3", + "serde", + "serde_spanned", "toml_datetime", "winnow 0.5.15", ] @@ -22524,6 +24064,28 @@ dependencies = [ "pin-project", "pin-project-lite", "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140" +dependencies = [ + "base64 0.21.7", + "bitflags 2.6.0", + "bytes", + "futures-core", + "futures-util", + "http 0.2.9", + "http-body 0.4.5", + "http-range-header", + "mime", + "pin-project-lite", "tower-layer", "tower-service", "tracing", @@ -22708,11 +24270,24 @@ dependencies = [ "keccak-hasher", "memory-db", "parity-scale-codec", - "trie-db", + "trie-db 0.29.1", "trie-root", "trie-standardmap", ] +[[package]] +name = "trie-db" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff28e0f815c2fea41ebddf148e008b077d2faddb026c9555b29696114d602642" +dependencies = [ + "hash-db", + "hashbrown 0.13.2", + "log", + "rustc-hex", + "smallvec", +] + [[package]] name = "trie-db" version = "0.29.1" @@ -23016,6 +24591,7 @@ dependencies = [ "form_urlencoded", "idna 0.5.0", "percent-encoding", + "serde", ] [[package]] @@ -23035,6 +24611,9 @@ name = "uuid" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" +dependencies = [ + "getrandom", +] [[package]] name = "valuable" @@ -23119,7 +24698,7 @@ dependencies = [ "digest 0.10.7", "rand", "rand_chacha", - "rand_core", + "rand_core 0.6.4", "sha2 0.10.8", "sha3", "thiserror", @@ -23375,7 +24954,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c128c039340ffd50d4195c3f8ce31aac357f06804cfc494c8b9508d4b30dca4" dependencies = [ "ahash 0.8.11", - "hashbrown 0.14.3", + "hashbrown 0.14.5", "string-interner", ] @@ -23466,7 +25045,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c86437fa68626fe896e5afc69234bb2b5894949083586535f200385adfd71213" dependencies = [ "anyhow", - "base64 0.21.2", + "base64 0.21.7", "bincode", "directories-next", "file-per-thread-logger", @@ -23685,8 +25264,8 @@ dependencies = [ "sp-authority-discovery", "sp-consensus-babe", "sp-consensus-beefy", - "sp-core", - "sp-runtime", + "sp-core 28.0.0", + "sp-runtime 31.0.1", "staging-xcm", "westend-runtime", "westend-runtime-constants", @@ -23773,21 +25352,21 @@ dependencies = [ "serde_json", "smallvec", "sp-api", - "sp-application-crypto", - "sp-arithmetic", + "sp-application-crypto 30.0.0", + "sp-arithmetic 23.0.0", "sp-authority-discovery", "sp-block-builder", "sp-consensus-babe", "sp-consensus-beefy", - "sp-core", + "sp-core 28.0.0", "sp-genesis-builder", "sp-inherents", - "sp-io", + "sp-io 30.0.0", "sp-keyring", "sp-mmr-primitives", "sp-npos-elections", "sp-offchain", - "sp-runtime", + "sp-runtime 31.0.1", "sp-session", "sp-staking", "sp-storage 19.0.0", @@ -23812,9 +25391,9 @@ dependencies = [ "polkadot-primitives", "polkadot-runtime-common", "smallvec", - "sp-core", - "sp-runtime", - "sp-weights", + "sp-core 28.0.0", + "sp-runtime 31.0.1", + "sp-weights 27.0.0", "staging-xcm", "staging-xcm-builder", ] @@ -24168,8 +25747,8 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb66477291e7e8d2b0ff1bcb900bf29489a9692816d79874bea351e7a8b6de96" dependencies = [ - "curve25519-dalek", - "rand_core", + "curve25519-dalek 4.1.3", + "rand_core 0.6.4", "serde", "zeroize", ] @@ -24232,8 +25811,8 @@ dependencies = [ "polkadot-sdk-frame", "scale-info", "simple-mermaid 0.1.0", - "sp-io", - "sp-runtime", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", "staging-xcm", "staging-xcm-builder", @@ -24264,11 +25843,11 @@ dependencies = [ "polkadot-parachain-primitives", "polkadot-primitives", "polkadot-runtime-parachains", - "sp-arithmetic", - "sp-core", - "sp-crypto-hashing", - "sp-io", - "sp-runtime", + "sp-arithmetic 23.0.0", + "sp-core 28.0.0", + "sp-crypto-hashing 0.1.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", "sp-tracing 16.0.0", "staging-xcm", @@ -24290,10 +25869,10 @@ dependencies = [ "polkadot-test-runtime", "polkadot-test-service", "sp-consensus", - "sp-core", + "sp-core 28.0.0", "sp-keyring", - "sp-runtime", - "sp-state-machine", + "sp-runtime 31.0.1", + "sp-state-machine 0.35.0", "sp-tracing 16.0.0", "staging-xcm", "staging-xcm-executor", @@ -24326,9 +25905,9 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-api", - "sp-io", + "sp-io 30.0.0", "sp-tracing 16.0.0", - "sp-weights", + "sp-weights 27.0.0", "staging-xcm", "staging-xcm-builder", "staging-xcm-executor", @@ -24347,8 +25926,8 @@ dependencies = [ "polkadot-primitives", "polkadot-runtime-parachains", "scale-info", - "sp-io", - "sp-runtime", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", "staging-xcm", "staging-xcm-builder", @@ -24371,9 +25950,9 @@ dependencies = [ "polkadot-parachain-primitives", "polkadot-runtime-parachains", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", "sp-tracing 16.0.0", "staging-xcm", @@ -24400,9 +25979,9 @@ dependencies = [ "polkadot-parachain-primitives", "polkadot-runtime-parachains", "scale-info", - "sp-core", - "sp-io", - "sp-runtime", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", "sp-std 14.0.0", "staging-xcm", "staging-xcm-builder", @@ -24446,6 +26025,12 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" +[[package]] +name = "yap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff4524214bc4629eba08d78ceb1d6507070cc0bcbbed23af74e19e6e924a24cf" + [[package]] name = "yasna" version = "0.5.2" @@ -24512,6 +26097,135 @@ dependencies = [ "url", ] +[[package]] +name = "zombienet-configuration" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23322e411b8d19b41b5c20ab8e88c10822189a4fcfd069c7fcd1542b8d3035aa" +dependencies = [ + "anyhow", + "lazy_static", + "multiaddr 0.18.1", + "regex", + "serde", + "serde_json", + "thiserror", + "toml 0.7.8", + "url", + "zombienet-support", +] + +[[package]] +name = "zombienet-orchestrator" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "381f701565b3918a909132743b3674569ce3da25b5c3a6493883abaf1046577a" +dependencies = [ + "anyhow", + "async-trait", + "futures", + "glob-match", + "hex", + "libp2p", + "libsecp256k1", + "multiaddr 0.18.1", + "rand", + "regex", + "reqwest", + "serde", + "serde_json", + "sha2 0.10.8", + "sp-core 31.0.0", + "subxt", + "subxt-signer", + "thiserror", + "tokio", + "tracing", + "uuid", + "zombienet-configuration", + "zombienet-prom-metrics-parser", + "zombienet-provider", + "zombienet-support", +] + +[[package]] +name = "zombienet-prom-metrics-parser" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dab79fa58bcfecbcd41485c6f13052853ccde8b09f173b601f78747d7abc2b7f" +dependencies = [ + "pest", + "pest_derive", + "thiserror", +] + +[[package]] +name = "zombienet-provider" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6af0264938da61b25da89f17ee0630393a4ba793582a4a8a1650eb15b47fc1ef" +dependencies = [ + "anyhow", + "async-trait", + "flate2", + "futures", + "hex", + "k8s-openapi", + "kube", + "nix 0.27.1", + "regex", + "reqwest", + "serde", + "serde_json", + "serde_yaml", + "sha2 0.10.8", + "tar", + "thiserror", + "tokio", + "tokio-util", + "tracing", + "url", + "uuid", + "zombienet-configuration", + "zombienet-support", +] + +[[package]] +name = "zombienet-sdk" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bc5b7ebfba4ab62486c8cb5bcd7345c4376487487cfe3481476cb4d4accc75e" +dependencies = [ + "async-trait", + "futures", + "lazy_static", + "subxt", + "tokio", + "zombienet-configuration", + "zombienet-orchestrator", + "zombienet-provider", + "zombienet-support", +] + +[[package]] +name = "zombienet-support" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f5b80d34a0eecca69dd84c2e13f84f1fae0cc378baf4f15f769027af068418b" +dependencies = [ + "anyhow", + "async-trait", + "futures", + "nix 0.27.1", + "rand", + "regex", + "reqwest", + "thiserror", + "tokio", + "tracing", + "uuid", +] + [[package]] name = "zstd" version = "0.11.2+zstd.1.5.2" diff --git a/Cargo.toml b/Cargo.toml index 7e48fa14ccc..a3c89a74bd6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -537,6 +537,7 @@ members = [ "templates/solochain/node", "templates/solochain/pallets/template", "templates/solochain/runtime", + "templates/zombienet", "umbrella", ] diff --git a/templates/zombienet/Cargo.toml b/templates/zombienet/Cargo.toml new file mode 100644 index 00000000000..cb2adf70dbd --- /dev/null +++ b/templates/zombienet/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "template-zombienet-tests" +description = "Zombienet test for templates." +version = "0.0.0" +license = "Unlicense" +authors.workspace = true +homepage.workspace = true +repository.workspace = true +edition.workspace = true +publish = false + +[dependencies] +env_logger = "0.11.2" +log = "0.4" +tokio = { version = "1.36.0", features = ["rt-multi-thread"] } +anyhow = "1.0.81" +zombienet-sdk = "0.2.8" + +[features] +zombienet = [] diff --git a/templates/zombienet/tests/smoke.rs b/templates/zombienet/tests/smoke.rs new file mode 100644 index 00000000000..ba5f42142f3 --- /dev/null +++ b/templates/zombienet/tests/smoke.rs @@ -0,0 +1,96 @@ +//! This test is setup to run with the `native` provider and needs these binaries in your PATH +//! `polkadot`, `polkadot-prepare-worker`, `polkadot-execute-worker`, `parachain-template-node`. +//! You can follow these steps to compile and export the binaries: +//! `cargo build --release -features fast-runtime --bin polkadot --bin polkadot-execute-worker --bin +//! polkadot-prepare-worker` +//! `cargo build --package parachain-template-node --release` +//! `cargo build --package minimal-template-node --release` +//! `export PATH=/target/release:$PATH +//! +//! The you can run the test with +//! `cargo test -p template-zombienet-tests` + +#[cfg(feature = "zombienet")] +mod smoke { + use anyhow::anyhow; + use zombienet_sdk::{NetworkConfig, NetworkConfigBuilder, NetworkConfigExt}; + + pub fn get_config(cmd: &str, para_cmd: Option<&str>) -> Result { + let chain = if cmd == "polkadot" { "rococo-local" } else { "dev" }; + let config = NetworkConfigBuilder::new().with_relaychain(|r| { + r.with_chain(chain) + .with_default_command(cmd) + .with_node(|node| node.with_name("alice")) + .with_node(|node| node.with_name("bob")) + }); + + let config = if let Some(para_cmd) = para_cmd { + config.with_parachain(|p| { + p.with_id(1000) + .with_default_command(para_cmd) + .with_collator(|n| n.with_name("collator")) + }) + } else { + config + }; + + config.build().map_err(|e| { + let errs = e.into_iter().map(|e| e.to_string()).collect::>().join(" "); + anyhow!("config errs: {errs}") + }) + } + + #[tokio::test(flavor = "multi_thread")] + async fn parachain_template_block_production_test() -> Result<(), anyhow::Error> { + let _ = env_logger::try_init_from_env( + env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "info"), + ); + + let config = get_config("polkadot", Some("parachain-template-node"))?; + + let network = config.spawn_native().await?; + + // wait 6 blocks of the para + let collator = network.get_node("collator")?; + assert!(collator + .wait_metric("block_height{status=\"best\"}", |b| b > 5_f64) + .await + .is_ok()); + + Ok(()) + } + + #[tokio::test(flavor = "multi_thread")] + async fn solochain_template_block_production_test() -> Result<(), anyhow::Error> { + let _ = env_logger::try_init_from_env( + env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "info"), + ); + + let config = get_config("solochain-template-node", None)?; + + let network = config.spawn_native().await?; + + // wait 6 blocks + let alice = network.get_node("alice")?; + assert!(alice.wait_metric("block_height{status=\"best\"}", |b| b > 5_f64).await.is_ok()); + + Ok(()) + } + + #[tokio::test(flavor = "multi_thread")] + async fn minimal_template_block_production_test() -> Result<(), anyhow::Error> { + let _ = env_logger::try_init_from_env( + env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "info"), + ); + + let config = get_config("minimal-template-node", None)?; + + let network = config.spawn_native().await?; + + // wait 6 blocks + let alice = network.get_node("alice")?; + assert!(alice.wait_metric("block_height{status=\"best\"}", |b| b > 5_f64).await.is_ok()); + + Ok(()) + } +} -- GitLab From 43cd6fd4370d3043272f64a79aeb9e6dc0edd13f Mon Sep 17 00:00:00 2001 From: Nazar Mokrynskyi Date: Tue, 17 Sep 2024 12:08:50 +0300 Subject: [PATCH 068/125] Syncing strategy refactoring (part 2) (#5666) # Description Follow-up to https://github.com/paritytech/polkadot-sdk/pull/5469 and mostly covering https://github.com/paritytech/polkadot-sdk/issues/5333. The primary change here is that syncing strategy is no longer created inside of syncing engine, instead syncing strategy is an argument of syncing engine, more specifically it is an argument to `build_network` that most downstream users will use. This also extracts addition of request-response protocols outside of network construction, making sure they are physically not present when they don't need to be (imagine syncing strategy that uses none of Substrate's protocols in its implementation for example). This technically allows to completely replace syncing strategy with whatever strategy chain might need. There will be at least one follow-up PR that will simplify `SyncingStrategy` trait and other public interfaces to remove mentions of block/state/warp sync requests, replacing them with generic APIs, such that strategies where warp sync is not applicable don't have to provide dummy method implementations, etc. ## Integration Downstream projects will have to write a bit of boilerplate calling `build_polkadot_syncing_strategy` function to create previously default syncing strategy. ## Review Notes Please review PR through individual commits rather than the final diff, it will be easier that way. The changes are mostly just moving code around one step at a time. # Checklist * [x] My PR includes a detailed description as outlined in the "Description" and its two subsections above. * [x] My PR follows the [labeling requirements]( https://github.com/paritytech/polkadot-sdk/blob/master/docs/contributor/CONTRIBUTING.md#Process ) of this project (at minimum one label for `T` required) * External contributors: ask maintainers to put the right label on your PR. * [x] I have made corresponding changes to the documentation (if applicable) --- cumulus/client/service/src/lib.rs | 23 +- polkadot/node/service/src/lib.rs | 14 +- prdoc/pr_5666.prdoc | 19 ++ substrate/bin/node/cli/src/service.rs | 13 +- substrate/client/network/sync/src/engine.rs | 77 ++---- substrate/client/network/sync/src/strategy.rs | 54 +++- .../network/sync/src/strategy/chain_sync.rs | 12 +- .../sync/src/strategy/chain_sync/test.rs | 117 +++++++-- .../client/network/sync/src/strategy/state.rs | 91 +++++-- .../client/network/sync/src/strategy/warp.rs | 88 ++++--- substrate/client/network/test/src/lib.rs | 30 ++- substrate/client/network/test/src/service.rs | 17 +- substrate/client/service/src/builder.rs | 245 ++++++++++-------- substrate/client/service/src/lib.rs | 10 +- templates/minimal/node/src/service.rs | 21 +- templates/solochain/node/src/service.rs | 17 +- 16 files changed, 562 insertions(+), 286 deletions(-) create mode 100644 prdoc/pr_5666.prdoc diff --git a/cumulus/client/service/src/lib.rs b/cumulus/client/service/src/lib.rs index dd14ca514b3..c95c72c370a 100644 --- a/cumulus/client/service/src/lib.rs +++ b/cumulus/client/service/src/lib.rs @@ -40,7 +40,10 @@ use sc_consensus::{ use sc_network::{config::SyncMode, service::traits::NetworkService, NetworkBackend}; use sc_network_sync::SyncingService; use sc_network_transactions::TransactionsHandlerController; -use sc_service::{Configuration, NetworkStarter, SpawnTaskHandle, TaskManager, WarpSyncConfig}; +use sc_service::{ + build_polkadot_syncing_strategy, Configuration, NetworkStarter, SpawnTaskHandle, TaskManager, + WarpSyncConfig, +}; use sc_telemetry::{log, TelemetryWorkerHandle}; use sc_utils::mpsc::TracingUnboundedSender; use sp_api::ProvideRuntimeApi; @@ -425,7 +428,7 @@ pub struct BuildNetworkParams< pub async fn build_network<'a, Block, Client, RCInterface, IQ, Network>( BuildNetworkParams { parachain_config, - net_config, + mut net_config, client, transaction_pool, para_id, @@ -462,7 +465,7 @@ where IQ: ImportQueue + 'static, Network: NetworkBackend::Hash>, { - let warp_sync_params = match parachain_config.network.sync_mode { + let warp_sync_config = match parachain_config.network.sync_mode { SyncMode::Warp => { log::debug!(target: LOG_TARGET_SYNC, "waiting for announce block..."); @@ -493,9 +496,19 @@ where }, }; let metrics = Network::register_notification_metrics( - parachain_config.prometheus_config.as_ref().map(|cfg| &cfg.registry), + parachain_config.prometheus_config.as_ref().map(|config| &config.registry), ); + let syncing_strategy = build_polkadot_syncing_strategy( + parachain_config.protocol_id(), + parachain_config.chain_spec.fork_id(), + &mut net_config, + warp_sync_config, + client.clone(), + &spawn_handle, + parachain_config.prometheus_config.as_ref().map(|config| &config.registry), + )?; + sc_service::build_network(sc_service::BuildNetworkParams { config: parachain_config, net_config, @@ -504,7 +517,7 @@ where spawn_handle, import_queue, block_announce_validator_builder: Some(Box::new(move |_| block_announce_validator)), - warp_sync_config: warp_sync_params, + syncing_strategy, block_relay: None, metrics, }) diff --git a/polkadot/node/service/src/lib.rs b/polkadot/node/service/src/lib.rs index fe96d29c1ce..dd35423e18e 100644 --- a/polkadot/node/service/src/lib.rs +++ b/polkadot/node/service/src/lib.rs @@ -84,7 +84,7 @@ use std::{collections::HashMap, path::PathBuf, sync::Arc, time::Duration}; use prometheus_endpoint::Registry; #[cfg(feature = "full-node")] use sc_service::KeystoreContainer; -use sc_service::{RpcHandlers, SpawnTaskHandle}; +use sc_service::{build_polkadot_syncing_strategy, RpcHandlers, SpawnTaskHandle}; use sc_telemetry::TelemetryWorker; #[cfg(feature = "full-node")] use sc_telemetry::{Telemetry, TelemetryWorkerHandle}; @@ -1028,6 +1028,16 @@ pub fn new_full< }) }; + let syncing_strategy = build_polkadot_syncing_strategy( + config.protocol_id(), + config.chain_spec.fork_id(), + &mut net_config, + Some(WarpSyncConfig::WithProvider(warp_sync)), + client.clone(), + &task_manager.spawn_handle(), + config.prometheus_config.as_ref().map(|config| &config.registry), + )?; + let (network, system_rpc_tx, tx_handler_controller, network_starter, sync_service) = sc_service::build_network(sc_service::BuildNetworkParams { config: &config, @@ -1037,7 +1047,7 @@ pub fn new_full< spawn_handle: task_manager.spawn_handle(), import_queue, block_announce_validator_builder: None, - warp_sync_config: Some(WarpSyncConfig::WithProvider(warp_sync)), + syncing_strategy, block_relay: None, metrics, })?; diff --git a/prdoc/pr_5666.prdoc b/prdoc/pr_5666.prdoc new file mode 100644 index 00000000000..08bd9815cdd --- /dev/null +++ b/prdoc/pr_5666.prdoc @@ -0,0 +1,19 @@ +title: Make syncing strategy an argument of the syncing engine + +doc: + - audience: Node Dev + description: | + Syncing strategy is no longer implicitly created when building network, but needs to be instantiated explicitly. + Previously default implementation can be created with new function `build_polkadot_syncing_strategy` or custom + syncing strategy could be implemented and used instead if desired, providing greater flexibility for chain + developers. + +crates: + - name: cumulus-client-service + bump: patch + - name: polkadot-service + bump: patch + - name: sc-service + bump: major + - name: sc-network-sync + bump: major diff --git a/substrate/bin/node/cli/src/service.rs b/substrate/bin/node/cli/src/service.rs index 1b345a23f27..69e953f54e4 100644 --- a/substrate/bin/node/cli/src/service.rs +++ b/substrate/bin/node/cli/src/service.rs @@ -32,6 +32,7 @@ use frame_system_rpc_runtime_api::AccountNonceApi; use futures::prelude::*; use kitchensink_runtime::RuntimeApi; use node_primitives::Block; +use polkadot_sdk::sc_service::build_polkadot_syncing_strategy; use sc_client_api::{Backend, BlockBackend}; use sc_consensus_babe::{self, SlotProportion}; use sc_network::{ @@ -506,6 +507,16 @@ pub fn new_full_base::Hash>>( Vec::default(), )); + let syncing_strategy = build_polkadot_syncing_strategy( + config.protocol_id(), + config.chain_spec.fork_id(), + &mut net_config, + Some(WarpSyncConfig::WithProvider(warp_sync)), + client.clone(), + &task_manager.spawn_handle(), + config.prometheus_config.as_ref().map(|config| &config.registry), + )?; + let (network, system_rpc_tx, tx_handler_controller, network_starter, sync_service) = sc_service::build_network(sc_service::BuildNetworkParams { config: &config, @@ -515,7 +526,7 @@ pub fn new_full_base::Hash>>( spawn_handle: task_manager.spawn_handle(), import_queue, block_announce_validator_builder: None, - warp_sync_config: Some(WarpSyncConfig::WithProvider(warp_sync)), + syncing_strategy, block_relay: None, metrics, })?; diff --git a/substrate/client/network/sync/src/engine.rs b/substrate/client/network/sync/src/engine.rs index 86c1a7abf74..aafbd950202 100644 --- a/substrate/client/network/sync/src/engine.rs +++ b/substrate/client/network/sync/src/engine.rs @@ -24,7 +24,6 @@ use crate::{ BlockAnnounceValidationResult, BlockAnnounceValidator as BlockAnnounceValidatorStream, }, block_relay_protocol::{BlockDownloader, BlockResponseError}, - block_request_handler::MAX_BLOCKS_IN_RESPONSE, pending_responses::{PendingResponses, ResponseEvent}, schema::v1::{StateRequest, StateResponse}, service::{ @@ -32,8 +31,8 @@ use crate::{ syncing_service::{SyncingService, ToServiceCommand}, }, strategy::{ - warp::{EncodedProof, WarpProofRequest, WarpSyncConfig}, - PolkadotSyncingStrategy, StrategyKey, SyncingAction, SyncingConfig, SyncingStrategy, + warp::{EncodedProof, WarpProofRequest}, + StrategyKey, SyncingAction, SyncingStrategy, }, types::{ BadPeer, ExtendedPeerInfo, OpaqueStateRequest, OpaqueStateResponse, PeerRequest, SyncEvent, @@ -189,7 +188,7 @@ pub struct Peer { pub struct SyncingEngine { /// Syncing strategy. - strategy: PolkadotSyncingStrategy, + strategy: Box>, /// Blockchain client. client: Arc, @@ -271,12 +270,6 @@ pub struct SyncingEngine { /// Block downloader block_downloader: Arc>, - /// Protocol name used to send out state requests - state_request_protocol_name: ProtocolName, - - /// Protocol name used to send out warp sync requests - warp_sync_protocol_name: Option, - /// Handle to import queue. import_queue: Box>, } @@ -301,35 +294,15 @@ where protocol_id: ProtocolId, fork_id: &Option, block_announce_validator: Box + Send>, - warp_sync_config: Option>, + syncing_strategy: Box>, network_service: service::network::NetworkServiceHandle, import_queue: Box>, block_downloader: Arc>, - state_request_protocol_name: ProtocolName, - warp_sync_protocol_name: Option, peer_store_handle: Arc, ) -> Result<(Self, SyncingService, N::NotificationProtocolConfig), ClientError> where N: NetworkBackend::Hash>, { - let mode = net_config.network_config.sync_mode; - let max_parallel_downloads = net_config.network_config.max_parallel_downloads; - let max_blocks_per_request = - if net_config.network_config.max_blocks_per_request > MAX_BLOCKS_IN_RESPONSE as u32 { - log::info!( - target: LOG_TARGET, - "clamping maximum blocks per request to {MAX_BLOCKS_IN_RESPONSE}", - ); - MAX_BLOCKS_IN_RESPONSE as u32 - } else { - net_config.network_config.max_blocks_per_request - }; - let syncing_config = SyncingConfig { - mode, - max_parallel_downloads, - max_blocks_per_request, - metrics_registry: metrics_registry.cloned(), - }; let cache_capacity = (net_config.network_config.default_peers_set.in_peers + net_config.network_config.default_peers_set.out_peers) .max(1); @@ -388,10 +361,6 @@ where Arc::clone(&peer_store_handle), ); - // Initialize syncing strategy. - let strategy = - PolkadotSyncingStrategy::new(syncing_config, client.clone(), warp_sync_config)?; - let block_announce_protocol_name = block_announce_config.protocol_name().clone(); let (tx, service_rx) = tracing_unbounded("mpsc_chain_sync", 100_000); let num_connected = Arc::new(AtomicUsize::new(0)); @@ -413,7 +382,7 @@ where Self { roles, client, - strategy, + strategy: syncing_strategy, network_service, peers: HashMap::new(), block_announce_data_cache: LruMap::new(ByLength::new(cache_capacity)), @@ -450,8 +419,6 @@ where }, pending_responses: PendingResponses::new(), block_downloader, - state_request_protocol_name, - warp_sync_protocol_name, import_queue, }, SyncingService::new(tx, num_connected, is_major_syncing), @@ -652,16 +619,16 @@ where "Processed {action:?}, response removed: {removed}.", ); }, - SyncingAction::SendStateRequest { peer_id, key, request } => { - self.send_state_request(peer_id, key, request); + SyncingAction::SendStateRequest { peer_id, key, protocol_name, request } => { + self.send_state_request(peer_id, key, protocol_name, request); trace!( target: LOG_TARGET, "Processed `ChainSyncAction::SendStateRequest` to {peer_id}.", ); }, - SyncingAction::SendWarpProofRequest { peer_id, key, request } => { - self.send_warp_proof_request(peer_id, key, request.clone()); + SyncingAction::SendWarpProofRequest { peer_id, key, protocol_name, request } => { + self.send_warp_proof_request(peer_id, key, protocol_name, request.clone()); trace!( target: LOG_TARGET, @@ -1054,6 +1021,7 @@ where &mut self, peer_id: PeerId, key: StrategyKey, + protocol_name: ProtocolName, request: OpaqueStateRequest, ) { if !self.peers.contains_key(&peer_id) { @@ -1070,7 +1038,7 @@ where Ok(data) => { self.network_service.start_request( peer_id, - self.state_request_protocol_name.clone(), + protocol_name, data, tx, IfDisconnected::ImmediateError, @@ -1089,6 +1057,7 @@ where &mut self, peer_id: PeerId, key: StrategyKey, + protocol_name: ProtocolName, request: WarpProofRequest, ) { if !self.peers.contains_key(&peer_id) { @@ -1101,21 +1070,13 @@ where self.pending_responses.insert(peer_id, key, PeerRequest::WarpProof, rx.boxed()); - match &self.warp_sync_protocol_name { - Some(name) => self.network_service.start_request( - peer_id, - name.clone(), - request.encode(), - tx, - IfDisconnected::ImmediateError, - ), - None => { - log::warn!( - target: LOG_TARGET, - "Trying to send warp sync request when no protocol is configured {request:?}", - ); - }, - } + self.network_service.start_request( + peer_id, + protocol_name, + request.encode(), + tx, + IfDisconnected::ImmediateError, + ); } fn encode_state_request(request: &OpaqueStateRequest) -> Result, String> { diff --git a/substrate/client/network/sync/src/strategy.rs b/substrate/client/network/sync/src/strategy.rs index f8d6976bbaa..81998b7576b 100644 --- a/substrate/client/network/sync/src/strategy.rs +++ b/substrate/client/network/sync/src/strategy.rs @@ -26,6 +26,7 @@ pub mod state_sync; pub mod warp; use crate::{ + block_request_handler::MAX_BLOCKS_IN_RESPONSE, types::{BadPeer, OpaqueStateRequest, OpaqueStateResponse, SyncStatus}, LOG_TARGET, }; @@ -34,6 +35,7 @@ use log::{debug, error, info}; use prometheus_endpoint::Registry; use sc_client_api::{BlockBackend, ProofProvider}; use sc_consensus::{BlockImportError, BlockImportStatus, IncomingBlock}; +use sc_network::ProtocolName; use sc_network_common::sync::{ message::{BlockAnnounce, BlockData, BlockRequest}, SyncMode, @@ -172,6 +174,8 @@ pub struct SyncingConfig { pub max_blocks_per_request: u32, /// Prometheus metrics registry. pub metrics_registry: Option, + /// Protocol name used to send out state requests + pub state_request_protocol_name: ProtocolName, } /// The key identifying a specific strategy for responses routing. @@ -190,9 +194,19 @@ pub enum SyncingAction { /// Send block request to peer. Always implies dropping a stale block request to the same peer. SendBlockRequest { peer_id: PeerId, key: StrategyKey, request: BlockRequest }, /// Send state request to peer. - SendStateRequest { peer_id: PeerId, key: StrategyKey, request: OpaqueStateRequest }, + SendStateRequest { + peer_id: PeerId, + key: StrategyKey, + protocol_name: ProtocolName, + request: OpaqueStateRequest, + }, /// Send warp proof request to peer. - SendWarpProofRequest { peer_id: PeerId, key: StrategyKey, request: WarpProofRequest }, + SendWarpProofRequest { + peer_id: PeerId, + key: StrategyKey, + protocol_name: ProtocolName, + request: WarpProofRequest, + }, /// Drop stale request. CancelRequest { peer_id: PeerId, key: StrategyKey }, /// Peer misbehaved. Disconnect, report it and cancel any requests to it. @@ -219,8 +233,13 @@ impl SyncingAction { impl From> for SyncingAction { fn from(action: WarpSyncAction) -> Self { match action { - WarpSyncAction::SendWarpProofRequest { peer_id, request } => - SyncingAction::SendWarpProofRequest { peer_id, key: StrategyKey::Warp, request }, + WarpSyncAction::SendWarpProofRequest { peer_id, protocol_name, request } => + SyncingAction::SendWarpProofRequest { + peer_id, + key: StrategyKey::Warp, + protocol_name, + request, + }, WarpSyncAction::SendBlockRequest { peer_id, request } => SyncingAction::SendBlockRequest { peer_id, key: StrategyKey::Warp, request }, WarpSyncAction::DropPeer(bad_peer) => SyncingAction::DropPeer(bad_peer), @@ -232,8 +251,13 @@ impl From> for SyncingAction { impl From> for SyncingAction { fn from(action: StateStrategyAction) -> Self { match action { - StateStrategyAction::SendStateRequest { peer_id, request } => - SyncingAction::SendStateRequest { peer_id, key: StrategyKey::State, request }, + StateStrategyAction::SendStateRequest { peer_id, protocol_name, request } => + SyncingAction::SendStateRequest { + peer_id, + key: StrategyKey::State, + protocol_name, + request, + }, StateStrategyAction::DropPeer(bad_peer) => SyncingAction::DropPeer(bad_peer), StateStrategyAction::ImportBlocks { origin, blocks } => SyncingAction::ImportBlocks { origin, blocks }, @@ -509,14 +533,24 @@ where { /// Initialize a new syncing strategy. pub fn new( - config: SyncingConfig, + mut config: SyncingConfig, client: Arc, warp_sync_config: Option>, + warp_sync_protocol_name: Option, ) -> Result { + if config.max_blocks_per_request > MAX_BLOCKS_IN_RESPONSE as u32 { + info!( + target: LOG_TARGET, + "clamping maximum blocks per request to {MAX_BLOCKS_IN_RESPONSE}", + ); + config.max_blocks_per_request = MAX_BLOCKS_IN_RESPONSE as u32; + } + if let SyncMode::Warp = config.mode { let warp_sync_config = warp_sync_config .expect("Warp sync configuration must be supplied in warp sync mode."); - let warp_sync = WarpSync::new(client.clone(), warp_sync_config); + let warp_sync = + WarpSync::new(client.clone(), warp_sync_config, warp_sync_protocol_name); Ok(Self { config, client, @@ -531,6 +565,7 @@ where client.clone(), config.max_parallel_downloads, config.max_blocks_per_request, + config.state_request_protocol_name.clone(), config.metrics_registry.as_ref(), std::iter::empty(), )?; @@ -564,6 +599,7 @@ where self.peer_best_blocks .iter() .map(|(peer_id, (_, best_number))| (*peer_id, *best_number)), + self.config.state_request_protocol_name.clone(), ); self.warp = None; @@ -580,6 +616,7 @@ where self.client.clone(), self.config.max_parallel_downloads, self.config.max_blocks_per_request, + self.config.state_request_protocol_name.clone(), self.config.metrics_registry.as_ref(), self.peer_best_blocks.iter().map(|(peer_id, (best_hash, best_number))| { (*peer_id, *best_hash, *best_number) @@ -608,6 +645,7 @@ where self.client.clone(), self.config.max_parallel_downloads, self.config.max_blocks_per_request, + self.config.state_request_protocol_name.clone(), self.config.metrics_registry.as_ref(), self.peer_best_blocks.iter().map(|(peer_id, (best_hash, best_number))| { (*peer_id, *best_hash, *best_number) diff --git a/substrate/client/network/sync/src/strategy/chain_sync.rs b/substrate/client/network/sync/src/strategy/chain_sync.rs index a8ba5558d1b..fd0e3ea1a76 100644 --- a/substrate/client/network/sync/src/strategy/chain_sync.rs +++ b/substrate/client/network/sync/src/strategy/chain_sync.rs @@ -47,6 +47,7 @@ use log::{debug, error, info, trace, warn}; use prometheus_endpoint::{register, Gauge, PrometheusError, Registry, U64}; use sc_client_api::{blockchain::BlockGap, BlockBackend, ProofProvider}; use sc_consensus::{BlockImportError, BlockImportStatus, IncomingBlock}; +use sc_network::ProtocolName; use sc_network_common::sync::message::{ BlockAnnounce, BlockAttributes, BlockData, BlockRequest, BlockResponse, Direction, FromBlock, }; @@ -318,6 +319,8 @@ pub struct ChainSync { max_parallel_downloads: u32, /// Maximum blocks per request. max_blocks_per_request: u32, + /// Protocol name used to send out state requests + state_request_protocol_name: ProtocolName, /// Total number of downloaded blocks. downloaded_blocks: usize, /// State sync in progress, if any. @@ -880,7 +883,12 @@ where self.actions.extend(justification_requests); let state_request = self.state_request().into_iter().map(|(peer_id, request)| { - SyncingAction::SendStateRequest { peer_id, key: StrategyKey::ChainSync, request } + SyncingAction::SendStateRequest { + peer_id, + key: StrategyKey::ChainSync, + protocol_name: self.state_request_protocol_name.clone(), + request, + } }); self.actions.extend(state_request); @@ -905,6 +913,7 @@ where client: Arc, max_parallel_downloads: u32, max_blocks_per_request: u32, + state_request_protocol_name: ProtocolName, metrics_registry: Option<&Registry>, initial_peers: impl Iterator)>, ) -> Result { @@ -923,6 +932,7 @@ where allowed_requests: Default::default(), max_parallel_downloads, max_blocks_per_request, + state_request_protocol_name, downloaded_blocks: 0, state_sync: None, import_existing: false, diff --git a/substrate/client/network/sync/src/strategy/chain_sync/test.rs b/substrate/client/network/sync/src/strategy/chain_sync/test.rs index 59436f387db..d13f034e2e8 100644 --- a/substrate/client/network/sync/src/strategy/chain_sync/test.rs +++ b/substrate/client/network/sync/src/strategy/chain_sync/test.rs @@ -38,9 +38,16 @@ fn processes_empty_response_on_justification_request_for_unknown_block() { let client = Arc::new(TestClientBuilder::new().build()); let peer_id = PeerId::random(); - let mut sync = - ChainSync::new(ChainSyncMode::Full, client.clone(), 1, 64, None, std::iter::empty()) - .unwrap(); + let mut sync = ChainSync::new( + ChainSyncMode::Full, + client.clone(), + 1, + 64, + ProtocolName::Static(""), + None, + std::iter::empty(), + ) + .unwrap(); let (a1_hash, a1_number) = { let a1 = BlockBuilderBuilder::new(&*client) @@ -95,9 +102,16 @@ fn restart_doesnt_affect_peers_downloading_finality_data() { // we request max 8 blocks to always initiate block requests to both peers for the test to be // deterministic - let mut sync = - ChainSync::new(ChainSyncMode::Full, client.clone(), 1, 8, None, std::iter::empty()) - .unwrap(); + let mut sync = ChainSync::new( + ChainSyncMode::Full, + client.clone(), + 1, + 8, + ProtocolName::Static(""), + None, + std::iter::empty(), + ) + .unwrap(); let peer_id1 = PeerId::random(); let peer_id2 = PeerId::random(); @@ -291,9 +305,16 @@ fn do_ancestor_search_when_common_block_to_best_queued_gap_is_to_big() { let client = Arc::new(TestClientBuilder::new().build()); let info = client.info(); - let mut sync = - ChainSync::new(ChainSyncMode::Full, client.clone(), 5, 64, None, std::iter::empty()) - .unwrap(); + let mut sync = ChainSync::new( + ChainSyncMode::Full, + client.clone(), + 5, + 64, + ProtocolName::Static(""), + None, + std::iter::empty(), + ) + .unwrap(); let peer_id1 = PeerId::random(); let peer_id2 = PeerId::random(); @@ -438,9 +459,16 @@ fn can_sync_huge_fork() { let info = client.info(); - let mut sync = - ChainSync::new(ChainSyncMode::Full, client.clone(), 5, 64, None, std::iter::empty()) - .unwrap(); + let mut sync = ChainSync::new( + ChainSyncMode::Full, + client.clone(), + 5, + 64, + ProtocolName::Static(""), + None, + std::iter::empty(), + ) + .unwrap(); let finalized_block = blocks[MAX_BLOCKS_TO_LOOK_BACKWARDS as usize * 2 - 1].clone(); let just = (*b"TEST", Vec::new()); @@ -572,9 +600,16 @@ fn syncs_fork_without_duplicate_requests() { let info = client.info(); - let mut sync = - ChainSync::new(ChainSyncMode::Full, client.clone(), 5, 64, None, std::iter::empty()) - .unwrap(); + let mut sync = ChainSync::new( + ChainSyncMode::Full, + client.clone(), + 5, + 64, + ProtocolName::Static(""), + None, + std::iter::empty(), + ) + .unwrap(); let finalized_block = blocks[MAX_BLOCKS_TO_LOOK_BACKWARDS as usize * 2 - 1].clone(); let just = (*b"TEST", Vec::new()); @@ -709,9 +744,16 @@ fn removes_target_fork_on_disconnect() { let client = Arc::new(TestClientBuilder::new().build()); let blocks = (0..3).map(|_| build_block(&client, None, false)).collect::>(); - let mut sync = - ChainSync::new(ChainSyncMode::Full, client.clone(), 1, 64, None, std::iter::empty()) - .unwrap(); + let mut sync = ChainSync::new( + ChainSyncMode::Full, + client.clone(), + 1, + 64, + ProtocolName::Static(""), + None, + std::iter::empty(), + ) + .unwrap(); let peer_id1 = PeerId::random(); let common_block = blocks[1].clone(); @@ -736,9 +778,16 @@ fn can_import_response_with_missing_blocks() { let empty_client = Arc::new(TestClientBuilder::new().build()); - let mut sync = - ChainSync::new(ChainSyncMode::Full, empty_client.clone(), 1, 64, None, std::iter::empty()) - .unwrap(); + let mut sync = ChainSync::new( + ChainSyncMode::Full, + empty_client.clone(), + 1, + 64, + ProtocolName::Static(""), + None, + std::iter::empty(), + ) + .unwrap(); let peer_id1 = PeerId::random(); let best_block = blocks[3].clone(); @@ -769,9 +818,16 @@ fn ancestor_search_repeat() { #[test] fn sync_restart_removes_block_but_not_justification_requests() { let client = Arc::new(TestClientBuilder::new().build()); - let mut sync = - ChainSync::new(ChainSyncMode::Full, client.clone(), 1, 64, None, std::iter::empty()) - .unwrap(); + let mut sync = ChainSync::new( + ChainSyncMode::Full, + client.clone(), + 1, + 64, + ProtocolName::Static(""), + None, + std::iter::empty(), + ) + .unwrap(); let peers = vec![PeerId::random(), PeerId::random()]; @@ -913,9 +969,16 @@ fn request_across_forks() { fork_blocks }; - let mut sync = - ChainSync::new(ChainSyncMode::Full, client.clone(), 5, 64, None, std::iter::empty()) - .unwrap(); + let mut sync = ChainSync::new( + ChainSyncMode::Full, + client.clone(), + 5, + 64, + ProtocolName::Static(""), + None, + std::iter::empty(), + ) + .unwrap(); // Add the peers, all at the common ancestor 100. let common_block = blocks.last().unwrap(); diff --git a/substrate/client/network/sync/src/strategy/state.rs b/substrate/client/network/sync/src/strategy/state.rs index 6f06f238fe3..a04ab8be4fe 100644 --- a/substrate/client/network/sync/src/strategy/state.rs +++ b/substrate/client/network/sync/src/strategy/state.rs @@ -30,6 +30,7 @@ use crate::{ use log::{debug, error, trace}; use sc_client_api::ProofProvider; use sc_consensus::{BlockImportError, BlockImportStatus, IncomingBlock}; +use sc_network::ProtocolName; use sc_network_common::sync::message::BlockAnnounce; use sc_network_types::PeerId; use sp_consensus::BlockOrigin; @@ -52,7 +53,7 @@ mod rep { /// Action that should be performed on [`StateStrategy`]'s behalf. pub enum StateStrategyAction { /// Send state request to peer. - SendStateRequest { peer_id: PeerId, request: OpaqueStateRequest }, + SendStateRequest { peer_id: PeerId, protocol_name: ProtocolName, request: OpaqueStateRequest }, /// Disconnect and report peer. DropPeer(BadPeer), /// Import blocks. @@ -83,6 +84,7 @@ pub struct StateStrategy { peers: HashMap>, disconnected_peers: DisconnectedPeers, actions: Vec>, + protocol_name: ProtocolName, succeeded: bool, } @@ -95,6 +97,7 @@ impl StateStrategy { target_justifications: Option, skip_proof: bool, initial_peers: impl Iterator)>, + protocol_name: ProtocolName, ) -> Self where Client: ProofProvider + Send + Sync + 'static, @@ -115,6 +118,7 @@ impl StateStrategy { peers, disconnected_peers: DisconnectedPeers::new(), actions: Vec::new(), + protocol_name, succeeded: false, } } @@ -125,6 +129,7 @@ impl StateStrategy { fn new_with_provider( state_sync_provider: Box>, initial_peers: impl Iterator)>, + protocol_name: ProtocolName, ) -> Self { Self { state_sync: state_sync_provider, @@ -135,6 +140,7 @@ impl StateStrategy { .collect(), disconnected_peers: DisconnectedPeers::new(), actions: Vec::new(), + protocol_name, succeeded: false, } } @@ -349,10 +355,13 @@ impl StateStrategy { /// Get actions that should be performed by the owner on [`WarpSync`]'s behalf #[must_use] pub fn actions(&mut self) -> impl Iterator> { - let state_request = self - .state_request() - .into_iter() - .map(|(peer_id, request)| StateStrategyAction::SendStateRequest { peer_id, request }); + let state_request = self.state_request().into_iter().map(|(peer_id, request)| { + StateStrategyAction::SendStateRequest { + peer_id, + protocol_name: self.protocol_name.clone(), + request, + } + }); self.actions.extend(state_request); std::mem::take(&mut self.actions).into_iter() @@ -409,8 +418,15 @@ mod test { .block; let target_header = target_block.header().clone(); - let mut state_strategy = - StateStrategy::new(client, target_header, None, None, false, std::iter::empty()); + let mut state_strategy = StateStrategy::new( + client, + target_header, + None, + None, + false, + std::iter::empty(), + ProtocolName::Static(""), + ); assert!(state_strategy .schedule_next_peer(PeerState::DownloadingState, Zero::zero()) @@ -442,6 +458,7 @@ mod test { None, false, initial_peers, + ProtocolName::Static(""), ); let peer_id = @@ -475,6 +492,7 @@ mod test { None, false, initial_peers, + ProtocolName::Static(""), ); let peer_id = state_strategy.schedule_next_peer(PeerState::DownloadingState, 10); @@ -508,6 +526,7 @@ mod test { None, false, initial_peers, + ProtocolName::Static(""), ); // Disconnecting a peer without an inflight request has no effect on persistent states. @@ -557,6 +576,7 @@ mod test { None, false, initial_peers, + ProtocolName::Static(""), ); let (_peer_id, mut opaque_request) = state_strategy.state_request().unwrap(); @@ -587,6 +607,7 @@ mod test { None, false, initial_peers, + ProtocolName::Static(""), ); // First request is sent. @@ -602,8 +623,11 @@ mod test { state_sync_provider.expect_import().return_once(|_| ImportResult::Continue); let peer_id = PeerId::random(); let initial_peers = std::iter::once((peer_id, 10)); - let mut state_strategy = - StateStrategy::new_with_provider(Box::new(state_sync_provider), initial_peers); + let mut state_strategy = StateStrategy::new_with_provider( + Box::new(state_sync_provider), + initial_peers, + ProtocolName::Static(""), + ); // Manually set the peer's state. state_strategy.peers.get_mut(&peer_id).unwrap().state = PeerState::DownloadingState; @@ -620,8 +644,11 @@ mod test { state_sync_provider.expect_import().return_once(|_| ImportResult::BadResponse); let peer_id = PeerId::random(); let initial_peers = std::iter::once((peer_id, 10)); - let mut state_strategy = - StateStrategy::new_with_provider(Box::new(state_sync_provider), initial_peers); + let mut state_strategy = StateStrategy::new_with_provider( + Box::new(state_sync_provider), + initial_peers, + ProtocolName::Static(""), + ); // Manually set the peer's state. state_strategy.peers.get_mut(&peer_id).unwrap().state = PeerState::DownloadingState; let dummy_response = OpaqueStateResponse(Box::new(StateResponse::default())); @@ -639,8 +666,11 @@ mod test { state_sync_provider.expect_import().return_once(|_| ImportResult::Continue); let peer_id = PeerId::random(); let initial_peers = std::iter::once((peer_id, 10)); - let mut state_strategy = - StateStrategy::new_with_provider(Box::new(state_sync_provider), initial_peers); + let mut state_strategy = StateStrategy::new_with_provider( + Box::new(state_sync_provider), + initial_peers, + ProtocolName::Static(""), + ); // Manually set the peer's state . state_strategy.peers.get_mut(&peer_id).unwrap().state = PeerState::DownloadingState; @@ -698,8 +728,11 @@ mod test { // Prepare `StateStrategy`. let peer_id = PeerId::random(); let initial_peers = std::iter::once((peer_id, 10)); - let mut state_strategy = - StateStrategy::new_with_provider(Box::new(state_sync_provider), initial_peers); + let mut state_strategy = StateStrategy::new_with_provider( + Box::new(state_sync_provider), + initial_peers, + ProtocolName::Static(""), + ); // Manually set the peer's state . state_strategy.peers.get_mut(&peer_id).unwrap().state = PeerState::DownloadingState; @@ -722,8 +755,11 @@ mod test { let mut state_sync_provider = MockStateSync::::new(); state_sync_provider.expect_target_hash().return_const(target_hash); - let mut state_strategy = - StateStrategy::new_with_provider(Box::new(state_sync_provider), std::iter::empty()); + let mut state_strategy = StateStrategy::new_with_provider( + Box::new(state_sync_provider), + std::iter::empty(), + ProtocolName::Static(""), + ); // Unknown block imported. state_strategy.on_blocks_processed( @@ -745,8 +781,11 @@ mod test { let mut state_sync_provider = MockStateSync::::new(); state_sync_provider.expect_target_hash().return_const(target_hash); - let mut state_strategy = - StateStrategy::new_with_provider(Box::new(state_sync_provider), std::iter::empty()); + let mut state_strategy = StateStrategy::new_with_provider( + Box::new(state_sync_provider), + std::iter::empty(), + ProtocolName::Static(""), + ); // Target block imported. state_strategy.on_blocks_processed( @@ -769,8 +808,11 @@ mod test { let mut state_sync_provider = MockStateSync::::new(); state_sync_provider.expect_target_hash().return_const(target_hash); - let mut state_strategy = - StateStrategy::new_with_provider(Box::new(state_sync_provider), std::iter::empty()); + let mut state_strategy = StateStrategy::new_with_provider( + Box::new(state_sync_provider), + std::iter::empty(), + ProtocolName::Static(""), + ); // Target block import failed. state_strategy.on_blocks_processed( @@ -797,8 +839,11 @@ mod test { // Get enough peers for possible spurious requests. let initial_peers = (1..=10).map(|best_number| (PeerId::random(), best_number)); - let mut state_strategy = - StateStrategy::new_with_provider(Box::new(state_sync_provider), initial_peers); + let mut state_strategy = StateStrategy::new_with_provider( + Box::new(state_sync_provider), + initial_peers, + ProtocolName::Static(""), + ); state_strategy.on_blocks_processed( 1, diff --git a/substrate/client/network/sync/src/strategy/warp.rs b/substrate/client/network/sync/src/strategy/warp.rs index 99405c2e5f0..cce6a93caf4 100644 --- a/substrate/client/network/sync/src/strategy/warp.rs +++ b/substrate/client/network/sync/src/strategy/warp.rs @@ -26,7 +26,8 @@ use crate::{ LOG_TARGET, }; use codec::{Decode, Encode}; -use log::{debug, error, trace}; +use log::{debug, error, trace, warn}; +use sc_network::ProtocolName; use sc_network_common::sync::message::{ BlockAnnounce, BlockAttributes, BlockData, BlockRequest, Direction, FromBlock, }; @@ -188,7 +189,11 @@ struct Peer { /// Action that should be performed on [`WarpSync`]'s behalf. pub enum WarpSyncAction { /// Send warp proof request to peer. - SendWarpProofRequest { peer_id: PeerId, request: WarpProofRequest }, + SendWarpProofRequest { + peer_id: PeerId, + protocol_name: ProtocolName, + request: WarpProofRequest, + }, /// Send block request to peer. Always implies dropping a stale block request to the same peer. SendBlockRequest { peer_id: PeerId, request: BlockRequest }, /// Disconnect and report peer. @@ -211,6 +216,7 @@ pub struct WarpSync { total_state_bytes: u64, peers: HashMap>, disconnected_peers: DisconnectedPeers, + protocol_name: Option, actions: Vec>, result: Option>, } @@ -223,7 +229,11 @@ where /// Create a new instance. When passing a warp sync provider we will be checking for proof and /// authorities. Alternatively we can pass a target block when we want to skip downloading /// proofs, in this case we will continue polling until the target block is known. - pub fn new(client: Arc, warp_sync_config: WarpSyncConfig) -> Self { + pub fn new( + client: Arc, + warp_sync_config: WarpSyncConfig, + protocol_name: Option, + ) -> Self { if client.info().finalized_state.is_some() { error!( target: LOG_TARGET, @@ -236,6 +246,7 @@ where total_state_bytes: 0, peers: HashMap::new(), disconnected_peers: DisconnectedPeers::new(), + protocol_name, actions: vec![WarpSyncAction::Finished], result: None, } @@ -254,6 +265,7 @@ where total_state_bytes: 0, peers: HashMap::new(), disconnected_peers: DisconnectedPeers::new(), + protocol_name, actions: Vec::new(), result: None, } @@ -469,7 +481,7 @@ where } /// Produce warp proof request. - fn warp_proof_request(&mut self) -> Option<(PeerId, WarpProofRequest)> { + fn warp_proof_request(&mut self) -> Option<(PeerId, ProtocolName, WarpProofRequest)> { let Phase::WarpProof { last_hash, .. } = &self.phase else { return None }; // Copy `last_hash` early to cut the borrowing tie. @@ -487,7 +499,17 @@ where let peer_id = self.schedule_next_peer(PeerState::DownloadingProofs, None)?; trace!(target: LOG_TARGET, "New WarpProofRequest to {peer_id}, begin hash: {begin}."); - Some((peer_id, WarpProofRequest { begin })) + let request = WarpProofRequest { begin }; + + let Some(protocol_name) = self.protocol_name.clone() else { + warn!( + target: LOG_TARGET, + "Trying to send warp sync request when no protocol is configured {request:?}", + ); + return None; + }; + + Some((peer_id, protocol_name, request)) } /// Produce target block request. @@ -585,10 +607,10 @@ where /// Get actions that should be performed by the owner on [`WarpSync`]'s behalf #[must_use] pub fn actions(&mut self) -> impl Iterator> { - let warp_proof_request = self - .warp_proof_request() - .into_iter() - .map(|(peer_id, request)| WarpSyncAction::SendWarpProofRequest { peer_id, request }); + let warp_proof_request = + self.warp_proof_request().into_iter().map(|(peer_id, protocol_name, request)| { + WarpSyncAction::SendWarpProofRequest { peer_id, protocol_name, request } + }); self.actions.extend(warp_proof_request); let target_block_request = self @@ -694,7 +716,7 @@ mod test { let client = mock_client_with_state(); let provider = MockWarpSyncProvider::::new(); let config = WarpSyncConfig::WithProvider(Arc::new(provider)); - let mut warp_sync = WarpSync::new(Arc::new(client), config); + let mut warp_sync = WarpSync::new(Arc::new(client), config, None); // Warp sync instantly finishes let actions = warp_sync.actions().collect::>(); @@ -715,7 +737,7 @@ mod test { Default::default(), Default::default(), )); - let mut warp_sync = WarpSync::new(Arc::new(client), config); + let mut warp_sync = WarpSync::new(Arc::new(client), config, None); // Warp sync instantly finishes let actions = warp_sync.actions().collect::>(); @@ -731,7 +753,7 @@ mod test { let client = mock_client_without_state(); let provider = MockWarpSyncProvider::::new(); let config = WarpSyncConfig::WithProvider(Arc::new(provider)); - let mut warp_sync = WarpSync::new(Arc::new(client), config); + let mut warp_sync = WarpSync::new(Arc::new(client), config, None); // No actions are emitted. assert_eq!(warp_sync.actions().count(), 0) @@ -747,7 +769,7 @@ mod test { Default::default(), Default::default(), )); - let mut warp_sync = WarpSync::new(Arc::new(client), config); + let mut warp_sync = WarpSync::new(Arc::new(client), config, None); // No actions are emitted. assert_eq!(warp_sync.actions().count(), 0) @@ -762,7 +784,7 @@ mod test { .once() .return_const(AuthorityList::default()); let config = WarpSyncConfig::WithProvider(Arc::new(provider)); - let mut warp_sync = WarpSync::new(Arc::new(client), config); + let mut warp_sync = WarpSync::new(Arc::new(client), config, None); // Warp sync is not started when there is not enough peers. for _ in 0..(MIN_PEERS_TO_START_WARP_SYNC - 1) { @@ -780,7 +802,7 @@ mod test { let client = mock_client_without_state(); let provider = MockWarpSyncProvider::::new(); let config = WarpSyncConfig::WithProvider(Arc::new(provider)); - let mut warp_sync = WarpSync::new(Arc::new(client), config); + let mut warp_sync = WarpSync::new(Arc::new(client), config, None); assert!(warp_sync.schedule_next_peer(PeerState::DownloadingProofs, None).is_none()); } @@ -804,7 +826,7 @@ mod test { .once() .return_const(AuthorityList::default()); let config = WarpSyncConfig::WithProvider(Arc::new(provider)); - let mut warp_sync = WarpSync::new(Arc::new(client), config); + let mut warp_sync = WarpSync::new(Arc::new(client), config, None); for best_number in 1..11 { warp_sync.add_peer(PeerId::random(), Hash::random(), best_number); @@ -825,7 +847,7 @@ mod test { .once() .return_const(AuthorityList::default()); let config = WarpSyncConfig::WithProvider(Arc::new(provider)); - let mut warp_sync = WarpSync::new(Arc::new(client), config); + let mut warp_sync = WarpSync::new(Arc::new(client), config, None); for best_number in 1..11 { warp_sync.add_peer(PeerId::random(), Hash::random(), best_number); @@ -845,7 +867,7 @@ mod test { .once() .return_const(AuthorityList::default()); let config = WarpSyncConfig::WithProvider(Arc::new(provider)); - let mut warp_sync = WarpSync::new(Arc::new(client), config); + let mut warp_sync = WarpSync::new(Arc::new(client), config, None); for best_number in 1..11 { warp_sync.add_peer(PeerId::random(), Hash::random(), best_number); @@ -889,7 +911,7 @@ mod test { .once() .return_const(AuthorityList::default()); let config = WarpSyncConfig::WithProvider(Arc::new(provider)); - let mut warp_sync = WarpSync::new(Arc::new(client), config); + let mut warp_sync = WarpSync::new(Arc::new(client), config, Some(ProtocolName::Static(""))); // Make sure we have enough peers to make a request. for best_number in 1..11 { @@ -918,7 +940,7 @@ mod test { .once() .return_const(AuthorityList::default()); let config = WarpSyncConfig::WithProvider(Arc::new(provider)); - let mut warp_sync = WarpSync::new(Arc::new(client), config); + let mut warp_sync = WarpSync::new(Arc::new(client), config, Some(ProtocolName::Static(""))); // Make sure we have enough peers to make a request. for best_number in 1..11 { @@ -936,7 +958,7 @@ mod test { _ => panic!("Invalid phase."), } - let (_peer_id, request) = warp_sync.warp_proof_request().unwrap(); + let (_peer_id, _protocol_name, request) = warp_sync.warp_proof_request().unwrap(); assert_eq!(request.begin, known_last_hash); } @@ -949,7 +971,7 @@ mod test { .once() .return_const(AuthorityList::default()); let config = WarpSyncConfig::WithProvider(Arc::new(provider)); - let mut warp_sync = WarpSync::new(Arc::new(client), config); + let mut warp_sync = WarpSync::new(Arc::new(client), config, Some(ProtocolName::Static(""))); // Make sure we have enough peers to make requests. for best_number in 1..11 { @@ -976,7 +998,7 @@ mod test { Err(Box::new(std::io::Error::new(ErrorKind::Other, "test-verification-failure"))) }); let config = WarpSyncConfig::WithProvider(Arc::new(provider)); - let mut warp_sync = WarpSync::new(Arc::new(client), config); + let mut warp_sync = WarpSync::new(Arc::new(client), config, Some(ProtocolName::Static(""))); // Make sure we have enough peers to make a request. for best_number in 1..11 { @@ -1017,7 +1039,7 @@ mod test { Ok(VerificationResult::Partial(set_id, authorities, Hash::random())) }); let config = WarpSyncConfig::WithProvider(Arc::new(provider)); - let mut warp_sync = WarpSync::new(Arc::new(client), config); + let mut warp_sync = WarpSync::new(Arc::new(client), config, Some(ProtocolName::Static(""))); // Make sure we have enough peers to make a request. for best_number in 1..11 { @@ -1061,7 +1083,7 @@ mod test { Ok(VerificationResult::Complete(set_id, authorities, target_header)) }); let config = WarpSyncConfig::WithProvider(Arc::new(provider)); - let mut warp_sync = WarpSync::new(client, config); + let mut warp_sync = WarpSync::new(client, config, Some(ProtocolName::Static(""))); // Make sure we have enough peers to make a request. for best_number in 1..11 { @@ -1094,7 +1116,7 @@ mod test { .once() .return_const(AuthorityList::default()); let config = WarpSyncConfig::WithProvider(Arc::new(provider)); - let mut warp_sync = WarpSync::new(Arc::new(client), config); + let mut warp_sync = WarpSync::new(Arc::new(client), config, None); // Make sure we have enough peers to make a request. for best_number in 1..11 { @@ -1129,7 +1151,7 @@ mod test { Ok(VerificationResult::Complete(set_id, authorities, target_header)) }); let config = WarpSyncConfig::WithProvider(Arc::new(provider)); - let mut warp_sync = WarpSync::new(client, config); + let mut warp_sync = WarpSync::new(client, config, None); // Make sure we have enough peers to make a request. for best_number in 1..11 { @@ -1161,7 +1183,7 @@ mod test { .block; let target_header = target_block.header().clone(); let config = WarpSyncConfig::WithTarget(target_header); - let mut warp_sync = WarpSync::new(client, config); + let mut warp_sync = WarpSync::new(client, config, None); // Make sure we have enough peers to make a request. for best_number in 1..11 { @@ -1201,7 +1223,7 @@ mod test { Ok(VerificationResult::Complete(set_id, authorities, target_header)) }); let config = WarpSyncConfig::WithProvider(Arc::new(provider)); - let mut warp_sync = WarpSync::new(client, config); + let mut warp_sync = WarpSync::new(client, config, None); // Make sure we have enough peers to make a request. for best_number in 1..11 { @@ -1239,7 +1261,7 @@ mod test { Ok(VerificationResult::Complete(set_id, authorities, target_header)) }); let config = WarpSyncConfig::WithProvider(Arc::new(provider)); - let mut warp_sync = WarpSync::new(client, config); + let mut warp_sync = WarpSync::new(client, config, None); // Make sure we have enough peers to make a request. for best_number in 1..11 { @@ -1293,7 +1315,7 @@ mod test { Ok(VerificationResult::Complete(set_id, authorities, target_header)) }); let config = WarpSyncConfig::WithProvider(Arc::new(provider)); - let mut warp_sync = WarpSync::new(client, config); + let mut warp_sync = WarpSync::new(client, config, None); // Make sure we have enough peers to make a request. for best_number in 1..11 { @@ -1370,7 +1392,7 @@ mod test { Ok(VerificationResult::Complete(set_id, authorities, target_header)) }); let config = WarpSyncConfig::WithProvider(Arc::new(provider)); - let mut warp_sync = WarpSync::new(client, config); + let mut warp_sync = WarpSync::new(client, config, None); // Make sure we have enough peers to make a request. for best_number in 1..11 { @@ -1423,7 +1445,7 @@ mod test { Ok(VerificationResult::Complete(set_id, authorities, target_header)) }); let config = WarpSyncConfig::WithProvider(Arc::new(provider)); - let mut warp_sync = WarpSync::new(client, config); + let mut warp_sync = WarpSync::new(client, config, None); // Make sure we have enough peers to make a request. for best_number in 1..11 { diff --git a/substrate/client/network/test/src/lib.rs b/substrate/client/network/test/src/lib.rs index f84f353fb4a..0f73e3194ba 100644 --- a/substrate/client/network/test/src/lib.rs +++ b/substrate/client/network/test/src/lib.rs @@ -66,8 +66,12 @@ use sc_network_sync::{ block_request_handler::BlockRequestHandler, service::{network::NetworkServiceProvider, syncing_service::SyncingService}, state_request_handler::StateRequestHandler, - strategy::warp::{ - AuthorityList, EncodedProof, SetId, VerificationResult, WarpSyncConfig, WarpSyncProvider, + strategy::{ + warp::{ + AuthorityList, EncodedProof, SetId, VerificationResult, WarpSyncConfig, + WarpSyncProvider, + }, + PolkadotSyncingStrategy, SyncingConfig, }, warp_request_handler, }; @@ -905,6 +909,24 @@ pub trait TestNetFactory: Default + Sized + Send { ::Hash, >>::register_notification_metrics(None); + let syncing_config = SyncingConfig { + mode: network_config.sync_mode, + max_parallel_downloads: network_config.max_parallel_downloads, + max_blocks_per_request: network_config.max_blocks_per_request, + metrics_registry: None, + state_request_protocol_name: state_request_protocol_config.name.clone(), + }; + // Initialize syncing strategy. + let syncing_strategy = Box::new( + PolkadotSyncingStrategy::new( + syncing_config, + client.clone(), + Some(warp_sync_config), + Some(warp_protocol_config.name.clone()), + ) + .unwrap(), + ); + let (engine, sync_service, block_announce_config) = sc_network_sync::engine::SyncingEngine::new( Roles::from(if config.is_authority { &Role::Authority } else { &Role::Full }), @@ -915,12 +937,10 @@ pub trait TestNetFactory: Default + Sized + Send { protocol_id.clone(), &fork_id, block_announce_validator, - Some(warp_sync_config), + syncing_strategy, chain_sync_network_handle, import_queue.service(), block_relay_params.downloader, - state_request_protocol_config.name.clone(), - Some(warp_protocol_config.name.clone()), peer_store_handle.clone(), ) .unwrap(); diff --git a/substrate/client/network/test/src/service.rs b/substrate/client/network/test/src/service.rs index a5cee97531c..ad2d1d9ec24 100644 --- a/substrate/client/network/test/src/service.rs +++ b/substrate/client/network/test/src/service.rs @@ -34,6 +34,7 @@ use sc_network_sync::{ engine::SyncingEngine, service::network::{NetworkServiceHandle, NetworkServiceProvider}, state_request_handler::StateRequestHandler, + strategy::{PolkadotSyncingStrategy, SyncingConfig}, }; use sp_blockchain::HeaderBackend; use sp_runtime::traits::{Block as BlockT, Zero}; @@ -202,6 +203,18 @@ impl TestNetworkBuilder { let peer_store_handle: Arc = Arc::new(peer_store.handle()); tokio::spawn(peer_store.run().boxed()); + let syncing_config = SyncingConfig { + mode: network_config.sync_mode, + max_parallel_downloads: network_config.max_parallel_downloads, + max_blocks_per_request: network_config.max_blocks_per_request, + metrics_registry: None, + state_request_protocol_name: state_request_protocol_config.name.clone(), + }; + // Initialize syncing strategy. + let syncing_strategy = Box::new( + PolkadotSyncingStrategy::new(syncing_config, client.clone(), None, None).unwrap(), + ); + let (engine, chain_sync_service, block_announce_config) = SyncingEngine::new( Roles::from(&config::Role::Full), client.clone(), @@ -211,12 +224,10 @@ impl TestNetworkBuilder { protocol_id.clone(), &None, Box::new(sp_consensus::block_validation::DefaultBlockAnnounceValidator), - None, + syncing_strategy, chain_sync_network_handle, import_queue.service(), block_relay_params.downloader, - state_request_protocol_config.name.clone(), - None, Arc::clone(&peer_store_handle), ) .unwrap(); diff --git a/substrate/client/service/src/builder.rs b/substrate/client/service/src/builder.rs index 28a76847ac0..f27b7ec6fba 100644 --- a/substrate/client/service/src/builder.rs +++ b/substrate/client/service/src/builder.rs @@ -42,7 +42,7 @@ use sc_executor::{ }; use sc_keystore::LocalKeystore; use sc_network::{ - config::{FullNetworkConfiguration, SyncMode}, + config::{FullNetworkConfiguration, ProtocolId, SyncMode}, multiaddr::Protocol, service::{ traits::{PeerStore, RequestResponseConfig}, @@ -53,10 +53,14 @@ use sc_network::{ use sc_network_common::role::Roles; use sc_network_light::light_client_requests::handler::LightClientRequestHandler; use sc_network_sync::{ - block_relay_protocol::BlockRelayParams, block_request_handler::BlockRequestHandler, - engine::SyncingEngine, service::network::NetworkServiceProvider, + block_relay_protocol::BlockRelayParams, + block_request_handler::BlockRequestHandler, + engine::SyncingEngine, + service::network::NetworkServiceProvider, state_request_handler::StateRequestHandler, - warp_request_handler::RequestHandler as WarpSyncRequestHandler, SyncingService, WarpSyncConfig, + strategy::{PolkadotSyncingStrategy, SyncingConfig, SyncingStrategy}, + warp_request_handler::RequestHandler as WarpSyncRequestHandler, + SyncingService, WarpSyncConfig, }; use sc_rpc::{ author::AuthorApiServer, @@ -777,65 +781,63 @@ where } /// Parameters to pass into `build_network`. -pub struct BuildNetworkParams< - 'a, - TBl: BlockT, - TNet: NetworkBackend::Hash>, - TExPool, - TImpQu, - TCl, -> { +pub struct BuildNetworkParams<'a, Block, Net, TxPool, IQ, Client> +where + Block: BlockT, + Net: NetworkBackend::Hash>, +{ /// The service configuration. pub config: &'a Configuration, /// Full network configuration. - pub net_config: FullNetworkConfiguration::Hash, TNet>, + pub net_config: FullNetworkConfiguration::Hash, Net>, /// A shared client returned by `new_full_parts`. - pub client: Arc, + pub client: Arc, /// A shared transaction pool. - pub transaction_pool: Arc, + pub transaction_pool: Arc, /// A handle for spawning tasks. pub spawn_handle: SpawnTaskHandle, /// An import queue. - pub import_queue: TImpQu, + pub import_queue: IQ, /// A block announce validator builder. - pub block_announce_validator_builder: - Option) -> Box + Send> + Send>>, - /// Optional warp sync config. - pub warp_sync_config: Option>, + pub block_announce_validator_builder: Option< + Box) -> Box + Send> + Send>, + >, + /// Syncing strategy to use in syncing engine. + pub syncing_strategy: Box>, /// User specified block relay params. If not specified, the default /// block request handler will be used. - pub block_relay: Option>, + pub block_relay: Option>, /// Metrics. pub metrics: NotificationMetrics, } /// Build the network service, the network status sinks and an RPC sender. -pub fn build_network( - params: BuildNetworkParams, +pub fn build_network( + params: BuildNetworkParams, ) -> Result< ( Arc, - TracingUnboundedSender>, - sc_network_transactions::TransactionsHandlerController<::Hash>, + TracingUnboundedSender>, + sc_network_transactions::TransactionsHandlerController<::Hash>, NetworkStarter, - Arc>, + Arc>, ), Error, > where - TBl: BlockT, - TCl: ProvideRuntimeApi - + HeaderMetadata - + Chain - + BlockBackend - + BlockIdTo - + ProofProvider - + HeaderBackend - + BlockchainEvents + Block: BlockT, + Client: ProvideRuntimeApi + + HeaderMetadata + + Chain + + BlockBackend + + BlockIdTo + + ProofProvider + + HeaderBackend + + BlockchainEvents + 'static, - TExPool: TransactionPool::Hash> + 'static, - TImpQu: ImportQueue + 'static, - TNet: NetworkBackend::Hash>, + TxPool: TransactionPool::Hash> + 'static, + IQ: ImportQueue + 'static, + Net: NetworkBackend::Hash>, { let BuildNetworkParams { config, @@ -845,30 +847,13 @@ where spawn_handle, import_queue, block_announce_validator_builder, - warp_sync_config, + syncing_strategy, block_relay, metrics, } = params; - if warp_sync_config.is_none() && config.network.sync_mode.is_warp() { - return Err("Warp sync enabled, but no warp sync provider configured.".into()) - } - - if client.requires_full_sync() { - match config.network.sync_mode { - SyncMode::LightState { .. } => - return Err("Fast sync doesn't work for archive nodes".into()), - SyncMode::Warp => return Err("Warp sync doesn't work for archive nodes".into()), - SyncMode::Full => {}, - } - } - let protocol_id = config.protocol_id(); - let genesis_hash = client - .block_hash(0u32.into()) - .ok() - .flatten() - .expect("Genesis block exists; qed"); + let genesis_hash = client.info().genesis_hash; let block_announce_validator = if let Some(f) = block_announce_validator_builder { f(client.clone()) @@ -882,7 +867,7 @@ where None => { // Custom protocol was not specified, use the default block handler. // Allow both outgoing and incoming requests. - let params = BlockRequestHandler::new::( + let params = BlockRequestHandler::new::( chain_sync_network_handle.clone(), &protocol_id, config.chain_spec.fork_id(), @@ -897,42 +882,9 @@ where block_server.run().await; }); - let (state_request_protocol_config, state_request_protocol_name) = { - let num_peer_hint = net_config.network_config.default_peers_set_num_full as usize + - net_config.network_config.default_peers_set.reserved_nodes.len(); - // Allow both outgoing and incoming requests. - let (handler, protocol_config) = StateRequestHandler::new::( - &protocol_id, - config.chain_spec.fork_id(), - client.clone(), - num_peer_hint, - ); - let config_name = protocol_config.protocol_name().clone(); - - spawn_handle.spawn("state-request-handler", Some("networking"), handler.run()); - (protocol_config, config_name) - }; - - let (warp_sync_protocol_config, warp_request_protocol_name) = match warp_sync_config.as_ref() { - Some(WarpSyncConfig::WithProvider(warp_with_provider)) => { - // Allow both outgoing and incoming requests. - let (handler, protocol_config) = WarpSyncRequestHandler::new::<_, TNet>( - protocol_id.clone(), - genesis_hash, - config.chain_spec.fork_id(), - warp_with_provider.clone(), - ); - let config_name = protocol_config.protocol_name().clone(); - - spawn_handle.spawn("warp-sync-request-handler", Some("networking"), handler.run()); - (Some(protocol_config), Some(config_name)) - }, - _ => (None, None), - }; - let light_client_request_protocol_config = { // Allow both outgoing and incoming requests. - let (handler, protocol_config) = LightClientRequestHandler::new::( + let (handler, protocol_config) = LightClientRequestHandler::new::( &protocol_id, config.chain_spec.fork_id(), client.clone(), @@ -943,15 +895,10 @@ where // install request handlers to `FullNetworkConfiguration` net_config.add_request_response_protocol(block_request_protocol_config); - net_config.add_request_response_protocol(state_request_protocol_config); net_config.add_request_response_protocol(light_client_request_protocol_config); - if let Some(config) = warp_sync_protocol_config { - net_config.add_request_response_protocol(config); - } - let bitswap_config = config.network.ipfs_server.then(|| { - let (handler, config) = TNet::bitswap_server(client.clone()); + let (handler, config) = Net::bitswap_server(client.clone()); spawn_handle.spawn("bitswap-request-handler", Some("networking"), handler); config @@ -960,7 +907,7 @@ where // create transactions protocol and add it to the list of supported protocols of let peer_store_handle = net_config.peer_store_handle(); let (transactions_handler_proto, transactions_config) = - sc_network_transactions::TransactionsHandlerPrototype::new::<_, TBl, TNet>( + sc_network_transactions::TransactionsHandlerPrototype::new::<_, Block, Net>( protocol_id.clone(), genesis_hash, config.chain_spec.fork_id(), @@ -983,19 +930,16 @@ where protocol_id.clone(), &config.chain_spec.fork_id().map(ToOwned::to_owned), block_announce_validator, - warp_sync_config, + syncing_strategy, chain_sync_network_handle, import_queue.service(), block_downloader, - state_request_protocol_name, - warp_request_protocol_name, Arc::clone(&peer_store_handle), )?; let sync_service_import_queue = sync_service.clone(); let sync_service = Arc::new(sync_service); - let genesis_hash = client.hash(Zero::zero()).ok().flatten().expect("Genesis block exists; qed"); - let network_params = sc_network::config::Params::::Hash, TNet> { + let network_params = sc_network::config::Params::::Hash, Net> { role: config.role, executor: { let spawn_handle = Clone::clone(&spawn_handle); @@ -1005,7 +949,7 @@ where }, network_config: net_config, genesis_hash, - protocol_id: protocol_id.clone(), + protocol_id, fork_id: config.chain_spec.fork_id().map(ToOwned::to_owned), metrics_registry: config.prometheus_config.as_ref().map(|config| config.registry.clone()), block_announce_config, @@ -1014,7 +958,7 @@ where }; let has_bootnodes = !network_params.network_config.network_config.boot_nodes.is_empty(); - let network_mut = TNet::new(network_params)?; + let network_mut = Net::new(network_params)?; let network = network_mut.network_service().clone(); let (tx_handler, tx_handler_controller) = transactions_handler_proto.build( @@ -1041,7 +985,7 @@ where spawn_handle.spawn( "system-rpc-handler", Some("networking"), - build_system_rpc_future::<_, _, ::Hash>( + build_system_rpc_future::<_, _, ::Hash>( config.role, network_mut.network_service(), sync_service.clone(), @@ -1051,7 +995,7 @@ where ), ); - let future = build_network_future::<_, _, ::Hash, _>( + let future = build_network_future::<_, _, ::Hash, _>( network_mut, client, sync_service.clone(), @@ -1103,6 +1047,91 @@ where )) } +/// Build standard polkadot syncing strategy +pub fn build_polkadot_syncing_strategy( + protocol_id: ProtocolId, + fork_id: Option<&str>, + net_config: &mut FullNetworkConfiguration::Hash, Net>, + warp_sync_config: Option>, + client: Arc, + spawn_handle: &SpawnTaskHandle, + metrics_registry: Option<&Registry>, +) -> Result>, Error> +where + Block: BlockT, + Client: HeaderBackend + + BlockBackend + + HeaderMetadata + + ProofProvider + + Send + + Sync + + 'static, + + Net: NetworkBackend::Hash>, +{ + if warp_sync_config.is_none() && net_config.network_config.sync_mode.is_warp() { + return Err("Warp sync enabled, but no warp sync provider configured.".into()) + } + + if client.requires_full_sync() { + match net_config.network_config.sync_mode { + SyncMode::LightState { .. } => + return Err("Fast sync doesn't work for archive nodes".into()), + SyncMode::Warp => return Err("Warp sync doesn't work for archive nodes".into()), + SyncMode::Full => {}, + } + } + + let genesis_hash = client.info().genesis_hash; + + let (state_request_protocol_config, state_request_protocol_name) = { + let num_peer_hint = net_config.network_config.default_peers_set_num_full as usize + + net_config.network_config.default_peers_set.reserved_nodes.len(); + // Allow both outgoing and incoming requests. + let (handler, protocol_config) = + StateRequestHandler::new::(&protocol_id, fork_id, client.clone(), num_peer_hint); + let config_name = protocol_config.protocol_name().clone(); + + spawn_handle.spawn("state-request-handler", Some("networking"), handler.run()); + (protocol_config, config_name) + }; + net_config.add_request_response_protocol(state_request_protocol_config); + + let (warp_sync_protocol_config, warp_sync_protocol_name) = match warp_sync_config.as_ref() { + Some(WarpSyncConfig::WithProvider(warp_with_provider)) => { + // Allow both outgoing and incoming requests. + let (handler, protocol_config) = WarpSyncRequestHandler::new::<_, Net>( + protocol_id, + genesis_hash, + fork_id, + warp_with_provider.clone(), + ); + let config_name = protocol_config.protocol_name().clone(); + + spawn_handle.spawn("warp-sync-request-handler", Some("networking"), handler.run()); + (Some(protocol_config), Some(config_name)) + }, + _ => (None, None), + }; + if let Some(config) = warp_sync_protocol_config { + net_config.add_request_response_protocol(config); + } + + let syncing_config = SyncingConfig { + mode: net_config.network_config.sync_mode, + max_parallel_downloads: net_config.network_config.max_parallel_downloads, + max_blocks_per_request: net_config.network_config.max_blocks_per_request, + metrics_registry: metrics_registry.cloned(), + state_request_protocol_name, + }; + Ok(Box::new(PolkadotSyncingStrategy::new( + syncing_config, + client, + warp_sync_config, + warp_sync_protocol_name, + )?)) +} + /// Object used to start the network. #[must_use] pub struct NetworkStarter(oneshot::Sender<()>); diff --git a/substrate/client/service/src/lib.rs b/substrate/client/service/src/lib.rs index babb76f022f..b6acdb8ed00 100644 --- a/substrate/client/service/src/lib.rs +++ b/substrate/client/service/src/lib.rs @@ -59,11 +59,11 @@ use sp_runtime::traits::{Block as BlockT, Header as HeaderT}; pub use self::{ builder::{ - build_network, gen_rpc_module, init_telemetry, new_client, new_db_backend, new_full_client, - new_full_parts, new_full_parts_record_import, new_full_parts_with_genesis_builder, - new_wasm_executor, propagate_transaction_notifications, spawn_tasks, BuildNetworkParams, - KeystoreContainer, NetworkStarter, SpawnTasksParams, TFullBackend, TFullCallExecutor, - TFullClient, + build_network, build_polkadot_syncing_strategy, gen_rpc_module, init_telemetry, new_client, + new_db_backend, new_full_client, new_full_parts, new_full_parts_record_import, + new_full_parts_with_genesis_builder, new_wasm_executor, + propagate_transaction_notifications, spawn_tasks, BuildNetworkParams, KeystoreContainer, + NetworkStarter, SpawnTasksParams, TFullBackend, TFullCallExecutor, TFullClient, }, client::{ClientConfig, LocalCallExecutor}, error::Error, diff --git a/templates/minimal/node/src/service.rs b/templates/minimal/node/src/service.rs index a42eb10ccec..08cd345f1e3 100644 --- a/templates/minimal/node/src/service.rs +++ b/templates/minimal/node/src/service.rs @@ -15,12 +15,15 @@ // See the License for the specific language governing permissions and // limitations under the License. +use crate::cli::Consensus; use futures::FutureExt; use minimal_template_runtime::{interface::OpaqueBlock as Block, RuntimeApi}; use polkadot_sdk::{ sc_client_api::backend::Backend, sc_executor::WasmExecutor, - sc_service::{error::Error as ServiceError, Configuration, TaskManager}, + sc_service::{ + build_polkadot_syncing_strategy, error::Error as ServiceError, Configuration, TaskManager, + }, sc_telemetry::{Telemetry, TelemetryWorker}, sc_transaction_pool_api::OffchainTransactionPoolFactory, sp_runtime::traits::Block as BlockT, @@ -28,8 +31,6 @@ use polkadot_sdk::{ }; use std::sync::Arc; -use crate::cli::Consensus; - type HostFunctions = sp_io::SubstrateHostFunctions; #[docify::export] @@ -120,7 +121,7 @@ pub fn new_full::Ha other: mut telemetry, } = new_partial(&config)?; - let net_config = sc_network::config::FullNetworkConfiguration::< + let mut net_config = sc_network::config::FullNetworkConfiguration::< Block, ::Hash, Network, @@ -132,6 +133,16 @@ pub fn new_full::Ha config.prometheus_config.as_ref().map(|cfg| &cfg.registry), ); + let syncing_strategy = build_polkadot_syncing_strategy( + config.protocol_id(), + config.chain_spec.fork_id(), + &mut net_config, + None, + client.clone(), + &task_manager.spawn_handle(), + config.prometheus_config.as_ref().map(|config| &config.registry), + )?; + let (network, system_rpc_tx, tx_handler_controller, network_starter, sync_service) = sc_service::build_network(sc_service::BuildNetworkParams { config: &config, @@ -141,7 +152,7 @@ pub fn new_full::Ha import_queue, net_config, block_announce_validator_builder: None, - warp_sync_config: None, + syncing_strategy, block_relay: None, metrics, })?; diff --git a/templates/solochain/node/src/service.rs b/templates/solochain/node/src/service.rs index 7d37c5ce87f..2de543235ec 100644 --- a/templates/solochain/node/src/service.rs +++ b/templates/solochain/node/src/service.rs @@ -4,7 +4,10 @@ use futures::FutureExt; use sc_client_api::{Backend, BlockBackend}; use sc_consensus_aura::{ImportQueueParams, SlotProportion, StartAuraParams}; use sc_consensus_grandpa::SharedVoterState; -use sc_service::{error::Error as ServiceError, Configuration, TaskManager, WarpSyncConfig}; +use sc_service::{ + build_polkadot_syncing_strategy, error::Error as ServiceError, Configuration, TaskManager, + WarpSyncConfig, +}; use sc_telemetry::{Telemetry, TelemetryWorker}; use sc_transaction_pool_api::OffchainTransactionPoolFactory; use solochain_template_runtime::{self, apis::RuntimeApi, opaque::Block}; @@ -166,6 +169,16 @@ pub fn new_full< Vec::default(), )); + let syncing_strategy = build_polkadot_syncing_strategy( + config.protocol_id(), + config.chain_spec.fork_id(), + &mut net_config, + Some(WarpSyncConfig::WithProvider(warp_sync)), + client.clone(), + &task_manager.spawn_handle(), + config.prometheus_config.as_ref().map(|config| &config.registry), + )?; + let (network, system_rpc_tx, tx_handler_controller, network_starter, sync_service) = sc_service::build_network(sc_service::BuildNetworkParams { config: &config, @@ -175,7 +188,7 @@ pub fn new_full< spawn_handle: task_manager.spawn_handle(), import_queue, block_announce_validator_builder: None, - warp_sync_config: Some(WarpSyncConfig::WithProvider(warp_sync)), + syncing_strategy, block_relay: None, metrics, })?; -- GitLab From 9cdbdc5a73f3c2d1d9afdf266799f249c3c04c2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Tue, 17 Sep 2024 15:21:50 +0200 Subject: [PATCH 069/125] pallet-treasury: Improve `remove_approval` benchmark (#5713) When `SpendOrigin` doesn't return any `succesful_origin`, it doesn't mean that `RejectOrigin` will do the same. Thus, this pr fixes a potential wrong benchmarked weight for when `SpendOrigin` is set to e.g. `NeverOrigin`. --- prdoc/pr_5713.prdoc | 10 +++++++ substrate/frame/treasury/src/benchmarking.rs | 29 +++++++++++++++----- 2 files changed, 32 insertions(+), 7 deletions(-) create mode 100644 prdoc/pr_5713.prdoc diff --git a/prdoc/pr_5713.prdoc b/prdoc/pr_5713.prdoc new file mode 100644 index 00000000000..54d3619cdca --- /dev/null +++ b/prdoc/pr_5713.prdoc @@ -0,0 +1,10 @@ +title: "pallet-treasury: Improve `remove_approval` benchmark" + +doc: + - audience: Runtime Dev + description: | + Fix the `remove_approval` benchmark when `SpendOrigin` doesn't return any `succesful_origin`. + +crates: + - name: pallet-treasury + bump: patch diff --git a/substrate/frame/treasury/src/benchmarking.rs b/substrate/frame/treasury/src/benchmarking.rs index 0bac78503f4..650e5376fa4 100644 --- a/substrate/frame/treasury/src/benchmarking.rs +++ b/substrate/frame/treasury/src/benchmarking.rs @@ -133,16 +133,31 @@ mod benchmarks { #[benchmark] fn remove_approval() -> Result<(), BenchmarkError> { - let origin = - T::SpendOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; - let (_, value, beneficiary_lookup) = setup_proposal::(SEED); - Treasury::::spend_local(origin, value, beneficiary_lookup)?; - let proposal_id = ProposalCount::::get() - 1; + let (spend_exists, proposal_id) = + if let Ok(origin) = T::SpendOrigin::try_successful_origin() { + let (_, value, beneficiary_lookup) = setup_proposal::(SEED); + Treasury::::spend_local(origin, value, beneficiary_lookup)?; + let proposal_id = ProposalCount::::get() - 1; + + (true, proposal_id) + } else { + (false, 0) + }; + let reject_origin = T::RejectOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; - #[extrinsic_call] - _(reject_origin as T::RuntimeOrigin, proposal_id); + #[block] + { + let res = + Treasury::::remove_approval(reject_origin as T::RuntimeOrigin, proposal_id); + + if spend_exists { + assert_ok!(res); + } else { + assert_err!(res, Error::::ProposalNotApproved); + } + } Ok(()) } -- GitLab From 69e96659984d10c67d4a8912b8eeaa5620bfa2cd Mon Sep 17 00:00:00 2001 From: gupnik Date: Wed, 18 Sep 2024 12:00:06 +0530 Subject: [PATCH 070/125] Adds support for generics in`derive-impl` (#5584) As raised by @kianenigma in https://github.com/sam0x17/macro_magic/issues/15, this PR adds the support for generics while using `derive_impl`. This can then be used in conjunction with `FliteFrameSystem` being defined [here](https://github.com/kianenigma/flite) as ```diff +#[derive_impl(FliteFrameSystem)] impl frame_system::Config for Runtime { type Block = Block; // .. Rest can be removed } ``` --------- Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> --- .../support/procedural/src/derive_impl.rs | 73 +++++++++++++++++-- substrate/frame/support/procedural/src/lib.rs | 1 + 2 files changed, 67 insertions(+), 7 deletions(-) diff --git a/substrate/frame/support/procedural/src/derive_impl.rs b/substrate/frame/support/procedural/src/derive_impl.rs index 54755f1163a..69117c02681 100644 --- a/substrate/frame/support/procedural/src/derive_impl.rs +++ b/substrate/frame/support/procedural/src/derive_impl.rs @@ -17,13 +17,13 @@ //! Implementation of the `derive_impl` attribute macro. -use derive_syn_parse::Parse; use macro_magic::mm_core::ForeignPath; use proc_macro2::TokenStream as TokenStream2; use quote::{quote, ToTokens}; use std::collections::HashSet; use syn::{ - parse2, parse_quote, spanned::Spanned, token, Ident, ImplItem, ItemImpl, Path, Result, Token, + parse2, parse_quote, spanned::Spanned, token, AngleBracketedGenericArguments, Ident, ImplItem, + ItemImpl, Path, PathArguments, PathSegment, Result, Token, }; mod keyword { @@ -56,18 +56,60 @@ fn is_runtime_type(item: &syn::ImplItemType) -> bool { false }) } - -#[derive(Parse, Debug)] pub struct DeriveImplAttrArgs { pub default_impl_path: Path, + pub generics: Option, _as: Option, - #[parse_if(_as.is_some())] pub disambiguation_path: Option, _comma: Option, - #[parse_if(_comma.is_some())] pub no_aggregated_types: Option, } +impl syn::parse::Parse for DeriveImplAttrArgs { + fn parse(input: syn::parse::ParseStream) -> Result { + let mut default_impl_path: Path = input.parse()?; + // Extract the generics if any + let (default_impl_path, generics) = match default_impl_path.clone().segments.last() { + Some(PathSegment { ident, arguments: PathArguments::AngleBracketed(args) }) => { + default_impl_path.segments.pop(); + default_impl_path + .segments + .push(PathSegment { ident: ident.clone(), arguments: PathArguments::None }); + (default_impl_path, Some(args.clone())) + }, + Some(PathSegment { arguments: PathArguments::None, .. }) => (default_impl_path, None), + _ => return Err(syn::Error::new(default_impl_path.span(), "Invalid default impl path")), + }; + + let lookahead = input.lookahead1(); + let (_as, disambiguation_path) = if lookahead.peek(Token![as]) { + let _as: Token![as] = input.parse()?; + let disambiguation_path: Path = input.parse()?; + (Some(_as), Some(disambiguation_path)) + } else { + (None, None) + }; + + let lookahead = input.lookahead1(); + let (_comma, no_aggregated_types) = if lookahead.peek(Token![,]) { + let _comma: Token![,] = input.parse()?; + let no_aggregated_types: keyword::no_aggregated_types = input.parse()?; + (Some(_comma), Some(no_aggregated_types)) + } else { + (None, None) + }; + + Ok(DeriveImplAttrArgs { + default_impl_path, + generics, + _as, + disambiguation_path, + _comma, + no_aggregated_types, + }) + } +} + impl ForeignPath for DeriveImplAttrArgs { fn foreign_path(&self) -> &Path { &self.default_impl_path @@ -77,6 +119,7 @@ impl ForeignPath for DeriveImplAttrArgs { impl ToTokens for DeriveImplAttrArgs { fn to_tokens(&self, tokens: &mut TokenStream2) { tokens.extend(self.default_impl_path.to_token_stream()); + tokens.extend(self.generics.to_token_stream()); tokens.extend(self._as.to_token_stream()); tokens.extend(self.disambiguation_path.to_token_stream()); tokens.extend(self._comma.to_token_stream()); @@ -117,6 +160,7 @@ fn combine_impls( default_impl_path: Path, disambiguation_path: Path, inject_runtime_types: bool, + generics: Option, ) -> ItemImpl { let (existing_local_keys, existing_unsupported_items): (HashSet, HashSet) = local_impl @@ -155,7 +199,7 @@ fn combine_impls( // modify and insert uncolliding type items let modified_item: ImplItem = parse_quote! { #( #cfg_attrs )* - type #ident = <#default_impl_path as #disambiguation_path>::#ident; + type #ident = <#default_impl_path #generics as #disambiguation_path>::#ident; }; return Some(modified_item) } @@ -216,6 +260,7 @@ pub fn derive_impl( local_tokens: TokenStream2, disambiguation_path: Option, no_aggregated_types: Option, + generics: Option, ) -> Result { let local_impl = parse2::(local_tokens)?; let foreign_impl = parse2::(foreign_tokens)?; @@ -234,6 +279,7 @@ pub fn derive_impl( default_impl_path, disambiguation_path, no_aggregated_types.is_none(), + generics, ); Ok(quote!(#combined_impl)) @@ -301,3 +347,16 @@ fn test_disambiguation_path() { compute_disambiguation_path(None, foreign_impl.clone(), parse_quote!(SomeType)); assert_eq!(disambiguation_path.unwrap(), parse_quote!(SomeTrait)); } + +#[test] +fn test_derive_impl_attr_args_parsing_with_generic() { + let args = parse2::(quote!( + some::path::TestDefaultConfig as some::path::DefaultConfig + )) + .unwrap(); + assert_eq!(args.default_impl_path, parse_quote!(some::path::TestDefaultConfig)); + assert_eq!(args.generics.unwrap().args[0], parse_quote!(Config)); + let args = parse2::(quote!(TestDefaultConfig)).unwrap(); + assert_eq!(args.default_impl_path, parse_quote!(TestDefaultConfig)); + assert_eq!(args.generics.unwrap().args[0], parse_quote!(Config2)); +} diff --git a/substrate/frame/support/procedural/src/lib.rs b/substrate/frame/support/procedural/src/lib.rs index 8554a5b830d..d40a571c9ea 100644 --- a/substrate/frame/support/procedural/src/lib.rs +++ b/substrate/frame/support/procedural/src/lib.rs @@ -683,6 +683,7 @@ pub fn derive_impl(attrs: TokenStream, input: TokenStream) -> TokenStream { input.into(), custom_attrs.disambiguation_path, custom_attrs.no_aggregated_types, + custom_attrs.generics, ) .unwrap_or_else(|r| r.into_compile_error()) .into() -- GitLab From ba38d31d8e7c12025e4de385c2a4d6372ee7c23b Mon Sep 17 00:00:00 2001 From: Javier Viola <363911+pepoviola@users.noreply.github.com> Date: Wed, 18 Sep 2024 06:50:46 -0300 Subject: [PATCH 071/125] add `coretime` test using `zombienet-sdk` (#4883) Related to #4882 cc: @s0me0ne-unkn0wn ```sh RUST_LOG=info,zombie=debug cargo test -p polkadot-zombienet-sdk-tests smoke::coretime_revenue::coretime_revenue_test --features zombie-metadata -- --exact ``` --- _Update_: This pr is now ready for review. `warp-sync` failing test are not related. --------- Co-authored-by: Dmitry Sinyavin Co-authored-by: s0me0ne-unkn0wn <48632512+s0me0ne-unkn0wn@users.noreply.github.com> --- .cargo/config.toml | 1 + .github/scripts/deny-git-deps.py | 3 +- .gitlab/pipeline/build.yml | 19 + .gitlab/pipeline/zombienet.yml | 4 + .gitlab/pipeline/zombienet/polkadot.yml | 22 + Cargo.lock | 1741 ++++++++++++++--- Cargo.toml | 1 + polkadot/zombienet-sdk-tests/Cargo.toml | 27 + polkadot/zombienet-sdk-tests/build.rs | 151 ++ .../metadata-files/.gitkeep | 0 polkadot/zombienet-sdk-tests/src/lib.rs | 2 + polkadot/zombienet-sdk-tests/tests/lib.rs | 4 + .../tests/smoke/coretime_revenue.rs | 505 +++++ .../zombienet-sdk-tests/tests/smoke/mod.rs | 5 + 14 files changed, 2181 insertions(+), 304 deletions(-) create mode 100644 polkadot/zombienet-sdk-tests/Cargo.toml create mode 100644 polkadot/zombienet-sdk-tests/build.rs create mode 100644 polkadot/zombienet-sdk-tests/metadata-files/.gitkeep create mode 100644 polkadot/zombienet-sdk-tests/src/lib.rs create mode 100644 polkadot/zombienet-sdk-tests/tests/lib.rs create mode 100644 polkadot/zombienet-sdk-tests/tests/smoke/coretime_revenue.rs create mode 100644 polkadot/zombienet-sdk-tests/tests/smoke/mod.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index f113e9114ac..1b8ffe1a1c8 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -8,3 +8,4 @@ rustdocflags = [ # Needed for musl builds so user doesn't have to install musl-tools. CC_x86_64_unknown_linux_musl = { value = ".cargo/musl-gcc", force = true, relative = true } CXX_x86_64_unknown_linux_musl = { value = ".cargo/musl-g++", force = true, relative = true } +CARGO_WORKSPACE_ROOT_DIR = { value = "", relative = true } diff --git a/.github/scripts/deny-git-deps.py b/.github/scripts/deny-git-deps.py index 622fc64c488..bd4fcf1f923 100644 --- a/.github/scripts/deny-git-deps.py +++ b/.github/scripts/deny-git-deps.py @@ -15,6 +15,7 @@ KNOWN_BAD_GIT_DEPS = { 'simple-mermaid': ['xcm-docs'], # Fix in 'bandersnatch_vrfs': ['sp-core'], + 'subwasmlib': ['polkadot-zombienet-sdk-tests'], } root = sys.argv[1] if len(sys.argv) > 1 else os.getcwd() @@ -24,7 +25,7 @@ errors = [] def check_dep(dep, used_by): if dep.location != DependencyLocation.GIT: return - + if used_by in KNOWN_BAD_GIT_DEPS.get(dep.name, []): print(f'🤨 Ignoring git dependency {dep.name} in {used_by}') else: diff --git a/.gitlab/pipeline/build.yml b/.gitlab/pipeline/build.yml index 74b6ccb4998..931aef80233 100644 --- a/.gitlab/pipeline/build.yml +++ b/.gitlab/pipeline/build.yml @@ -173,6 +173,25 @@ build-short-benchmark: - target/release/polkadot --version - cp ./target/release/polkadot ./artifacts/ +build-polkadot-zombienet-tests: + stage: build + extends: + - .docker-env + - .common-refs + - .run-immediately + - .collect-artifacts + needs: + - job: build-linux-stable + artifacts: true + - job: build-linux-stable-cumulus + artifacts: true + + script: + - cargo nextest --manifest-path polkadot/zombienet-sdk-tests/Cargo.toml archive --features zombie-metadata --archive-file polkadot-zombienet-tests.tar.zst + - mkdir -p artifacts + - cp polkadot-zombienet-tests.tar.zst ./artifacts + + # build jobs from cumulus build-linux-stable-cumulus: diff --git a/.gitlab/pipeline/zombienet.yml b/.gitlab/pipeline/zombienet.yml index 23521b299b1..c17366dbe4c 100644 --- a/.gitlab/pipeline/zombienet.yml +++ b/.gitlab/pipeline/zombienet.yml @@ -4,6 +4,10 @@ ZOMBIENET_IMAGE: "docker.io/paritytech/zombienet:v1.3.105" PUSHGATEWAY_URL: "http://zombienet-prometheus-pushgateway.managed-monitoring:9091/metrics/job/zombie-metrics" DEBUG: "zombie,zombie::network-node,zombie::kube::client::logs" + ZOMBIE_PROVIDER: "k8s" + RUST_LOG: "info,zombienet_orchestrator=debug" + RUN_IN_CI: "1" + timeout: 60m include: # substrate tests diff --git a/.gitlab/pipeline/zombienet/polkadot.yml b/.gitlab/pipeline/zombienet/polkadot.yml index b4ef4bb7446..93fc4bbb578 100644 --- a/.gitlab/pipeline/zombienet/polkadot.yml +++ b/.gitlab/pipeline/zombienet/polkadot.yml @@ -31,6 +31,12 @@ - echo "colander image ${COL_IMAGE}" - echo "cumulus image ${CUMULUS_IMAGE}" - echo "malus image ${MALUS_IMAGE}" + # RUN_IN_CONTAINER is env var that is set in the dockerfile + - if [[ -v RUN_IN_CONTAINER ]]; then + echo "Initializing zombie cluster"; + gcloud auth activate-service-account --key-file "/etc/zombie-net/sa-zombie.json"; + gcloud container clusters get-credentials parity-zombienet --zone europe-west3-b --project parity-zombienet; + fi stage: zombienet image: "${ZOMBIENET_IMAGE}" needs: @@ -54,6 +60,7 @@ MALUS_IMAGE: "docker.io/paritypr/malus" GH_DIR: "https://github.com/paritytech/substrate/tree/${CI_COMMIT_SHA}/zombienet" LOCAL_DIR: "/builds/parity/mirrors/polkadot-sdk/polkadot/zombienet_tests" + LOCAL_SDK_TEST: "/builds/parity/mirrors/polkadot-sdk/polkadot/zombienet-sdk-tests" FF_DISABLE_UMASK_FOR_DOCKER_EXECUTOR: 1 RUN_IN_CONTAINER: "1" artifacts: @@ -335,3 +342,18 @@ zombienet-polkadot-malus-0001-dispute-valid: - /home/nonroot/zombie-net/scripts/ci/run-test-local-env-manager.sh --local-dir="${LOCAL_DIR}/integrationtests" --test="0001-dispute-valid-block.zndsl" + +zombienet-polkadot-coretime-revenue: + extends: + - .zombienet-polkadot-common + needs: + - job: build-polkadot-zombienet-tests + artifacts: true + before_script: + - !reference [".zombienet-polkadot-common", "before_script"] + - export POLKADOT_IMAGE="${ZOMBIENET_INTEGRATION_TEST_IMAGE}" + script: + # we want to use `--no-capture` in zombienet tests. + - unset NEXTEST_FAILURE_OUTPUT + - unset NEXTEST_SUCCESS_OUTPUT + - cargo nextest run --archive-file ./artifacts/polkadot-zombienet-tests.tar.zst --no-capture -- smoke::coretime_revenue::coretime_revenue_test diff --git a/Cargo.lock b/Cargo.lock index 7d894af7b7f..3985f9f994b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -77,6 +77,15 @@ dependencies = [ "subtle 2.5.0", ] +[[package]] +name = "affix" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e7ea84d3fa2009f355f8429a0b418a96849135a4188fadf384f59127d5d4bc" +dependencies = [ + "convert_case 0.5.0", +] + [[package]] name = "ahash" version = "0.7.8" @@ -656,7 +665,7 @@ dependencies = [ "ark-std 0.4.0", "digest 0.10.7", "rand_core 0.6.4", - "sha3", + "sha3 0.10.8", ] [[package]] @@ -680,6 +689,12 @@ dependencies = [ "nodrop", ] +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + [[package]] name = "arrayvec" version = "0.7.4" @@ -890,7 +905,7 @@ dependencies = [ "scale-info", "serde_json", "snowbridge-router-primitives", - "sp-api", + "sp-api 26.0.0", "sp-block-builder", "sp-consensus-aura", "sp-core 28.0.0", @@ -901,7 +916,7 @@ dependencies = [ "sp-session", "sp-storage 19.0.0", "sp-transaction-pool", - "sp-version", + "sp-version 29.0.0", "sp-weights 27.0.0", "staging-parachain-info", "staging-xcm", @@ -1021,7 +1036,7 @@ dependencies = [ "primitive-types", "scale-info", "snowbridge-router-primitives", - "sp-api", + "sp-api 26.0.0", "sp-block-builder", "sp-consensus-aura", "sp-core 28.0.0", @@ -1033,7 +1048,7 @@ dependencies = [ "sp-std 14.0.0", "sp-storage 19.0.0", "sp-transaction-pool", - "sp-version", + "sp-version 29.0.0", "staging-parachain-info", "staging-xcm", "staging-xcm-builder", @@ -1087,7 +1102,7 @@ dependencies = [ "parachains-common", "parity-scale-codec", "scale-info", - "sp-api", + "sp-api 26.0.0", "sp-runtime 31.0.1", "staging-xcm", "staging-xcm-builder", @@ -1703,6 +1718,17 @@ dependencies = [ "constant_time_eq 0.1.5", ] +[[package]] +name = "blake2b_simd" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587" +dependencies = [ + "arrayref", + "arrayvec 0.5.2", + "constant_time_eq 0.1.5", +] + [[package]] name = "blake2b_simd" version = "1.0.2" @@ -1714,6 +1740,17 @@ dependencies = [ "constant_time_eq 0.3.0", ] +[[package]] +name = "blake2s_simd" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e461a7034e85b211a4acb57ee2e6730b32912b06c08cc242243c39fc21ae6a2" +dependencies = [ + "arrayref", + "arrayvec 0.5.2", + "constant_time_eq 0.1.5", +] + [[package]] name = "blake2s_simd" version = "1.0.1" @@ -1744,6 +1781,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ + "block-padding", "generic-array 0.14.7", ] @@ -1756,6 +1794,12 @@ dependencies = [ "generic-array 0.14.7", ] +[[package]] +name = "block-padding" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" + [[package]] name = "blocking" version = "1.3.1" @@ -1840,7 +1884,7 @@ dependencies = [ "frame-support", "frame-system", "polkadot-primitives", - "sp-api", + "sp-api 26.0.0", "sp-std 14.0.0", ] @@ -1852,7 +1896,7 @@ dependencies = [ "bp-messages", "bp-runtime", "frame-support", - "sp-api", + "sp-api 26.0.0", "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -1865,7 +1909,7 @@ dependencies = [ "bp-messages", "bp-runtime", "frame-support", - "sp-api", + "sp-api 26.0.0", "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -1880,7 +1924,7 @@ dependencies = [ "bp-xcm-bridge-hub", "frame-support", "parity-scale-codec", - "sp-api", + "sp-api 26.0.0", "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -1895,7 +1939,7 @@ dependencies = [ "bp-xcm-bridge-hub", "frame-support", "parity-scale-codec", - "sp-api", + "sp-api 26.0.0", "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -1927,7 +1971,7 @@ dependencies = [ "bp-polkadot-core", "bp-runtime", "frame-support", - "sp-api", + "sp-api 26.0.0", "sp-std 14.0.0", ] @@ -1972,7 +2016,7 @@ dependencies = [ "bp-polkadot-core", "bp-runtime", "frame-support", - "sp-api", + "sp-api 26.0.0", "sp-std 14.0.0", ] @@ -1988,7 +2032,7 @@ dependencies = [ "frame-system", "parity-scale-codec", "scale-info", - "sp-api", + "sp-api 26.0.0", "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -2038,7 +2082,7 @@ dependencies = [ "bp-polkadot-core", "bp-runtime", "frame-support", - "sp-api", + "sp-api 26.0.0", "sp-std 14.0.0", ] @@ -2092,7 +2136,7 @@ dependencies = [ "bp-polkadot-core", "bp-runtime", "frame-support", - "sp-api", + "sp-api 26.0.0", "sp-std 14.0.0", ] @@ -2260,7 +2304,7 @@ dependencies = [ "snowbridge-runtime-common", "snowbridge-runtime-test-common", "snowbridge-system-runtime-api", - "sp-api", + "sp-api 26.0.0", "sp-block-builder", "sp-consensus-aura", "sp-core 28.0.0", @@ -2274,7 +2318,7 @@ dependencies = [ "sp-std 14.0.0", "sp-storage 19.0.0", "sp-transaction-pool", - "sp-version", + "sp-version 29.0.0", "staging-parachain-info", "staging-xcm", "staging-xcm-builder", @@ -2446,7 +2490,7 @@ dependencies = [ "snowbridge-runtime-common", "snowbridge-runtime-test-common", "snowbridge-system-runtime-api", - "sp-api", + "sp-api 26.0.0", "sp-block-builder", "sp-consensus-aura", "sp-core 28.0.0", @@ -2460,7 +2504,7 @@ dependencies = [ "sp-std 14.0.0", "sp-storage 19.0.0", "sp-transaction-pool", - "sp-version", + "sp-version 29.0.0", "staging-parachain-info", "staging-xcm", "staging-xcm-builder", @@ -2591,6 +2635,25 @@ dependencies = [ "ppv-lite86", ] +[[package]] +name = "calm_io" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ea0608700fe42d90ec17ad0f86335cf229b67df2e34e7f463e8241ce7b8fa5f" +dependencies = [ + "calmio_filters", +] + +[[package]] +name = "calmio_filters" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "846501f4575cd66766a40bb7ab6d8e960adc7eb49f753c8232bd8e0e09cf6ca2" +dependencies = [ + "quote 1.0.37", + "syn 1.0.109", +] + [[package]] name = "camino" version = "1.1.6" @@ -2788,6 +2851,17 @@ dependencies = [ "half", ] +[[package]] +name = "cid" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8709d481fb78b9808f34a1b4b4fadd08a15a0971052c18bc2b751faefaed595e" +dependencies = [ + "multibase 0.8.0", + "multihash 0.11.4", + "unsigned-varint 0.3.3", +] + [[package]] name = "cid" version = "0.9.0" @@ -2795,7 +2869,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9b68e3193982cd54187d71afdb2a271ad4cf8af157858e9cb911b91321de143" dependencies = [ "core2", - "multibase", + "multibase 0.9.1", "multihash 0.17.0", "serde", "unsigned-varint 0.7.2", @@ -2808,7 +2882,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd94671561e36e4e7de75f753f577edafb0e7c05d6e4547229fdf7938fbcd2c3" dependencies = [ "core2", - "multibase", + "multibase 0.9.1", "multihash 0.18.1", "serde", "unsigned-varint 0.7.2", @@ -3082,7 +3156,7 @@ dependencies = [ "polkadot-parachain-primitives", "polkadot-runtime-common", "scale-info", - "sp-api", + "sp-api 26.0.0", "sp-arithmetic 23.0.0", "sp-block-builder", "sp-consensus-aura", @@ -3095,7 +3169,7 @@ dependencies = [ "sp-session", "sp-storage 19.0.0", "sp-transaction-pool", - "sp-version", + "sp-version 29.0.0", "staging-parachain-info", "staging-xcm", "staging-xcm-builder", @@ -3200,6 +3274,42 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2382f75942f4b3be3690fe4f86365e9c853c1587d6ee58212cebf6e2a9ccd101" +[[package]] +name = "comparable" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb513ee8037bf08c5270ecefa48da249f4c58e57a71ccfce0a5b0877d2a20eb2" +dependencies = [ + "comparable_derive", + "comparable_helper", + "pretty_assertions", + "serde", +] + +[[package]] +name = "comparable_derive" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a54b9c40054eb8999c5d1d36fdc90e4e5f7ff0d1d9621706f360b3cbc8beb828" +dependencies = [ + "convert_case 0.4.0", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 1.0.109", +] + +[[package]] +name = "comparable_helper" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5437e327e861081c91270becff184859f706e3e50f5301a9d4dc8eb50752c3" +dependencies = [ + "convert_case 0.6.0", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 1.0.109", +] + [[package]] name = "concurrent-queue" version = "2.2.0" @@ -3340,7 +3450,7 @@ dependencies = [ "polkadot-runtime-common", "rococo-runtime-constants", "scale-info", - "sp-api", + "sp-api 26.0.0", "sp-block-builder", "sp-consensus-aura", "sp-core 28.0.0", @@ -3351,7 +3461,7 @@ dependencies = [ "sp-session", "sp-storage 19.0.0", "sp-transaction-pool", - "sp-version", + "sp-version 29.0.0", "staging-parachain-info", "staging-xcm", "staging-xcm-builder", @@ -3367,6 +3477,21 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" +[[package]] +name = "convert_case" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb4a24b1aaf0fd0ce8b45161144d6f42cd91677fd5940fd431183eb023b3a2b8" + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -3471,7 +3596,7 @@ dependencies = [ "rococo-runtime-constants", "scale-info", "serde", - "sp-api", + "sp-api 26.0.0", "sp-block-builder", "sp-consensus-aura", "sp-core 28.0.0", @@ -3482,7 +3607,7 @@ dependencies = [ "sp-session", "sp-storage 19.0.0", "sp-transaction-pool", - "sp-version", + "sp-version 29.0.0", "staging-parachain-info", "staging-xcm", "staging-xcm-builder", @@ -3569,7 +3694,7 @@ dependencies = [ "polkadot-runtime-common", "scale-info", "serde", - "sp-api", + "sp-api 26.0.0", "sp-block-builder", "sp-consensus-aura", "sp-core 28.0.0", @@ -3580,7 +3705,7 @@ dependencies = [ "sp-session", "sp-storage 19.0.0", "sp-transaction-pool", - "sp-version", + "sp-version 29.0.0", "staging-parachain-info", "staging-xcm", "staging-xcm-builder", @@ -3910,10 +4035,10 @@ dependencies = [ "polkadot-overseer", "polkadot-primitives", "sc-client-api", - "sp-api", + "sp-api 26.0.0", "sp-consensus", "sp-core 28.0.0", - "sp-maybe-compressed-blob", + "sp-maybe-compressed-blob 11.0.0", "sp-runtime 31.0.1", "sp-state-machine 0.35.0", "sp-tracing 16.0.0", @@ -3947,7 +4072,7 @@ dependencies = [ "sc-telemetry", "sc-utils", "schnellru", - "sp-api", + "sp-api 26.0.0", "sp-application-crypto 30.0.0", "sp-block-builder", "sp-blockchain", @@ -3992,7 +4117,7 @@ dependencies = [ "sp-timestamp", "sp-tracing 16.0.0", "sp-trie 29.0.0", - "sp-version", + "sp-version 29.0.0", "substrate-prometheus-endpoint", "tracing", ] @@ -4022,7 +4147,7 @@ dependencies = [ "futures", "parking_lot 0.12.3", "sc-consensus", - "sp-api", + "sp-api 26.0.0", "sp-block-builder", "sp-blockchain", "sp-consensus", @@ -4055,7 +4180,7 @@ dependencies = [ "rstest", "sc-cli", "sc-client-api", - "sp-api", + "sp-api 26.0.0", "sp-blockchain", "sp-consensus", "sp-core 28.0.0", @@ -4063,7 +4188,7 @@ dependencies = [ "sp-keystore 0.34.0", "sp-runtime 31.0.1", "sp-state-machine 0.35.0", - "sp-version", + "sp-version 29.0.0", "substrate-test-utils", "tokio", "tracing", @@ -4081,7 +4206,7 @@ dependencies = [ "cumulus-test-relay-sproof-builder", "parity-scale-codec", "sc-client-api", - "sp-api", + "sp-api 26.0.0", "sp-crypto-hashing 0.1.0", "sp-inherents", "sp-runtime 31.0.1", @@ -4115,13 +4240,13 @@ dependencies = [ "sc-client-api", "sc-consensus", "sc-utils", - "sp-api", + "sp-api 26.0.0", "sp-blockchain", "sp-consensus", - "sp-maybe-compressed-blob", + "sp-maybe-compressed-blob 11.0.0", "sp-runtime 31.0.1", "sp-tracing 16.0.0", - "sp-version", + "sp-version 29.0.0", "substrate-test-utils", "tokio", "tracing", @@ -4154,7 +4279,7 @@ dependencies = [ "sc-telemetry", "sc-transaction-pool", "sc-utils", - "sp-api", + "sp-api 26.0.0", "sp-blockchain", "sp-consensus", "sp-core 28.0.0", @@ -4239,7 +4364,7 @@ dependencies = [ "sp-std 14.0.0", "sp-tracing 16.0.0", "sp-trie 29.0.0", - "sp-version", + "sp-version 29.0.0", "staging-xcm", "staging-xcm-builder", "trie-db 0.29.1", @@ -4346,10 +4471,10 @@ dependencies = [ "polkadot-node-primitives", "polkadot-parachain-primitives", "polkadot-primitives", - "sc-executor", + "sc-executor 0.32.0", "sp-core 28.0.0", "sp-io 30.0.0", - "sp-maybe-compressed-blob", + "sp-maybe-compressed-blob 11.0.0", "tracing", "tracing-subscriber 0.3.18", ] @@ -4358,7 +4483,7 @@ dependencies = [ name = "cumulus-primitives-aura" version = "0.7.0" dependencies = [ - "sp-api", + "sp-api 26.0.0", "sp-consensus-aura", ] @@ -4371,7 +4496,7 @@ dependencies = [ "polkadot-parachain-primitives", "polkadot-primitives", "scale-info", - "sp-api", + "sp-api 26.0.0", "sp-runtime 31.0.1", "sp-trie 29.0.0", "staging-xcm", @@ -4465,7 +4590,7 @@ dependencies = [ "sc-sysinfo", "sc-telemetry", "sc-tracing", - "sp-api", + "sp-api 26.0.0", "sp-consensus", "sp-core 28.0.0", "sp-keyring", @@ -4484,10 +4609,10 @@ dependencies = [ "parity-scale-codec", "polkadot-overseer", "sc-client-api", - "sp-api", + "sp-api 26.0.0", "sp-blockchain", "sp-state-machine 0.35.0", - "sp-version", + "sp-version 29.0.0", "thiserror", ] @@ -4515,7 +4640,7 @@ dependencies = [ "sc-service", "sc-tracing", "sc-utils", - "sp-api", + "sp-api 26.0.0", "sp-blockchain", "sp-consensus", "sp-consensus-babe", @@ -4549,14 +4674,14 @@ dependencies = [ "serde_json", "smoldot 0.11.0", "smoldot-light 0.9.0", - "sp-api", + "sp-api 26.0.0", "sp-authority-discovery", "sp-consensus-babe", "sp-core 28.0.0", "sp-runtime 31.0.1", "sp-state-machine 0.35.0", "sp-storage 19.0.0", - "sp-version", + "sp-version 29.0.0", "thiserror", "tokio", "tokio-util", @@ -4584,10 +4709,10 @@ dependencies = [ "sc-block-builder", "sc-consensus", "sc-consensus-aura", - "sc-executor", - "sc-executor-common", + "sc-executor 0.32.0", + "sc-executor-common 0.29.0", "sc-service", - "sp-api", + "sp-api 26.0.0", "sp-application-crypto 30.0.0", "sp-blockchain", "sp-consensus-aura", @@ -4638,7 +4763,7 @@ dependencies = [ "pallet-transaction-payment", "parity-scale-codec", "scale-info", - "sp-api", + "sp-api 26.0.0", "sp-block-builder", "sp-consensus-aura", "sp-core 28.0.0", @@ -4649,7 +4774,7 @@ dependencies = [ "sp-runtime 31.0.1", "sp-session", "sp-transaction-pool", - "sp-version", + "sp-version 29.0.0", "staging-parachain-info", "substrate-wasm-builder", ] @@ -4702,9 +4827,9 @@ dependencies = [ "sc-client-api", "sc-consensus", "sc-consensus-aura", - "sc-executor", - "sc-executor-common", - "sc-executor-wasmtime", + "sc-executor 0.32.0", + "sc-executor-common 0.29.0", + "sc-executor-wasmtime 0.29.0", "sc-network", "sc-service", "sc-telemetry", @@ -4713,7 +4838,7 @@ dependencies = [ "sc-transaction-pool-api", "serde", "serde_json", - "sp-api", + "sp-api 26.0.0", "sp-arithmetic 23.0.0", "sp-authority-discovery", "sp-blockchain", @@ -5077,7 +5202,7 @@ version = "0.99.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" dependencies = [ - "convert_case", + "convert_case 0.4.0", "proc-macro2 1.0.86", "quote 1.0.37", "rustc_version 0.4.0", @@ -5243,6 +5368,15 @@ dependencies = [ "walkdir", ] +[[package]] +name = "document-features" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb6969eaabd2421f8a2775cfd2471a2b634372b4a25d41e3bd647b79912850a0" +dependencies = [ + "litrs", +] + [[package]] name = "downcast" version = "0.11.0" @@ -6036,7 +6170,7 @@ dependencies = [ "rusty-fork", "scale-info", "serde", - "sp-api", + "sp-api 26.0.0", "sp-application-crypto 30.0.0", "sp-core 28.0.0", "sp-io 30.0.0", @@ -6073,12 +6207,12 @@ dependencies = [ "sc-cli", "sc-client-api", "sc-client-db", - "sc-executor", + "sc-executor 0.32.0", "sc-service", "sc-sysinfo", "serde", "serde_json", - "sp-api", + "sp-api 26.0.0", "sp-blockchain", "sp-core 28.0.0", "sp-database", @@ -6178,7 +6312,7 @@ dependencies = [ "sp-io 30.0.0", "sp-runtime 31.0.1", "sp-tracing 16.0.0", - "sp-version", + "sp-version 29.0.0", ] [[package]] @@ -6217,7 +6351,7 @@ dependencies = [ "merkleized-metadata", "parity-scale-codec", "scale-info", - "sp-api", + "sp-api 26.0.0", "sp-runtime 31.0.1", "sp-tracing 16.0.0", "sp-transaction-pool", @@ -6286,16 +6420,16 @@ dependencies = [ "serde", "serde_json", "smallvec", - "sp-api", + "sp-api 26.0.0", "sp-arithmetic 23.0.0", "sp-core 28.0.0", "sp-crypto-hashing 0.1.0", - "sp-crypto-hashing-proc-macro", + "sp-crypto-hashing-proc-macro 0.1.0", "sp-debug-derive 14.0.0", "sp-genesis-builder", "sp-inherents", "sp-io 30.0.0", - "sp-metadata-ir", + "sp-metadata-ir 0.6.0", "sp-runtime 31.0.1", "sp-staking", "sp-state-machine 0.35.0", @@ -6332,7 +6466,7 @@ dependencies = [ "sp-core 28.0.0", "sp-crypto-hashing 0.1.0", "sp-io 30.0.0", - "sp-metadata-ir", + "sp-metadata-ir 0.6.0", "sp-runtime 31.0.1", "static_assertions", "syn 2.0.65", @@ -6373,14 +6507,14 @@ dependencies = [ "rustversion", "scale-info", "serde", - "sp-api", + "sp-api 26.0.0", "sp-arithmetic 23.0.0", "sp-core 28.0.0", "sp-io 30.0.0", - "sp-metadata-ir", + "sp-metadata-ir 0.6.0", "sp-runtime 31.0.1", "sp-state-machine 0.35.0", - "sp-version", + "sp-version 29.0.0", "static_assertions", "trybuild", ] @@ -6395,7 +6529,7 @@ dependencies = [ "scale-info", "sp-core 28.0.0", "sp-runtime 31.0.1", - "sp-version", + "sp-version 29.0.0", ] [[package]] @@ -6436,7 +6570,7 @@ dependencies = [ "sp-io 30.0.0", "sp-runtime 31.0.1", "sp-std 14.0.0", - "sp-version", + "sp-version 29.0.0", "sp-weights 27.0.0", "substrate-test-runtime-client", ] @@ -6454,7 +6588,7 @@ dependencies = [ "sp-externalities 0.25.0", "sp-io 30.0.0", "sp-runtime 31.0.1", - "sp-version", + "sp-version 29.0.0", ] [[package]] @@ -6463,7 +6597,7 @@ version = "26.0.0" dependencies = [ "docify", "parity-scale-codec", - "sp-api", + "sp-api 26.0.0", ] [[package]] @@ -6472,7 +6606,7 @@ version = "0.34.0" dependencies = [ "frame-support", "parity-scale-codec", - "sp-api", + "sp-api 26.0.0", "sp-runtime 31.0.1", ] @@ -6813,7 +6947,7 @@ dependencies = [ "parachains-common", "parity-scale-codec", "scale-info", - "sp-api", + "sp-api 26.0.0", "sp-block-builder", "sp-consensus-aura", "sp-core 28.0.0", @@ -6824,7 +6958,7 @@ dependencies = [ "sp-session", "sp-storage 19.0.0", "sp-transaction-pool", - "sp-version", + "sp-version 29.0.0", "staging-parachain-info", "staging-xcm", "staging-xcm-builder", @@ -7300,6 +7434,7 @@ dependencies = [ "tokio", "tokio-rustls 0.26.0", "tower-service", + "webpki-roots 0.26.3", ] [[package]] @@ -7628,7 +7763,30 @@ dependencies = [ "socket2 0.5.7", "widestring", "windows-sys 0.48.0", - "winreg", + "winreg 0.50.0", +] + +[[package]] +name = "ipfs-hasher" +version = "0.21.3" +source = "git+https://github.com/chevdor/subwasm?rev=v0.21.3#aa8acb6fdfb34144ac51ab95618a9b37fa251295" +dependencies = [ + "ipfs-unixfs", + "thiserror", +] + +[[package]] +name = "ipfs-unixfs" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67d1cf65363f3d01682283456651d1cea436019de5be7a974bb61716c940d44f" +dependencies = [ + "cid 0.5.1", + "either", + "filetime", + "multihash 0.11.4", + "quick-protobuf 0.7.0", + "sha2 0.9.9", ] [[package]] @@ -8526,7 +8684,7 @@ dependencies = [ "once_cell", "parking_lot 0.12.3", "pin-project", - "quick-protobuf", + "quick-protobuf 0.8.1", "rand", "rw-stream-sink", "smallvec", @@ -8567,7 +8725,7 @@ dependencies = [ "libp2p-swarm", "log", "lru 0.12.3", - "quick-protobuf", + "quick-protobuf 0.8.1", "quick-protobuf-codec", "smallvec", "thiserror", @@ -8584,7 +8742,7 @@ dependencies = [ "ed25519-dalek", "hkdf", "multihash 0.19.1", - "quick-protobuf", + "quick-protobuf 0.8.1", "rand", "sha2 0.10.8", "thiserror", @@ -8610,7 +8768,7 @@ dependencies = [ "libp2p-identity", "libp2p-swarm", "log", - "quick-protobuf", + "quick-protobuf 0.8.1", "quick-protobuf-codec", "rand", "sha2 0.10.8", @@ -8674,7 +8832,7 @@ dependencies = [ "multiaddr 0.18.1", "multihash 0.19.1", "once_cell", - "quick-protobuf", + "quick-protobuf 0.8.1", "rand", "sha2 0.10.8", "snow", @@ -8717,7 +8875,7 @@ dependencies = [ "libp2p-tls", "log", "parking_lot 0.12.3", - "quinn", + "quinn 0.10.2", "rand", "ring 0.16.20", "rustls 0.21.7", @@ -9088,6 +9246,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "litrs" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" + [[package]] name = "lock_api" version = "0.4.10" @@ -9492,7 +9656,7 @@ dependencies = [ "sc-block-builder", "sc-client-api", "sc-offchain", - "sp-api", + "sp-api 26.0.0", "sp-blockchain", "sp-consensus", "sp-consensus-beefy", @@ -9512,7 +9676,7 @@ dependencies = [ "parity-scale-codec", "serde", "serde_json", - "sp-api", + "sp-api 26.0.0", "sp-blockchain", "sp-core 28.0.0", "sp-mmr-primitives", @@ -9588,7 +9752,7 @@ dependencies = [ "byteorder", "data-encoding", "log", - "multibase", + "multibase 0.9.1", "multihash 0.17.0", "percent-encoding", "serde", @@ -9607,7 +9771,7 @@ dependencies = [ "byteorder", "data-encoding", "libp2p-identity", - "multibase", + "multibase 0.9.1", "multihash 0.19.1", "percent-encoding", "serde", @@ -9616,6 +9780,17 @@ dependencies = [ "url", ] +[[package]] +name = "multibase" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b78c60039650ff12e140ae867ef5299a58e19dded4d334c849dc7177083667e2" +dependencies = [ + "base-x", + "data-encoding", + "data-encoding-macro", +] + [[package]] name = "multibase" version = "0.9.1" @@ -9627,20 +9802,35 @@ dependencies = [ "data-encoding-macro", ] +[[package]] +name = "multihash" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567122ab6492f49b59def14ecc36e13e64dca4188196dd0cd41f9f3f979f3df6" +dependencies = [ + "blake2b_simd 0.5.11", + "blake2s_simd 0.5.11", + "digest 0.9.0", + "sha-1", + "sha2 0.9.9", + "sha3 0.9.1", + "unsigned-varint 0.5.1", +] + [[package]] name = "multihash" version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "835d6ff01d610179fbce3de1694d007e500bf33a7f29689838941d6bf783ae40" dependencies = [ - "blake2b_simd", - "blake2s_simd", + "blake2b_simd 1.0.2", + "blake2s_simd 1.0.1", "blake3", "core2", "digest 0.10.7", "multihash-derive", "sha2 0.10.8", - "sha3", + "sha3 0.10.8", "unsigned-varint 0.7.2", ] @@ -9650,14 +9840,14 @@ version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfd8a792c1694c6da4f68db0a9d707c72bd260994da179e6030a5dcee00bb815" dependencies = [ - "blake2b_simd", - "blake2s_simd", + "blake2b_simd 1.0.2", + "blake2s_simd 1.0.1", "blake3", "core2", "digest 0.10.7", "multihash-derive", "sha2 0.10.8", - "sha3", + "sha3 0.10.8", "unsigned-varint 0.7.2", ] @@ -9965,7 +10155,7 @@ dependencies = [ "sc-rpc", "sc-sync-state-rpc", "sc-transaction-pool-api", - "sp-api", + "sp-api 26.0.0", "sp-application-crypto 30.0.0", "sp-block-builder", "sp-blockchain", @@ -10023,9 +10213,9 @@ dependencies = [ "sc-client-api", "sc-client-db", "sc-consensus", - "sc-executor", + "sc-executor 0.32.0", "sc-service", - "sp-api", + "sp-api 26.0.0", "sp-block-builder", "sp-blockchain", "sp-consensus", @@ -10453,7 +10643,7 @@ dependencies = [ "parity-scale-codec", "primitive-types", "scale-info", - "sp-api", + "sp-api 26.0.0", "sp-arithmetic 23.0.0", "sp-core 28.0.0", "sp-io 30.0.0", @@ -10765,7 +10955,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "serde", - "sp-api", + "sp-api 26.0.0", "sp-consensus-beefy", "sp-core 28.0.0", "sp-io 30.0.0", @@ -10922,7 +11112,7 @@ dependencies = [ "parity-scale-codec", "pretty_assertions", "scale-info", - "sp-api", + "sp-api 26.0.0", "sp-arithmetic 23.0.0", "sp-core 28.0.0", "sp-io 30.0.0", @@ -11034,7 +11224,7 @@ dependencies = [ "scale-info", "serde", "smallvec", - "sp-api", + "sp-api 26.0.0", "sp-core 28.0.0", "sp-io 30.0.0", "sp-keystore 0.34.0", @@ -11086,7 +11276,7 @@ dependencies = [ "polkadot-runtime-parachains", "pretty_assertions", "scale-info", - "sp-api", + "sp-api 26.0.0", "sp-core 28.0.0", "sp-io 30.0.0", "sp-keystore 0.34.0", @@ -11392,7 +11582,7 @@ dependencies = [ "sp-core 28.0.0", "sp-io 30.0.0", "sp-runtime 31.0.1", - "sp-version", + "sp-version 29.0.0", ] [[package]] @@ -11645,13 +11835,13 @@ dependencies = [ "parity-scale-codec", "pretty_assertions", "scale-info", - "sp-api", + "sp-api 26.0.0", "sp-block-builder", "sp-core 28.0.0", "sp-io 30.0.0", "sp-runtime 31.0.1", "sp-tracing 16.0.0", - "sp-version", + "sp-version 29.0.0", ] [[package]] @@ -11758,7 +11948,7 @@ version = "14.0.0" dependencies = [ "pallet-nfts", "parity-scale-codec", - "sp-api", + "sp-api 26.0.0", ] [[package]] @@ -11853,7 +12043,7 @@ version = "23.0.0" dependencies = [ "pallet-nomination-pools", "parity-scale-codec", - "sp-api", + "sp-api 26.0.0", ] [[package]] @@ -11960,7 +12150,7 @@ dependencies = [ "scale-info", "sp-core 28.0.0", "sp-io 30.0.0", - "sp-metadata-ir", + "sp-metadata-ir 0.6.0", "sp-runtime 31.0.1", ] @@ -12137,7 +12327,7 @@ dependencies = [ "rlp", "scale-info", "serde", - "sp-api", + "sp-api 26.0.0", "sp-core 28.0.0", "sp-io 30.0.0", "sp-keystore 0.34.0", @@ -12189,7 +12379,7 @@ dependencies = [ "polkadot-runtime-parachains", "pretty_assertions", "scale-info", - "sp-api", + "sp-api 26.0.0", "sp-core 28.0.0", "sp-io 30.0.0", "sp-keystore 0.34.0", @@ -12468,7 +12658,7 @@ name = "pallet-staking-runtime-api" version = "14.0.0" dependencies = [ "parity-scale-codec", - "sp-api", + "sp-api 26.0.0", "sp-staking", ] @@ -12506,7 +12696,7 @@ dependencies = [ "pallet-balances", "parity-scale-codec", "scale-info", - "sp-api", + "sp-api 26.0.0", "sp-core 28.0.0", "sp-io 30.0.0", "sp-runtime 31.0.1", @@ -12603,7 +12793,7 @@ dependencies = [ "jsonrpsee 0.24.3", "pallet-transaction-payment-rpc-runtime-api", "parity-scale-codec", - "sp-api", + "sp-api 26.0.0", "sp-blockchain", "sp-core 28.0.0", "sp-rpc", @@ -12617,7 +12807,7 @@ version = "28.0.0" dependencies = [ "pallet-transaction-payment", "parity-scale-codec", - "sp-api", + "sp-api 26.0.0", "sp-runtime 31.0.1", "sp-weights 27.0.0", ] @@ -12741,7 +12931,7 @@ dependencies = [ "pallet-preimage", "parity-scale-codec", "scale-info", - "sp-api", + "sp-api 26.0.0", "sp-core 28.0.0", "sp-io 30.0.0", "sp-runtime 31.0.1", @@ -12871,7 +13061,7 @@ dependencies = [ "sc-cli", "sc-client-api", "sc-consensus", - "sc-executor", + "sc-executor 0.32.0", "sc-network", "sc-network-sync", "sc-offchain", @@ -12884,7 +13074,7 @@ dependencies = [ "sc-transaction-pool-api", "serde", "serde_json", - "sp-api", + "sp-api 26.0.0", "sp-block-builder", "sp-blockchain", "sp-consensus-aura", @@ -12942,7 +13132,7 @@ dependencies = [ "scale-info", "serde_json", "smallvec", - "sp-api", + "sp-api 26.0.0", "sp-block-builder", "sp-consensus-aura", "sp-core 28.0.0", @@ -12952,7 +13142,7 @@ dependencies = [ "sp-runtime 31.0.1", "sp-session", "sp-transaction-pool", - "sp-version", + "sp-version 29.0.0", "staging-parachain-info", "staging-xcm", "staging-xcm-builder", @@ -13303,7 +13493,7 @@ dependencies = [ "primitive-types", "scale-info", "smallvec", - "sp-api", + "sp-api 26.0.0", "sp-block-builder", "sp-consensus-aura", "sp-core 28.0.0", @@ -13314,7 +13504,7 @@ dependencies = [ "sp-session", "sp-storage 19.0.0", "sp-transaction-pool", - "sp-version", + "sp-version 29.0.0", "staging-parachain-info", "staging-xcm", "staging-xcm-builder", @@ -13401,7 +13591,7 @@ dependencies = [ "rococo-runtime-constants", "scale-info", "serde", - "sp-api", + "sp-api 26.0.0", "sp-block-builder", "sp-consensus-aura", "sp-core 28.0.0", @@ -13412,7 +13602,7 @@ dependencies = [ "sp-session", "sp-storage 19.0.0", "sp-transaction-pool", - "sp-version", + "sp-version 29.0.0", "staging-parachain-info", "staging-xcm", "staging-xcm-builder", @@ -13500,7 +13690,7 @@ dependencies = [ "polkadot-runtime-common", "scale-info", "serde", - "sp-api", + "sp-api 26.0.0", "sp-block-builder", "sp-consensus-aura", "sp-core 28.0.0", @@ -13511,7 +13701,7 @@ dependencies = [ "sp-session", "sp-storage 19.0.0", "sp-transaction-pool", - "sp-version", + "sp-version 29.0.0", "staging-parachain-info", "staging-xcm", "staging-xcm-builder", @@ -13831,7 +14021,7 @@ dependencies = [ "pyroscope", "pyroscope_pprofrs", "sc-cli", - "sc-executor", + "sc-executor 0.32.0", "sc-service", "sc-storage-monitor", "sc-sysinfo", @@ -13839,7 +14029,7 @@ dependencies = [ "sp-core 28.0.0", "sp-io 30.0.0", "sp-keyring", - "sp-maybe-compressed-blob", + "sp-maybe-compressed-blob 11.0.0", "sp-runtime 31.0.1", "substrate-build-script-utils", "thiserror", @@ -14011,7 +14201,7 @@ dependencies = [ "rstest", "sp-core 28.0.0", "sp-keyring", - "sp-maybe-compressed-blob", + "sp-maybe-compressed-blob 11.0.0", "thiserror", "tracing-gum", ] @@ -14158,7 +14348,7 @@ dependencies = [ "sp-core 28.0.0", "sp-keyring", "sp-keystore 0.34.0", - "sp-maybe-compressed-blob", + "sp-maybe-compressed-blob 11.0.0", "tracing-gum", ] @@ -14323,7 +14513,7 @@ dependencies = [ "sc-sysinfo", "slotmap", "sp-core 28.0.0", - "sp-maybe-compressed-blob", + "sp-maybe-compressed-blob 11.0.0", "tempfile", "test-parachain-adder", "test-parachain-halt", @@ -14368,9 +14558,9 @@ dependencies = [ "parity-scale-codec", "polkadot-parachain-primitives", "polkadot-primitives", - "sc-executor", - "sc-executor-common", - "sc-executor-wasmtime", + "sc-executor 0.32.0", + "sc-executor-common 0.29.0", + "sc-executor-wasmtime 0.29.0", "seccompiler", "sp-core 28.0.0", "sp-crypto-hashing 0.1.0", @@ -14395,7 +14585,7 @@ dependencies = [ "polkadot-node-primitives", "polkadot-parachain-primitives", "polkadot-primitives", - "sp-maybe-compressed-blob", + "sp-maybe-compressed-blob 11.0.0", "tracing-gum", ] @@ -14414,9 +14604,9 @@ dependencies = [ "polkadot-primitives", "rayon", "rococo-runtime", - "sc-executor-common", - "sc-executor-wasmtime", - "sp-maybe-compressed-blob", + "sc-executor-common 0.29.0", + "sc-executor-wasmtime 0.29.0", + "sp-maybe-compressed-blob 11.0.0", "staging-tracking-allocator", "tikv-jemalloc-ctl", "tikv-jemallocator", @@ -14437,7 +14627,7 @@ dependencies = [ "polkadot-primitives", "polkadot-primitives-test-helpers", "schnellru", - "sp-api", + "sp-api 26.0.0", "sp-consensus-babe", "sp-core 28.0.0", "sp-keyring", @@ -14536,7 +14726,7 @@ dependencies = [ "sp-consensus-slots", "sp-core 28.0.0", "sp-keystore 0.34.0", - "sp-maybe-compressed-blob", + "sp-maybe-compressed-blob 11.0.0", "sp-runtime 31.0.1", "thiserror", "zstd 0.12.4", @@ -14592,7 +14782,7 @@ dependencies = [ "sc-network-types", "sc-transaction-pool-api", "smallvec", - "sp-api", + "sp-api 26.0.0", "sp-authority-discovery", "sp-blockchain", "sp-consensus-babe", @@ -14664,7 +14854,7 @@ dependencies = [ "polkadot-primitives-test-helpers", "prioritized-metered-channel", "sc-client-api", - "sp-api", + "sp-api 26.0.0", "sp-core 28.0.0", "tikv-jemalloc-ctl", "tracing-gum", @@ -14750,7 +14940,7 @@ dependencies = [ "sc-client-api", "sc-client-db", "sc-consensus", - "sc-executor", + "sc-executor 0.32.0", "sc-network", "sc-rpc", "sc-service", @@ -14760,7 +14950,7 @@ dependencies = [ "sc-transaction-pool", "serde", "serde_json", - "sp-api", + "sp-api 26.0.0", "sp-block-builder", "sp-consensus-aura", "sp-core 28.0.0", @@ -14771,7 +14961,7 @@ dependencies = [ "sp-session", "sp-timestamp", "sp-transaction-pool", - "sp-version", + "sp-version 29.0.0", "sp-weights 27.0.0", "substrate-frame-rpc-system", "substrate-prometheus-endpoint", @@ -14808,7 +14998,7 @@ dependencies = [ "polkadot-primitives-test-helpers", "scale-info", "serde", - "sp-api", + "sp-api 26.0.0", "sp-application-crypto 30.0.0", "sp-arithmetic 23.0.0", "sp-authority-discovery", @@ -14855,7 +15045,7 @@ dependencies = [ "sc-rpc-spec-v2", "sc-sync-state-rpc", "sc-transaction-pool-api", - "sp-api", + "sp-api 26.0.0", "sp-application-crypto 30.0.0", "sp-block-builder", "sp-blockchain", @@ -14907,7 +15097,7 @@ dependencies = [ "serde_derive", "serde_json", "slot-range-helper", - "sp-api", + "sp-api 26.0.0", "sp-core 28.0.0", "sp-inherents", "sp-io 30.0.0", @@ -14974,7 +15164,7 @@ dependencies = [ "scale-info", "serde", "serde_json", - "sp-api", + "sp-api 26.0.0", "sp-application-crypto 30.0.0", "sp-arithmetic 23.0.0", "sp-core 28.0.0", @@ -15225,7 +15415,7 @@ dependencies = [ "polkadot-service", "polkadot-statement-distribution", "polkadot-statement-table", - "sc-allocator", + "sc-allocator 23.0.0", "sc-authority-discovery", "sc-basic-authorship", "sc-block-builder", @@ -15246,10 +15436,10 @@ dependencies = [ "sc-consensus-manual-seal", "sc-consensus-pow", "sc-consensus-slots", - "sc-executor", - "sc-executor-common", - "sc-executor-polkavm", - "sc-executor-wasmtime", + "sc-executor 0.32.0", + "sc-executor-common 0.29.0", + "sc-executor-polkavm 0.29.0", + "sc-executor-wasmtime 0.29.0", "sc-informant", "sc-keystore", "sc-mixnet", @@ -15295,8 +15485,8 @@ dependencies = [ "snowbridge-runtime-common", "snowbridge-runtime-test-common", "snowbridge-system-runtime-api", - "sp-api", - "sp-api-proc-macro", + "sp-api 26.0.0", + "sp-api-proc-macro 15.0.0", "sp-application-crypto 30.0.0", "sp-arithmetic 23.0.0", "sp-authority-discovery", @@ -15314,7 +15504,7 @@ dependencies = [ "sp-core-hashing-proc-macro", "sp-crypto-ec-utils 0.10.0", "sp-crypto-hashing 0.1.0", - "sp-crypto-hashing-proc-macro", + "sp-crypto-hashing-proc-macro 0.1.0", "sp-database", "sp-debug-derive 14.0.0", "sp-externalities 0.25.0", @@ -15323,8 +15513,8 @@ dependencies = [ "sp-io 30.0.0", "sp-keyring", "sp-keystore 0.34.0", - "sp-maybe-compressed-blob", - "sp-metadata-ir", + "sp-maybe-compressed-blob 11.0.0", + "sp-metadata-ir 0.6.0", "sp-mixnet", "sp-mmr-primitives", "sp-npos-elections", @@ -15345,8 +15535,8 @@ dependencies = [ "sp-transaction-pool", "sp-transaction-storage-proof", "sp-trie 29.0.0", - "sp-version", - "sp-version-proc-macro", + "sp-version 29.0.0", + "sp-version-proc-macro 13.0.0", "sp-wasm-interface 20.0.0", "sp-weights 27.0.0", "staging-chain-spec-builder", @@ -15432,7 +15622,7 @@ dependencies = [ "sc-consensus-grandpa", "sc-consensus-manual-seal", "sc-consensus-pow", - "sc-executor", + "sc-executor 0.32.0", "sc-network", "sc-rpc", "sc-rpc-api", @@ -15440,7 +15630,7 @@ dependencies = [ "scale-info", "simple-mermaid 0.1.1", "solochain-template-runtime", - "sp-api", + "sp-api 26.0.0", "sp-arithmetic 23.0.0", "sp-core 28.0.0", "sp-genesis-builder", @@ -15451,7 +15641,7 @@ dependencies = [ "sp-runtime-interface 24.0.0", "sp-std 14.0.0", "sp-tracing 16.0.0", - "sp-version", + "sp-version 29.0.0", "staging-chain-spec-builder", "staging-node-cli", "staging-parachain-info", @@ -15480,7 +15670,7 @@ dependencies = [ "pallet-examples", "parity-scale-codec", "scale-info", - "sp-api", + "sp-api 26.0.0", "sp-arithmetic 23.0.0", "sp-block-builder", "sp-consensus-aura", @@ -15493,7 +15683,7 @@ dependencies = [ "sp-session", "sp-storage 19.0.0", "sp-transaction-pool", - "sp-version", + "sp-version 29.0.0", ] [[package]] @@ -15574,7 +15764,7 @@ dependencies = [ "sc-consensus-beefy", "sc-consensus-grandpa", "sc-consensus-slots", - "sc-executor", + "sc-executor 0.32.0", "sc-keystore", "sc-network", "sc-network-common", @@ -15590,7 +15780,7 @@ dependencies = [ "serde", "serde_json", "serial_test", - "sp-api", + "sp-api 26.0.0", "sp-authority-discovery", "sp-block-builder", "sp-blockchain", @@ -15612,7 +15802,7 @@ dependencies = [ "sp-timestamp", "sp-tracing 16.0.0", "sp-transaction-pool", - "sp-version", + "sp-version 29.0.0", "sp-weights 27.0.0", "staging-xcm", "substrate-prometheus-endpoint", @@ -15758,7 +15948,7 @@ dependencies = [ "sc-consensus", "sc-offchain", "sc-service", - "sp-api", + "sp-api 26.0.0", "sp-blockchain", "sp-consensus", "sp-consensus-babe", @@ -15838,7 +16028,7 @@ dependencies = [ "scale-info", "serde", "serde_json", - "sp-api", + "sp-api 26.0.0", "sp-authority-discovery", "sp-block-builder", "sp-consensus-babe", @@ -15855,7 +16045,7 @@ dependencies = [ "sp-staking", "sp-transaction-pool", "sp-trie 29.0.0", - "sp-version", + "sp-version 29.0.0", "staging-xcm", "staging-xcm-builder", "staging-xcm-executor", @@ -15926,6 +16116,24 @@ dependencies = [ "westend-runtime", ] +[[package]] +name = "polkadot-zombienet-sdk-tests" +version = "0.1.0" +dependencies = [ + "anyhow", + "env_logger 0.11.3", + "log", + "parity-scale-codec", + "serde", + "serde_json", + "substrate-build-script-utils", + "subwasmlib", + "subxt", + "subxt-signer", + "tokio", + "zombienet-sdk", +] + [[package]] name = "polkavm" version = "0.9.3" @@ -16670,7 +16878,7 @@ dependencies = [ "log", "names", "prost 0.11.9", - "reqwest", + "reqwest 0.11.20", "thiserror", "url", "winapi", @@ -16710,6 +16918,15 @@ version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" +[[package]] +name = "quick-protobuf" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e489d4a83c17ea69b0291630229b5d4c92a94a3bf0165f7f72f506e94cda8b4b" +dependencies = [ + "byteorder", +] + [[package]] name = "quick-protobuf" version = "0.8.1" @@ -16727,7 +16944,7 @@ checksum = "f8ededb1cd78531627244d51dd0c7139fbe736c7d57af0092a76f0ffb2f56e98" dependencies = [ "asynchronous-codec", "bytes", - "quick-protobuf", + "quick-protobuf 0.8.1", "thiserror", "unsigned-varint 0.7.2", ] @@ -16763,8 +16980,8 @@ dependencies = [ "bytes", "futures-io", "pin-project-lite", - "quinn-proto", - "quinn-udp", + "quinn-proto 0.10.6", + "quinn-udp 0.4.1", "rustc-hash 1.1.0", "rustls 0.21.7", "thiserror", @@ -16772,6 +16989,24 @@ dependencies = [ "tracing", ] +[[package]] +name = "quinn" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c7c5fdde3cdae7203427dc4f0a68fe0ed09833edc525a03456b153b79828684" +dependencies = [ + "bytes", + "pin-project-lite", + "quinn-proto 0.11.8", + "quinn-udp 0.5.4", + "rustc-hash 2.0.0", + "rustls 0.23.10", + "socket2 0.5.7", + "thiserror", + "tokio", + "tracing", +] + [[package]] name = "quinn-proto" version = "0.10.6" @@ -16789,6 +17024,23 @@ dependencies = [ "tracing", ] +[[package]] +name = "quinn-proto" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fadfaed2cd7f389d0161bb73eeb07b7b78f8691047a6f3e73caaeae55310a4a6" +dependencies = [ + "bytes", + "rand", + "ring 0.17.7", + "rustc-hash 2.0.0", + "rustls 0.23.10", + "slab", + "thiserror", + "tinyvec", + "tracing", +] + [[package]] name = "quinn-udp" version = "0.4.1" @@ -16802,6 +17054,19 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "quinn-udp" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bffec3605b73c6f1754535084a85229fa8a30f86014e6c81aeec4abb68b0285" +dependencies = [ + "libc", + "once_cell", + "socket2 0.5.7", + "tracing", + "windows-sys 0.52.0", +] + [[package]] name = "quote" version = "0.6.13" @@ -17158,7 +17423,7 @@ dependencies = [ "sp-runtime 31.0.1", "sp-std 14.0.0", "sp-trie 29.0.0", - "sp-version", + "sp-version 29.0.0", "staging-xcm", "thiserror", "tokio", @@ -17244,7 +17509,50 @@ dependencies = [ "wasm-bindgen-futures", "web-sys", "webpki-roots 0.25.2", - "winreg", + "winreg 0.50.0", +] + +[[package]] +name = "reqwest" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7d6d2a27d57148378eb5e111173f4276ad26340ecc5c49a4a2152167a2d6a37" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", + "http-body-util", + "hyper 1.3.1", + "hyper-rustls 0.27.2", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "quinn 0.11.5", + "rustls 0.23.10", + "rustls-pemfile 2.0.0", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls 0.26.0", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots 0.26.3", + "winreg 0.52.0", ] [[package]] @@ -17387,7 +17695,7 @@ dependencies = [ "polkadot-parachain-primitives", "polkadot-runtime-common", "scale-info", - "sp-api", + "sp-api 26.0.0", "sp-block-builder", "sp-consensus-aura", "sp-core 28.0.0", @@ -17397,7 +17705,7 @@ dependencies = [ "sp-runtime 31.0.1", "sp-session", "sp-transaction-pool", - "sp-version", + "sp-version 29.0.0", "staging-parachain-info", "staging-xcm", "staging-xcm-builder", @@ -17480,7 +17788,7 @@ dependencies = [ "serde_derive", "serde_json", "smallvec", - "sp-api", + "sp-api 26.0.0", "sp-arithmetic 23.0.0", "sp-authority-discovery", "sp-block-builder", @@ -17501,7 +17809,7 @@ dependencies = [ "sp-tracing 16.0.0", "sp-transaction-pool", "sp-trie 29.0.0", - "sp-version", + "sp-version 29.0.0", "staging-xcm", "staging-xcm-builder", "staging-xcm-executor", @@ -18000,6 +18308,18 @@ dependencies = [ "thiserror", ] +[[package]] +name = "sc-allocator" +version = "28.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3f01218e73ea57916be5f08987995ac802d6f4ede4ea5ce0242e468c590e4e2" +dependencies = [ + "log", + "sp-core 33.0.1", + "sp-wasm-interface 21.0.0", + "thiserror", +] + [[package]] name = "sc-authority-discovery" version = "0.34.0" @@ -18020,7 +18340,7 @@ dependencies = [ "sc-client-api", "sc-network", "sc-network-types", - "sp-api", + "sp-api 26.0.0", "sp-authority-discovery", "sp-blockchain", "sp-core 28.0.0", @@ -18047,7 +18367,7 @@ dependencies = [ "sc-telemetry", "sc-transaction-pool", "sc-transaction-pool-api", - "sp-api", + "sp-api 26.0.0", "sp-blockchain", "sp-consensus", "sp-core 28.0.0", @@ -18062,7 +18382,7 @@ name = "sc-block-builder" version = "0.33.0" dependencies = [ "parity-scale-codec", - "sp-api", + "sp-api 26.0.0", "sp-block-builder", "sp-blockchain", "sp-core 28.0.0", @@ -18086,7 +18406,7 @@ dependencies = [ "regex", "sc-chain-spec-derive", "sc-client-api", - "sc-executor", + "sc-executor 0.32.0", "sc-network", "sc-telemetry", "serde", @@ -18152,7 +18472,7 @@ dependencies = [ "sp-panic-handler 13.0.0", "sp-runtime 31.0.1", "sp-tracing 16.0.0", - "sp-version", + "sp-version 29.0.0", "tempfile", "thiserror", "tokio", @@ -18167,10 +18487,10 @@ dependencies = [ "log", "parity-scale-codec", "parking_lot 0.12.3", - "sc-executor", + "sc-executor 0.32.0", "sc-transaction-pool-api", "sc-utils", - "sp-api", + "sp-api 26.0.0", "sp-blockchain", "sp-consensus", "sp-core 28.0.0", @@ -18233,7 +18553,7 @@ dependencies = [ "sc-network-types", "sc-utils", "serde", - "sp-api", + "sp-api 26.0.0", "sp-blockchain", "sp-consensus", "sp-core 28.0.0", @@ -18261,7 +18581,7 @@ dependencies = [ "sc-network", "sc-network-test", "sc-telemetry", - "sp-api", + "sp-api 26.0.0", "sp-application-crypto 30.0.0", "sp-block-builder", "sp-blockchain", @@ -18303,7 +18623,7 @@ dependencies = [ "sc-network-test", "sc-telemetry", "sc-transaction-pool-api", - "sp-api", + "sp-api 26.0.0", "sp-application-crypto 30.0.0", "sp-block-builder", "sp-blockchain", @@ -18338,7 +18658,7 @@ dependencies = [ "sc-transaction-pool-api", "serde", "serde_json", - "sp-api", + "sp-api 26.0.0", "sp-application-crypto 30.0.0", "sp-blockchain", "sp-consensus", @@ -18374,7 +18694,7 @@ dependencies = [ "sc-network-types", "sc-utils", "serde", - "sp-api", + "sp-api 26.0.0", "sp-application-crypto 30.0.0", "sp-arithmetic 23.0.0", "sp-blockchain", @@ -18462,7 +18782,7 @@ dependencies = [ "sc-utils", "serde", "serde_json", - "sp-api", + "sp-api 26.0.0", "sp-application-crypto 30.0.0", "sp-arithmetic 23.0.0", "sp-blockchain", @@ -18524,7 +18844,7 @@ dependencies = [ "sc-transaction-pool", "sc-transaction-pool-api", "serde", - "sp-api", + "sp-api 26.0.0", "sp-blockchain", "sp-consensus", "sp-consensus-aura", @@ -18554,7 +18874,7 @@ dependencies = [ "parking_lot 0.12.3", "sc-client-api", "sc-consensus", - "sp-api", + "sp-api 26.0.0", "sp-block-builder", "sp-blockchain", "sp-consensus", @@ -18601,25 +18921,25 @@ dependencies = [ "parking_lot 0.12.3", "paste", "regex", - "sc-executor-common", - "sc-executor-polkavm", - "sc-executor-wasmtime", + "sc-executor-common 0.29.0", + "sc-executor-polkavm 0.29.0", + "sc-executor-wasmtime 0.29.0", "sc-runtime-test", "sc-tracing", "schnellru", - "sp-api", + "sp-api 26.0.0", "sp-core 28.0.0", "sp-crypto-hashing 0.1.0", "sp-externalities 0.25.0", "sp-io 30.0.0", - "sp-maybe-compressed-blob", + "sp-maybe-compressed-blob 11.0.0", "sp-panic-handler 13.0.0", "sp-runtime 31.0.1", "sp-runtime-interface 24.0.0", "sp-state-machine 0.35.0", "sp-tracing 16.0.0", "sp-trie 29.0.0", - "sp-version", + "sp-version 29.0.0", "sp-wasm-interface 20.0.0", "substrate-test-runtime", "tempfile", @@ -18629,27 +18949,77 @@ dependencies = [ ] [[package]] -name = "sc-executor-common" -version = "0.29.0" +name = "sc-executor" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "321e9431a3d5c95514b1ba775dd425efd4b18bd79dfdb6d8e397f0c96d6831e9" dependencies = [ - "polkavm 0.9.3", - "sc-allocator", - "sp-maybe-compressed-blob", + "parity-scale-codec", + "parking_lot 0.12.3", + "sc-executor-common 0.34.0", + "sc-executor-polkavm 0.31.0", + "sc-executor-wasmtime 0.34.0", + "schnellru", + "sp-api 32.0.0", + "sp-core 33.0.1", + "sp-externalities 0.28.0", + "sp-io 36.0.0", + "sp-panic-handler 13.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "sp-runtime-interface 27.0.0", + "sp-trie 35.0.0", + "sp-version 35.0.0", + "sp-wasm-interface 21.0.0", + "tracing", +] + +[[package]] +name = "sc-executor-common" +version = "0.29.0" +dependencies = [ + "polkavm 0.9.3", + "sc-allocator 23.0.0", + "sp-maybe-compressed-blob 11.0.0", "sp-wasm-interface 20.0.0", "thiserror", "wasm-instrument", ] +[[package]] +name = "sc-executor-common" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aad16187c613f81feab35f0d6c12c15c1d88eea0794c886b5dca3495d26746de" +dependencies = [ + "polkavm 0.9.3", + "sc-allocator 28.0.0", + "sp-maybe-compressed-blob 11.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "sp-wasm-interface 21.0.0", + "thiserror", + "wasm-instrument", +] + [[package]] name = "sc-executor-polkavm" version = "0.29.0" dependencies = [ "log", "polkavm 0.9.3", - "sc-executor-common", + "sc-executor-common 0.29.0", "sp-wasm-interface 20.0.0", ] +[[package]] +name = "sc-executor-polkavm" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db336a08ea53b6a89972a6ad6586e664c15db2add9d1cfb508afc768de387304" +dependencies = [ + "log", + "polkavm 0.9.3", + "sc-executor-common 0.34.0", + "sp-wasm-interface 21.0.0", +] + [[package]] name = "sc-executor-wasmtime" version = "0.29.0" @@ -18663,8 +19033,8 @@ dependencies = [ "parking_lot 0.12.3", "paste", "rustix 0.36.15", - "sc-allocator", - "sc-executor-common", + "sc-allocator 23.0.0", + "sc-executor-common 0.29.0", "sc-runtime-test", "sp-io 30.0.0", "sp-runtime-interface 24.0.0", @@ -18674,6 +19044,25 @@ dependencies = [ "wat", ] +[[package]] +name = "sc-executor-wasmtime" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b97b324b2737447b7b208e913fef4988d5c38ecc21f57c3dd33e3f1e1e3bb08" +dependencies = [ + "anyhow", + "cfg-if", + "libc", + "log", + "parking_lot 0.12.3", + "rustix 0.36.15", + "sc-allocator 28.0.0", + "sc-executor-common 0.34.0", + "sp-runtime-interface 27.0.0", + "sp-wasm-interface 21.0.0", + "wasmtime", +] + [[package]] name = "sc-informant" version = "0.33.0" @@ -18723,7 +19112,7 @@ dependencies = [ "sc-network", "sc-network-types", "sc-transaction-pool-api", - "sp-api", + "sp-api 26.0.0", "sp-consensus", "sp-core 28.0.0", "sp-keystore 0.34.0", @@ -19011,7 +19400,7 @@ dependencies = [ "sc-transaction-pool", "sc-transaction-pool-api", "sc-utils", - "sp-api", + "sp-api 26.0.0", "sp-consensus", "sp-core 28.0.0", "sp-externalities 0.25.0", @@ -19056,7 +19445,7 @@ dependencies = [ "sc-transaction-pool-api", "sc-utils", "serde_json", - "sp-api", + "sp-api 26.0.0", "sp-blockchain", "sp-consensus", "sp-core 28.0.0", @@ -19068,7 +19457,7 @@ dependencies = [ "sp-runtime 31.0.1", "sp-session", "sp-statement-store", - "sp-version", + "sp-version 29.0.0", "substrate-test-runtime-client", "tokio", ] @@ -19088,7 +19477,7 @@ dependencies = [ "sp-core 28.0.0", "sp-rpc", "sp-runtime 31.0.1", - "sp-version", + "sp-version 29.0.0", "thiserror", ] @@ -19141,15 +19530,15 @@ dependencies = [ "schnellru", "serde", "serde_json", - "sp-api", + "sp-api 26.0.0", "sp-blockchain", "sp-consensus", "sp-core 28.0.0", "sp-externalities 0.25.0", - "sp-maybe-compressed-blob", + "sp-maybe-compressed-blob 11.0.0", "sp-rpc", "sp-runtime 31.0.1", - "sp-version", + "sp-version 29.0.0", "substrate-test-runtime", "substrate-test-runtime-client", "substrate-test-runtime-transaction-pool", @@ -19188,7 +19577,7 @@ dependencies = [ "sc-client-api", "sc-client-db", "sc-consensus", - "sc-executor", + "sc-executor 0.32.0", "sc-informant", "sc-keystore", "sc-network", @@ -19209,7 +19598,7 @@ dependencies = [ "schnellru", "serde", "serde_json", - "sp-api", + "sp-api 26.0.0", "sp-blockchain", "sp-consensus", "sp-core 28.0.0", @@ -19222,7 +19611,7 @@ dependencies = [ "sp-transaction-pool", "sp-transaction-storage-proof", "sp-trie 29.0.0", - "sp-version", + "sp-version 29.0.0", "static_init", "substrate-prometheus-endpoint", "substrate-test-runtime", @@ -19249,12 +19638,12 @@ dependencies = [ "sc-client-api", "sc-client-db", "sc-consensus", - "sc-executor", + "sc-executor 0.32.0", "sc-network", "sc-network-sync", "sc-service", "sc-transaction-pool-api", - "sp-api", + "sp-api 26.0.0", "sp-blockchain", "sp-consensus", "sp-core 28.0.0", @@ -19289,7 +19678,7 @@ dependencies = [ "parking_lot 0.12.3", "sc-client-api", "sc-keystore", - "sp-api", + "sp-api 26.0.0", "sp-blockchain", "sp-core 28.0.0", "sp-runtime 31.0.1", @@ -19388,7 +19777,7 @@ dependencies = [ "sc-client-api", "sc-tracing-proc-macro", "serde", - "sp-api", + "sp-api 26.0.0", "sp-blockchain", "sp-core 28.0.0", "sp-rpc", @@ -19429,7 +19818,7 @@ dependencies = [ "sc-transaction-pool-api", "sc-utils", "serde", - "sp-api", + "sp-api 26.0.0", "sp-blockchain", "sp-consensus", "sp-core 28.0.0", @@ -19814,7 +20203,7 @@ dependencies = [ "parachains-common", "parity-scale-codec", "scale-info", - "sp-api", + "sp-api 26.0.0", "sp-block-builder", "sp-consensus-aura", "sp-core 28.0.0", @@ -19824,7 +20213,7 @@ dependencies = [ "sp-runtime 31.0.1", "sp-session", "sp-transaction-pool", - "sp-version", + "sp-version 29.0.0", "staging-parachain-info", "substrate-wasm-builder", ] @@ -20090,6 +20479,18 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "sha3" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809" +dependencies = [ + "block-buffer 0.9.0", + "digest 0.9.0", + "keccak", + "opaque-debug 0.3.0", +] + [[package]] name = "sha3" version = "0.10.8" @@ -20127,7 +20528,7 @@ dependencies = [ "parachains-common", "parity-scale-codec", "scale-info", - "sp-api", + "sp-api 26.0.0", "sp-block-builder", "sp-consensus-aura", "sp-core 28.0.0", @@ -20137,7 +20538,7 @@ dependencies = [ "sp-runtime 31.0.1", "sp-session", "sp-transaction-pool", - "sp-version", + "sp-version 29.0.0", "staging-parachain-info", "staging-xcm", "staging-xcm-builder", @@ -20362,7 +20763,7 @@ dependencies = [ "serde", "serde_json", "sha2 0.10.8", - "sha3", + "sha3 0.10.8", "siphasher 0.3.11", "slab", "smallvec", @@ -20417,7 +20818,7 @@ dependencies = [ "serde", "serde_json", "sha2 0.10.8", - "sha3", + "sha3 0.10.8", "siphasher 1.0.1", "slab", "smallvec", @@ -20639,7 +21040,7 @@ dependencies = [ "parity-scale-codec", "snowbridge-core", "snowbridge-outbound-queue-merkle-tree", - "sp-api", + "sp-api 26.0.0", "sp-std 14.0.0", ] @@ -20839,7 +21240,7 @@ version = "0.2.0" dependencies = [ "parity-scale-codec", "snowbridge-core", - "sp-api", + "sp-api 26.0.0", "sp-std 14.0.0", "staging-xcm", ] @@ -20913,7 +21314,7 @@ dependencies = [ "sc-consensus", "sc-consensus-aura", "sc-consensus-grandpa", - "sc-executor", + "sc-executor 0.32.0", "sc-network", "sc-offchain", "sc-service", @@ -20922,7 +21323,7 @@ dependencies = [ "sc-transaction-pool-api", "serde_json", "solochain-template-runtime", - "sp-api", + "sp-api 26.0.0", "sp-block-builder", "sp-blockchain", "sp-consensus-aura", @@ -20959,7 +21360,7 @@ dependencies = [ "pallet-transaction-payment-rpc-runtime-api", "parity-scale-codec", "scale-info", - "sp-api", + "sp-api 26.0.0", "sp-block-builder", "sp-consensus-aura", "sp-consensus-grandpa", @@ -20971,7 +21372,7 @@ dependencies = [ "sp-session", "sp-storage 19.0.0", "sp-transaction-pool", - "sp-version", + "sp-version 29.0.0", "substrate-wasm-builder", ] @@ -20984,16 +21385,39 @@ dependencies = [ "log", "parity-scale-codec", "scale-info", - "sp-api-proc-macro", + "sp-api-proc-macro 15.0.0", "sp-core 28.0.0", "sp-externalities 0.25.0", - "sp-metadata-ir", + "sp-metadata-ir 0.6.0", "sp-runtime 31.0.1", "sp-runtime-interface 24.0.0", "sp-state-machine 0.35.0", "sp-test-primitives", "sp-trie 29.0.0", - "sp-version", + "sp-version 29.0.0", + "thiserror", +] + +[[package]] +name = "sp-api" +version = "32.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f84f09c4b928e814e07dede0ece91f1f6eae1bff946a0e5e4a76bed19a095f1" +dependencies = [ + "hash-db", + "log", + "parity-scale-codec", + "scale-info", + "sp-api-proc-macro 19.0.0", + "sp-core 33.0.1", + "sp-externalities 0.28.0", + "sp-metadata-ir 0.7.0", + "sp-runtime 37.0.0", + "sp-runtime-interface 27.0.0", + "sp-state-machine 0.41.0", + "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "sp-trie 35.0.0", + "sp-version 35.0.0", "thiserror", ] @@ -21011,6 +21435,21 @@ dependencies = [ "syn 2.0.65", ] +[[package]] +name = "sp-api-proc-macro" +version = "19.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213a4bec1b18bd0750e7b81d11d8276c24f68b53cde83950b00b178ecc9ab24a" +dependencies = [ + "Inflector", + "blake2 0.10.6", + "expander", + "proc-macro-crate 3.1.0", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.65", +] + [[package]] name = "sp-api-test" version = "2.0.1" @@ -21022,13 +21461,13 @@ dependencies = [ "rustversion", "sc-block-builder", "scale-info", - "sp-api", + "sp-api 26.0.0", "sp-consensus", "sp-core 28.0.0", "sp-runtime 31.0.1", "sp-state-machine 0.35.0", "sp-tracing 16.0.0", - "sp-version", + "sp-version 29.0.0", "static_assertions", "substrate-test-runtime-client", "trybuild", @@ -21059,11 +21498,39 @@ dependencies = [ "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "sp-application-crypto" +version = "35.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57541120624a76379cc993cbb85064a5148957a92da032567e54bce7977f51fc" +dependencies = [ + "parity-scale-codec", + "scale-info", + "serde", + "sp-core 32.0.0", + "sp-io 35.0.0", + "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "sp-application-crypto" +version = "36.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "296282f718f15d4d812664415942665302a484d3495cf8d2e2ab3192b32d2c73" +dependencies = [ + "parity-scale-codec", + "scale-info", + "serde", + "sp-core 33.0.1", + "sp-io 36.0.0", + "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "sp-application-crypto-test" version = "2.0.0" dependencies = [ - "sp-api", + "sp-api 26.0.0", "sp-application-crypto 30.0.0", "sp-core 28.0.0", "sp-keystore 0.34.0", @@ -21102,6 +21569,22 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "sp-arithmetic" +version = "26.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46d0d0a4c591c421d3231ddd5e27d828618c24456d51445d21a1f79fcee97c23" +dependencies = [ + "docify", + "integer-sqrt", + "num-traits", + "parity-scale-codec", + "scale-info", + "serde", + "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "static_assertions", +] + [[package]] name = "sp-arithmetic-fuzzer" version = "2.0.0" @@ -21137,7 +21620,7 @@ version = "26.0.0" dependencies = [ "parity-scale-codec", "scale-info", - "sp-api", + "sp-api 26.0.0", "sp-application-crypto 30.0.0", "sp-runtime 31.0.1", ] @@ -21146,7 +21629,7 @@ dependencies = [ name = "sp-block-builder" version = "26.0.0" dependencies = [ - "sp-api", + "sp-api 26.0.0", "sp-inherents", "sp-runtime 31.0.1", ] @@ -21159,7 +21642,7 @@ dependencies = [ "parity-scale-codec", "parking_lot 0.12.3", "schnellru", - "sp-api", + "sp-api 26.0.0", "sp-consensus", "sp-core 28.0.0", "sp-database", @@ -21191,7 +21674,7 @@ dependencies = [ "async-trait", "parity-scale-codec", "scale-info", - "sp-api", + "sp-api 26.0.0", "sp-application-crypto 30.0.0", "sp-consensus-slots", "sp-inherents", @@ -21207,7 +21690,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "serde", - "sp-api", + "sp-api 26.0.0", "sp-application-crypto 30.0.0", "sp-consensus-slots", "sp-core 28.0.0", @@ -21225,7 +21708,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "serde", - "sp-api", + "sp-api 26.0.0", "sp-application-crypto 30.0.0", "sp-core 28.0.0", "sp-crypto-hashing 0.1.0", @@ -21247,7 +21730,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "serde", - "sp-api", + "sp-api 26.0.0", "sp-application-crypto 30.0.0", "sp-core 28.0.0", "sp-keystore 0.34.0", @@ -21259,7 +21742,7 @@ name = "sp-consensus-pow" version = "0.32.0" dependencies = [ "parity-scale-codec", - "sp-api", + "sp-api 26.0.0", "sp-core 28.0.0", "sp-runtime 31.0.1", ] @@ -21271,7 +21754,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "serde", - "sp-api", + "sp-api 26.0.0", "sp-application-crypto 30.0.0", "sp-consensus-slots", "sp-core 28.0.0", @@ -21385,6 +21868,100 @@ dependencies = [ "zeroize", ] +[[package]] +name = "sp-core" +version = "32.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb2dac7e47c7ddbb61efe196d5cce99f6ea88926c961fa39909bfeae46fc5a7b" +dependencies = [ + "array-bytes", + "bitflags 1.3.2", + "blake2 0.10.6", + "bounded-collections", + "bs58", + "dyn-clonable", + "ed25519-zebra 3.1.0", + "futures", + "hash-db", + "hash256-std-hasher", + "impl-serde", + "itertools 0.10.5", + "k256", + "libsecp256k1", + "log", + "merlin", + "parity-bip39", + "parity-scale-codec", + "parking_lot 0.12.3", + "paste", + "primitive-types", + "rand", + "scale-info", + "schnorrkel 0.11.4", + "secp256k1", + "secrecy", + "serde", + "sp-crypto-hashing 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "sp-debug-derive 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "sp-externalities 0.28.0", + "sp-runtime-interface 27.0.0", + "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "sp-storage 21.0.0", + "ss58-registry", + "substrate-bip39 0.6.0", + "thiserror", + "tracing", + "w3f-bls", + "zeroize", +] + +[[package]] +name = "sp-core" +version = "33.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3368e32f6fda6e20b8af51f94308d033ab70a021e87f6abbd3fed5aca942b745" +dependencies = [ + "array-bytes", + "bitflags 1.3.2", + "blake2 0.10.6", + "bounded-collections", + "bs58", + "dyn-clonable", + "ed25519-zebra 4.0.3", + "futures", + "hash-db", + "hash256-std-hasher", + "impl-serde", + "itertools 0.11.0", + "k256", + "libsecp256k1", + "log", + "merlin", + "parity-bip39", + "parity-scale-codec", + "parking_lot 0.12.3", + "paste", + "primitive-types", + "rand", + "scale-info", + "schnorrkel 0.11.4", + "secp256k1", + "secrecy", + "serde", + "sp-crypto-hashing 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "sp-debug-derive 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "sp-externalities 0.28.0", + "sp-runtime-interface 27.0.0", + "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "sp-storage 21.0.0", + "ss58-registry", + "substrate-bip39 0.6.0", + "thiserror", + "tracing", + "w3f-bls", + "zeroize", +] + [[package]] name = "sp-core-fuzz" version = "0.0.0" @@ -21406,7 +21983,7 @@ dependencies = [ name = "sp-core-hashing-proc-macro" version = "15.0.0" dependencies = [ - "sp-crypto-hashing-proc-macro", + "sp-crypto-hashing-proc-macro 0.1.0", ] [[package]] @@ -21453,13 +22030,13 @@ dependencies = [ name = "sp-crypto-hashing" version = "0.1.0" dependencies = [ - "blake2b_simd", + "blake2b_simd 1.0.2", "byteorder", "criterion", "digest 0.10.7", "sha2 0.10.8", - "sha3", - "sp-crypto-hashing-proc-macro", + "sha3 0.10.8", + "sp-crypto-hashing-proc-macro 0.1.0", "twox-hash", ] @@ -21469,11 +22046,11 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc9927a7f81334ed5b8a98a4a978c81324d12bd9713ec76b5c68fd410174c5eb" dependencies = [ - "blake2b_simd", + "blake2b_simd 1.0.2", "byteorder", "digest 0.10.7", "sha2 0.10.8", - "sha3", + "sha3 0.10.8", "twox-hash", ] @@ -21486,6 +22063,17 @@ dependencies = [ "syn 2.0.65", ] +[[package]] +name = "sp-crypto-hashing-proc-macro" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b85d0f1f1e44bd8617eb2a48203ee854981229e3e79e6f468c7175d5fd37489b" +dependencies = [ + "quote 1.0.37", + "sp-crypto-hashing 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 2.0.65", +] + [[package]] name = "sp-database" version = "10.0.0" @@ -21556,6 +22144,17 @@ dependencies = [ "sp-storage 20.0.0", ] +[[package]] +name = "sp-externalities" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33abaec4be69b1613796bbf430decbbcaaf978756379e2016e683a4d6379cd02" +dependencies = [ + "environmental", + "parity-scale-codec", + "sp-storage 21.0.0", +] + [[package]] name = "sp-genesis-builder" version = "0.8.0" @@ -21563,7 +22162,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "serde_json", - "sp-api", + "sp-api 26.0.0", "sp-runtime 31.0.1", ] @@ -21632,6 +22231,60 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "sp-io" +version = "35.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b64ab18a0e29def6511139a8c45a59c14a846105aab6f9cc653523bd3b81f55" +dependencies = [ + "bytes", + "ed25519-dalek", + "libsecp256k1", + "log", + "parity-scale-codec", + "polkavm-derive 0.9.1", + "rustversion", + "secp256k1", + "sp-core 32.0.0", + "sp-crypto-hashing 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "sp-externalities 0.28.0", + "sp-keystore 0.38.0", + "sp-runtime-interface 27.0.0", + "sp-state-machine 0.40.0", + "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "sp-tracing 17.0.0", + "sp-trie 34.0.0", + "tracing", + "tracing-core", +] + +[[package]] +name = "sp-io" +version = "36.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a31ce27358b73656a09b4933f09a700019d63afa15ede966f7c9893c1d4db5" +dependencies = [ + "bytes", + "ed25519-dalek", + "libsecp256k1", + "log", + "parity-scale-codec", + "polkavm-derive 0.9.1", + "rustversion", + "secp256k1", + "sp-core 33.0.1", + "sp-crypto-hashing 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "sp-externalities 0.28.0", + "sp-keystore 0.39.0", + "sp-runtime-interface 27.0.0", + "sp-state-machine 0.41.0", + "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "sp-tracing 17.0.0", + "sp-trie 35.0.0", + "tracing", + "tracing-core", +] + [[package]] name = "sp-keyring" version = "31.0.0" @@ -21665,9 +22318,43 @@ dependencies = [ "sp-externalities 0.27.0", ] +[[package]] +name = "sp-keystore" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e6c7a7abd860a5211a356cf9d5fcabf0eb37d997985e5d722b6b33dcc815528" +dependencies = [ + "parity-scale-codec", + "parking_lot 0.12.3", + "sp-core 32.0.0", + "sp-externalities 0.28.0", +] + +[[package]] +name = "sp-keystore" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92a909528663a80829b95d582a20dd4c9acd6e575650dee2bcaf56f4740b305e" +dependencies = [ + "parity-scale-codec", + "parking_lot 0.12.3", + "sp-core 33.0.1", + "sp-externalities 0.28.0", +] + +[[package]] +name = "sp-maybe-compressed-blob" +version = "11.0.0" +dependencies = [ + "thiserror", + "zstd 0.12.4", +] + [[package]] name = "sp-maybe-compressed-blob" version = "11.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c768c11afbe698a090386876911da4236af199cd38a5866748df4d8628aeff" dependencies = [ "thiserror", "zstd 0.12.4", @@ -21682,13 +22369,24 @@ dependencies = [ "scale-info", ] +[[package]] +name = "sp-metadata-ir" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a616fa51350b35326682a472ee8e6ba742fdacb18babac38ecd46b3e05ead869" +dependencies = [ + "frame-metadata 16.0.0", + "parity-scale-codec", + "scale-info", +] + [[package]] name = "sp-mixnet" version = "0.4.0" dependencies = [ "parity-scale-codec", "scale-info", - "sp-api", + "sp-api 26.0.0", "sp-application-crypto 30.0.0", ] @@ -21702,7 +22400,7 @@ dependencies = [ "polkadot-ckb-merkle-mountain-range", "scale-info", "serde", - "sp-api", + "sp-api 26.0.0", "sp-core 28.0.0", "sp-debug-derive 14.0.0", "sp-runtime 31.0.1", @@ -21738,7 +22436,7 @@ dependencies = [ name = "sp-offchain" version = "26.0.0" dependencies = [ - "sp-api", + "sp-api 26.0.0", "sp-core 28.0.0", "sp-runtime 31.0.1", ] @@ -21790,7 +22488,7 @@ dependencies = [ "serde", "serde_json", "simple-mermaid 0.1.1", - "sp-api", + "sp-api 26.0.0", "sp-application-crypto 30.0.0", "sp-arithmetic 23.0.0", "sp-core 28.0.0", @@ -21830,6 +22528,57 @@ dependencies = [ "sp-weights 30.0.0", ] +[[package]] +name = "sp-runtime" +version = "36.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6b85cb874b78ebb17307a910fc27edf259a0455ac5155d87eaed8754c037e07" +dependencies = [ + "docify", + "either", + "hash256-std-hasher", + "impl-trait-for-tuples", + "log", + "parity-scale-codec", + "paste", + "rand", + "scale-info", + "serde", + "simple-mermaid 0.1.1", + "sp-application-crypto 35.0.0", + "sp-arithmetic 26.0.0", + "sp-core 32.0.0", + "sp-io 35.0.0", + "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "sp-weights 31.0.0", +] + +[[package]] +name = "sp-runtime" +version = "37.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c2a6148bf0ba74999ecfea9b4c1ade544f0663e0baba19630bb7761b2142b19" +dependencies = [ + "docify", + "either", + "hash256-std-hasher", + "impl-trait-for-tuples", + "log", + "num-traits", + "parity-scale-codec", + "paste", + "rand", + "scale-info", + "serde", + "simple-mermaid 0.1.1", + "sp-application-crypto 36.0.0", + "sp-arithmetic 26.0.0", + "sp-core 33.0.1", + "sp-io 36.0.0", + "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "sp-weights 31.0.0", +] + [[package]] name = "sp-runtime-interface" version = "17.0.0" @@ -21892,6 +22641,26 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "sp-runtime-interface" +version = "27.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "647db5e1dc481686628b41554e832df6ab400c4b43a6a54e54d3b0a71ca404aa" +dependencies = [ + "bytes", + "impl-trait-for-tuples", + "parity-scale-codec", + "polkavm-derive 0.9.1", + "primitive-types", + "sp-externalities 0.28.0", + "sp-runtime-interface-proc-macro 18.0.0", + "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "sp-storage 21.0.0", + "sp-tracing 17.0.0", + "sp-wasm-interface 21.0.0", + "static_assertions", +] + [[package]] name = "sp-runtime-interface-proc-macro" version = "11.0.0" @@ -21934,8 +22703,8 @@ dependencies = [ name = "sp-runtime-interface-test" version = "2.0.0" dependencies = [ - "sc-executor", - "sc-executor-common", + "sc-executor 0.32.0", + "sc-executor-common 0.29.0", "sp-io 30.0.0", "sp-runtime 31.0.1", "sp-runtime-interface 24.0.0", @@ -21973,7 +22742,7 @@ version = "27.0.0" dependencies = [ "parity-scale-codec", "scale-info", - "sp-api", + "sp-api 26.0.0", "sp-core 28.0.0", "sp-keystore 0.34.0", "sp-runtime 31.0.1", @@ -22018,9 +22787,52 @@ dependencies = [ [[package]] name = "sp-state-machine" -version = "0.38.0" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1eae0eac8034ba14437e772366336f579398a46d101de13dbb781ab1e35e67c5" +dependencies = [ + "hash-db", + "log", + "parity-scale-codec", + "parking_lot 0.12.3", + "rand", + "smallvec", + "sp-core 31.0.0", + "sp-externalities 0.27.0", + "sp-panic-handler 13.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "sp-trie 32.0.0", + "thiserror", + "tracing", + "trie-db 0.28.0", +] + +[[package]] +name = "sp-state-machine" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18084cb996c27d5d99a88750e0a8eb4af6870a40df97872a5923e6d293d95fb9" +dependencies = [ + "hash-db", + "log", + "parity-scale-codec", + "parking_lot 0.12.3", + "rand", + "smallvec", + "sp-core 32.0.0", + "sp-externalities 0.28.0", + "sp-panic-handler 13.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "sp-trie 34.0.0", + "thiserror", + "tracing", + "trie-db 0.29.1", +] + +[[package]] +name = "sp-state-machine" +version = "0.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1eae0eac8034ba14437e772366336f579398a46d101de13dbb781ab1e35e67c5" +checksum = "6f6ac196ea92c4d0613c071e1a050765dbfa30107a990224a4aba02c7dbcd063" dependencies = [ "hash-db", "log", @@ -22028,14 +22840,13 @@ dependencies = [ "parking_lot 0.12.3", "rand", "smallvec", - "sp-core 31.0.0", - "sp-externalities 0.27.0", + "sp-core 33.0.1", + "sp-externalities 0.28.0", "sp-panic-handler 13.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "sp-trie 32.0.0", + "sp-trie 35.0.0", "thiserror", "tracing", - "trie-db 0.28.0", + "trie-db 0.29.1", ] [[package]] @@ -22050,7 +22861,7 @@ dependencies = [ "rand", "scale-info", "sha2 0.10.8", - "sp-api", + "sp-api 26.0.0", "sp-application-crypto 30.0.0", "sp-core 28.0.0", "sp-crypto-hashing 0.1.0", @@ -22114,6 +22925,19 @@ dependencies = [ "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "sp-storage" +version = "21.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99c82989b3a4979a7e1ad848aad9f5d0b4388f1f454cc131766526601ab9e8f8" +dependencies = [ + "impl-serde", + "parity-scale-codec", + "ref-cast", + "serde", + "sp-debug-derive 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "sp-test-primitives" version = "2.0.0" @@ -22172,11 +22996,23 @@ dependencies = [ "tracing-subscriber 0.2.25", ] +[[package]] +name = "sp-tracing" +version = "17.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90b3decf116db9f1dfaf1f1597096b043d0e12c952d3bcdc018c6d6b77deec7e" +dependencies = [ + "parity-scale-codec", + "tracing", + "tracing-core", + "tracing-subscriber 0.2.25", +] + [[package]] name = "sp-transaction-pool" version = "26.0.0" dependencies = [ - "sp-api", + "sp-api 26.0.0", "sp-runtime 31.0.1", ] @@ -22245,6 +23081,54 @@ dependencies = [ "trie-root", ] +[[package]] +name = "sp-trie" +version = "34.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87727eced997f14d0f79e3a5186a80e38a9de87f6e9dc0baea5ebf8b7f9d8b66" +dependencies = [ + "ahash 0.8.11", + "hash-db", + "lazy_static", + "memory-db", + "nohash-hasher", + "parity-scale-codec", + "parking_lot 0.12.3", + "rand", + "scale-info", + "schnellru", + "sp-core 32.0.0", + "sp-externalities 0.28.0", + "thiserror", + "tracing", + "trie-db 0.29.1", + "trie-root", +] + +[[package]] +name = "sp-trie" +version = "35.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a61ab0c3e003f457203702e4753aa5fe9e762380543fada44650b1217e4aa5a5" +dependencies = [ + "ahash 0.8.11", + "hash-db", + "lazy_static", + "memory-db", + "nohash-hasher", + "parity-scale-codec", + "parking_lot 0.12.3", + "rand", + "scale-info", + "schnellru", + "sp-core 33.0.1", + "sp-externalities 0.28.0", + "thiserror", + "tracing", + "trie-db 0.29.1", + "trie-root", +] + [[package]] name = "sp-version" version = "29.0.0" @@ -22254,10 +23138,28 @@ dependencies = [ "parity-wasm", "scale-info", "serde", - "sp-crypto-hashing-proc-macro", + "sp-crypto-hashing-proc-macro 0.1.0", "sp-runtime 31.0.1", "sp-std 14.0.0", - "sp-version-proc-macro", + "sp-version-proc-macro 13.0.0", + "thiserror", +] + +[[package]] +name = "sp-version" +version = "35.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ff74bf12b4f7d29387eb1caeec5553209a505f90a2511d2831143b970f89659" +dependencies = [ + "impl-serde", + "parity-scale-codec", + "parity-wasm", + "scale-info", + "serde", + "sp-crypto-hashing-proc-macro 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "sp-runtime 37.0.0", + "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "sp-version-proc-macro 14.0.0", "thiserror", ] @@ -22269,7 +23171,19 @@ dependencies = [ "proc-macro-warning 1.0.0", "proc-macro2 1.0.86", "quote 1.0.37", - "sp-version", + "sp-version 29.0.0", + "syn 2.0.65", +] + +[[package]] +name = "sp-version-proc-macro" +version = "14.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aee8f6730641a65fcf0c8f9b1e448af4b3bb083d08058b47528188bccc7b7a7" +dependencies = [ + "parity-scale-codec", + "proc-macro2 1.0.86", + "quote 1.0.37", "syn 2.0.65", ] @@ -22311,6 +23225,19 @@ dependencies = [ "wasmtime", ] +[[package]] +name = "sp-wasm-interface" +version = "21.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b04b919e150b4736d85089d49327eab65507deb1485eec929af69daa2278eb3" +dependencies = [ + "anyhow", + "impl-trait-for-tuples", + "log", + "parity-scale-codec", + "wasmtime", +] + [[package]] name = "sp-weights" version = "27.0.0" @@ -22341,6 +23268,21 @@ dependencies = [ "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "sp-weights" +version = "31.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93cdaf72a1dad537bbb130ba4d47307ebe5170405280ed1aa31fa712718a400e" +dependencies = [ + "bounded-collections", + "parity-scale-codec", + "scale-info", + "serde", + "smallvec", + "sp-arithmetic 26.0.0", + "sp-debug-derive 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "spin" version = "0.5.2" @@ -22731,6 +23673,17 @@ dependencies = [ "sc-cli", ] +[[package]] +name = "subrpcer" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a00780fcd4ebedf099da78a562744c6f17bda08d1223928c3104dd26081b44" +dependencies = [ + "affix", + "serde", + "serde_json", +] + [[package]] name = "substrate-bip39" version = "0.4.7" @@ -22757,6 +23710,19 @@ dependencies = [ "zeroize", ] +[[package]] +name = "substrate-bip39" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca58ffd742f693dc13d69bdbb2e642ae239e0053f6aab3b104252892f856700a" +dependencies = [ + "hmac 0.12.1", + "pbkdf2", + "schnorrkel 0.11.4", + "sha2 0.10.8", + "zeroize", +] + [[package]] name = "substrate-build-script-utils" version = "11.0.0" @@ -22778,6 +23744,23 @@ dependencies = [ "tokio", ] +[[package]] +name = "substrate-differ" +version = "0.21.3" +source = "git+https://github.com/chevdor/subwasm?rev=v0.21.3#aa8acb6fdfb34144ac51ab95618a9b37fa251295" +dependencies = [ + "comparable", + "document-features", + "frame-metadata 16.0.0", + "log", + "num-format", + "scale-info", + "serde", + "serde_json", + "thiserror", + "wasm-testbed", +] + [[package]] name = "substrate-frame-rpc-support" version = "29.0.0" @@ -22809,7 +23792,7 @@ dependencies = [ "sc-rpc-api", "sc-transaction-pool", "sc-transaction-pool-api", - "sp-api", + "sp-api 26.0.0", "sp-block-builder", "sp-blockchain", "sp-core 28.0.0", @@ -22890,6 +23873,22 @@ dependencies = [ "tokio", ] +[[package]] +name = "substrate-runtime-proposal-hash" +version = "0.21.3" +source = "git+https://github.com/chevdor/subwasm?rev=v0.21.3#aa8acb6fdfb34144ac51ab95618a9b37fa251295" +dependencies = [ + "blake2 0.10.6", + "frame-metadata 16.0.0", + "hex", + "parity-scale-codec", + "sp-core 32.0.0", + "sp-io 35.0.0", + "sp-runtime 36.0.0", + "sp-wasm-interface 21.0.0", + "thiserror", +] + [[package]] name = "substrate-state-trie-migration-rpc" version = "27.0.0" @@ -22918,7 +23917,7 @@ dependencies = [ "sc-client-api", "sc-client-db", "sc-consensus", - "sc-executor", + "sc-executor 0.32.0", "sc-offchain", "sc-service", "serde", @@ -22951,13 +23950,13 @@ dependencies = [ "parity-scale-codec", "sc-block-builder", "sc-chain-spec", - "sc-executor", - "sc-executor-common", + "sc-executor 0.32.0", + "sc-executor-common 0.29.0", "sc-service", "scale-info", "serde", "serde_json", - "sp-api", + "sp-api 26.0.0", "sp-application-crypto 30.0.0", "sp-block-builder", "sp-consensus", @@ -22978,7 +23977,7 @@ dependencies = [ "sp-tracing 16.0.0", "sp-transaction-pool", "sp-trie 29.0.0", - "sp-version", + "sp-version 29.0.0", "substrate-test-runtime-client", "substrate-wasm-builder", "tracing", @@ -22993,7 +23992,7 @@ dependencies = [ "sc-block-builder", "sc-client-api", "sc-consensus", - "sp-api", + "sp-api 26.0.0", "sp-blockchain", "sp-consensus", "sp-core 28.0.0", @@ -23042,12 +24041,12 @@ dependencies = [ "parity-scale-codec", "parity-wasm", "polkavm-linker 0.9.2", - "sc-executor", + "sc-executor 0.32.0", "sp-core 28.0.0", "sp-io 30.0.0", - "sp-maybe-compressed-blob", + "sp-maybe-compressed-blob 11.0.0", "sp-tracing 16.0.0", - "sp-version", + "sp-version 29.0.0", "strum 0.26.2", "tempfile", "toml 0.8.12", @@ -23073,6 +24072,32 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" +[[package]] +name = "subwasmlib" +version = "0.21.3" +source = "git+https://github.com/chevdor/subwasm?rev=v0.21.3#aa8acb6fdfb34144ac51ab95618a9b37fa251295" +dependencies = [ + "calm_io", + "frame-metadata 16.0.0", + "hex", + "ipfs-hasher", + "log", + "num-format", + "rand", + "reqwest 0.12.5", + "scale-info", + "semver 1.0.18", + "serde", + "serde_json", + "sp-version 35.0.0", + "substrate-differ", + "thiserror", + "url", + "uuid", + "wasm-loader", + "wasm-testbed", +] + [[package]] name = "subxt" version = "0.37.0" @@ -23362,6 +24387,12 @@ dependencies = [ "syn 2.0.65", ] +[[package]] +name = "sync_wrapper" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" + [[package]] name = "synstructure" version = "0.12.6" @@ -23956,7 +24987,7 @@ dependencies = [ "rustls-native-certs 0.6.3", "tokio", "tokio-rustls 0.24.1", - "tungstenite", + "tungstenite 0.20.1", ] [[package]] @@ -24439,6 +25470,28 @@ dependencies = [ "utf-8", ] +[[package]] +name = "tungstenite" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http 1.1.0", + "httparse", + "log", + "rand", + "rustls 0.22.4", + "rustls-native-certs 0.7.0", + "rustls-pki-types", + "sha1", + "thiserror", + "url", + "utf-8", +] + [[package]] name = "tuplex" version = "0.1.2" @@ -24548,6 +25601,18 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" +[[package]] +name = "unsigned-varint" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f67332660eb59a6f1eb24ff1220c9e8d01738a8503c6002e30bcfe4bd9f2b4a9" + +[[package]] +name = "unsigned-varint" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7fdeedbf205afadfe39ae559b75c3240f24e257d0ca27e85f85cb82aa19ac35" + [[package]] name = "unsigned-varint" version = "0.7.2" @@ -24582,6 +25647,24 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "ureq" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b74fc6b57825be3373f7054754755f03ac3a8f5d70015ccad699ba2029956f4a" +dependencies = [ + "base64 0.22.1", + "flate2", + "log", + "once_cell", + "rustls 0.23.10", + "rustls-pki-types", + "serde", + "serde_json", + "url", + "webpki-roots 0.26.3", +] + [[package]] name = "url" version = "2.5.2" @@ -24700,7 +25783,7 @@ dependencies = [ "rand_chacha", "rand_core 0.6.4", "sha2 0.10.8", - "sha3", + "sha3 0.10.8", "thiserror", "zeroize", ] @@ -24856,6 +25939,25 @@ dependencies = [ "parity-wasm", ] +[[package]] +name = "wasm-loader" +version = "0.21.3" +source = "git+https://github.com/chevdor/subwasm?rev=v0.21.3#aa8acb6fdfb34144ac51ab95618a9b37fa251295" +dependencies = [ + "array-bytes", + "log", + "multibase 0.9.1", + "multihash 0.19.1", + "serde", + "serde_json", + "sp-maybe-compressed-blob 11.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "subrpcer", + "thiserror", + "tungstenite 0.21.0", + "ureq", + "url", +] + [[package]] name = "wasm-opt" version = "0.116.0" @@ -24896,6 +25998,29 @@ dependencies = [ "cxx-build", ] +[[package]] +name = "wasm-testbed" +version = "0.21.3" +source = "git+https://github.com/chevdor/subwasm?rev=v0.21.3#aa8acb6fdfb34144ac51ab95618a9b37fa251295" +dependencies = [ + "frame-metadata 16.0.0", + "hex", + "log", + "parity-scale-codec", + "sc-executor 0.38.0", + "sc-executor-common 0.34.0", + "scale-info", + "sp-core 33.0.1", + "sp-io 36.0.0", + "sp-runtime 37.0.0", + "sp-state-machine 0.41.0", + "sp-version 35.0.0", + "sp-wasm-interface 21.0.0", + "substrate-runtime-proposal-hash", + "thiserror", + "wasm-loader", +] + [[package]] name = "wasm-timer" version = "0.2.5" @@ -25351,7 +26476,7 @@ dependencies = [ "serde_derive", "serde_json", "smallvec", - "sp-api", + "sp-api 26.0.0", "sp-application-crypto 30.0.0", "sp-arithmetic 23.0.0", "sp-authority-discovery", @@ -25372,7 +26497,7 @@ dependencies = [ "sp-storage 19.0.0", "sp-tracing 16.0.0", "sp-transaction-pool", - "sp-version", + "sp-version 29.0.0", "staging-xcm", "staging-xcm-builder", "staging-xcm-executor", @@ -25732,6 +26857,16 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "winreg" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + [[package]] name = "wyz" version = "0.5.1" @@ -25904,7 +27039,7 @@ dependencies = [ "pallet-xcm", "parity-scale-codec", "scale-info", - "sp-api", + "sp-api 26.0.0", "sp-io 30.0.0", "sp-tracing 16.0.0", "sp-weights 27.0.0", @@ -26087,7 +27222,7 @@ dependencies = [ "futures-util", "lazy_static", "parity-scale-codec", - "reqwest", + "reqwest 0.11.20", "serde", "serde_json", "thiserror", @@ -26131,7 +27266,7 @@ dependencies = [ "multiaddr 0.18.1", "rand", "regex", - "reqwest", + "reqwest 0.11.20", "serde", "serde_json", "sha2 0.10.8", @@ -26174,7 +27309,7 @@ dependencies = [ "kube", "nix 0.27.1", "regex", - "reqwest", + "reqwest 0.11.20", "serde", "serde_json", "serde_yaml", @@ -26219,7 +27354,7 @@ dependencies = [ "nix 0.27.1", "rand", "regex", - "reqwest", + "reqwest 0.11.20", "thiserror", "tokio", "tracing", diff --git a/Cargo.toml b/Cargo.toml index a3c89a74bd6..463cf82a1de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -236,6 +236,7 @@ members = [ "polkadot/xcm/xcm-simulator", "polkadot/xcm/xcm-simulator/example", "polkadot/xcm/xcm-simulator/fuzzer", + "polkadot/zombienet-sdk-tests", "substrate/bin/node/bench", "substrate/bin/node/cli", "substrate/bin/node/inspect", diff --git a/polkadot/zombienet-sdk-tests/Cargo.toml b/polkadot/zombienet-sdk-tests/Cargo.toml new file mode 100644 index 00000000000..3374ad572b9 --- /dev/null +++ b/polkadot/zombienet-sdk-tests/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "polkadot-zombienet-sdk-tests" +version = "0.1.0" +description = "Zomebienet-sdk tests." +authors.workspace = true +edition.workspace = true +license.workspace = true +publish = false + +[dependencies] +env_logger = "0.11.2" +log = "0.4" +subxt = { version = "0.37", features = ["substrate-compat"] } +subxt-signer = { version = "0.37" } +tokio = { version = "1.36.0", features = ["rt-multi-thread"] } +anyhow = "1.0.81" +zombienet-sdk = "0.2.6" +serde = "1.0.197" +serde_json = "1.0.114" +parity-scale-codec = { version = "3.6.9", features = ["derive"] } + +[features] +zombie-metadata = [] + +[build-dependencies] +substrate-build-script-utils = { workspace = true, default-features = true } +subwasmlib = { git = "https://github.com/chevdor/subwasm", rev = "v0.21.3" } diff --git a/polkadot/zombienet-sdk-tests/build.rs b/polkadot/zombienet-sdk-tests/build.rs new file mode 100644 index 00000000000..240d86386af --- /dev/null +++ b/polkadot/zombienet-sdk-tests/build.rs @@ -0,0 +1,151 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +use std::{ + env, fs, path, + path::{Path, PathBuf}, + process::Command, +}; + +use subwasmlib::{source::Source, OutputFormat, Subwasm}; + +macro_rules! debug_output { + ($($tokens: tt)*) => { + if env::var("ZOMBIE_METADATA_BUILD_DEBUG").is_ok() { + println!("cargo:warning={}", format!($($tokens)*)) + } + } +} + +fn replace_dashes(k: &str) -> String { + k.replace('-', "_") +} + +fn make_env_key(k: &str) -> String { + replace_dashes(&k.to_ascii_uppercase()) +} + +fn find_wasm(chain: &str) -> Option { + const PROFILES: [&str; 2] = ["release", "testnet"]; + let manifest_path = env::var("CARGO_WORKSPACE_ROOT_DIR").unwrap(); + let manifest_path = manifest_path.strip_suffix('/').unwrap(); + debug_output!("manifest_path is : {}", manifest_path); + let package = format!("{chain}-runtime"); + let profile = PROFILES.into_iter().find(|p| { + let full_path = format!( + "{}/target/{}/wbuild/{}/{}.wasm", + manifest_path, + p, + &package, + replace_dashes(&package) + ); + debug_output!("checking wasm at : {}", full_path); + matches!(path::PathBuf::from(&full_path).try_exists(), Ok(true)) + }); + + debug_output!("profile is : {:?}", profile); + profile.map(|profile| { + PathBuf::from(&format!( + "{}/target/{}/wbuild/{}/{}.wasm", + manifest_path, + profile, + &package, + replace_dashes(&package) + )) + }) +} + +// based on https://gist.github.com/s0me0ne-unkn0wn/bbd83fe32ce10327086adbf13e750eec +fn build_wasm(chain: &str) -> PathBuf { + let package = format!("{chain}-runtime"); + + let cargo = env::var("CARGO").unwrap(); + let target = env::var("TARGET").unwrap(); + let out_dir = env::var("OUT_DIR").unwrap(); + let target_dir = format!("{}/runtimes", out_dir); + let args = vec![ + "build", + "-p", + &package, + "--profile", + "release", + "--target", + &target, + "--target-dir", + &target_dir, + ]; + debug_output!("building metadata with args: {}", args.join(" ")); + Command::new(cargo) + .env_remove("SKIP_WASM_BUILD") // force build to get the metadata + .args(&args) + .status() + .unwrap(); + + let wasm_path = &format!( + "{target_dir}/{target}/release/wbuild/{}/{}.wasm", + &package, + replace_dashes(&package) + ); + PathBuf::from(wasm_path) +} + +fn generate_metadata_file(wasm_path: &Path, output_path: &Path) { + let source = Source::from_options(Some(wasm_path.to_path_buf()), None, None, None).unwrap(); + let subwasm = Subwasm::new(&source.try_into().unwrap()).unwrap(); + let mut output_file = std::fs::File::create(output_path).unwrap(); + subwasm.write_metadata(OutputFormat::Scale, None, &mut output_file).unwrap(); +} + +fn fetch_metadata_file(chain: &str, output_path: &Path) { + // First check if we have an explicit path to use + let env_key = format!("{}_METADATA_FILE", make_env_key(chain)); + + if let Ok(path_to_use) = env::var(env_key) { + debug_output!("metadata file to use (from env): {}\n", path_to_use); + let metadata_file = PathBuf::from(&path_to_use); + fs::copy(metadata_file, output_path).unwrap(); + } else if let Some(exisiting_wasm) = find_wasm(chain) { + debug_output!("exisiting wasm: {:?}", exisiting_wasm); + // generate metadata + generate_metadata_file(&exisiting_wasm, output_path); + } else { + // build runtime + let wasm_path = build_wasm(chain); + debug_output!("created wasm: {:?}", wasm_path); + // genetate metadata + generate_metadata_file(&wasm_path, output_path); + } +} + +fn main() { + if env::var("CARGO_FEATURE_ZOMBIE_METADATA").is_err() { + debug_output!("zombie-metadata feature not enabled, not need to check metadata files."); + return; + } + + // Ensure we have the needed metadata files in place to run zombienet tests + let manifest_path = env::var("CARGO_MANIFEST_DIR").unwrap(); + const METADATA_DIR: &str = "metadata-files"; + const CHAINS: [&str; 2] = ["rococo", "coretime-rococo"]; + + let metadata_path = format!("{manifest_path}/{METADATA_DIR}"); + + for chain in CHAINS { + let full_path = format!("{metadata_path}/{chain}-local.scale"); + let output_path = path::PathBuf::from(&full_path); + + match output_path.try_exists() { + Ok(true) => { + debug_output!("got: {}", full_path); + }, + _ => { + debug_output!("needs: {}", full_path); + fetch_metadata_file(chain, &output_path); + }, + }; + } + + substrate_build_script_utils::generate_cargo_keys(); + substrate_build_script_utils::rerun_if_git_head_changed(); + println!("cargo:rerun-if-changed={}", metadata_path); +} diff --git a/polkadot/zombienet-sdk-tests/metadata-files/.gitkeep b/polkadot/zombienet-sdk-tests/metadata-files/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/polkadot/zombienet-sdk-tests/src/lib.rs b/polkadot/zombienet-sdk-tests/src/lib.rs new file mode 100644 index 00000000000..fe0aa995d77 --- /dev/null +++ b/polkadot/zombienet-sdk-tests/src/lib.rs @@ -0,0 +1,2 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 diff --git a/polkadot/zombienet-sdk-tests/tests/lib.rs b/polkadot/zombienet-sdk-tests/tests/lib.rs new file mode 100644 index 00000000000..74cdc076560 --- /dev/null +++ b/polkadot/zombienet-sdk-tests/tests/lib.rs @@ -0,0 +1,4 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +mod smoke; diff --git a/polkadot/zombienet-sdk-tests/tests/smoke/coretime_revenue.rs b/polkadot/zombienet-sdk-tests/tests/smoke/coretime_revenue.rs new file mode 100644 index 00000000000..7880dc782d0 --- /dev/null +++ b/polkadot/zombienet-sdk-tests/tests/smoke/coretime_revenue.rs @@ -0,0 +1,505 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +//! Binaries for this test should be built with `fast-runtime` feature enabled: +//! `cargo build -r -F fast-runtime -p polkadot-parachain-bin && \` +//! `cargo build -r -F fast-runtime --bin polkadot --bin polkadot-execute-worker --bin +//! polkadot-prepare-worker` +//! +//! Running with normal runtimes is possible but would take ages. Running fast relay runtime with +//! normal parachain runtime WILL mess things up. + +use anyhow::anyhow; +#[subxt::subxt(runtime_metadata_path = "metadata-files/rococo-local.scale")] +pub mod rococo {} + +#[subxt::subxt(runtime_metadata_path = "metadata-files/coretime-rococo-local.scale")] +mod coretime_rococo {} + +use rococo::runtime_types::{ + staging_xcm::v4::{ + asset::{Asset, AssetId, Assets, Fungibility}, + junction::Junction, + junctions::Junctions, + location::Location, + }, + xcm::{VersionedAssets, VersionedLocation}, +}; +use serde_json::json; +use std::{fmt::Display, sync::Arc}; +use subxt::{events::StaticEvent, utils::AccountId32, OnlineClient, PolkadotConfig}; +use subxt_signer::sr25519::dev; +use tokio::sync::RwLock; +use zombienet_sdk::NetworkConfigBuilder; + +use coretime_rococo::{ + self as coretime_api, + broker::events as broker_events, + runtime_types::{ + pallet_broker::types::{ConfigRecord as BrokerConfigRecord, Finality as BrokerFinality}, + sp_arithmetic::per_things::Perbill, + }, +}; + +use rococo::{self as rococo_api, runtime_types::polkadot_parachain_primitives::primitives}; + +type CoretimeRuntimeCall = coretime_api::runtime_types::coretime_rococo_runtime::RuntimeCall; +type CoretimeUtilityCall = coretime_api::runtime_types::pallet_utility::pallet::Call; +type CoretimeBrokerCall = coretime_api::runtime_types::pallet_broker::pallet::Call; + +// On-demand coretime base fee (set at the genesis) +const ON_DEMAND_BASE_FEE: u128 = 50_000_000; + +async fn get_total_issuance( + relay: OnlineClient, + coretime: OnlineClient, +) -> (u128, u128) { + ( + relay + .storage() + .at_latest() + .await + .unwrap() + .fetch(&rococo_api::storage().balances().total_issuance()) + .await + .unwrap() + .unwrap(), + coretime + .storage() + .at_latest() + .await + .unwrap() + .fetch(&coretime_api::storage().balances().total_issuance()) + .await + .unwrap() + .unwrap(), + ) +} + +async fn assert_total_issuance( + relay: OnlineClient, + coretime: OnlineClient, + ti: (u128, u128), +) { + let actual_ti = get_total_issuance(relay, coretime).await; + log::debug!("Asserting total issuance: actual: {actual_ti:?}, expected: {ti:?}"); + assert_eq!(ti, actual_ti); +} + +type ParaEvents = Arc)>>>; + +macro_rules! trace_event { + ($event:ident : $mod:ident => $($ev:ident),*) => { + match $event.variant_name() { + $( + stringify!($ev) => + log::trace!("{:#?}", $event.as_event::<$mod::$ev>().unwrap().unwrap()), + )* + _ => () + } + }; +} + +async fn para_watcher(api: OnlineClient, events: ParaEvents) +where + ::Number: Display, +{ + let mut blocks_sub = api.blocks().subscribe_finalized().await.unwrap(); + + log::debug!("Starting parachain watcher"); + while let Some(block) = blocks_sub.next().await { + let block = block.unwrap(); + log::debug!("Finalized parachain block {}", block.number()); + + for event in block.events().await.unwrap().iter() { + let event = event.unwrap(); + log::debug!("Got event: {} :: {}", event.pallet_name(), event.variant_name()); + { + events.write().await.push((block.number().into(), event.clone())); + } + + if event.pallet_name() == "Broker" { + trace_event!(event: broker_events => + Purchased, SaleInitialized, HistoryInitialized, CoreAssigned, Pooled, + ClaimsReady, RevenueClaimBegun, RevenueClaimItem, RevenueClaimPaid + ); + } + } + } +} + +async fn wait_for_para_event bool + Copy>( + events: ParaEvents, + pallet: &'static str, + variant: &'static str, + predicate: P, +) -> E { + loop { + let mut events = events.write().await; + if let Some(entry) = events.iter().find(|&e| { + e.1.pallet_name() == pallet && + e.1.variant_name() == variant && + predicate(&e.1.as_event::().unwrap().unwrap()) + }) { + let entry = entry.clone(); + events.retain(|e| e.0 > entry.0); + return entry.1.as_event::().unwrap().unwrap(); + } + drop(events); + tokio::time::sleep(std::time::Duration::from_secs(6)).await; + } +} + +async fn ti_watcher(api: OnlineClient, prefix: &'static str) +where + ::Number: Display, +{ + let mut blocks_sub = api.blocks().subscribe_finalized().await.unwrap(); + + let mut issuance = 0i128; + + log::debug!("Starting parachain watcher"); + while let Some(block) = blocks_sub.next().await { + let block = block.unwrap(); + + let ti = api + .storage() + .at(block.reference()) + .fetch(&rococo_api::storage().balances().total_issuance()) + .await + .unwrap() + .unwrap() as i128; + + let diff = ti - issuance; + if diff != 0 { + log::info!("{} #{} issuance {} ({:+})", prefix, block.number(), ti, diff); + } + issuance = ti; + } +} + +#[tokio::test(flavor = "multi_thread")] +async fn coretime_revenue_test() -> Result<(), anyhow::Error> { + env_logger::init_from_env( + env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "info"), + ); + + let images = zombienet_sdk::environment::get_images_from_env(); + let config = NetworkConfigBuilder::new() + .with_relaychain(|r| { + r.with_chain("rococo-local") + .with_default_command("polkadot") + .with_default_image(images.polkadot.as_str()) + .with_genesis_overrides( + json!({ "configuration": { "config": { "scheduler_params": { "on_demand_base_fee": ON_DEMAND_BASE_FEE }}}}), + ) + .with_node(|node| node.with_name("alice")) + .with_node(|node| node.with_name("bob")) + .with_node(|node| node.with_name("charlie")) + }) + .with_parachain(|p| { + p.with_id(1005) + .with_default_command("polkadot-parachain") + .with_default_image(images.cumulus.as_str()) + .with_chain("coretime-rococo-local") + .with_collator(|n| n.with_name("coretime")) + }) + .build() + .map_err(|e| { + let errs = e.into_iter().map(|e| e.to_string()).collect::>().join(" "); + anyhow!("config errs: {errs}") + })?; + + let spawn_fn = zombienet_sdk::environment::get_spawn_fn(); + let network = spawn_fn(config).await?; + + let relay_node = network.get_node("alice")?; + let para_node = network.get_node("coretime")?; + + let relay_client: OnlineClient = relay_node.wait_client().await?; + let para_client: OnlineClient = para_node.wait_client().await?; + + // Get total issuance on both sides + let mut total_issuance = get_total_issuance(relay_client.clone(), para_client.clone()).await; + log::info!("Reference total issuance: {total_issuance:?}"); + + // Prepare everything + let alice = dev::alice(); + let alice_acc = AccountId32(alice.public_key().0); + + let bob = dev::bob(); + + let para_events: ParaEvents = Arc::new(RwLock::new(Vec::new())); + let p_api = para_node.wait_client().await?; + let p_events = para_events.clone(); + + let _subscriber = tokio::spawn(async move { + para_watcher(p_api, p_events).await; + }); + + let api: OnlineClient = para_node.wait_client().await?; + let _s1 = tokio::spawn(async move { + ti_watcher(api, "PARA").await; + }); + let api: OnlineClient = relay_node.wait_client().await?; + let _s2 = tokio::spawn(async move { + ti_watcher(api, "RELAY").await; + }); + + log::info!("Initiating teleport from RC's account of Alice to PC's one"); + + // Teleport some Alice's tokens to the Coretime chain. Although her account is pre-funded on + // the PC, that is still neccessary to bootstrap RC's `CheckedAccount`. + relay_client + .tx() + .sign_and_submit_default( + &rococo_api::tx().xcm_pallet().teleport_assets( + VersionedLocation::V4(Location { + parents: 0, + interior: Junctions::X1([Junction::Parachain(1005)]), + }), + VersionedLocation::V4(Location { + parents: 0, + interior: Junctions::X1([Junction::AccountId32 { + network: None, + id: alice.public_key().0, + }]), + }), + VersionedAssets::V4(Assets(vec![Asset { + id: AssetId(Location { parents: 0, interior: Junctions::Here }), + fun: Fungibility::Fungible(1_500_000_000), + }])), + 0, + ), + &alice, + ) + .await?; + + wait_for_para_event( + para_events.clone(), + "Balances", + "Minted", + |e: &coretime_api::balances::events::Minted| e.who == alice_acc, + ) + .await; + + // RC's total issuance doen't change, but PC's one increases after the teleport. + + total_issuance.1 += 1_500_000_000; + assert_total_issuance(relay_client.clone(), para_client.clone(), total_issuance).await; + + log::info!("Initializing broker and starting sales"); + + // Initialize broker and start sales + + para_client + .tx() + .sign_and_submit_default( + &coretime_api::tx().sudo().sudo(CoretimeRuntimeCall::Utility( + CoretimeUtilityCall::batch { + calls: vec![ + CoretimeRuntimeCall::Broker(CoretimeBrokerCall::configure { + config: BrokerConfigRecord { + advance_notice: 5, + interlude_length: 1, + leadin_length: 1, + region_length: 1, + ideal_bulk_proportion: Perbill(100), + limit_cores_offered: None, + renewal_bump: Perbill(10), + contribution_timeout: 5, + }, + }), + CoretimeRuntimeCall::Broker(CoretimeBrokerCall::set_lease { + task: 1005, + until: 1000, + }), + CoretimeRuntimeCall::Broker(CoretimeBrokerCall::start_sales { + end_price: 45_000_000, + extra_cores: 2, + }), + ], + }, + )), + &alice, + ) + .await?; + + log::info!("Waiting for a full-length sale to begin"); + + // Skip the first sale completeley as it may be a short one. Also, `request_code_count` requires + // two session boundaries to propagate. Given that the `fast-runtime` session is 10 blocks and + // the timeslice is 20 blocks, we should be just in time. + + let _: coretime_api::broker::events::SaleInitialized = + wait_for_para_event(para_events.clone(), "Broker", "SaleInitialized", |_| true).await; + log::info!("Skipped short sale"); + + let sale: coretime_api::broker::events::SaleInitialized = + wait_for_para_event(para_events.clone(), "Broker", "SaleInitialized", |_| true).await; + log::info!("{:?}", sale); + + // Alice buys a region + + log::info!("Alice is going to buy a region"); + + para_client + .tx() + .sign_and_submit_default(&coretime_api::tx().broker().purchase(1_000_000_000), &alice) + .await?; + + let purchase = wait_for_para_event( + para_events.clone(), + "Broker", + "Purchased", + |e: &broker_events::Purchased| e.who == alice_acc, + ) + .await; + + let region_begin = purchase.region_id.begin; + + // Somewhere below this point, the revenue from this sale will be teleported to the RC and burnt + // on both chains. Let's account that but not assert just yet. + + total_issuance.0 -= purchase.price; + total_issuance.1 -= purchase.price; + + // Alice pools the region + + log::info!("Alice is going to put the region into the pool"); + + para_client + .tx() + .sign_and_submit_default( + &coretime_api::tx().broker().pool( + purchase.region_id, + alice_acc.clone(), + BrokerFinality::Final, + ), + &alice, + ) + .await?; + + let pooled = wait_for_para_event( + para_events.clone(), + "Broker", + "Pooled", + |e: &broker_events::Pooled| e.region_id.begin == region_begin, + ) + .await; + + // Wait until the beginning of the timeslice where the region belongs to + + log::info!("Waiting for the region to begin"); + + let hist = wait_for_para_event( + para_events.clone(), + "Broker", + "HistoryInitialized", + |e: &broker_events::HistoryInitialized| e.when == pooled.region_id.begin, + ) + .await; + + // Alice's private contribution should be there + + assert!(hist.private_pool_size > 0); + + // Bob places an order to buy insta coretime as RC + + log::info!("Bob is going to buy an on-demand core"); + + let r = relay_client + .tx() + .sign_and_submit_then_watch_default( + &rococo_api::tx() + .on_demand_assignment_provider() + .place_order_allow_death(100_000_000, primitives::Id(100)), + &bob, + ) + .await? + .wait_for_finalized_success() + .await?; + + let order = r + .find_first::()? + .unwrap(); + + // As there's no spot traffic, Bob will only pay base fee + + assert_eq!(order.spot_price, ON_DEMAND_BASE_FEE); + + // Somewhere below this point, revenue is generated and is teleported to the PC (that happens + // once a timeslice so we're not ready to assert it yet, let's just account). That checks out + // tokens from the RC and mints them on the PC. + + total_issuance.1 += ON_DEMAND_BASE_FEE; + + // As soon as the PC receives the tokens, it divides them half by half into system and private + // contributions (we have 3 cores, one is leased to Coretime itself, one is pooled by the + // system, and one is pooled by Alice). + + // Now we're waiting for the moment when Alice may claim her revenue + + log::info!("Waiting for Alice's revenue to be ready to claim"); + + let claims_ready = wait_for_para_event( + para_events.clone(), + "Broker", + "ClaimsReady", + |e: &broker_events::ClaimsReady| e.when == pooled.region_id.begin, + ) + .await; + + // The revenue should be half of the spot price, which is equal to the base fee. + + assert_eq!(claims_ready.private_payout, ON_DEMAND_BASE_FEE / 2); + + // By this moment, we're sure that revenue was received by the PC and can assert the total + // issuance + + assert_total_issuance(relay_client.clone(), para_client.clone(), total_issuance).await; + + // Alice claims her revenue + + log::info!("Alice is going to claim her revenue"); + + para_client + .tx() + .sign_and_submit_default( + &coretime_api::tx().broker().claim_revenue(pooled.region_id, pooled.duration), + &alice, + ) + .await?; + + let claim_paid = wait_for_para_event( + para_events.clone(), + "Broker", + "RevenueClaimPaid", + |e: &broker_events::RevenueClaimPaid| e.who == alice_acc, + ) + .await; + + log::info!("Revenue claimed, waiting for 2 timeslices until the system revenue is burnt"); + + assert_eq!(claim_paid.amount, ON_DEMAND_BASE_FEE / 2); + + // As for the system revenue, it is teleported back to the RC and burnt there. Those burns are + // batched and are processed once a timeslice, after a new one starts. So we have to wait for + // two timeslice boundaries to pass to be sure the teleport has already happened somewhere in + // between. + + let _: coretime_api::broker::events::SaleInitialized = + wait_for_para_event(para_events.clone(), "Broker", "SaleInitialized", |_| true).await; + + total_issuance.0 -= ON_DEMAND_BASE_FEE / 2; + total_issuance.1 -= ON_DEMAND_BASE_FEE / 2; + + let _: coretime_api::broker::events::SaleInitialized = + wait_for_para_event(para_events.clone(), "Broker", "SaleInitialized", |_| true).await; + + assert_total_issuance(relay_client.clone(), para_client.clone(), total_issuance).await; + + log::info!("Test finished successfuly"); + + Ok(()) +} diff --git a/polkadot/zombienet-sdk-tests/tests/smoke/mod.rs b/polkadot/zombienet-sdk-tests/tests/smoke/mod.rs new file mode 100644 index 00000000000..a3fe1538267 --- /dev/null +++ b/polkadot/zombienet-sdk-tests/tests/smoke/mod.rs @@ -0,0 +1,5 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +#[cfg(feature = "zombie-metadata")] +mod coretime_revenue; -- GitLab From c0d5c4d8d672d9b1c15f5fec491c53988ef2314b Mon Sep 17 00:00:00 2001 From: Cyrill Leutwiler Date: Wed, 18 Sep 2024 11:56:12 +0200 Subject: [PATCH 072/125] [pallet-revive] write sandbox output according to the provided output buffer length (#5743) Instead of error out if the provided output buffer is smaller than what we want to write, we can just write what fits into the output buffer instead. We already write back the actual bytes written to the in-out pointer, so contracts can check it anyways. This in turn introduces the benefit of allowing contracts to implicitly request only a portion of the returned data from calls and incantations. Which is especially beneficial for YUL as the `call` family opcodes have a return data size argument and this change removes the need to work around it in contract code. --------- Signed-off-by: xermicus --- prdoc/pr_5743.prdoc | 22 ++++ .../contracts/call_diverging_out_len.rs | 110 ++++++++++++++++++ .../fixtures/contracts/create1_with_value.rs | 4 +- substrate/frame/revive/src/lib.rs | 2 - substrate/frame/revive/src/tests.rs | 18 +++ substrate/frame/revive/src/wasm/runtime.rs | 15 +-- 6 files changed, 158 insertions(+), 13 deletions(-) create mode 100644 prdoc/pr_5743.prdoc create mode 100644 substrate/frame/revive/fixtures/contracts/call_diverging_out_len.rs diff --git a/prdoc/pr_5743.prdoc b/prdoc/pr_5743.prdoc new file mode 100644 index 00000000000..0059cbaf790 --- /dev/null +++ b/prdoc/pr_5743.prdoc @@ -0,0 +1,22 @@ +title: "[pallet-revive] write sandbox output according to the provided output buffer length" + +doc: + - audience: Runtime Dev + description: | + Instead of error out if the provided output buffer is smaller than what we want to write, + we can just write what fits into the output buffer instead. + We already write back the actual bytes written to the in-out pointer, + so contracts can check it anyways. + + This in turn introduces the benefit of allowing contracts to implicitly request only a portion + of the returned data from calls and incantations. + Which is especially beneficial for YUL as the call family opcodes have a return data size + argument and this change removes the need to work around it in contract code. + +crates: + - name: pallet-revive + bump: major + - name: pallet-revive-fixtures + bump: patch + - name: pallet-revive-uapi + bump: patch diff --git a/substrate/frame/revive/fixtures/contracts/call_diverging_out_len.rs b/substrate/frame/revive/fixtures/contracts/call_diverging_out_len.rs new file mode 100644 index 00000000000..e13162f8fb4 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/call_diverging_out_len.rs @@ -0,0 +1,110 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This tests that the correct output data is written when the provided +//! output buffer length is smaller than what was actually returned during +//! calls and instantiations. +//! +//! To not need an additional callee fixture, we call ourself recursively +//! and also instantiate our own code hash (constructor and recursive calls +//! always return `BUF_SIZE` bytes of data). + +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api}; + +const BUF_SIZE: usize = 8; +static DATA: [u8; BUF_SIZE] = [1, 2, 3, 4, 5, 6, 7, 8]; + +/// Call `callee_address` with an output buf of size `N` +/// and expect the call output to match `expected_output`. +fn assert_call(callee_address: &[u8; 20], expected_output: [u8; BUF_SIZE]) { + let mut output_buf = [0u8; BUF_SIZE]; + let mut output_buf_capped = &mut &mut output_buf[..N]; + + api::call( + uapi::CallFlags::ALLOW_REENTRY, + callee_address, + 0u64, + 0u64, + None, + &[0u8; 32], + &[], + Some(output_buf_capped), + ) + .unwrap(); + + // The (capped) output buf should get properly resized + assert_eq!(output_buf_capped.len(), N); + assert_eq!(output_buf, expected_output); +} + +/// Instantiate this contract with an output buf of size `N` +/// and expect the instantiate output to match `expected_output`. +fn assert_instantiate(expected_output: [u8; BUF_SIZE]) { + let mut code_hash = [0; 32]; + api::own_code_hash(&mut code_hash); + + let mut output_buf = [0u8; BUF_SIZE]; + let mut output_buf_capped = &mut &mut output_buf[..N]; + + api::instantiate( + &code_hash, + 0u64, + 0u64, + None, + &[0; 32], + &[0; 32], + None, + Some(output_buf_capped), + None, + ) + .unwrap(); + + // The (capped) output buf should get properly resized + assert_eq!(output_buf_capped.len(), N); + assert_eq!(output_buf, expected_output); +} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() { + api::return_value(uapi::ReturnFlags::empty(), &DATA); +} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + let mut caller_address = [0u8; 20]; + api::caller(&mut caller_address); + + let mut callee_address = [0u8; 20]; + api::address(&mut callee_address); + + // we already recurse; return data + if caller_address == callee_address { + api::return_value(uapi::ReturnFlags::empty(), &DATA); + } + + assert_call::<0>(&callee_address, [0; 8]); + assert_call::<4>(&callee_address, [1, 2, 3, 4, 0, 0, 0, 0]); + + assert_instantiate::<0>([0; 8]); + assert_instantiate::<4>([1, 2, 3, 4, 0, 0, 0, 0]); +} diff --git a/substrate/frame/revive/fixtures/contracts/create1_with_value.rs b/substrate/frame/revive/fixtures/contracts/create1_with_value.rs index 644777aff99..c6adab82886 100644 --- a/substrate/frame/revive/fixtures/contracts/create1_with_value.rs +++ b/substrate/frame/revive/fixtures/contracts/create1_with_value.rs @@ -18,8 +18,8 @@ #![no_std] #![no_main] -use common::{input, u256_bytes}; -use uapi::{HostFn, HostFnImpl as api, ReturnErrorCode}; +use common::input; +use uapi::{HostFn, HostFnImpl as api}; #[no_mangle] #[polkavm_derive::polkavm_export] diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 1cc77a673b1..ccbc65873d0 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -467,8 +467,6 @@ pub mod pallet { InvalidCallFlags, /// The executed contract exhausted its gas limit. OutOfGas, - /// The output buffer supplied to a contract API call was too small. - OutputBufferTooSmall, /// Performing the requested transfer failed. Probably because there isn't enough /// free balance in the sender's account. TransferFailed, diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index c7185caf0ef..8a18c09e86f 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -4254,4 +4254,22 @@ mod run_tests { assert_eq!(usable_balance, value); }); } + + #[test] + fn call_diverging_out_len_works() { + let (code, _) = compile_module("call_diverging_out_len").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Create the contract: Constructor does nothing + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + // Call the contract: It will issue calls and deploys, asserting on + // correct output if the supplied output length was smaller than + // than what the callee returned. + assert_ok!(builder::call(addr).build()); + }); + } } diff --git a/substrate/frame/revive/src/wasm/runtime.rs b/substrate/frame/revive/src/wasm/runtime.rs index 80daac8f9db..34cce533c14 100644 --- a/substrate/frame/revive/src/wasm/runtime.rs +++ b/substrate/frame/revive/src/wasm/runtime.rs @@ -651,11 +651,12 @@ impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { /// Write the given buffer and its length to the designated locations in sandbox memory and /// charge gas according to the token returned by `create_token`. - // + /// /// `out_ptr` is the location in sandbox memory where `buf` should be written to. /// `out_len_ptr` is an in-out location in sandbox memory. It is read to determine the - /// length of the buffer located at `out_ptr`. If that buffer is large enough the actual - /// `buf.len()` is written to this location. + /// length of the buffer located at `out_ptr`. If that buffer is smaller than the actual + /// `buf.len()`, only what fits into that buffer is written to `out_ptr`. + /// The actual amount of bytes copied to `out_ptr` is written to `out_len_ptr`. /// /// If `out_ptr` is set to the sentinel value of `SENTINEL` and `allow_skip` is true the /// operation is skipped and `Ok` is returned. This is supposed to help callers to make copying @@ -681,18 +682,14 @@ impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { return Ok(()) } - let buf_len = buf.len() as u32; let len = memory.read_u32(out_len_ptr)?; - - if len < buf_len { - return Err(Error::::OutputBufferTooSmall.into()) - } + let buf_len = len.min(buf.len() as u32); if let Some(costs) = create_token(buf_len) { self.charge_gas(costs)?; } - memory.write(out_ptr, buf)?; + memory.write(out_ptr, &buf[..buf_len as usize])?; memory.write(out_len_ptr, &buf_len.encode()) } -- GitLab From 5524c1166c6b6174b42b57785fe7fbf5982b09f3 Mon Sep 17 00:00:00 2001 From: Alexander Samusev <41779041+alvicsam@users.noreply.github.com> Date: Wed, 18 Sep 2024 13:32:28 +0200 Subject: [PATCH 073/125] [ci] Remove cargo check osx from gitlab (#5752) PR removes cargo check job that runs on osx runner from gitlab and moves `cargo-check-each-crate-macos` job to be required cc https://github.com/paritytech/ci_cd/issues/1037 --- .github/workflows/tests-misc.yml | 4 ++-- .gitlab-ci.yml | 27 +++++++++++---------------- .gitlab/pipeline/test.yml | 22 ---------------------- 3 files changed, 13 insertions(+), 40 deletions(-) diff --git a/.github/workflows/tests-misc.yml b/.github/workflows/tests-misc.yml index 0f2b617b847..dc6128a3f6e 100644 --- a/.github/workflows/tests-misc.yml +++ b/.github/workflows/tests-misc.yml @@ -14,7 +14,6 @@ concurrency: # Jobs in this workflow depend on each other, only for limiting peak amount of spawned workers jobs: - preflight: uses: ./.github/workflows/reusable-preflight.yml @@ -321,7 +320,7 @@ jobs: cargo-check-all-crate-macos: timeout-minutes: 30 - needs: [ preflight ] + needs: [preflight] runs-on: parity-macos env: SKIP_WASM_BUILD: 1 @@ -366,6 +365,7 @@ jobs: - check-tracing - cargo-check-each-crate - test-deterministic-wasm + - cargo-check-all-crate-macos # - cargo-hfuzz remove from required for now, as it's flaky if: always() && !cancelled() steps: diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 43123cdbfc4..5c6b3928b46 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -21,7 +21,7 @@ workflow: - if: $CI_COMMIT_BRANCH variables: - CI_IMAGE: !reference [ .ci-unified, variables, CI_IMAGE ] + CI_IMAGE: !reference [.ci-unified, variables, CI_IMAGE] # BUILDAH_IMAGE is defined in group variables BUILDAH_COMMAND: "buildah --storage-driver overlay2" RELENG_SCRIPTS_BRANCH: "master" @@ -39,7 +39,7 @@ default: - runner_system_failure - unknown_failure - api_failure - cache: { } + cache: {} interruptible: true .collect-artifacts: @@ -68,8 +68,8 @@ default: .common-before-script: before_script: - - !reference [ .job-switcher, before_script ] - - !reference [ .pipeline-stopper-vars, script ] + - !reference [.job-switcher, before_script] + - !reference [.pipeline-stopper-vars, script] .job-switcher: before_script: @@ -78,8 +78,8 @@ default: .kubernetes-env: image: "${CI_IMAGE}" before_script: - - !reference [ .common-before-script, before_script ] - - !reference [ .prepare-env, before_script ] + - !reference [.common-before-script, before_script] + - !reference [.prepare-env, before_script] tags: - kubernetes-parity-build @@ -107,12 +107,12 @@ default: .docker-env: image: "${CI_IMAGE}" variables: - FL_FORKLIFT_VERSION: !reference [ .forklift, variables, FL_FORKLIFT_VERSION ] + FL_FORKLIFT_VERSION: !reference [.forklift, variables, FL_FORKLIFT_VERSION] before_script: - - !reference [ .common-before-script, before_script ] - - !reference [ .prepare-env, before_script ] - - !reference [ .rust-info-script, script ] - - !reference [ .forklift-cache, before_script ] + - !reference [.common-before-script, before_script] + - !reference [.prepare-env, before_script] + - !reference [.rust-info-script, script] + - !reference [.forklift-cache, before_script] tags: - linux-docker @@ -288,8 +288,3 @@ cancel-pipeline-build-short-benchmark: extends: .cancel-pipeline-template needs: - job: build-short-benchmark - -cancel-pipeline-cargo-check-each-crate-macos: - extends: .cancel-pipeline-template - needs: - - job: cargo-check-each-crate-macos \ No newline at end of file diff --git a/.gitlab/pipeline/test.yml b/.gitlab/pipeline/test.yml index 0879870ae13..00a0aa2c977 100644 --- a/.gitlab/pipeline/test.yml +++ b/.gitlab/pipeline/test.yml @@ -153,25 +153,3 @@ quick-benchmarks-omni: script: - time cargo build --locked --quiet --release -p asset-hub-westend-runtime --features runtime-benchmarks - time cargo run --locked --release -p frame-omni-bencher --quiet -- v1 benchmark pallet --runtime target/release/wbuild/asset-hub-westend-runtime/asset_hub_westend_runtime.compact.compressed.wasm --all --steps 2 --repeat 1 --quiet - -cargo-check-each-crate-macos: - stage: test - extends: - - .docker-env - - .common-refs - - .run-immediately - # - .collect-artifacts - before_script: - # skip timestamp script, the osx bash doesn't support printf %()T - - !reference [.job-switcher, before_script] - - !reference [.rust-info-script, script] - - !reference [.pipeline-stopper-vars, script] - variables: - SKIP_WASM_BUILD: 1 - script: - # TODO: use parallel jobs, as per cargo-check-each-crate, once more Mac runners are available - # - time ./scripts/ci/gitlab/check-each-crate.py 1 1 - - time cargo check --workspace --locked - timeout: 2h - tags: - - osx -- GitLab From 70633959eb5ba0c61b6da57b575e0af758e3da1f Mon Sep 17 00:00:00 2001 From: Kazunobu Ndong <33208377+ndkazu@users.noreply.github.com> Date: Wed, 18 Sep 2024 20:38:38 +0900 Subject: [PATCH 074/125] Remove libp2p dependency from sc-network-sync (#4974) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Issue https://github.com/paritytech/polkadot-sdk/issues/4858 ## Description This PR removes `libp2p::request_response::OutboundFailure` from `substrate/client/network/sync/src/engine.rs`. This way, the dependency with the library `libp2p` is removed from `sc-network-sync`. --------- Co-authored-by: Bastian Köcher Co-authored-by: command-bot <> Co-authored-by: Dmitry Markin Co-authored-by: Alexandru Vasile <60601340+lexnv@users.noreply.github.com> --- Cargo.lock | 1 - prdoc/pr_4974.prdoc | 15 ++++ .../client/network/src/request_responses.rs | 74 +++++++++++++++++-- substrate/client/network/sync/Cargo.toml | 1 - substrate/client/network/sync/src/engine.rs | 3 +- 5 files changed, 85 insertions(+), 9 deletions(-) create mode 100644 prdoc/pr_4974.prdoc diff --git a/Cargo.lock b/Cargo.lock index 3985f9f994b..50e42667b94 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19275,7 +19275,6 @@ dependencies = [ "fork-tree", "futures", "futures-timer", - "libp2p", "log", "mockall 0.11.4", "parity-scale-codec", diff --git a/prdoc/pr_4974.prdoc b/prdoc/pr_4974.prdoc new file mode 100644 index 00000000000..f764ea3f46f --- /dev/null +++ b/prdoc/pr_4974.prdoc @@ -0,0 +1,15 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "Remove libp2p dependency from sc-network-sync" + +doc: + - audience: Node Dev + description: | + This PR removes `libp2p::request_response::OutboundFailure` from `substrate/client/network/sync/src/engine.rs`. + +crates: + - name: sc-network + bump: patch + - name: sc-network-sync + bump: patch diff --git a/substrate/client/network/src/request_responses.rs b/substrate/client/network/src/request_responses.rs index 3671d76ea63..6c2631924df 100644 --- a/substrate/client/network/src/request_responses.rs +++ b/substrate/client/network/src/request_responses.rs @@ -64,7 +64,69 @@ use std::{ time::{Duration, Instant}, }; -pub use libp2p::request_response::{Config, InboundFailure, OutboundFailure, RequestId}; +pub use libp2p::request_response::{Config, RequestId}; + +/// Possible failures occurring in the context of sending an outbound request and receiving the +/// response. +#[derive(Debug, thiserror::Error)] +pub enum OutboundFailure { + /// The request could not be sent because a dialing attempt failed. + #[error("Failed to dial the requested peer")] + DialFailure, + /// The request timed out before a response was received. + #[error("Timeout while waiting for a response")] + Timeout, + /// The connection closed before a response was received. + #[error("Connection was closed before a response was received")] + ConnectionClosed, + /// The remote supports none of the requested protocols. + #[error("The remote supports none of the requested protocols")] + UnsupportedProtocols, +} + +impl From for OutboundFailure { + fn from(out: request_response::OutboundFailure) -> Self { + match out { + request_response::OutboundFailure::DialFailure => OutboundFailure::DialFailure, + request_response::OutboundFailure::Timeout => OutboundFailure::Timeout, + request_response::OutboundFailure::ConnectionClosed => + OutboundFailure::ConnectionClosed, + request_response::OutboundFailure::UnsupportedProtocols => + OutboundFailure::UnsupportedProtocols, + } + } +} + +/// Possible failures occurring in the context of receiving an inbound request and sending a +/// response. +#[derive(Debug, thiserror::Error)] +pub enum InboundFailure { + /// The inbound request timed out, either while reading the incoming request or before a + /// response is sent + #[error("Timeout while receiving request or sending response")] + Timeout, + /// The connection closed before a response could be send. + #[error("Connection was closed before a response could be sent")] + ConnectionClosed, + /// The local peer supports none of the protocols requested by the remote. + #[error("The local peer supports none of the protocols requested by the remote")] + UnsupportedProtocols, + /// The local peer failed to respond to an inbound request + #[error("The response channel was dropped without sending a response to the remote")] + ResponseOmission, +} + +impl From for InboundFailure { + fn from(out: request_response::InboundFailure) -> Self { + match out { + request_response::InboundFailure::ResponseOmission => InboundFailure::ResponseOmission, + request_response::InboundFailure::Timeout => InboundFailure::Timeout, + request_response::InboundFailure::ConnectionClosed => InboundFailure::ConnectionClosed, + request_response::InboundFailure::UnsupportedProtocols => + InboundFailure::UnsupportedProtocols, + } + } +} /// Error in a request. #[derive(Debug, thiserror::Error)] @@ -808,7 +870,9 @@ impl NetworkBehaviour for RequestResponsesBehaviour { }) => { // Try using the fallback request if the protocol was not // supported. - if let OutboundFailure::UnsupportedProtocols = error { + if let request_response::OutboundFailure::UnsupportedProtocols = + error + { if let Some((fallback_request, fallback_protocol)) = fallback_request { @@ -829,7 +893,7 @@ impl NetworkBehaviour for RequestResponsesBehaviour { } if response_tx - .send(Err(RequestFailure::Network(error.clone()))) + .send(Err(RequestFailure::Network(error.clone().into()))) .is_err() { log::debug!( @@ -856,7 +920,7 @@ impl NetworkBehaviour for RequestResponsesBehaviour { peer, protocol: protocol.clone(), duration: started.elapsed(), - result: Err(RequestFailure::Network(error)), + result: Err(RequestFailure::Network(error.into())), }; return Poll::Ready(ToSwarm::GenerateEvent(out)) @@ -873,7 +937,7 @@ impl NetworkBehaviour for RequestResponsesBehaviour { let out = Event::InboundRequest { peer, protocol: protocol.clone(), - result: Err(ResponseFailure::Network(error)), + result: Err(ResponseFailure::Network(error.into())), }; return Poll::Ready(ToSwarm::GenerateEvent(out)) }, diff --git a/substrate/client/network/sync/Cargo.toml b/substrate/client/network/sync/Cargo.toml index b29a9ccaaf1..378b7c12e9b 100644 --- a/substrate/client/network/sync/Cargo.toml +++ b/substrate/client/network/sync/Cargo.toml @@ -25,7 +25,6 @@ async-trait = { workspace = true } codec = { features = ["derive"], workspace = true, default-features = true } futures = { workspace = true } futures-timer = { workspace = true } -libp2p = { workspace = true } log = { workspace = true, default-features = true } mockall = { workspace = true } prost = { workspace = true } diff --git a/substrate/client/network/sync/src/engine.rs b/substrate/client/network/sync/src/engine.rs index aafbd950202..96c1750b311 100644 --- a/substrate/client/network/sync/src/engine.rs +++ b/substrate/client/network/sync/src/engine.rs @@ -42,7 +42,6 @@ use crate::{ use codec::{Decode, DecodeAll, Encode}; use futures::{channel::oneshot, FutureExt, StreamExt}; -use libp2p::request_response::OutboundFailure; use log::{debug, error, trace, warn}; use prometheus_endpoint::{ register, Counter, Gauge, MetricSource, Opts, PrometheusError, Registry, SourcedGauge, U64, @@ -56,7 +55,7 @@ use sc_consensus::{import_queue::ImportQueueService, IncomingBlock}; use sc_network::{ config::{FullNetworkConfiguration, NotificationHandshake, ProtocolId, SetConfig}, peer_store::PeerStoreProvider, - request_responses::{IfDisconnected, RequestFailure}, + request_responses::{IfDisconnected, OutboundFailure, RequestFailure}, service::{ traits::{Direction, NotificationConfig, NotificationEvent, ValidationResult}, NotificationMetrics, -- GitLab From 08d171e36801bae4eec28d1e1efe7bce07639bb2 Mon Sep 17 00:00:00 2001 From: Javier Viola <363911+pepoviola@users.noreply.github.com> Date: Wed, 18 Sep 2024 09:39:54 -0300 Subject: [PATCH 075/125] Add timeout for script/after_script and debug (#5748) Fix tests: - minimal_template_block_production_test - parachain_template_block_production_test --- .gitlab/pipeline/zombienet/parachain-template.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.gitlab/pipeline/zombienet/parachain-template.yml b/.gitlab/pipeline/zombienet/parachain-template.yml index 815a46c60d7..6ed63182ec5 100644 --- a/.gitlab/pipeline/zombienet/parachain-template.yml +++ b/.gitlab/pipeline/zombienet/parachain-template.yml @@ -17,6 +17,8 @@ RUST_LOG: "info,zombienet_orchestrator=debug" FF_DISABLE_UMASK_FOR_DOCKER_EXECUTOR: 1 RUN_IN_CONTAINER: "1" + RUNNER_SCRIPT_TIMEOUT: 15m + RUNNER_AFTER_SCRIPT_TIMEOUT: 5m artifacts: name: "${CI_JOB_NAME}_${CI_COMMIT_REF_NAME}" when: always @@ -25,9 +27,9 @@ - ./zombienet-logs after_script: - mkdir -p ./zombienet-logs - - cp /tmp/zombie*/logs/* ./zombienet-logs/ + - cp /tmp/zombie*/*/*.log ./zombienet-logs/ retry: 2 - timeout: 15m + timeout: 20m tags: - linux-docker -- GitLab From 310ef5ce1086affdc522c4d1736211de2a7dd99e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Wed, 18 Sep 2024 16:48:30 +0200 Subject: [PATCH 076/125] revive: Limit the amount of static memory a contract can use (#5726) This will make sure that when uploading new code that the declared static memory fits within a defined limit. We apply different limits to code and data. Reason is that code will consume much more memory per byte once decoded during lazy execution. This PR: 1) Remove the MaxCodeLen from the `Config` to we maintain tight control over it. 2) Defines a single `STATIC_MEMORY_BYTES` knob that limits the maximum decoded size. 3) Enforces them only on upload but not on execution so we can raise them later. 4) Adapt the worst case calculation in `integrity_check`. 5) Bumps the max stack depth from 5 to 10 as this will still fit within our memory envelope. 6) The memory limit per contract is now a cool 1MB that can be spent on data or code. 7) Bump PolkaVM for good measure 8) The blob is limited to 256kb which is just a sanity check to not even try parsing very big inputs. --------- Co-authored-by: Cyrill Leutwiler --- Cargo.lock | 62 +++++------ prdoc/pr_5726.prdoc | 14 +++ substrate/bin/node/runtime/src/lib.rs | 1 - substrate/frame/revive/Cargo.toml | 2 +- substrate/frame/revive/fixtures/Cargo.toml | 2 +- .../frame/revive/fixtures/build/Cargo.toml | 2 +- .../frame/revive/fixtures/contracts/oom_ro.rs | 44 ++++++++ .../fixtures/contracts/oom_rw_included.rs | 44 ++++++++ .../fixtures/contracts/oom_rw_trailing.rs | 44 ++++++++ .../frame/revive/src/benchmarking/mod.rs | 30 +++--- substrate/frame/revive/src/lib.rs | 100 ++++++----------- substrate/frame/revive/src/limits.rs | 101 ++++++++++++++++-- substrate/frame/revive/src/tests.rs | 38 +++++++ substrate/frame/revive/src/wasm/mod.rs | 20 ++-- substrate/frame/revive/uapi/Cargo.toml | 2 +- 15 files changed, 371 insertions(+), 135 deletions(-) create mode 100644 prdoc/pr_5726.prdoc create mode 100644 substrate/frame/revive/fixtures/contracts/oom_ro.rs create mode 100644 substrate/frame/revive/fixtures/contracts/oom_rw_included.rs create mode 100644 substrate/frame/revive/fixtures/contracts/oom_rw_trailing.rs diff --git a/Cargo.lock b/Cargo.lock index 50e42667b94..dd75a9ec43f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12322,7 +12322,7 @@ dependencies = [ "pallet-utility", "parity-scale-codec", "paste", - "polkavm 0.10.0", + "polkavm 0.11.0", "pretty_assertions", "rlp", "scale-info", @@ -12347,7 +12347,7 @@ dependencies = [ "frame-system", "log", "parity-wasm", - "polkavm-linker 0.10.0", + "polkavm-linker 0.11.0", "sp-core 28.0.0", "sp-io 30.0.0", "sp-runtime 31.0.1", @@ -12407,7 +12407,7 @@ dependencies = [ "bitflags 1.3.2", "parity-scale-codec", "paste", - "polkavm-derive 0.10.0", + "polkavm-derive 0.11.0", "scale-info", ] @@ -16149,15 +16149,15 @@ dependencies = [ [[package]] name = "polkavm" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7ec0c5935f2eff23cfc4653002f4f8d12b37f87a720e0631282d188c32089d6" +checksum = "1195fbc12f11645143a4f3974bf909d25c7f7efddcc6f4e57688d9a518c90bae" dependencies = [ "libc", "log", - "polkavm-assembler 0.10.0", - "polkavm-common 0.10.0", - "polkavm-linux-raw 0.10.0", + "polkavm-assembler 0.11.0", + "polkavm-common 0.11.0", + "polkavm-linux-raw 0.11.0", ] [[package]] @@ -16171,9 +16171,9 @@ dependencies = [ [[package]] name = "polkavm-assembler" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8e4fd5a43100bf1afe9727b8130d01f966f5cfc9144d5604b21e795c2bcd80e" +checksum = "f0b0399659fe7a5370c3e3464188888d29069cfa46d99631d19834a379c15826" dependencies = [ "log", ] @@ -16195,12 +16195,12 @@ dependencies = [ [[package]] name = "polkavm-common" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0097b48bc0bedf9f3f537ce8f37e8f1202d8d83f9b621bdb21ff2c59b9097c50" +checksum = "254b19b64ff9b57c06b32c0affed961cb9a32429b8d3e5cf2633cad7fbb3e270" dependencies = [ "log", - "polkavm-assembler 0.10.0", + "polkavm-assembler 0.11.0", ] [[package]] @@ -16223,11 +16223,11 @@ dependencies = [ [[package]] name = "polkavm-derive" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dcc701385c08c31bdb0569f0c51a290c580d892fa77f1dd88a7352a62679ecf" +checksum = "f565f0106fbb3598d64b8528d5cb966b6a87a8dd93fbcfe09fb6388ff2865711" dependencies = [ - "polkavm-derive-impl-macro 0.10.0", + "polkavm-derive-impl-macro 0.11.0", ] [[package]] @@ -16256,11 +16256,11 @@ dependencies = [ [[package]] name = "polkavm-derive-impl" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7855353a5a783dd5d09e3b915474bddf66575f5a3cf45dec8d1c5e051ba320dc" +checksum = "314445fb5688b4769354087d92be2ac94c487e63ffe74a6fb7bb312e57f20827" dependencies = [ - "polkavm-common 0.10.0", + "polkavm-common 0.11.0", "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.65", @@ -16288,11 +16288,11 @@ dependencies = [ [[package]] name = "polkavm-derive-impl-macro" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9324fe036de37c17829af233b46ef6b5562d4a0c09bb7fdb9f8378856dee30cf" +checksum = "1bf952e05bc5ce7d81293bae18cb44c271c78615b201d75e983cdcc40d5c6ef1" dependencies = [ - "polkavm-derive-impl 0.10.0", + "polkavm-derive-impl 0.11.0", "syn 2.0.65", ] @@ -16313,15 +16313,15 @@ dependencies = [ [[package]] name = "polkavm-linker" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d704edfe7bdcc876784f19436d53d515b65eb07bc9a0fae77085d552c2dbbb5" +checksum = "535a2095a186ccde2cd2fa721d6370c495586d47714817565b2f6621d31164b3" dependencies = [ "gimli 0.28.0", "hashbrown 0.14.5", "log", "object 0.36.1", - "polkavm-common 0.10.0", + "polkavm-common 0.11.0", "regalloc2 0.9.3", "rustc-demangle", ] @@ -16334,9 +16334,9 @@ checksum = "26e85d3456948e650dff0cfc85603915847faf893ed1e66b020bb82ef4557120" [[package]] name = "polkavm-linux-raw" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26e45fa59c7e1bb12ef5289080601e9ec9b31435f6e32800a5c90c132453d126" +checksum = "3011697430dfcfe800d1d7c540ef69e3bdd66e9037cc38f01fee1c2e0908011e" [[package]] name = "polling" @@ -16766,7 +16766,7 @@ checksum = "80b776a1b2dc779f5ee0641f8ade0125bc1298dd41a9a0c16d8bd57b42d222b1" dependencies = [ "bytes", "heck 0.5.0", - "itertools 0.11.0", + "itertools 0.12.1", "log", "multimap", "once_cell", @@ -16787,7 +16787,7 @@ checksum = "f8650aabb6c35b860610e9cff5dc1af886c9e25073b7b1712a68972af4281302" dependencies = [ "bytes", "heck 0.5.0", - "itertools 0.11.0", + "itertools 0.12.1", "log", "multimap", "once_cell", @@ -16820,7 +16820,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" dependencies = [ "anyhow", - "itertools 0.11.0", + "itertools 0.12.1", "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.65", @@ -16833,7 +16833,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acf0c195eebb4af52c752bec4f52f645da98b6e92077a04110c7f349477ae5ac" dependencies = [ "anyhow", - "itertools 0.11.0", + "itertools 0.12.1", "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.65", diff --git a/prdoc/pr_5726.prdoc b/prdoc/pr_5726.prdoc new file mode 100644 index 00000000000..ce666647bad --- /dev/null +++ b/prdoc/pr_5726.prdoc @@ -0,0 +1,14 @@ +title: "revive: Limit the amount of static memory" + +doc: + - audience: Runtime Dev + description: | + Limit the amount of static memory a contract can declare. + +crates: + - name: pallet-revive + bump: major + - name: pallet-revive-fixtures + bump: minor + - name: pallet-revive-uapi + bump: patch diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index c8409078af5..bfcaf5e9af8 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -1410,7 +1410,6 @@ impl pallet_revive::Config for Runtime { type WeightInfo = pallet_revive::weights::SubstrateWeight; type ChainExtension = (); type AddressMapper = pallet_revive::DefaultAddressMapper; - type MaxCodeLen = ConstU32<{ 123 * 1024 }>; type RuntimeMemory = ConstU32<{ 128 * 1024 * 1024 }>; type PVFMemory = ConstU32<{ 512 * 1024 * 1024 }>; type UnsafeUnstableInterface = ConstBool; diff --git a/substrate/frame/revive/Cargo.toml b/substrate/frame/revive/Cargo.toml index 667328ac2d0..177b3c3e9ee 100644 --- a/substrate/frame/revive/Cargo.toml +++ b/substrate/frame/revive/Cargo.toml @@ -19,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] environmental = { workspace = true } paste = { workspace = true } -polkavm = { version = "0.10.0", default-features = false } +polkavm = { version = "0.11.0", default-features = false } bitflags = { workspace = true } codec = { features = [ "derive", diff --git a/substrate/frame/revive/fixtures/Cargo.toml b/substrate/frame/revive/fixtures/Cargo.toml index 903298d2df2..53db08a3911 100644 --- a/substrate/frame/revive/fixtures/Cargo.toml +++ b/substrate/frame/revive/fixtures/Cargo.toml @@ -22,7 +22,7 @@ log = { workspace = true } parity-wasm = { workspace = true } tempfile = { workspace = true } toml = { workspace = true } -polkavm-linker = { version = "0.10.0" } +polkavm-linker = { version = "0.11.0" } anyhow = { workspace = true, default-features = true } [features] diff --git a/substrate/frame/revive/fixtures/build/Cargo.toml b/substrate/frame/revive/fixtures/build/Cargo.toml index 7dead51b230..5b3a21b5122 100644 --- a/substrate/frame/revive/fixtures/build/Cargo.toml +++ b/substrate/frame/revive/fixtures/build/Cargo.toml @@ -11,7 +11,7 @@ edition = "2021" [dependencies] uapi = { package = 'pallet-revive-uapi', path = "", default-features = false } common = { package = 'pallet-revive-fixtures-common', path = "" } -polkavm-derive = { version = "0.10.0" } +polkavm-derive = { version = "0.11.0" } [profile.release] opt-level = 3 diff --git a/substrate/frame/revive/fixtures/contracts/oom_ro.rs b/substrate/frame/revive/fixtures/contracts/oom_ro.rs new file mode 100644 index 00000000000..41c080d5847 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/oom_ro.rs @@ -0,0 +1,44 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This creates a large ro section. Even though it is zero +//! initialized we expect them to be included into the blob. +//! This means it will fail at the blob size check. + +#![no_std] +#![no_main] + +extern crate common; + +use uapi::{HostFn, HostFnImpl as api, ReturnFlags}; + +static BUFFER: [u8; 1025 * 1024] = [0; 1025 * 1024]; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call_never() { + // make sure the buffer is not optimized away + api::return_value(ReturnFlags::empty(), &BUFFER); +} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() {} diff --git a/substrate/frame/revive/fixtures/contracts/oom_rw_included.rs b/substrate/frame/revive/fixtures/contracts/oom_rw_included.rs new file mode 100644 index 00000000000..2cdcf7bafed --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/oom_rw_included.rs @@ -0,0 +1,44 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This creates a large rw section but with its contents +//! included into the blob. It should be rejected for its +//! blob size. + +#![no_std] +#![no_main] + +extern crate common; + +use uapi::{HostFn, HostFnImpl as api, ReturnFlags}; + +static mut BUFFER: [u8; 513 * 1024] = [42; 513 * 1024]; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub unsafe extern "C" fn call_never() { + // make sure the buffer is not optimized away + api::return_value(ReturnFlags::empty(), &BUFFER); +} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() {} diff --git a/substrate/frame/revive/fixtures/contracts/oom_rw_trailing.rs b/substrate/frame/revive/fixtures/contracts/oom_rw_trailing.rs new file mode 100644 index 00000000000..993be8e9cda --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/oom_rw_trailing.rs @@ -0,0 +1,44 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This creates a large rw section but the trailing zeroes +//! are removed by the linker. It should be rejected even +//! though the blob is small enough. + +#![no_std] +#![no_main] + +extern crate common; + +use uapi::{HostFn, HostFnImpl as api, ReturnFlags}; + +static mut BUFFER: [u8; 1025 * 1024] = [0; 1025 * 1024]; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub unsafe extern "C" fn call_never() { + // make sure the buffer is not optimized away + api::return_value(ReturnFlags::empty(), &BUFFER); +} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() {} diff --git a/substrate/frame/revive/src/benchmarking/mod.rs b/substrate/frame/revive/src/benchmarking/mod.rs index 332c425d714..cbc4cc62d48 100644 --- a/substrate/frame/revive/src/benchmarking/mod.rs +++ b/substrate/frame/revive/src/benchmarking/mod.rs @@ -258,7 +258,7 @@ mod benchmarks { // call_with_code_per_byte(0)`. #[benchmark(pov_mode = Measured)] fn call_with_code_per_byte( - c: Linear<0, { T::MaxCodeLen::get() }>, + c: Linear<0, { limits::code::BLOB_BYTES }>, ) -> Result<(), BenchmarkError> { let instance = Contract::::with_caller(whitelisted_caller(), WasmModule::sized(c), vec![])?; @@ -283,8 +283,8 @@ mod benchmarks { // `i`: Size of the input in bytes. #[benchmark(pov_mode = Measured)] fn instantiate_with_code( - c: Linear<0, { T::MaxCodeLen::get() }>, - i: Linear<0, { limits::MEMORY_BYTES }>, + c: Linear<0, { limits::code::BLOB_BYTES }>, + i: Linear<0, { limits::code::BLOB_BYTES }>, ) { let input = vec![42u8; i as usize]; let salt = [42u8; 32]; @@ -316,7 +316,7 @@ mod benchmarks { // `i`: Size of the input in bytes. // `s`: Size of e salt in bytes. #[benchmark(pov_mode = Measured)] - fn instantiate(i: Linear<0, { limits::MEMORY_BYTES }>) -> Result<(), BenchmarkError> { + fn instantiate(i: Linear<0, { limits::code::BLOB_BYTES }>) -> Result<(), BenchmarkError> { let input = vec![42u8; i as usize]; let salt = [42u8; 32]; let value = Pallet::::min_balance(); @@ -401,7 +401,7 @@ mod benchmarks { // It creates a maximum number of metering blocks per byte. // `c`: Size of the code in bytes. #[benchmark(pov_mode = Measured)] - fn upload_code(c: Linear<0, { T::MaxCodeLen::get() }>) { + fn upload_code(c: Linear<0, { limits::code::BLOB_BYTES }>) { let caller = whitelisted_caller(); T::Currency::set_balance(&caller, caller_funding::()); let WasmModule { code, hash, .. } = WasmModule::sized(c); @@ -695,7 +695,7 @@ mod benchmarks { } #[benchmark(pov_mode = Measured)] - fn seal_input(n: Linear<0, { limits::MEMORY_BYTES - 4 }>) { + fn seal_input(n: Linear<0, { limits::code::BLOB_BYTES - 4 }>) { let mut setup = CallSetup::::default(); let (mut ext, _) = setup.ext(); let mut runtime = crate::wasm::Runtime::new(&mut ext, vec![42u8; n as usize]); @@ -710,7 +710,7 @@ mod benchmarks { } #[benchmark(pov_mode = Measured)] - fn seal_return(n: Linear<0, { limits::MEMORY_BYTES - 4 }>) { + fn seal_return(n: Linear<0, { limits::code::BLOB_BYTES - 4 }>) { build_runtime!(runtime, memory: [n.to_le_bytes(), vec![42u8; n as usize], ]); let result; @@ -800,7 +800,7 @@ mod benchmarks { // buffer size, whichever is less. #[benchmark] fn seal_debug_message( - i: Linear<0, { (limits::MEMORY_BYTES).min(limits::DEBUG_BUFFER_BYTES) }>, + i: Linear<0, { (limits::code::BLOB_BYTES).min(limits::DEBUG_BUFFER_BYTES) }>, ) { let mut setup = CallSetup::::default(); setup.enable_debug_message(); @@ -1394,7 +1394,7 @@ mod benchmarks { // t: with or without some value to transfer // i: size of the input data #[benchmark(pov_mode = Measured)] - fn seal_call(t: Linear<0, 1>, i: Linear<0, { limits::MEMORY_BYTES }>) { + fn seal_call(t: Linear<0, 1>, i: Linear<0, { limits::code::BLOB_BYTES }>) { let Contract { account_id: callee, .. } = Contract::::with_index(1, WasmModule::dummy(), vec![]).unwrap(); let callee_bytes = callee.encode(); @@ -1469,7 +1469,7 @@ mod benchmarks { // t: value to transfer // i: size of input in bytes #[benchmark(pov_mode = Measured)] - fn seal_instantiate(i: Linear<0, { limits::MEMORY_BYTES }>) -> Result<(), BenchmarkError> { + fn seal_instantiate(i: Linear<0, { limits::code::BLOB_BYTES }>) -> Result<(), BenchmarkError> { let code = WasmModule::dummy(); let hash = Contract::::with_index(1, WasmModule::dummy(), vec![])?.info()?.code_hash; let hash_bytes = hash.encode(); @@ -1535,7 +1535,7 @@ mod benchmarks { // `n`: Input to hash in bytes #[benchmark(pov_mode = Measured)] - fn seal_hash_sha2_256(n: Linear<0, { limits::MEMORY_BYTES }>) { + fn seal_hash_sha2_256(n: Linear<0, { limits::code::BLOB_BYTES }>) { build_runtime!(runtime, memory: [[0u8; 32], vec![0u8; n as usize], ]); let result; @@ -1549,7 +1549,7 @@ mod benchmarks { // `n`: Input to hash in bytes #[benchmark(pov_mode = Measured)] - fn seal_hash_keccak_256(n: Linear<0, { limits::MEMORY_BYTES }>) { + fn seal_hash_keccak_256(n: Linear<0, { limits::code::BLOB_BYTES }>) { build_runtime!(runtime, memory: [[0u8; 32], vec![0u8; n as usize], ]); let result; @@ -1563,7 +1563,7 @@ mod benchmarks { // `n`: Input to hash in bytes #[benchmark(pov_mode = Measured)] - fn seal_hash_blake2_256(n: Linear<0, { limits::MEMORY_BYTES }>) { + fn seal_hash_blake2_256(n: Linear<0, { limits::code::BLOB_BYTES }>) { build_runtime!(runtime, memory: [[0u8; 32], vec![0u8; n as usize], ]); let result; @@ -1577,7 +1577,7 @@ mod benchmarks { // `n`: Input to hash in bytes #[benchmark(pov_mode = Measured)] - fn seal_hash_blake2_128(n: Linear<0, { limits::MEMORY_BYTES }>) { + fn seal_hash_blake2_128(n: Linear<0, { limits::code::BLOB_BYTES }>) { build_runtime!(runtime, memory: [[0u8; 16], vec![0u8; n as usize], ]); let result; @@ -1592,7 +1592,7 @@ mod benchmarks { // `n`: Message input length to verify in bytes. // need some buffer so the code size does not exceed the max code size. #[benchmark(pov_mode = Measured)] - fn seal_sr25519_verify(n: Linear<0, { T::MaxCodeLen::get() - 255 }>) { + fn seal_sr25519_verify(n: Linear<0, { limits::code::BLOB_BYTES - 255 }>) { let message = (0..n).zip((32u8..127u8).cycle()).map(|(_, c)| c).collect::>(); let message_len = message.len() as u32; diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index ccbc65873d0..bf980369056 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -90,7 +90,7 @@ pub use crate::wasm::SyscallDoc; type TrieId = BoundedVec>; type BalanceOf = <::Currency as Inspect<::AccountId>>::Balance; -type CodeVec = BoundedVec::MaxCodeLen>; +type CodeVec = BoundedVec>; type EventRecordOf = EventRecord<::RuntimeEvent, ::Hash>; type DebugBuffer = BoundedVec>; @@ -232,14 +232,6 @@ pub mod pallet { #[pallet::no_default_bounds] type AddressMapper: AddressMapper>; - /// The maximum length of a contract code in bytes. - /// - /// This value hugely affects the memory requirements of this pallet since all the code of - /// all contracts on the call stack will need to be held in memory. Setting of a correct - /// value will be enforced in [`Pallet::integrity_test`]. - #[pallet::constant] - type MaxCodeLen: Get; - /// Make contract callable functions marked as `#[unstable]` available. /// /// Contracts that use `#[unstable]` functions won't be able to be uploaded unless @@ -363,7 +355,6 @@ pub mod pallet { type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent; type DepositPerByte = DepositPerByte; type DepositPerItem = DepositPerItem; - type MaxCodeLen = ConstU32<{ 123 * 1024 }>; type Time = Self; type UnsafeUnstableInterface = ConstBool; type UploadOrigin = EnsureSigned; @@ -475,9 +466,6 @@ pub mod pallet { MaxCallDepthReached, /// No contract was found at the specified address. ContractNotFound, - /// The code supplied to `instantiate_with_code` exceeds the limit specified in the - /// current schedule. - CodeTooLarge, /// No code could be found at the supplied code hash. CodeNotFound, /// No code info could be found at the supplied code hash. @@ -531,6 +519,11 @@ pub mod pallet { /// A more detailed error can be found on the node console if debug messages are enabled /// by supplying `-lruntime::revive=debug`. CodeRejected, + /// The code blob supplied is larger than [`limits::code::BLOB_BYTES`]. + BlobTooLarge, + /// The static memory consumption of the blob will be larger than + /// [`limits::code::STATIC_MEMORY_BYTES`]. + StaticMemoryTooLarge, /// The contract has reached its maximum number of delegate dependencies. MaxDelegateDependenciesReached, /// The dependency was not found in the contract's delegate dependencies. @@ -562,7 +555,7 @@ pub mod pallet { /// A mapping from a contract's code hash to its code. #[pallet::storage] - pub(crate) type PristineCode = StorageMap<_, Identity, H256, CodeVec>; + pub(crate) type PristineCode = StorageMap<_, Identity, H256, CodeVec>; /// A mapping from a contract's code hash to its code info. #[pallet::storage] @@ -602,13 +595,10 @@ pub mod pallet { } fn integrity_test() { - // Total runtime memory limit + use limits::code::STATIC_MEMORY_BYTES; + + // The memory available in the block building runtime let max_runtime_mem: u32 = T::RuntimeMemory::get(); - // Memory limits for a single contract: - // Value stack size: 1Mb per contract, default defined in wasmi - const MAX_STACK_SIZE: u32 = 1024 * 1024; - // Heap limit is normally 16 mempages of 64kb each = 1Mb per contract - let max_heap_size = limits::MEMORY_BYTES; // The root frame is not accounted in CALL_STACK_DEPTH let max_call_depth = limits::CALL_STACK_DEPTH.checked_add(1).expect("CallStack size is too big"); @@ -618,50 +608,36 @@ pub mod pallet { .checked_mul(2) .expect("MaxTransientStorageSize is too large"); - // Check that given configured `MaxCodeLen`, runtime heap memory limit can't be broken. - // - // In worst case, the decoded Wasm contract code would be `x16` times larger than the - // encoded one. This is because even a single-byte wasm instruction has 16-byte size in - // wasmi. This gives us `MaxCodeLen*16` safety margin. - // - // Next, the pallet keeps the Wasm blob for each - // contract, hence we add up `MaxCodeLen` to the safety margin. - // + // We only allow 50% of the runtime memory to be utilized by the contracts call + // stack, keeping the rest for other facilities, such as PoV, etc. + const TOTAL_MEMORY_DEVIDER: u32 = 2; + // The inefficiencies of the freeing-bump allocator // being used in the client for the runtime memory allocations, could lead to possible - // memory allocations for contract code grow up to `x4` times in some extreme cases, - // which gives us total multiplier of `17*4` for `MaxCodeLen`. - // - // That being said, for every contract executed in runtime, at least `MaxCodeLen*17*4` - // memory should be available. Note that maximum allowed heap memory and stack size per - // each contract (stack frame) should also be counted. - // - // The pallet holds transient storage with a size up to `max_transient_storage_size`. - // - // Finally, we allow 50% of the runtime memory to be utilized by the contracts call - // stack, keeping the rest for other facilities, such as PoV, etc. - // - // This gives us the following formula: + // memory allocations grow up to `x4` times in some extreme cases. + const MEMORY_ALLOCATOR_INEFFICENCY_DEVIDER: u32 = 4; + + // Check that the configured `STATIC_MEMORY_BYTES` fits into runtime memory. // - // `(MaxCodeLen * 17 * 4 + MAX_STACK_SIZE + max_heap_size) * max_call_depth + - // max_transient_storage_size < max_runtime_mem/2` + // `STATIC_MEMORY_BYTES` is the amount of memory that a contract can consume + // in memory and is enforced at upload time. // - // Hence the upper limit for the `MaxCodeLen` can be defined as follows: - let code_len_limit = max_runtime_mem - .saturating_div(2) + // Dynamic allocations are not available, yet. Hence are not taken into consideration + // here. + let static_memory_limit = max_runtime_mem + .saturating_div(TOTAL_MEMORY_DEVIDER) .saturating_sub(max_transient_storage_size) .saturating_div(max_call_depth) - .saturating_sub(max_heap_size) - .saturating_sub(MAX_STACK_SIZE) - .saturating_div(17 * 4); + .saturating_sub(STATIC_MEMORY_BYTES) + .saturating_div(MEMORY_ALLOCATOR_INEFFICENCY_DEVIDER); assert!( - T::MaxCodeLen::get() < code_len_limit, - "Given `CallStack` height {:?}, `MaxCodeLen` should be set less than {:?} \ + STATIC_MEMORY_BYTES < static_memory_limit, + "Given `CallStack` height {:?}, `STATIC_MEMORY_LIMIT` should be set less than {:?} \ (current value is {:?}), to avoid possible runtime oom issues.", max_call_depth, - code_len_limit, - T::MaxCodeLen::get(), + static_memory_limit, + STATIC_MEMORY_BYTES, ); // Validators are configured to be able to use more memory than block builders. This is @@ -1060,12 +1036,8 @@ where let (executable, upload_deposit) = match code { Code::Upload(code) => { let upload_account = T::UploadOrigin::ensure_origin(origin)?; - let (executable, upload_deposit) = Self::try_upload_code( - upload_account, - code, - storage_deposit_limit, - debug_message.as_mut(), - )?; + let (executable, upload_deposit) = + Self::try_upload_code(upload_account, code, storage_deposit_limit)?; storage_deposit_limit.saturating_reduce(upload_deposit); (executable, upload_deposit) }, @@ -1117,7 +1089,7 @@ where storage_deposit_limit: BalanceOf, ) -> CodeUploadResult> { let origin = T::UploadOrigin::ensure_origin(origin)?; - let (module, deposit) = Self::try_upload_code(origin, code, storage_deposit_limit, None)?; + let (module, deposit) = Self::try_upload_code(origin, code, storage_deposit_limit)?; Ok(CodeUploadReturnValue { code_hash: *module.code_hash(), deposit }) } @@ -1135,12 +1107,8 @@ where origin: T::AccountId, code: Vec, storage_deposit_limit: BalanceOf, - mut debug_message: Option<&mut DebugBuffer>, ) -> Result<(WasmBlob, BalanceOf), DispatchError> { - let mut module = WasmBlob::from_code(code, origin).map_err(|(err, msg)| { - debug_message.as_mut().map(|d| d.try_extend(msg.bytes())); - err - })?; + let mut module = WasmBlob::from_code(code, origin)?; let deposit = module.store_code()?; ensure!(storage_deposit_limit >= deposit, >::StorageDepositLimitExhausted); Ok((module, deposit)) diff --git a/substrate/frame/revive/src/limits.rs b/substrate/frame/revive/src/limits.rs index 1a714a89d48..f712493d3bc 100644 --- a/substrate/frame/revive/src/limits.rs +++ b/substrate/frame/revive/src/limits.rs @@ -22,15 +22,21 @@ //! is meant for. This is true for either increasing or decreasing the limit. //! //! Limits in this file are different from the limits configured on the [`Config`] trait which are -//! generally only affect actions that cannot be performed by a contract: For example, uploading new -//! code only be done via a transaction but not by a contract. Hence the maximum contract size can -//! be raised (but not lowered) by the runtime configuration. +//! generally only affect actions that cannot be performed by a contract: For example things related +//! to deposits and weights are allowed to be changed as they are paid by root callers which +//! are not contracts. +//! +//! Exceptions to this rule apply: Limits in the [`code`] module can be increased +//! without emulating the old values for existing contracts. Reason is that those limits are only +//! applied **once** at code upload time. Since this action cannot be performed by contracts we +//! can change those limits without breaking existing contracts. Please keep in mind that we should +//! only ever **increase** those values but never decrease. /// The maximum depth of the call stack. /// /// A 0 means that no callings of other contracts are possible. In other words only the origin /// called "root contract" is allowed to execute then. -pub const CALL_STACK_DEPTH: u32 = 5; +pub const CALL_STACK_DEPTH: u32 = 10; /// The maximum number of topics a call to [`crate::SyscallDoc::deposit_event`] can emit. /// @@ -40,10 +46,7 @@ pub const NUM_EVENT_TOPICS: u32 = 4; /// The maximum number of code hashes a contract can lock. pub const DELEGATE_DEPENDENCIES: u32 = 32; -/// How much memory do we allow the contract to allocate. -pub const MEMORY_BYTES: u32 = 16 * 64 * 1024; - -/// Maximum size of events (excluding topics) and storage values. +/// Maximum size of events (including topics) and storage values. pub const PAYLOAD_BYTES: u32 = 512; /// The maximum size of the transient storage in bytes. @@ -58,3 +61,85 @@ pub const STORAGE_KEY_BYTES: u32 = 128; /// /// The buffer will always be disabled for on-chain execution. pub const DEBUG_BUFFER_BYTES: u32 = 2 * 1024 * 1024; + +/// The page size in which PolkaVM should allocate memory chunks. +pub const PAGE_SIZE: u32 = 4 * 1024; + +/// Limits that are only enforced on code upload. +/// +/// # Note +/// +/// This limit can be increased later without breaking existing contracts +/// as it is only enforced at code upload time. Code already uploaded +/// will not be affected by those limits. +pub mod code { + use super::PAGE_SIZE; + use crate::{CodeVec, Config, Error, LOG_TARGET}; + use alloc::vec::Vec; + use frame_support::ensure; + use sp_runtime::DispatchError; + + /// The maximum length of a code blob in bytes. + /// + /// This mostly exist to prevent parsing too big blobs and to + /// have a maximum encoded length. The actual memory calculation + /// is purely based off [`STATIC_MEMORY_BYTES`]. + pub const BLOB_BYTES: u32 = 256 * 1024; + + /// Maximum size the program is allowed to take in memory. + /// + /// This includes data and code. Increasing this limit will allow + /// for more code or more data. However, since code will decompress + /// into a bigger representation on compilation it will only increase + /// the allowed code size by [`BYTE_PER_INSTRUCTION`]. + pub const STATIC_MEMORY_BYTES: u32 = 1024 * 1024; + + /// How much memory each instruction will take in-memory after compilation. + /// + /// This is `size_of() + 16`. But we don't use `usize` here so it isn't + /// different on the native runtime (used for testing). + const BYTES_PER_INSTRUCTION: u32 = 20; + + /// The code is stored multiple times as part of the compiled program. + const EXTRA_OVERHEAD_PER_CODE_BYTE: u32 = 4; + + /// Make sure that the various program parts are within the defined limits. + pub fn enforce(blob: Vec) -> Result { + fn round_page(n: u32) -> u64 { + // performing the rounding in u64 in order to prevent overflow + u64::from(n).next_multiple_of(PAGE_SIZE.into()) + } + + let blob: CodeVec = blob.try_into().map_err(|_| >::BlobTooLarge)?; + + let program = polkavm::ProgramBlob::parse(blob.as_slice().into()).map_err(|err| { + log::debug!(target: LOG_TARGET, "failed to parse polkavm blob: {err:?}"); + Error::::CodeRejected + })?; + + // this is O(n) but it allows us to be more precise + let num_instructions = program.instructions().count() as u64; + + // The memory consumptions is the byte size of the whole blob, + // minus the RO data payload in the blob, + // minus the RW data payload in the blob, + // plus the RO data in memory (which is always equal or bigger than the RO payload), + // plus RW data in memory, plus stack size in memory. + // plus the overhead of instructions in memory which is derived from the code + // size itself and the number of instruction + let memory_size = (blob.len() as u64) + .saturating_add(round_page(program.ro_data_size())) + .saturating_sub(program.ro_data().len() as u64) + .saturating_add(round_page(program.rw_data_size())) + .saturating_sub(program.rw_data().len() as u64) + .saturating_add(round_page(program.stack_size())) + .saturating_add((num_instructions).saturating_mul(BYTES_PER_INSTRUCTION.into())) + .saturating_add( + (program.code().len() as u64).saturating_mul(EXTRA_OVERHEAD_PER_CODE_BYTE.into()), + ); + + ensure!(memory_size <= STATIC_MEMORY_BYTES as u64, >::StaticMemoryTooLarge); + + Ok(blob) + } +} diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index 8a18c09e86f..167e8da201b 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -4255,6 +4255,44 @@ mod run_tests { }); } + #[test] + fn static_data_limit_is_enforced() { + let (oom_rw_trailing, _) = compile_module("oom_rw_trailing").unwrap(); + let (oom_rw_included, _) = compile_module("oom_rw_included").unwrap(); + let (oom_ro, _) = compile_module("oom_ro").unwrap(); + + ExtBuilder::default().build().execute_with(|| { + let _ = Balances::set_balance(&ALICE, 1_000_000); + + assert_err!( + Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + oom_rw_trailing, + deposit_limit::(), + ), + >::StaticMemoryTooLarge + ); + + assert_err!( + Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + oom_rw_included, + deposit_limit::(), + ), + >::BlobTooLarge + ); + + assert_err!( + Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + oom_ro, + deposit_limit::(), + ), + >::BlobTooLarge + ); + }); + } + #[test] fn call_diverging_out_len_works() { let (code, _) = compile_module("call_diverging_out_len").unwrap(); diff --git a/substrate/frame/revive/src/wasm/mod.rs b/substrate/frame/revive/src/wasm/mod.rs index b8f6eef126b..cd274873975 100644 --- a/substrate/frame/revive/src/wasm/mod.rs +++ b/substrate/frame/revive/src/wasm/mod.rs @@ -35,6 +35,7 @@ use crate::{ address::AddressMapper, exec::{ExecResult, Executable, ExportedFunction, Ext}, gas::{GasMeter, Token}, + limits, storage::meter::Diff, weights::WeightInfo, AccountIdOf, BadOrigin, BalanceOf, CodeInfoOf, CodeVec, Config, Error, Event, ExecError, @@ -56,7 +57,7 @@ use sp_runtime::DispatchError; #[codec(mel_bound())] #[scale_info(skip_type_params(T))] pub struct WasmBlob { - code: CodeVec, + code: CodeVec, // This isn't needed for contract execution and is not stored alongside it. #[codec(skip)] code_info: CodeInfo, @@ -128,12 +129,11 @@ where BalanceOf: Into + TryFrom, { /// We only check for size and nothing else when the code is uploaded. - pub fn from_code( - code: Vec, - owner: AccountIdOf, - ) -> Result { - let code: CodeVec = - code.try_into().map_err(|_| (>::CodeTooLarge.into(), ""))?; + pub fn from_code(code: Vec, owner: AccountIdOf) -> Result { + // We do size checks when new code is deployed. This allows us to increase + // the limits later without affecting already deployed code. + let code = limits::code::enforce::(code)?; + let code_len = code.len() as u32; let bytes_added = code_len.saturating_add(>::max_encoded_len() as u32); let deposit = Diff { bytes_added, items_added: 2, ..Default::default() } @@ -283,16 +283,16 @@ impl WasmBlob { entry_point: ExportedFunction, api_version: ApiVersion, ) -> Result, ExecError> { - let code = self.code.as_slice(); - let mut config = polkavm::Config::default(); config.set_backend(Some(polkavm::BackendKind::Interpreter)); let engine = polkavm::Engine::new(&config).expect("interpreter is available on all plattforms; qed"); let mut module_config = polkavm::ModuleConfig::new(); + module_config.set_page_size(limits::PAGE_SIZE); module_config.set_gas_metering(Some(polkavm::GasMeteringKind::Sync)); - let module = polkavm::Module::new(&engine, &module_config, code.into()).map_err(|err| { + let module = polkavm::Module::new(&engine, &module_config, self.code.into_inner().into()) + .map_err(|err| { log::debug!(target: LOG_TARGET, "failed to create polkavm module: {err:?}"); Error::::CodeRejected })?; diff --git a/substrate/frame/revive/uapi/Cargo.toml b/substrate/frame/revive/uapi/Cargo.toml index 862bf36f07c..52de77a1094 100644 --- a/substrate/frame/revive/uapi/Cargo.toml +++ b/substrate/frame/revive/uapi/Cargo.toml @@ -21,7 +21,7 @@ codec = { features = [ ], optional = true, workspace = true } [target.'cfg(target_arch = "riscv32")'.dependencies] -polkavm-derive = { version = "0.10.0" } +polkavm-derive = { version = "0.11.0" } [package.metadata.docs.rs] default-target = ["wasm32-unknown-unknown"] -- GitLab From 37bdc897e313adb9323f5c44abef4009970e18ff Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 18 Sep 2024 19:38:20 +0000 Subject: [PATCH 077/125] Bump soketto from 0.7.1 to 0.8.0 (#5719) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [soketto](https://github.com/paritytech/soketto) from 0.7.1 to 0.8.0.
Release notes

Sourced from soketto's releases.

v0.8.0

0.8.0

  • [changed] move to rust 2021 #56
  • [changed] Replace sha-1 v0.9 with sha1 v0.10 #62
  • [changed] Update hyper requirement from v0.14 to v1.0 #99
  • [changed] Update base64 requirement from 0.13 to 0.22 #97
  • [changed] Bump MSRV to 1.71.1.
  • [fixed] doc typo on Client resource field #79
Changelog

Sourced from soketto's changelog.

0.8.0

  • [changed] move to rust 2021 #56
  • [changed] Replace sha-1 v0.9 with sha1 v0.10 #62
  • [changed] Update hyper requirement from v0.14 to v1.0 #99
  • [changed] Update base64 requirement from 0.13 to 0.22 #97
  • [changed] Bump MSRV to 1.71.1.
  • [fixed] doc typo on Client resource field #79
Commits
  • 7fee766 Merge pull request #100 from paritytech/chore-release-soketto-0.8
  • f392c85 chore: release v0.8.0
  • 6f31ad1 Merge pull request #88 from paritytech/dependabot/github_actions/actions/chec...
  • 3222405 Merge pull request #99 from paritytech/chore-update-hyper
  • afe56f5 Merge pull request #97 from paritytech/dependabot/cargo/base64-0.22
  • 420216a chore(deps): update hyper v1.0
  • 050d44b Update base64 requirement from 0.21 to 0.22
  • 3d65c54 Merge pull request #95 from paritytech/dependabot/cargo/env_logger-0.11.1
  • 153cd94 Update env_logger requirement from 0.10.0 to 0.11.1
  • 26a2fc6 Merge pull request #87 from kayabaNerve/master
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=soketto&package-manager=cargo&previous-version=0.7.1&new-version=0.8.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Bastian Köcher --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dd75a9ec43f..373e62c093a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23399,7 +23399,7 @@ dependencies = [ "scale-info", "serde", "serde_json", - "soketto 0.7.1", + "soketto 0.8.0", "staging-node-inspect", "substrate-cli-test-utils", "tempfile", diff --git a/Cargo.toml b/Cargo.toml index 463cf82a1de..6db55afb976 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1217,7 +1217,7 @@ snowbridge-router-primitives = { path = "bridges/snowbridge/primitives/router", snowbridge-runtime-common = { path = "bridges/snowbridge/runtime/runtime-common", default-features = false } snowbridge-runtime-test-common = { path = "bridges/snowbridge/runtime/test-common", default-features = false } snowbridge-system-runtime-api = { path = "bridges/snowbridge/pallets/system/runtime-api", default-features = false } -soketto = { version = "0.7.1" } +soketto = { version = "0.8.0" } solochain-template-runtime = { path = "templates/solochain/runtime" } sp-api = { path = "substrate/primitives/api", default-features = false } sp-api-proc-macro = { path = "substrate/primitives/api/proc-macro", default-features = false } -- GitLab From 69b02a3cff430403c33401d4bb084bcdf4e5c62f Mon Sep 17 00:00:00 2001 From: Maksym H <1177472+mordamax@users.noreply.github.com> Date: Wed, 18 Sep 2024 21:44:23 +0100 Subject: [PATCH 078/125] make update-ui-test executable (#5760) --- scripts/update-ui-tests.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 scripts/update-ui-tests.sh diff --git a/scripts/update-ui-tests.sh b/scripts/update-ui-tests.sh old mode 100644 new mode 100755 -- GitLab From b230b0e32b8a12ab9b53b6a1040ba26a55704947 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Thu, 19 Sep 2024 08:38:00 +0200 Subject: [PATCH 079/125] [xcm-emulator] Better logs for message execution and processing (#5712) When running XCM emulated tests and seeing the logs with `RUST_LOG=xcm` or `RUST_LOG=xcm=trace`, it's sometimes a bit hard to figure out the chain where the logs are coming from. I added a log whenever `execute_with` is called, to know the chain which makes the following logs. Looks like so: Screenshot 2024-09-13 at 20 14 13 There are already log targets for when UMP, DMP and HRMP messages are being processed. To see them, you have to use the log targets `ump`, `dmp`, and `hrmp` respectively. So `RUST_LOG=xcm,ump,dmp,hrmp` would let you see every log. I prefixed the targets with `xcm::` so you can get all the relevant logs just by filtering by `xcm`. You can always use the whole target to see just the messages being processed. These logs showed the message as an array of bytes, I made them show a hexadecimal string instead since that's easier to copy in case you want to decode it or use it in another tool. They look like this now: Screenshot 2024-09-13 at 20 17 15 The HRMP and UMP ones are very similar. --- Cargo.lock | 1 + cumulus/xcm/xcm-emulator/Cargo.toml | 1 + cumulus/xcm/xcm-emulator/src/lib.rs | 34 ++++++++++++++++++++--------- prdoc/pr_5712.prdoc | 18 +++++++++++++++ 4 files changed, 44 insertions(+), 10 deletions(-) create mode 100644 prdoc/pr_5712.prdoc diff --git a/Cargo.lock b/Cargo.lock index 373e62c093a..2a8f570fabe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26959,6 +26959,7 @@ dependencies = [ name = "xcm-emulator" version = "0.5.0" dependencies = [ + "array-bytes", "cumulus-pallet-parachain-system", "cumulus-pallet-xcmp-queue", "cumulus-primitives-core", diff --git a/cumulus/xcm/xcm-emulator/Cargo.toml b/cumulus/xcm/xcm-emulator/Cargo.toml index ba1097fba07..6924f11292d 100644 --- a/cumulus/xcm/xcm-emulator/Cargo.toml +++ b/cumulus/xcm/xcm-emulator/Cargo.toml @@ -15,6 +15,7 @@ paste = { workspace = true, default-features = true } log = { workspace = true } lazy_static = { workspace = true } impl-trait-for-tuples = { workspace = true } +array-bytes = { workspace = true } # Substrate frame-support = { workspace = true, default-features = true } diff --git a/cumulus/xcm/xcm-emulator/src/lib.rs b/cumulus/xcm/xcm-emulator/src/lib.rs index 76bbad38d5e..d393d453773 100644 --- a/cumulus/xcm/xcm-emulator/src/lib.rs +++ b/cumulus/xcm/xcm-emulator/src/lib.rs @@ -16,6 +16,7 @@ extern crate alloc; +pub use array_bytes; pub use codec::{Decode, Encode, EncodeLike, MaxEncodedLen}; pub use lazy_static::lazy_static; pub use log; @@ -526,7 +527,10 @@ macro_rules! __impl_test_ext_for_relay_chain { <$network>::init(); // Execute - let r = $local_ext.with(|v| v.borrow_mut().execute_with(execute)); + let r = $local_ext.with(|v| { + $crate::log::info!(target: "xcm::emulator::execute_with", "Executing as {}", stringify!($name)); + v.borrow_mut().execute_with(execute) + }); // Send messages if needed $local_ext.with(|v| { @@ -550,7 +554,7 @@ macro_rules! __impl_test_ext_for_relay_chain { // log events Self::events().iter().for_each(|event| { - $crate::log::debug!(target: concat!("events::", stringify!($name)), "{:?}", event); + $crate::log::info!(target: concat!("events::", stringify!($name)), "{:?}", event); }); // clean events @@ -826,7 +830,10 @@ macro_rules! __impl_test_ext_for_parachain { Self::new_block(); // Execute - let r = $local_ext.with(|v| v.borrow_mut().execute_with(execute)); + let r = $local_ext.with(|v| { + $crate::log::info!(target: "xcm::emulator::execute_with", "Executing as {}", stringify!($name)); + v.borrow_mut().execute_with(execute) + }); // Finalize the block Self::finalize_block(); @@ -872,7 +879,7 @@ macro_rules! __impl_test_ext_for_parachain { // log events ::events().iter().for_each(|event| { - $crate::log::debug!(target: concat!("events::", stringify!($name)), "{:?}", event); + $crate::log::info!(target: concat!("events::", stringify!($name)), "{:?}", event); }); // clean events @@ -1024,7 +1031,10 @@ macro_rules! decl_test_networks { &mut msg.using_encoded($crate::blake2_256), ); }); - $crate::log::debug!(target: concat!("dmp::", stringify!($name)) , "DMP messages processed {:?} to para_id {:?}", msgs.clone(), &to_para_id); + let messages = msgs.clone().iter().map(|(block, message)| { + (*block, $crate::array_bytes::bytes2hex("0x", message)) + }).collect::>(); + $crate::log::info!(target: concat!("xcm::dmp::", stringify!($name)) , "Downward messages processed by para_id {:?}: {:?}", &to_para_id, messages); $crate::DMP_DONE.with(|b| b.borrow_mut().get_mut(Self::name()).unwrap().push_back((to_para_id, block, msg))); } } @@ -1037,7 +1047,7 @@ macro_rules! decl_test_networks { while let Some((to_para_id, messages)) = $crate::HORIZONTAL_MESSAGES.with(|b| b.borrow_mut().get_mut(Self::name()).unwrap().pop_front()) { - let iter = messages.iter().map(|(p, b, m)| (*p, *b, &m[..])).collect::>().into_iter(); + let iter = messages.iter().map(|(para_id, relay_block_number, message)| (*para_id, *relay_block_number, &message[..])).collect::>().into_iter(); $( let para_id: u32 = <$parachain>::para_id().into(); @@ -1047,7 +1057,10 @@ macro_rules! decl_test_networks { // Nudge the MQ pallet to process immediately instead of in the next block. let _ = <$parachain as Parachain>::MessageProcessor::service_queues($crate::Weight::MAX); }); - $crate::log::debug!(target: concat!("hrmp::", stringify!($name)) , "HRMP messages processed {:?} to para_id {:?}", &messages, &to_para_id); + let messages = messages.clone().iter().map(|(para_id, relay_block_number, message)| { + (*para_id, *relay_block_number, $crate::array_bytes::bytes2hex("0x", message)) + }).collect::>(); + $crate::log::info!(target: concat!("xcm::hrmp::", stringify!($name)), "Horizontal messages processed by para_id {:?}: {:?}", &to_para_id, &messages); } )* } @@ -1066,7 +1079,8 @@ macro_rules! decl_test_networks { &mut msg.using_encoded($crate::blake2_256), ); }); - $crate::log::debug!(target: concat!("ump::", stringify!($name)) , "Upward message processed {:?} from para_id {:?}", &msg, &from_para_id); + let message = $crate::array_bytes::bytes2hex("0x", msg.clone()); + $crate::log::info!(target: concat!("xcm::ump::", stringify!($name)) , "Upward message processed from para_id {:?}: {:?}", &from_para_id, &message); } } @@ -1086,7 +1100,7 @@ macro_rules! decl_test_networks { <::Source as TestExt>::ext_wrapper(|| { <::Handler as BridgeMessageHandler>::notify_source_message_delivery(msg.lane_id.clone()); }); - $crate::log::debug!(target: concat!("bridge::", stringify!($name)) , "Bridged message processed {:?}", msg); + $crate::log::info!(target: concat!("bridge::", stringify!($name)) , "Bridged message processed {:?}", msg); } } } @@ -1297,7 +1311,7 @@ macro_rules! assert_expected_events { if !message.is_empty() { // Log events as they will not be logged after the panic <$chain as $crate::Chain>::events().iter().for_each(|event| { - $crate::log::debug!(target: concat!("events::", stringify!($chain)), "{:?}", event); + $crate::log::info!(target: concat!("events::", stringify!($chain)), "{:?}", event); }); panic!("{}", message.concat()) } diff --git a/prdoc/pr_5712.prdoc b/prdoc/pr_5712.prdoc new file mode 100644 index 00000000000..321ed12f313 --- /dev/null +++ b/prdoc/pr_5712.prdoc @@ -0,0 +1,18 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Better logs for XCM emulator + +doc: + - audience: Runtime Dev + description: | + Now the XCM emulator has a log every time `execute_with` is called, to know + which chain is being used. + Also, the logs for UMP, DMP, HRMP processing were included in the `xcm` log filter + and changed from showing the message as an array of bytes to a hex string. + This means running the tests with `RUST_LOG=xcm` should give you everything you need, + you can always filter by `RUST_LOG=xcm::hrmp` or any other if you need it. + +crates: + - name: xcm-emulator + bump: patch -- GitLab From d31bb8ac990286f4d4caa759ea960bb9866fc2b0 Mon Sep 17 00:00:00 2001 From: yjh Date: Thu, 19 Sep 2024 20:49:04 +0800 Subject: [PATCH 080/125] chore: fast return for invalid request of node health (#5762) Co-authored-by: command-bot <> --- prdoc/pr_5762.prdoc | 10 ++++++++++ .../client/rpc-servers/src/middleware/node_health.rs | 6 +++--- 2 files changed, 13 insertions(+), 3 deletions(-) create mode 100644 prdoc/pr_5762.prdoc diff --git a/prdoc/pr_5762.prdoc b/prdoc/pr_5762.prdoc new file mode 100644 index 00000000000..730b3a46df8 --- /dev/null +++ b/prdoc/pr_5762.prdoc @@ -0,0 +1,10 @@ +title: Fast return for invalid request of node health + +doc: + - audience: Node Dev + description: | + Return directly when invalid request for node health api + +crates: + - name: sc-rpc-server + bump: patch diff --git a/substrate/client/rpc-servers/src/middleware/node_health.rs b/substrate/client/rpc-servers/src/middleware/node_health.rs index 69c9e0829ac..105199d9b4b 100644 --- a/substrate/client/rpc-servers/src/middleware/node_health.rs +++ b/substrate/client/rpc-servers/src/middleware/node_health.rs @@ -98,17 +98,17 @@ where let fut = self.0.call(req); async move { - let res = fut.await.map_err(|err| err.into())?; - Ok(match maybe_intercept { InterceptRequest::Deny => http_response(StatusCode::METHOD_NOT_ALLOWED, HttpBody::empty()), - InterceptRequest::No => res, + InterceptRequest::No => fut.await.map_err(|err| err.into())?, InterceptRequest::Health => { + let res = fut.await.map_err(|err| err.into())?; let health = parse_rpc_response(res.into_body()).await?; http_ok_response(serde_json::to_string(&health)?) }, InterceptRequest::Readiness => { + let res = fut.await.map_err(|err| err.into())?; let health = parse_rpc_response(res.into_body()).await?; if (!health.is_syncing && health.peers > 0) || !health.should_have_peers { http_ok_response(HttpBody::empty()) -- GitLab From 0c9d8fedc6ef1fde939346c91d304226cb297ec1 Mon Sep 17 00:00:00 2001 From: Andrei Eres Date: Thu, 19 Sep 2024 17:45:35 +0200 Subject: [PATCH 081/125] Use maximum allowed response size for request/response protocols (#5753) # Description Adjust the PoV response size to the default values used in the substrate. Fixes https://github.com/paritytech/polkadot-sdk/issues/5503 ## Integration The changes shouldn't impact downstream projects since we are only increasing the limit. ## Review Notes You can't see it from the changes, but it affects all protocols that use the `POV_RESPONSE_SIZE` constant. - Protocol::ChunkFetchingV1 - Protocol::ChunkFetchingV2 - Protocol::CollationFetchingV1 - Protocol::CollationFetchingV2 - Protocol::PoVFetchingV1 - Protocol::AvailableDataFetchingV1 ## Increasing timeouts https://github.com/paritytech/polkadot-sdk/blob/fae15379cba0c876aa16c77e11809c83d1db8f5c/polkadot/node/network/protocol/src/request_response/mod.rs#L126-L129 I assume the current PoV request timeout is set to 1.2s to handle 5 consecutive requests during a 6s block. This setting does not relate to the PoV response size. I see no reason to change the current timeouts after adjusting the response size. However, we should consider networking speed limitations if we want to increase the maximum PoV size to 10 MB. With the number of parallel requests set to 10, validators will need the following networking speeds: - 5 MB PoV: at least 42 MB/s, ideally 50 MB/s. - 10 MB PoV: at least 84 MB/s, ideally 100 MB/s. The current required speed of 50 MB/s aligns with the 62.5 MB/s specified [in the reference hardware requirements](https://wiki.polkadot.network/docs/maintain-guides-how-to-validate-polkadot#reference-hardware). Increasing the PoV size to 10 MB may require a higher networking speed. --------- Co-authored-by: Andrei Sandu <54316454+sandreim@users.noreply.github.com> --- .../protocol/src/request_response/mod.rs | 13 +++++------- prdoc/pr_5753.prdoc | 21 +++++++++++++++++++ .../light/src/light_client_requests.rs | 6 ++++-- substrate/client/network/src/bitswap/mod.rs | 3 ++- substrate/client/network/src/lib.rs | 3 +++ substrate/client/network/src/protocol.rs | 3 ++- .../network/sync/src/block_request_handler.rs | 4 ++-- .../network/sync/src/state_request_handler.rs | 4 ++-- .../network/sync/src/warp_request_handler.rs | 4 +--- .../client/network/transactions/src/config.rs | 3 ++- 10 files changed, 44 insertions(+), 20 deletions(-) create mode 100644 prdoc/pr_5753.prdoc diff --git a/polkadot/node/network/protocol/src/request_response/mod.rs b/polkadot/node/network/protocol/src/request_response/mod.rs index fe06593bd7a..b498de55dce 100644 --- a/polkadot/node/network/protocol/src/request_response/mod.rs +++ b/polkadot/node/network/protocol/src/request_response/mod.rs @@ -51,8 +51,8 @@ use std::{collections::HashMap, time::Duration, u64}; -use polkadot_primitives::{MAX_CODE_SIZE, MAX_POV_SIZE}; -use sc_network::NetworkBackend; +use polkadot_primitives::MAX_CODE_SIZE; +use sc_network::{NetworkBackend, MAX_RESPONSE_SIZE}; use sp_runtime::traits::Block; use strum::{EnumIter, IntoEnumIterator}; @@ -159,11 +159,8 @@ pub const MAX_PARALLEL_ATTESTED_CANDIDATE_REQUESTS: u32 = 5; /// Response size limit for responses of POV like data. /// -/// This is larger than `MAX_POV_SIZE` to account for protocol overhead and for additional data in -/// `CollationFetchingV1` or `AvailableDataFetchingV1` for example. We try to err on larger limits -/// here as a too large limit only allows an attacker to waste our bandwidth some more, a too low -/// limit might have more severe effects. -const POV_RESPONSE_SIZE: u64 = MAX_POV_SIZE as u64 + 10_000; +/// Same as what we use in substrate networking. +const POV_RESPONSE_SIZE: u64 = MAX_RESPONSE_SIZE; /// Maximum response sizes for `StatementFetchingV1`. /// @@ -217,7 +214,7 @@ impl Protocol { name, legacy_names, 1_000, - POV_RESPONSE_SIZE as u64 * 3, + POV_RESPONSE_SIZE, // We are connected to all validators: CHUNK_REQUEST_TIMEOUT, tx, diff --git a/prdoc/pr_5753.prdoc b/prdoc/pr_5753.prdoc new file mode 100644 index 00000000000..dca181ff5c4 --- /dev/null +++ b/prdoc/pr_5753.prdoc @@ -0,0 +1,21 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Use maximum allowed response size for request/response protocols + +doc: + - audience: Node Dev + description: | + Increase maximum PoV response size to 16MB which is equal to the default value used in the substrate. + +crates: + - name: sc-network + bump: patch + - name: sc-network-light + bump: patch + - name: sc-network-sync + bump: patch + - name: polkadot-node-network-protocol + bump: patch + - name: sc-network-transactions + bump: patch diff --git a/substrate/client/network/light/src/light_client_requests.rs b/substrate/client/network/light/src/light_client_requests.rs index e55ceb62d7c..a8ce601d6fc 100644 --- a/substrate/client/network/light/src/light_client_requests.rs +++ b/substrate/client/network/light/src/light_client_requests.rs @@ -18,7 +18,9 @@ //! Helpers for outgoing and incoming light client requests. -use sc_network::{config::ProtocolId, request_responses::IncomingRequest, NetworkBackend}; +use sc_network::{ + config::ProtocolId, request_responses::IncomingRequest, NetworkBackend, MAX_RESPONSE_SIZE, +}; use sp_runtime::traits::Block; use std::time::Duration; @@ -57,7 +59,7 @@ pub fn generate_protocol_config< generate_protocol_name(genesis_hash, fork_id).into(), std::iter::once(generate_legacy_protocol_name(protocol_id).into()).collect(), 1 * 1024 * 1024, - 16 * 1024 * 1024, + MAX_RESPONSE_SIZE, Duration::from_secs(15), Some(inbound_queue), ) diff --git a/substrate/client/network/src/bitswap/mod.rs b/substrate/client/network/src/bitswap/mod.rs index 1e20572eeeb..e45c95c7d3c 100644 --- a/substrate/client/network/src/bitswap/mod.rs +++ b/substrate/client/network/src/bitswap/mod.rs @@ -23,6 +23,7 @@ use crate::{ request_responses::{IncomingRequest, OutgoingResponse, ProtocolConfig}, types::ProtocolName, + MAX_RESPONSE_SIZE, }; use cid::{self, Version}; @@ -47,7 +48,7 @@ const LOG_TARGET: &str = "bitswap"; // https://github.com/ipfs/js-ipfs-bitswap/blob/ // d8f80408aadab94c962f6b88f343eb9f39fa0fcc/src/decision-engine/index.js#L16 // We set it to the same value as max substrate protocol message -const MAX_PACKET_SIZE: u64 = 16 * 1024 * 1024; +const MAX_PACKET_SIZE: u64 = MAX_RESPONSE_SIZE; /// Max number of queued responses before denying requests. const MAX_REQUEST_QUEUE: usize = 20; diff --git a/substrate/client/network/src/lib.rs b/substrate/client/network/src/lib.rs index 99a972f914e..9300cbccc9a 100644 --- a/substrate/client/network/src/lib.rs +++ b/substrate/client/network/src/lib.rs @@ -302,3 +302,6 @@ const MAX_CONNECTIONS_PER_PEER: usize = 2; /// The maximum number of concurrent established connections that were incoming. const MAX_CONNECTIONS_ESTABLISHED_INCOMING: u32 = 10_000; + +/// Maximum response size limit. +pub const MAX_RESPONSE_SIZE: u64 = 16 * 1024 * 1024; diff --git a/substrate/client/network/src/protocol.rs b/substrate/client/network/src/protocol.rs index 977c4c4de66..402baa7bb2a 100644 --- a/substrate/client/network/src/protocol.rs +++ b/substrate/client/network/src/protocol.rs @@ -22,6 +22,7 @@ use crate::{ protocol_controller::{self, SetId}, service::{metrics::NotificationMetrics, traits::Direction}, types::ProtocolName, + MAX_RESPONSE_SIZE, }; use codec::Encode; @@ -56,7 +57,7 @@ pub mod message; /// Maximum size used for notifications in the block announce and transaction protocols. // Must be equal to `max(MAX_BLOCK_ANNOUNCE_SIZE, MAX_TRANSACTIONS_SIZE)`. -pub(crate) const BLOCK_ANNOUNCES_TRANSACTIONS_SUBSTREAM_SIZE: u64 = 16 * 1024 * 1024; +pub(crate) const BLOCK_ANNOUNCES_TRANSACTIONS_SUBSTREAM_SIZE: u64 = MAX_RESPONSE_SIZE; /// Identifier of the peerset for the block announces protocol. const HARDCODED_PEERSETS_SYNC: SetId = SetId::from(0); diff --git a/substrate/client/network/sync/src/block_request_handler.rs b/substrate/client/network/sync/src/block_request_handler.rs index 5aa374057a4..6e970b39931 100644 --- a/substrate/client/network/sync/src/block_request_handler.rs +++ b/substrate/client/network/sync/src/block_request_handler.rs @@ -39,7 +39,7 @@ use sc_network::{ request_responses::{IfDisconnected, IncomingRequest, OutgoingResponse, RequestFailure}, service::traits::RequestResponseConfig, types::ProtocolName, - NetworkBackend, + NetworkBackend, MAX_RESPONSE_SIZE, }; use sc_network_common::sync::message::{BlockAttributes, BlockData, BlockRequest, FromBlock}; use sc_network_types::PeerId; @@ -89,7 +89,7 @@ pub fn generate_protocol_config< generate_protocol_name(genesis_hash, fork_id).into(), std::iter::once(generate_legacy_protocol_name(protocol_id).into()).collect(), 1024 * 1024, - 16 * 1024 * 1024, + MAX_RESPONSE_SIZE, Duration::from_secs(20), Some(inbound_queue), ) diff --git a/substrate/client/network/sync/src/state_request_handler.rs b/substrate/client/network/sync/src/state_request_handler.rs index 0e713626eca..36a15f1f424 100644 --- a/substrate/client/network/sync/src/state_request_handler.rs +++ b/substrate/client/network/sync/src/state_request_handler.rs @@ -33,7 +33,7 @@ use sc_client_api::{BlockBackend, ProofProvider}; use sc_network::{ config::ProtocolId, request_responses::{IncomingRequest, OutgoingResponse}, - NetworkBackend, + NetworkBackend, MAX_RESPONSE_SIZE, }; use sp_runtime::traits::Block as BlockT; @@ -69,7 +69,7 @@ pub fn generate_protocol_config< generate_protocol_name(genesis_hash, fork_id).into(), std::iter::once(generate_legacy_protocol_name(protocol_id).into()).collect(), 1024 * 1024, - 16 * 1024 * 1024, + MAX_RESPONSE_SIZE, Duration::from_secs(40), Some(inbound_queue), ) diff --git a/substrate/client/network/sync/src/warp_request_handler.rs b/substrate/client/network/sync/src/warp_request_handler.rs index 371b04ec9e4..8d0b757ff82 100644 --- a/substrate/client/network/sync/src/warp_request_handler.rs +++ b/substrate/client/network/sync/src/warp_request_handler.rs @@ -27,14 +27,12 @@ use crate::{ use sc_network::{ config::ProtocolId, request_responses::{IncomingRequest, OutgoingResponse}, - NetworkBackend, + NetworkBackend, MAX_RESPONSE_SIZE, }; use sp_runtime::traits::Block as BlockT; use std::{sync::Arc, time::Duration}; -const MAX_RESPONSE_SIZE: u64 = 16 * 1024 * 1024; - /// Incoming warp requests bounded queue size. const MAX_WARP_REQUEST_QUEUE: usize = 20; diff --git a/substrate/client/network/transactions/src/config.rs b/substrate/client/network/transactions/src/config.rs index fdf81fcd9ff..239b76b5148 100644 --- a/substrate/client/network/transactions/src/config.rs +++ b/substrate/client/network/transactions/src/config.rs @@ -19,6 +19,7 @@ //! Configuration of the transaction protocol use futures::prelude::*; +use sc_network::MAX_RESPONSE_SIZE; use sc_network_common::ExHashT; use sp_runtime::traits::Block as BlockT; use std::{collections::HashMap, future::Future, pin::Pin, time}; @@ -32,7 +33,7 @@ pub(crate) const PROPAGATE_TIMEOUT: time::Duration = time::Duration::from_millis pub(crate) const MAX_KNOWN_TRANSACTIONS: usize = 10240; // ~300kb per peer + overhead. /// Maximum allowed size for a transactions notification. -pub(crate) const MAX_TRANSACTIONS_SIZE: u64 = 16 * 1024 * 1024; +pub(crate) const MAX_TRANSACTIONS_SIZE: u64 = MAX_RESPONSE_SIZE; /// Maximum number of transaction validation request we keep at any moment. pub(crate) const MAX_PENDING_TRANSACTIONS: usize = 8192; -- GitLab From c8d5e5a383c01ee02c3cc49fbd5e07540b6b79cc Mon Sep 17 00:00:00 2001 From: Iulian Barbu <14218860+iulianbarbu@users.noreply.github.com> Date: Thu, 19 Sep 2024 20:09:51 +0300 Subject: [PATCH 082/125] cumulus/minimal-node: added prometheus metrics for the RPC client (#5572) # Description When we start a node with connections to external RPC servers (as a minimal node), we lack metrics around how many individual calls we're doing to the remote RPC servers and their duration. This PR adds metrics that measure durations of each RPC call made by the minimal nodes, and implicitly how many calls there are. Closes #5409 Closes #5689 ## Integration Node operators should be able to track minimal node metrics and decide appropriate actions according to how the metrics are interpreted/felt. The added metrics can be observed by curl'ing the prometheus metrics endpoint for the ~relaychain~ parachain (it was changed based on the review). The metrics are represented by ~`polkadot_parachain_relay_chain_rpc_interface`~ `relay_chain_rpc_interface` namespace (I realized lining up `parachain_relay_chain` in the same metric might be confusing :). Excerpt from the curl: ``` relay_chain_rpc_interface_bucket{method="chain_getBlockHash",chain="rococo_local_testnet",le="0.001"} 15 relay_chain_rpc_interface_bucket{method="chain_getBlockHash",chain="rococo_local_testnet",le="0.004"} 23 relay_chain_rpc_interface_bucket{method="chain_getBlockHash",chain="rococo_local_testnet",le="0.016"} 23 relay_chain_rpc_interface_bucket{method="chain_getBlockHash",chain="rococo_local_testnet",le="0.064"} 23 relay_chain_rpc_interface_bucket{method="chain_getBlockHash",chain="rococo_local_testnet",le="0.256"} 24 relay_chain_rpc_interface_bucket{method="chain_getBlockHash",chain="rococo_local_testnet",le="1.024"} 24 relay_chain_rpc_interface_bucket{method="chain_getBlockHash",chain="rococo_local_testnet",le="4.096"} 24 relay_chain_rpc_interface_bucket{method="chain_getBlockHash",chain="rococo_local_testnet",le="16.384"} 24 relay_chain_rpc_interface_bucket{method="chain_getBlockHash",chain="rococo_local_testnet",le="65.536"} 24 relay_chain_rpc_interface_bucket{method="chain_getBlockHash",chain="rococo_local_testnet",le="+Inf"} 24 relay_chain_rpc_interface_sum{method="chain_getBlockHash",chain="rococo_local_testnet"} 0.11719075 relay_chain_rpc_interface_count{method="chain_getBlockHash",chain="rococo_local_testnet"} 24 ``` ## Review Notes The way we measure durations/hits is based on `HistogramVec` struct which allows us to collect timings for each RPC client method called from the minimal node., It can be extended to measure the RPCs against other dimensions too (status codes, response sizes, etc). The timing measuring is done at the level of the `relay-chain-rpc-interface`, in the `RelayChainRpcClient` struct's method 'request_tracing'. A single entry point for all RPC requests done through the relay-chain-rpc-interface. The requests durations will fall under exponential buckets described by start `0.001`, factor `4` and count `9`. --------- Signed-off-by: Iulian Barbu --- Cargo.lock | 3 ++ .../relay-chain-minimal-node/src/lib.rs | 11 +++-- .../relay-chain-rpc-interface/Cargo.toml | 2 + .../relay-chain-rpc-interface/src/lib.rs | 6 ++- .../relay-chain-rpc-interface/src/metrics.rs | 49 +++++++++++++++++++ .../src/rpc_client.rs | 25 ++++++++-- cumulus/client/service/src/lib.rs | 1 + cumulus/test/service/Cargo.toml | 1 + cumulus/test/service/src/lib.rs | 21 ++++---- cumulus/test/service/src/main.rs | 37 +++++++------- prdoc/pr_5572.prdoc | 21 ++++++++ substrate/utils/prometheus/src/lib.rs | 7 +-- 12 files changed, 145 insertions(+), 39 deletions(-) create mode 100644 cumulus/client/relay-chain-rpc-interface/src/metrics.rs create mode 100644 prdoc/pr_5572.prdoc diff --git a/Cargo.lock b/Cargo.lock index 2a8f570fabe..43d7e66b8d2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4665,6 +4665,7 @@ dependencies = [ "pin-project", "polkadot-overseer", "portpicker", + "prometheus", "rand", "sc-client-api", "sc-rpc-api", @@ -4682,6 +4683,7 @@ dependencies = [ "sp-state-machine 0.35.0", "sp-storage 19.0.0", "sp-version 29.0.0", + "substrate-prometheus-endpoint", "thiserror", "tokio", "tokio-util", @@ -4819,6 +4821,7 @@ dependencies = [ "polkadot-service", "polkadot-test-service", "portpicker", + "prometheus", "rand", "sc-basic-authorship", "sc-block-builder", diff --git a/cumulus/client/relay-chain-minimal-node/src/lib.rs b/cumulus/client/relay-chain-minimal-node/src/lib.rs index cea7e6e4a03..a3d858ea40c 100644 --- a/cumulus/client/relay-chain-minimal-node/src/lib.rs +++ b/cumulus/client/relay-chain-minimal-node/src/lib.rs @@ -96,19 +96,20 @@ async fn build_interface( client: RelayChainRpcClient, ) -> RelayChainResult<(Arc<(dyn RelayChainInterface + 'static)>, Option)> { let collator_pair = CollatorPair::generate().0; + let blockchain_rpc_client = Arc::new(BlockChainRpcClient::new(client.clone())); let collator_node = match polkadot_config.network.network_backend { sc_network::config::NetworkBackendType::Libp2p => new_minimal_relay_chain::>( polkadot_config, collator_pair.clone(), - Arc::new(BlockChainRpcClient::new(client.clone())), + blockchain_rpc_client, ) .await?, sc_network::config::NetworkBackendType::Litep2p => new_minimal_relay_chain::( polkadot_config, collator_pair.clone(), - Arc::new(BlockChainRpcClient::new(client.clone())), + blockchain_rpc_client, ) .await?, }; @@ -120,17 +121,19 @@ async fn build_interface( } pub async fn build_minimal_relay_chain_node_with_rpc( - polkadot_config: Configuration, + relay_chain_config: Configuration, + parachain_prometheus_registry: Option<&Registry>, task_manager: &mut TaskManager, relay_chain_url: Vec, ) -> RelayChainResult<(Arc<(dyn RelayChainInterface + 'static)>, Option)> { let client = cumulus_relay_chain_rpc_interface::create_client_and_start_worker( relay_chain_url, task_manager, + parachain_prometheus_registry, ) .await?; - build_interface(polkadot_config, task_manager, client).await + build_interface(relay_chain_config, task_manager, client).await } pub async fn build_minimal_relay_chain_node_light_client( diff --git a/cumulus/client/relay-chain-rpc-interface/Cargo.toml b/cumulus/client/relay-chain-rpc-interface/Cargo.toml index c2deddc5341..fb4cb4ceed4 100644 --- a/cumulus/client/relay-chain-rpc-interface/Cargo.toml +++ b/cumulus/client/relay-chain-rpc-interface/Cargo.toml @@ -29,6 +29,7 @@ sp-version = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } sc-rpc-api = { workspace = true, default-features = true } sc-service = { workspace = true, default-features = true } +prometheus-endpoint = { workspace = true, default-features = true } tokio = { features = ["sync"], workspace = true, default-features = true } tokio-util = { features = ["compat"], workspace = true } @@ -49,3 +50,4 @@ either = { workspace = true, default-features = true } thiserror = { workspace = true } rand = { workspace = true, default-features = true } pin-project = { workspace = true } +prometheus = { workspace = true } diff --git a/cumulus/client/relay-chain-rpc-interface/src/lib.rs b/cumulus/client/relay-chain-rpc-interface/src/lib.rs index e32ec6a41a4..3698938bfd8 100644 --- a/cumulus/client/relay-chain-rpc-interface/src/lib.rs +++ b/cumulus/client/relay-chain-rpc-interface/src/lib.rs @@ -39,6 +39,7 @@ use cumulus_primitives_core::relay_chain::BlockId; pub use url::Url; mod light_client_worker; +mod metrics; mod reconnecting_ws_client; mod rpc_client; mod tokio_platform; @@ -87,12 +88,13 @@ impl RelayChainInterface for RelayChainRpcInterface { async fn header(&self, block_id: BlockId) -> RelayChainResult> { let hash = match block_id { BlockId::Hash(hash) => hash, - BlockId::Number(num) => + BlockId::Number(num) => { if let Some(hash) = self.rpc_client.chain_get_block_hash(Some(num)).await? { hash } else { return Ok(None) - }, + } + }, }; let header = self.rpc_client.chain_get_header(Some(hash)).await?; diff --git a/cumulus/client/relay-chain-rpc-interface/src/metrics.rs b/cumulus/client/relay-chain-rpc-interface/src/metrics.rs new file mode 100644 index 00000000000..4d09464d237 --- /dev/null +++ b/cumulus/client/relay-chain-rpc-interface/src/metrics.rs @@ -0,0 +1,49 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus 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. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . + +use prometheus::{Error as PrometheusError, HistogramTimer, Registry}; +use prometheus_endpoint::{HistogramOpts, HistogramVec, Opts}; + +/// Gathers metrics about the blockchain RPC client. +#[derive(Clone)] +pub(crate) struct RelaychainRpcMetrics { + rpc_request: HistogramVec, +} + +impl RelaychainRpcMetrics { + pub(crate) fn register(registry: &Registry) -> Result { + Ok(Self { + rpc_request: prometheus_endpoint::register( + HistogramVec::new( + HistogramOpts { + common_opts: Opts::new( + "relay_chain_rpc_interface", + "Tracks stats about cumulus relay chain RPC interface", + ), + buckets: prometheus::exponential_buckets(0.001, 4.0, 9) + .expect("function parameters are constant and always valid; qed"), + }, + &["method"], + )?, + registry, + )?, + }) + } + + pub(crate) fn start_request_timer(&self, method: &str) -> HistogramTimer { + self.rpc_request.with_label_values(&[method]).start_timer() + } +} diff --git a/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs b/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs index c7eaa45958b..6e282281de6 100644 --- a/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs +++ b/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs @@ -22,6 +22,7 @@ use jsonrpsee::{ core::{params::ArrayParams, ClientError as JsonRpseeError}, rpc_params, }; +use prometheus::Registry; use serde::de::DeserializeOwned; use serde_json::Value as JsonValue; use std::collections::{btree_map::BTreeMap, VecDeque}; @@ -52,6 +53,7 @@ use sp_version::RuntimeVersion; use crate::{ light_client_worker::{build_smoldot_client, LightClientRpcWorker}, + metrics::RelaychainRpcMetrics, reconnecting_ws_client::ReconnectingWebsocketWorker, }; pub use url::Url; @@ -87,6 +89,7 @@ pub enum RpcDispatcherMessage { pub async fn create_client_and_start_worker( urls: Vec, task_manager: &mut TaskManager, + prometheus_registry: Option<&Registry>, ) -> RelayChainResult { let (worker, sender) = ReconnectingWebsocketWorker::new(urls).await; @@ -94,7 +97,7 @@ pub async fn create_client_and_start_worker( .spawn_essential_handle() .spawn("relay-chain-rpc-worker", None, worker.run()); - let client = RelayChainRpcClient::new(sender); + let client = RelayChainRpcClient::new(sender, prometheus_registry); Ok(client) } @@ -113,7 +116,8 @@ pub async fn create_client_and_start_light_client_worker( .spawn_essential_handle() .spawn("relay-light-client-worker", None, worker.run()); - let client = RelayChainRpcClient::new(sender); + // We'll not setup prometheus exporter metrics for the light client worker. + let client = RelayChainRpcClient::new(sender, None); Ok(client) } @@ -123,6 +127,7 @@ pub async fn create_client_and_start_light_client_worker( pub struct RelayChainRpcClient { /// Sender to send messages to the worker. worker_channel: TokioSender, + metrics: Option, } impl RelayChainRpcClient { @@ -130,8 +135,17 @@ impl RelayChainRpcClient { /// /// This client expects a channel connected to a worker that processes /// requests sent via this channel. - pub(crate) fn new(worker_channel: TokioSender) -> Self { - RelayChainRpcClient { worker_channel } + pub(crate) fn new( + worker_channel: TokioSender, + prometheus_registry: Option<&Registry>, + ) -> Self { + RelayChainRpcClient { + worker_channel, + metrics: prometheus_registry + .and_then(|inner| RelaychainRpcMetrics::register(inner).map_err(|err| { + tracing::warn!(target: LOG_TARGET, error = %err, "Unable to instantiate the RPC client metrics, continuing w/o metrics setup."); + }).ok()), + } } /// Call a call to `state_call` rpc method. @@ -148,6 +162,7 @@ impl RelayChainRpcClient { payload_bytes, hash }; + let res = self .request_tracing::("state_call", params, |err| { tracing::trace!( @@ -190,6 +205,8 @@ impl RelayChainRpcClient { R: DeserializeOwned + std::fmt::Debug, OR: Fn(&RelayChainError), { + let _timer = self.metrics.as_ref().map(|inner| inner.start_request_timer(method)); + let (tx, rx) = futures::channel::oneshot::channel(); let message = RpcDispatcherMessage::Request(method.into(), params, tx); diff --git a/cumulus/client/service/src/lib.rs b/cumulus/client/service/src/lib.rs index c95c72c370a..92dc64371f3 100644 --- a/cumulus/client/service/src/lib.rs +++ b/cumulus/client/service/src/lib.rs @@ -373,6 +373,7 @@ pub async fn build_relay_chain_interface( cumulus_client_cli::RelayChainMode::ExternalRpc(rpc_target_urls) => build_minimal_relay_chain_node_with_rpc( relay_chain_config, + parachain_config.prometheus_registry(), task_manager, rpc_target_urls, ) diff --git a/cumulus/test/service/Cargo.toml b/cumulus/test/service/Cargo.toml index f766d123632..a1b70c52395 100644 --- a/cumulus/test/service/Cargo.toml +++ b/cumulus/test/service/Cargo.toml @@ -18,6 +18,7 @@ clap = { features = ["derive"], workspace = true } codec = { workspace = true, default-features = true } criterion = { features = ["async_tokio"], workspace = true, default-features = true } jsonrpsee = { features = ["server"], workspace = true } +prometheus = { workspace = true } rand = { workspace = true, default-features = true } serde = { features = ["derive"], workspace = true, default-features = true } serde_json = { workspace = true, default-features = true } diff --git a/cumulus/test/service/src/lib.rs b/cumulus/test/service/src/lib.rs index a600dcce3d6..db771f5fe53 100644 --- a/cumulus/test/service/src/lib.rs +++ b/cumulus/test/service/src/lib.rs @@ -32,6 +32,7 @@ use cumulus_client_consensus_aura::{ ImportQueueParams, }; use cumulus_client_consensus_proposer::Proposer; +use prometheus::Registry; use runtime::AccountId; use sc_executor::{HeapAllocStrategy, WasmExecutor, DEFAULT_HEAP_ALLOC_STRATEGY}; use sp_consensus_aura::sr25519::AuthorityPair; @@ -264,11 +265,12 @@ pub fn new_partial( async fn build_relay_chain_interface( relay_chain_config: Configuration, + parachain_prometheus_registry: Option<&Registry>, collator_key: Option, collator_options: CollatorOptions, task_manager: &mut TaskManager, ) -> RelayChainResult> { - let relay_chain_full_node = match collator_options.relay_chain_mode { + let relay_chain_node = match collator_options.relay_chain_mode { cumulus_client_cli::RelayChainMode::Embedded => polkadot_test_service::new_full( relay_chain_config, if let Some(ref key) = collator_key { @@ -283,6 +285,7 @@ async fn build_relay_chain_interface( cumulus_client_cli::RelayChainMode::ExternalRpc(rpc_target_urls) => return build_minimal_relay_chain_node_with_rpc( relay_chain_config, + parachain_prometheus_registry, task_manager, rpc_target_urls, ) @@ -294,13 +297,13 @@ async fn build_relay_chain_interface( .map(|r| r.0), }; - task_manager.add_child(relay_chain_full_node.task_manager); + task_manager.add_child(relay_chain_node.task_manager); tracing::info!("Using inprocess node."); Ok(Arc::new(RelayChainInProcessInterface::new( - relay_chain_full_node.client.clone(), - relay_chain_full_node.backend.clone(), - relay_chain_full_node.sync_service.clone(), - relay_chain_full_node.overseer_handle.ok_or(RelayChainError::GenericError( + relay_chain_node.client.clone(), + relay_chain_node.backend.clone(), + relay_chain_node.sync_service.clone(), + relay_chain_node.overseer_handle.ok_or(RelayChainError::GenericError( "Overseer should be running in full node.".to_string(), ))?, ))) @@ -344,9 +347,9 @@ where let backend = params.backend.clone(); let block_import = params.other; - let relay_chain_interface = build_relay_chain_interface( relay_chain_config, + parachain_config.prometheus_registry(), collator_key.clone(), collator_options.clone(), &mut task_manager, @@ -494,7 +497,7 @@ where slot_drift: Duration::from_secs(1), }; - let (collation_future, block_builer_future) = + let (collation_future, block_builder_future) = slot_based::run::(params); task_manager.spawn_essential_handle().spawn( "collation-task", @@ -504,7 +507,7 @@ where task_manager.spawn_essential_handle().spawn( "block-builder-task", None, - block_builer_future, + block_builder_future, ); } else { tracing::info!(target: LOG_TARGET, "Starting block authoring with lookahead collator."); diff --git a/cumulus/test/service/src/main.rs b/cumulus/test/service/src/main.rs index 9357978b769..caa672e611f 100644 --- a/cumulus/test/service/src/main.rs +++ b/cumulus/test/service/src/main.rs @@ -61,36 +61,39 @@ fn main() -> Result<(), sc_cli::Error> { let collator_options = cli.run.collator_options(); let tokio_runtime = sc_cli::build_runtime()?; let tokio_handle = tokio_runtime.handle(); - let config = cli + let parachain_config = cli .run .normalize() .create_configuration(&cli, tokio_handle.clone()) .expect("Should be able to generate config"); - let polkadot_cli = RelayChainCli::new( - &config, + let relay_chain_cli = RelayChainCli::new( + ¶chain_config, [RelayChainCli::executable_name()].iter().chain(cli.relaychain_args.iter()), ); - - let tokio_handle = config.tokio_handle.clone(); - let polkadot_config = - SubstrateCli::create_configuration(&polkadot_cli, &polkadot_cli, tokio_handle) - .map_err(|err| format!("Relay chain argument error: {}", err))?; - - let parachain_id = chain_spec::Extensions::try_get(&*config.chain_spec) + let tokio_handle = parachain_config.tokio_handle.clone(); + let relay_chain_config = SubstrateCli::create_configuration( + &relay_chain_cli, + &relay_chain_cli, + tokio_handle, + ) + .map_err(|err| format!("Relay chain argument error: {}", err))?; + + let parachain_id = chain_spec::Extensions::try_get(&*parachain_config.chain_spec) .map(|e| e.para_id) .ok_or("Could not find parachain extension in chain-spec.")?; tracing::info!("Parachain id: {:?}", parachain_id); tracing::info!( "Is collating: {}", - if config.role.is_authority() { "yes" } else { "no" } + if parachain_config.role.is_authority() { "yes" } else { "no" } ); if cli.fail_pov_recovery { tracing::info!("PoV recovery failure enabled"); } - let collator_key = config.role.is_authority().then(|| CollatorPair::generate().0); + let collator_key = + parachain_config.role.is_authority().then(|| CollatorPair::generate().0); let consensus = cli .use_null_consensus @@ -102,15 +105,15 @@ fn main() -> Result<(), sc_cli::Error> { let (mut task_manager, _, _, _, _, _) = tokio_runtime .block_on(async move { - match polkadot_config.network.network_backend { + match relay_chain_config.network.network_backend { sc_network::config::NetworkBackendType::Libp2p => cumulus_test_service::start_node_impl::< _, sc_network::NetworkWorker<_, _>, >( - config, + parachain_config, collator_key, - polkadot_config, + relay_chain_config, parachain_id.into(), cli.disable_block_announcements.then(wrap_announce_block), cli.fail_pov_recovery, @@ -126,9 +129,9 @@ fn main() -> Result<(), sc_cli::Error> { _, sc_network::Litep2pNetworkBackend, >( - config, + parachain_config, collator_key, - polkadot_config, + relay_chain_config, parachain_id.into(), cli.disable_block_announcements.then(wrap_announce_block), cli.fail_pov_recovery, diff --git a/prdoc/pr_5572.prdoc b/prdoc/pr_5572.prdoc new file mode 100644 index 00000000000..c0707e4b7eb --- /dev/null +++ b/prdoc/pr_5572.prdoc @@ -0,0 +1,21 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: added RPC metrics for the collator + +doc: + - audience: [ Node Dev, Node Operator ] + description: | + The metric is named `relay_chain_rpc_interface` and can be scraped by prometheus agents from the parachain prometheus exporter. The metric provide information about `count`, `sum` and `duration` in seconds (with exponential buckets with parameters as start = 0.001, factor = 4, count = 9) for all RPC requests made with the `relay-chain-rpc-interface`. +crates: + - name: cumulus-relay-chain-rpc-interface + bump: major + - name: cumulus-relay-chain-minimal-node + bump: major + - name: cumulus-test-service + bump: patch + - name: substrate-prometheus-endpoint + bump: patch + - name: cumulus-client-service + bump: patch + diff --git a/substrate/utils/prometheus/src/lib.rs b/substrate/utils/prometheus/src/lib.rs index 7a8c6559060..460640bcd8e 100644 --- a/substrate/utils/prometheus/src/lib.rs +++ b/substrate/utils/prometheus/src/lib.rs @@ -86,9 +86,10 @@ async fn request_metrics( /// Initializes the metrics context, and starts an HTTP server /// to serve metrics. pub async fn init_prometheus(prometheus_addr: SocketAddr, registry: Registry) -> Result<(), Error> { - let listener = tokio::net::TcpListener::bind(&prometheus_addr) - .await - .map_err(|_| Error::PortInUse(prometheus_addr))?; + let listener = tokio::net::TcpListener::bind(&prometheus_addr).await.map_err(|e| { + log::error!(target: "prometheus", "Error binding to '{:#?}': {:#?}", prometheus_addr, e); + Error::PortInUse(prometheus_addr) + })?; init_prometheus_with_listener(listener, registry).await } -- GitLab From 221565d28582b70f409db40db393289811213e35 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Sep 2024 20:54:39 +0000 Subject: [PATCH 083/125] Bump the known_good_semver group across 1 directory with 2 updates (#5736) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the known_good_semver group with 2 updates in the / directory: [clap](https://github.com/clap-rs/clap) and [syn](https://github.com/dtolnay/syn). Updates `clap` from 4.5.11 to 4.5.13
Release notes

Sourced from clap's releases.

v4.5.13

[4.5.13] - 2024-07-31

Fixes

  • (derive) Improve error message when #[flatten]ing an optional #[group(skip)]
  • (help) Properly wrap long subcommand descriptions in help

v4.5.12

[4.5.12] - 2024-07-31

Changelog

Sourced from clap's changelog.

[4.5.13] - 2024-07-31

Fixes

  • (derive) Improve error message when #[flatten]ing an optional #[group(skip)]
  • (help) Properly wrap long subcommand descriptions in help

[4.5.12] - 2024-07-31

Commits
  • d222ae4 chore: Release
  • a8abcb4 docs: Update changelog
  • 2690e1b Merge pull request #5621 from shannmu/dynamic_valuehint
  • 7fd7b3e feat(clap_complete): Support to complete custom value of argument
  • fc6aaca Merge pull request #5638 from epage/cargo
  • 631e54b docs(cookbook): Style cargo plugin
  • 6fb49d0 Merge pull request #5636 from gibfahn/styles_const
  • 6f215ee refactor(styles): make styles example use a const
  • bbb2e6f test: Add test case for completing custom value of argument
  • 999071c fix: Change visible to hidden
  • Additional commits viewable in compare view

Updates `syn` from 2.0.65 to 2.0.77
Release notes

Sourced from syn's releases.

2.0.77

  • Support parsing Expr::Tuple in non-"full" mode (#1727)

2.0.76

  • Enforce that tail call become keyword is followed by an expression (#1725)

2.0.75

  • Automatically fill in missing turbofish when printing ExprPath and other paths in expression position (#1722)

2.0.74

  • Fix "temporary is dropped and runs the destructor for type `impl Iterator`" regression affecting certain use of Generics iterator methods (#1719)

2.0.73

2.0.72

2.0.71

2.0.70

2.0.69

  • Correctly parenthesize labeled loops inside a break value (#1692)
  • Add Punctuated::get and get_mut (#1693)

2.0.68

  • Improve panic location when parse_quote! parses invalid syntax (#1690, thanks @​stepancheg)
  • More efficient peek implementation for Group and Lifetime (#1687)

2.0.67

  • Produce more accurate error message locations for errors located at the end of a nested group (#1679, #1680)
  • Support peeking LitCStr in ParseStream::peek (#1682)

2.0.66

  • Allow braced structs when parsing ExprLet (#1671)
Commits
  • 6232266 Release 2.0.77
  • 97acbf0 Merge pull request #1727 from dtolnay/exprparen
  • a3b5a5c Support parsing Expr::Tuple in derive
  • 3c24f57 Run upload-artifact action regardless of previous step failure
  • 78608a3 Upload CI Cargo.lock for reproducing failures
  • ef3e9c6 Release 2.0.76
  • 8f7365f Merge pull request #1725 from dtolnay/tailcall
  • 6cddd9e Make tail call expr mandatory
  • d1746fe Release 2.0.75
  • b693682 Merge pull request #1722 from dtolnay/exprpath
  • Additional commits viewable in compare view

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
--------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: command-bot <> --- Cargo.lock | 262 +++++++++--------- Cargo.toml | 4 +- .../call_weight_inherited_invalid5.stderr | 4 +- 3 files changed, 135 insertions(+), 135 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 43d7e66b8d2..5e85570055b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -168,7 +168,7 @@ dependencies = [ "proc-macro-error", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", "syn-solidity", "tiny-keccak", ] @@ -295,7 +295,7 @@ dependencies = [ "proc-macro-error", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -753,7 +753,7 @@ checksum = "7378575ff571966e99a744addeff0bff98b8ada0dedf1956d59e634db95eaac1" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", "synstructure 0.13.1", ] @@ -776,7 +776,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -1379,7 +1379,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -1396,7 +1396,7 @@ checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -1611,7 +1611,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -2962,12 +2962,12 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.11" +version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35723e6a11662c2afb578bcf0b88bf6ea8e21282a953428f240574fcc3a2b5b3" +checksum = "0fbb260a053428790f3de475e304ff84cdbc4face759ea7a3e64c1edd938a7fc" dependencies = [ "clap_builder", - "clap_derive 4.5.11", + "clap_derive 4.5.13", ] [[package]] @@ -2981,9 +2981,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.11" +version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49eb96cbfa7cfa35017b7cd548c75b14c3118c98b423041d70562665e07fb0fa" +checksum = "64b17d7ea74e9f833c7dbf2cbe4fb12ff26783eda4782a8975b72f895c9b4d99" dependencies = [ "anstream", "anstyle", @@ -2998,7 +2998,7 @@ version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa3c596da3cf0983427b0df0dba359df9182c13bd5b519b585a482b0c351f4e8" dependencies = [ - "clap 4.5.11", + "clap 4.5.13", ] [[package]] @@ -3016,14 +3016,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.11" +version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d029b67f89d30bbb547c89fd5161293c0aec155fc691d7924b64550662db93e" +checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" dependencies = [ "heck 0.5.0", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -3869,7 +3869,7 @@ dependencies = [ "anes", "cast", "ciborium", - "clap 4.5.11", + "clap 4.5.13", "criterion-plot", "futures", "is-terminal", @@ -4003,7 +4003,7 @@ dependencies = [ name = "cumulus-client-cli" version = "0.7.0" dependencies = [ - "clap 4.5.11", + "clap 4.5.13", "parity-scale-codec", "sc-chain-spec", "sc-cli", @@ -4378,7 +4378,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -4466,7 +4466,7 @@ name = "cumulus-pov-validator" version = "0.1.0" dependencies = [ "anyhow", - "clap 4.5.11", + "clap 4.5.13", "parity-scale-codec", "polkadot-node-primitives", "polkadot-parachain-primitives", @@ -4786,7 +4786,7 @@ name = "cumulus-test-service" version = "0.1.0" dependencies = [ "async-trait", - "clap 4.5.11", + "clap 4.5.13", "criterion", "cumulus-client-cli", "cumulus-client-collator", @@ -4930,7 +4930,7 @@ checksum = "83fdaf97f4804dcebfa5862639bc9ce4121e82140bec2a987ac5140294865b5b" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -4970,7 +4970,7 @@ dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", "scratch", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -4987,7 +4987,7 @@ checksum = "50c49547d73ba8dcfd4ad7325d64c6d5391ff4224d498fc39a6f3f49825a530d" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -5035,7 +5035,7 @@ dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", "strsim 0.11.1", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -5057,7 +5057,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core 0.20.10", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -5174,7 +5174,7 @@ checksum = "d65d7ce8132b7c0e54497a4d9a55a1c2a0912a0d786cf894472ba818fba45762" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -5185,7 +5185,7 @@ checksum = "62d671cc41a825ebabc75757b62d3d168c577f9149b2d49ece1dad1f72119d25" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -5196,7 +5196,7 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -5304,7 +5304,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -5365,7 +5365,7 @@ dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", "regex", - "syn 2.0.65", + "syn 2.0.77", "termcolor", "toml 0.8.12", "walkdir", @@ -5596,7 +5596,7 @@ dependencies = [ "heck 0.4.1", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -5616,7 +5616,7 @@ checksum = "5e9a1f9f7d83e59740248a6e14ecf93929ade55027844dfcea78beafccc15745" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -5627,7 +5627,7 @@ checksum = "6fd000fd6988e73bbe993ea3db9b1aa64906ab88766d654973924340c8cddb42" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -5842,7 +5842,7 @@ dependencies = [ "prettyplease", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -5914,7 +5914,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -6191,7 +6191,7 @@ dependencies = [ "Inflector", "array-bytes", "chrono", - "clap 4.5.11", + "clap 4.5.13", "comfy-table", "frame-benchmarking", "frame-support", @@ -6258,7 +6258,7 @@ dependencies = [ "quote 1.0.37", "scale-info", "sp-arithmetic 23.0.0", - "syn 2.0.65", + "syn 2.0.77", "trybuild", ] @@ -6283,7 +6283,7 @@ dependencies = [ name = "frame-election-solution-type-fuzzer" version = "2.0.0-alpha.5" dependencies = [ - "clap 4.5.11", + "clap 4.5.13", "frame-election-provider-solution-type", "frame-election-provider-support", "frame-support", @@ -6366,7 +6366,7 @@ dependencies = [ name = "frame-omni-bencher" version = "0.1.0" dependencies = [ - "clap 4.5.11", + "clap 4.5.13", "cumulus-primitives-proof-size-hostfunction", "frame-benchmarking-cli", "log", @@ -6472,7 +6472,7 @@ dependencies = [ "sp-metadata-ir 0.6.0", "sp-runtime 31.0.1", "static_assertions", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -6483,7 +6483,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -6492,7 +6492,7 @@ version = "11.0.0" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -6748,7 +6748,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -8206,7 +8206,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -8938,7 +8938,7 @@ dependencies = [ "proc-macro-warning 0.4.2", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -9355,7 +9355,7 @@ dependencies = [ "macro_magic_core", "macro_magic_macros", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -9369,7 +9369,7 @@ dependencies = [ "macro_magic_core_macros", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -9380,7 +9380,7 @@ checksum = "b02abfe41815b5bd98dbd4260173db2c116dda171dc0fe7838cb206333b83308" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -9391,7 +9391,7 @@ checksum = "73ea28ee64b88876bf45277ed9a5817c1817df061a74f2b988971a12570e5869" dependencies = [ "macro_magic_core", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -9583,7 +9583,7 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" name = "minimal-template-node" version = "0.0.0" dependencies = [ - "clap 4.5.11", + "clap 4.5.13", "docify", "futures", "futures-timer", @@ -9736,7 +9736,7 @@ dependencies = [ "cfg-if", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -10099,7 +10099,7 @@ name = "node-bench" version = "0.9.0-dev" dependencies = [ "array-bytes", - "clap 4.5.11", + "clap 4.5.13", "derive_more", "fs_extra", "futures", @@ -10176,7 +10176,7 @@ dependencies = [ name = "node-runtime-generate-bags" version = "3.0.0" dependencies = [ - "clap 4.5.11", + "clap 4.5.13", "generate-bags", "kitchensink-runtime", ] @@ -10185,7 +10185,7 @@ dependencies = [ name = "node-template-release" version = "3.0.0" dependencies = [ - "clap 4.5.11", + "clap 4.5.13", "flate2", "fs_extra", "glob", @@ -10341,7 +10341,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -10517,7 +10517,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -11297,7 +11297,7 @@ version = "18.0.0" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -12400,7 +12400,7 @@ version = "0.1.0" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -12645,7 +12645,7 @@ dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", "sp-runtime 31.0.1", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -13037,7 +13037,7 @@ dependencies = [ name = "parachain-template-node" version = "0.0.0" dependencies = [ - "clap 4.5.11", + "clap 4.5.13", "color-print", "cumulus-client-cli", "cumulus-client-collator", @@ -13751,7 +13751,7 @@ dependencies = [ "pest_meta", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -13792,7 +13792,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -14014,7 +14014,7 @@ name = "polkadot-cli" version = "7.0.0" dependencies = [ "cfg-if", - "clap 4.5.11", + "clap 4.5.13", "frame-benchmarking-cli", "futures", "log", @@ -14907,7 +14907,7 @@ version = "0.1.0" dependencies = [ "assert_cmd", "async-trait", - "clap 4.5.11", + "clap 4.5.13", "color-print", "cumulus-client-cli", "cumulus-client-collator", @@ -15870,7 +15870,7 @@ dependencies = [ "async-trait", "bincode", "bitvec", - "clap 4.5.11", + "clap 4.5.13", "clap-num", "color-eyre", "colored", @@ -15971,7 +15971,7 @@ version = "1.0.0" dependencies = [ "assert_matches", "async-trait", - "clap 4.5.11", + "clap 4.5.13", "color-eyre", "futures", "futures-timer", @@ -16113,7 +16113,7 @@ dependencies = [ name = "polkadot-voter-bags" version = "7.0.0" dependencies = [ - "clap 4.5.11", + "clap 4.5.13", "generate-bags", "sp-io 30.0.0", "westend-runtime", @@ -16242,7 +16242,7 @@ dependencies = [ "polkavm-common 0.8.0", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -16254,7 +16254,7 @@ dependencies = [ "polkavm-common 0.9.0", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -16266,7 +16266,7 @@ dependencies = [ "polkavm-common 0.11.0", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -16276,7 +16276,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15e85319a0d5129dc9f021c62607e0804f5fb777a05cdda44d750ac0732def66" dependencies = [ "polkavm-derive-impl 0.8.0", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -16286,7 +16286,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ba81f7b5faac81e528eb6158a6f3c9e0bb1008e0ffa19653bc8dea925ecb429" dependencies = [ "polkavm-derive-impl 0.9.0", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -16296,7 +16296,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bf952e05bc5ce7d81293bae18cb44c271c78615b201d75e983cdcc40d5c6ef1" dependencies = [ "polkavm-derive-impl 0.11.0", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -16513,7 +16513,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c64d9ba0963cdcea2e1b2230fbae2bab30eb25a174be395c41e764bfb65dd62" dependencies = [ "proc-macro2 1.0.86", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -16604,7 +16604,7 @@ checksum = "3d1eaa7fa0aa1929ffdf7eeb6eac234dde6268914a14ad44d23521ab6a9b258e" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -16615,7 +16615,7 @@ checksum = "9b698b0b09d40e9b7c1a47b132d66a8b54bcd20583d9b6d06e4535e383b4405c" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -16696,7 +16696,7 @@ checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -16778,7 +16778,7 @@ dependencies = [ "prost 0.12.6", "prost-types 0.12.4", "regex", - "syn 2.0.65", + "syn 2.0.77", "tempfile", ] @@ -16799,7 +16799,7 @@ dependencies = [ "prost 0.13.2", "prost-types 0.13.2", "regex", - "syn 2.0.65", + "syn 2.0.77", "tempfile", ] @@ -16826,7 +16826,7 @@ dependencies = [ "itertools 0.12.1", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -16839,7 +16839,7 @@ dependencies = [ "itertools 0.12.1", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -17308,7 +17308,7 @@ checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -17462,7 +17462,7 @@ dependencies = [ name = "remote-ext-tests-bags-list" version = "1.0.0" dependencies = [ - "clap 4.5.11", + "clap 4.5.13", "frame-system", "log", "pallet-bags-list-remote-tests", @@ -17907,7 +17907,7 @@ dependencies = [ "regex", "relative-path", "rustc_version 0.4.0", - "syn 2.0.65", + "syn 2.0.77", "unicode-ident", ] @@ -18401,7 +18401,7 @@ name = "sc-chain-spec" version = "28.0.0" dependencies = [ "array-bytes", - "clap 4.5.11", + "clap 4.5.13", "docify", "log", "memmap2 0.9.3", @@ -18435,7 +18435,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -18444,7 +18444,7 @@ version = "0.36.0" dependencies = [ "array-bytes", "chrono", - "clap 4.5.11", + "clap 4.5.13", "fdlimit", "futures", "futures-timer", @@ -19695,7 +19695,7 @@ dependencies = [ name = "sc-storage-monitor" version = "0.16.0" dependencies = [ - "clap 4.5.11", + "clap 4.5.13", "fs4", "log", "sp-core 28.0.0", @@ -19798,7 +19798,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -19978,7 +19978,7 @@ dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", "scale-info", - "syn 2.0.65", + "syn 2.0.77", "thiserror", ] @@ -20328,7 +20328,7 @@ checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -20430,7 +20430,7 @@ checksum = "91d129178576168c589c9ec973feedf7d3126c01ac2bf08795109aa35b69fb8f" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -21302,7 +21302,7 @@ dependencies = [ name = "solochain-template-node" version = "0.0.0" dependencies = [ - "clap 4.5.11", + "clap 4.5.13", "frame-benchmarking-cli", "frame-metadata-hash-extension", "frame-system", @@ -21434,7 +21434,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -21449,7 +21449,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -22062,7 +22062,7 @@ version = "0.1.0" dependencies = [ "quote 1.0.37", "sp-crypto-hashing 0.1.0", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -22073,7 +22073,7 @@ checksum = "b85d0f1f1e44bd8617eb2a48203ee854981229e3e79e6f468c7175d5fd37489b" dependencies = [ "quote 1.0.37", "sp-crypto-hashing 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -22091,7 +22091,7 @@ source = "git+https://github.com/paritytech/polkadot-sdk#82912acb33a9030c0ef3bf5 dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -22100,7 +22100,7 @@ version = "14.0.0" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -22111,7 +22111,7 @@ checksum = "48d09fa0a5f7299fb81ee25ae3853d26200f7a348148aed6de76be905c007dbe" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -22427,7 +22427,7 @@ dependencies = [ name = "sp-npos-elections-fuzzer" version = "2.0.0-alpha.5" dependencies = [ - "clap 4.5.11", + "clap 4.5.13", "honggfuzz", "rand", "sp-npos-elections", @@ -22672,7 +22672,7 @@ dependencies = [ "proc-macro-crate 1.3.1", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -22684,7 +22684,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -22698,7 +22698,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -23174,7 +23174,7 @@ dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", "sp-version 29.0.0", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -23186,7 +23186,7 @@ dependencies = [ "parity-scale-codec", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -23366,7 +23366,7 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" name = "staging-chain-spec-builder" version = "1.6.1" dependencies = [ - "clap 4.5.11", + "clap 4.5.13", "log", "sc-chain-spec", "serde", @@ -23381,7 +23381,7 @@ version = "3.0.0-dev" dependencies = [ "array-bytes", "assert_cmd", - "clap 4.5.11", + "clap 4.5.13", "clap_complete", "criterion", "futures", @@ -23416,7 +23416,7 @@ dependencies = [ name = "staging-node-inspect" version = "0.12.0" dependencies = [ - "clap 4.5.11", + "clap 4.5.13", "parity-scale-codec", "sc-cli", "sc-client-api", @@ -23651,7 +23651,7 @@ dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", "rustversion", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -23664,14 +23664,14 @@ dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", "rustversion", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] name = "subkey" version = "9.0.0" dependencies = [ - "clap 4.5.11", + "clap 4.5.13", "sc-cli", ] @@ -24152,7 +24152,7 @@ dependencies = [ "scale-info", "scale-typegen", "subxt-metadata", - "syn 2.0.65", + "syn 2.0.77", "thiserror", "tokio", ] @@ -24215,7 +24215,7 @@ dependencies = [ "quote 1.0.37", "scale-typegen", "subxt-codegen", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -24368,9 +24368,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.65" +version = "2.0.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2863d96a84c6439701d7a38f9de935ec562c8832cc55d1dde0f513b52fad106" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", @@ -24386,7 +24386,7 @@ dependencies = [ "paste", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -24415,7 +24415,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -24545,7 +24545,7 @@ checksum = "5999e24eaa32083191ba4e425deb75cdf25efefabe5aaccb7446dd0d4122a3f5" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -24564,7 +24564,7 @@ dependencies = [ name = "test-parachain-adder-collator" version = "1.0.0" dependencies = [ - "clap 4.5.11", + "clap 4.5.13", "futures", "futures-timer", "log", @@ -24611,7 +24611,7 @@ dependencies = [ name = "test-parachain-undying-collator" version = "1.0.0" dependencies = [ - "clap 4.5.11", + "clap 4.5.13", "futures", "futures-timer", "log", @@ -24720,7 +24720,7 @@ checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -24896,7 +24896,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -25172,7 +25172,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -25214,7 +25214,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -25854,7 +25854,7 @@ dependencies = [ "once_cell", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", "wasm-bindgen-shared", ] @@ -25888,7 +25888,7 @@ checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -27024,7 +27024,7 @@ dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", "staging-xcm", - "syn 2.0.65", + "syn 2.0.77", "trybuild", ] @@ -27195,7 +27195,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -27215,7 +27215,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 6db55afb976..a4be2cc0d0a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -668,7 +668,7 @@ chain-spec-builder = { path = "substrate/bin/utils/chain-spec-builder", default- chain-spec-guide-runtime = { path = "docs/sdk/src/reference_docs/chain_spec_runtime" } chrono = { version = "0.4.31" } cid = { version = "0.9.0" } -clap = { version = "4.5.10" } +clap = { version = "4.5.13" } clap-num = { version = "1.0.2" } clap_complete = { version = "4.5.13" } coarsetime = { version = "0.1.22" } @@ -1299,7 +1299,7 @@ substrate-test-runtime-client = { path = "substrate/test-utils/runtime/client" } substrate-test-runtime-transaction-pool = { path = "substrate/test-utils/runtime/transaction-pool" } substrate-test-utils = { path = "substrate/test-utils" } substrate-wasm-builder = { path = "substrate/utils/wasm-builder", default-features = false } -syn = { version = "2.0.65" } +syn = { version = "2.0.77" } sysinfo = { version = "0.30" } tar = { version = "0.4" } tempfile = { version = "3.8.1" } diff --git a/substrate/frame/support/test/tests/pallet_ui/call_weight_inherited_invalid5.stderr b/substrate/frame/support/test/tests/pallet_ui/call_weight_inherited_invalid5.stderr index e12fbfcf4b4..477dc05d2e7 100644 --- a/substrate/frame/support/test/tests/pallet_ui/call_weight_inherited_invalid5.stderr +++ b/substrate/frame/support/test/tests/pallet_ui/call_weight_inherited_invalid5.stderr @@ -1,10 +1,10 @@ -error: unexpected token +error: unexpected token, expected `)` --> tests/pallet_ui/call_weight_inherited_invalid5.rs:31:50 | 31 | #[pallet::call(weight(::WeightInfo straycat))] | ^^^^^^^^ -error: unexpected token +error: unexpected token, expected `)` --> tests/pallet_ui/call_weight_inherited_invalid5.rs:51:52 | 51 | #[pallet::call(weight = ::WeightInfo straycat)] -- GitLab From 86bb5cb5068463f006fda3a4ac4236686c989b86 Mon Sep 17 00:00:00 2001 From: Ron Date: Fri, 20 Sep 2024 14:45:48 +0800 Subject: [PATCH 084/125] Add Snowbridge initialize migration on Westend (#5747) # Description Fix https://github.com/paritytech/polkadot-sdk/pull/5074 which missed the runtime migration to initialize channels of the bridge. --------- Co-authored-by: Adrian Catangiu --- Cargo.lock | 2 +- .../bridge-hubs/bridge-hub-westend/Cargo.toml | 2 +- .../bridge-hubs/bridge-hub-westend/src/lib.rs | 9 ++++++++- prdoc/pr_5747.prdoc | 13 +++++++++++++ 4 files changed, 23 insertions(+), 3 deletions(-) create mode 100644 prdoc/pr_5747.prdoc diff --git a/Cargo.lock b/Cargo.lock index 5e85570055b..7b3e54ff1fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2419,7 +2419,7 @@ dependencies = [ [[package]] name = "bridge-hub-westend-runtime" -version = "0.2.0" +version = "0.3.0" dependencies = [ "bp-asset-hub-rococo", "bp-asset-hub-westend", diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml index 67d4eff0f7f..e0ad1acc047 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bridge-hub-westend-runtime" -version = "0.2.0" +version = "0.3.0" authors.workspace = true edition.workspace = true description = "Westend's BridgeHub parachain runtime" diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs index ddd40dbf60e..bdac664ee43 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs @@ -100,6 +100,8 @@ use snowbridge_core::{ use testnet_parachains_constants::westend::{consensus::*, currency::*, fee::WeightToFee, time::*}; use xcm::VersionedLocation; +use westend_runtime_constants::system_parachain::{ASSET_HUB_ID, BRIDGE_HUB_ID}; + /// The address format for describing accounts. pub type Address = MultiAddress; @@ -152,6 +154,11 @@ pub type Migrations = ( >, // permanent pallet_xcm::migration::MigrateToLatestXcmVersion, + snowbridge_pallet_system::migration::v0::InitializeOnUpgrade< + Runtime, + ConstU32, + ConstU32, + >, ); parameter_types! { @@ -209,7 +216,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("bridge-hub-westend"), impl_name: create_runtime_str!("bridge-hub-westend"), authoring_version: 1, - spec_version: 1_016_000, + spec_version: 1_016_001, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 5, diff --git a/prdoc/pr_5747.prdoc b/prdoc/pr_5747.prdoc new file mode 100644 index 00000000000..ee786db658c --- /dev/null +++ b/prdoc/pr_5747.prdoc @@ -0,0 +1,13 @@ +title: "Snowbridge runtime migration on Westend" + +doc: + - audience: Runtime Dev + description: | + This is a backport for https://github.com/paritytech/polkadot-sdk/pull/5074 which missed + the runtime migration to initialize channels of the bridge. + +crates: + - name: bridge-hub-westend-runtime + bump: patch + + -- GitLab From 5a431470d2a4155a4bb00ea6ac07d7f9707f8e82 Mon Sep 17 00:00:00 2001 From: tmpolaczyk <44604217+tmpolaczyk@users.noreply.github.com> Date: Fri, 20 Sep 2024 09:17:39 +0200 Subject: [PATCH 085/125] Allow to call arbitrary runtime apis using RelayChainInterface (#5521) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When using the relay chain though a `Arc` there is no way to call arbitrary runtime apis. Both implementations of that trait allow this, so it feels natural to expose this functionality in the trait. This PR adds a `call_runtime_api` method to RelayChainInterface trait, and a separate function also named `call_runtime_api` which allows the caller to specify the input and output types, as opposed to having to encode them. This generic function cannot be part of the trait because a `dyn Trait` object cannot have generic methods. --------- Co-authored-by: Bastian Köcher --- cumulus/client/consensus/common/src/tests.rs | 9 +++++ cumulus/client/network/src/tests.rs | 9 +++++ cumulus/client/pov-recovery/src/tests.rs | 9 +++++ .../src/lib.rs | 19 +++++++++- .../client/relay-chain-interface/src/lib.rs | 37 ++++++++++++++++++- .../relay-chain-rpc-interface/src/lib.rs | 12 ++++++ .../src/rpc_client.rs | 26 ++++++++++--- prdoc/pr_5521.prdoc | 24 ++++++++++++ 8 files changed, 136 insertions(+), 9 deletions(-) create mode 100644 prdoc/pr_5521.prdoc diff --git a/cumulus/client/consensus/common/src/tests.rs b/cumulus/client/consensus/common/src/tests.rs index 06f90330d47..794ce30de3e 100644 --- a/cumulus/client/consensus/common/src/tests.rs +++ b/cumulus/client/consensus/common/src/tests.rs @@ -268,6 +268,15 @@ impl RelayChainInterface for Relaychain { async fn version(&self, _: PHash) -> RelayChainResult { unimplemented!("Not needed for test") } + + async fn call_runtime_api( + &self, + _method_name: &'static str, + _hash: PHash, + _payload: &[u8], + ) -> RelayChainResult> { + unimplemented!("Not needed for test") + } } fn sproof_with_best_parent(client: &Client) -> RelayStateSproofBuilder { diff --git a/cumulus/client/network/src/tests.rs b/cumulus/client/network/src/tests.rs index 1c8edd803ed..686943063bb 100644 --- a/cumulus/client/network/src/tests.rs +++ b/cumulus/client/network/src/tests.rs @@ -326,6 +326,15 @@ impl RelayChainInterface for DummyRelayChainInterface { system_version: 1, }) } + + async fn call_runtime_api( + &self, + _method_name: &'static str, + _hash: PHash, + _payload: &[u8], + ) -> RelayChainResult> { + unimplemented!("Not needed for test") + } } fn make_validator_and_api() -> ( diff --git a/cumulus/client/pov-recovery/src/tests.rs b/cumulus/client/pov-recovery/src/tests.rs index 5935824e173..539f7f33ad3 100644 --- a/cumulus/client/pov-recovery/src/tests.rs +++ b/cumulus/client/pov-recovery/src/tests.rs @@ -487,6 +487,15 @@ impl RelayChainInterface for Relaychain { ) -> RelayChainResult>>> { unimplemented!("Not needed for test"); } + + async fn call_runtime_api( + &self, + _method_name: &'static str, + _hash: PHash, + _payload: &[u8], + ) -> RelayChainResult> { + unimplemented!("Not needed for test") + } } fn make_candidate_chain(candidate_number_range: Range) -> Vec { diff --git a/cumulus/client/relay-chain-inprocess-interface/src/lib.rs b/cumulus/client/relay-chain-inprocess-interface/src/lib.rs index 629fa728be3..0455c03fc4d 100644 --- a/cumulus/client/relay-chain-inprocess-interface/src/lib.rs +++ b/cumulus/client/relay-chain-inprocess-interface/src/lib.rs @@ -36,7 +36,7 @@ use sc_client_api::{ StorageProof, }; use sc_telemetry::TelemetryWorkerHandle; -use sp_api::ProvideRuntimeApi; +use sp_api::{CallApiAt, CallApiAtParams, CallContext, ProvideRuntimeApi}; use sp_consensus::SyncOracle; use sp_core::Pair; use sp_state_machine::{Backend as StateBackend, StorageValue}; @@ -180,6 +180,23 @@ impl RelayChainInterface for RelayChainInProcessInterface { Ok(self.backend.blockchain().info().finalized_hash) } + async fn call_runtime_api( + &self, + method_name: &'static str, + hash: PHash, + payload: &[u8], + ) -> RelayChainResult> { + Ok(self.full_client.call_api_at(CallApiAtParams { + at: hash, + function: method_name, + arguments: payload.to_vec(), + overlayed_changes: &Default::default(), + call_context: CallContext::Offchain, + recorder: &None, + extensions: &Default::default(), + })?) + } + async fn is_major_syncing(&self) -> RelayChainResult { Ok(self.sync_oracle.is_major_syncing()) } diff --git a/cumulus/client/relay-chain-interface/src/lib.rs b/cumulus/client/relay-chain-interface/src/lib.rs index d02035e84e9..8d172e423eb 100644 --- a/cumulus/client/relay-chain-interface/src/lib.rs +++ b/cumulus/client/relay-chain-interface/src/lib.rs @@ -22,11 +22,11 @@ use sc_client_api::StorageProof; use sp_version::RuntimeVersion; use async_trait::async_trait; -use codec::Error as CodecError; +use codec::{Decode, Encode, Error as CodecError}; use jsonrpsee_core::ClientError as JsonRpcError; use sp_api::ApiError; -use cumulus_primitives_core::relay_chain::BlockId; +use cumulus_primitives_core::relay_chain::{BlockId, Hash as RelayHash}; pub use cumulus_primitives_core::{ relay_chain::{ BlockNumber, CommittedCandidateReceipt, CoreState, Hash as PHash, Header as PHeader, @@ -117,6 +117,14 @@ pub trait RelayChainInterface: Send + Sync { /// Get the hash of the finalized block. async fn finalized_block_hash(&self) -> RelayChainResult; + /// Call an arbitrary runtime api. The input and output are SCALE-encoded. + async fn call_runtime_api( + &self, + method_name: &'static str, + hash: RelayHash, + payload: &[u8], + ) -> RelayChainResult>; + /// Returns the whole contents of the downward message queue for the parachain we are collating /// for. /// @@ -296,6 +304,15 @@ where (**self).finalized_block_hash().await } + async fn call_runtime_api( + &self, + method_name: &'static str, + hash: RelayHash, + payload: &[u8], + ) -> RelayChainResult> { + (**self).call_runtime_api(method_name, hash, payload).await + } + async fn is_major_syncing(&self) -> RelayChainResult { (**self).is_major_syncing().await } @@ -364,3 +381,19 @@ where (**self).version(relay_parent).await } } + +/// Helper function to call an arbitrary runtime API using a `RelayChainInterface` client. +/// Unlike the trait method, this function can be generic, so it handles the encoding of input and +/// output params. +pub async fn call_runtime_api( + client: &(impl RelayChainInterface + ?Sized), + method_name: &'static str, + hash: RelayHash, + payload: impl Encode, +) -> RelayChainResult +where + R: Decode, +{ + let res = client.call_runtime_api(method_name, hash, &payload.encode()).await?; + Decode::decode(&mut &*res).map_err(Into::into) +} diff --git a/cumulus/client/relay-chain-rpc-interface/src/lib.rs b/cumulus/client/relay-chain-rpc-interface/src/lib.rs index 3698938bfd8..77dc1d7318a 100644 --- a/cumulus/client/relay-chain-rpc-interface/src/lib.rs +++ b/cumulus/client/relay-chain-rpc-interface/src/lib.rs @@ -165,6 +165,18 @@ impl RelayChainInterface for RelayChainRpcInterface { self.rpc_client.chain_get_finalized_head().await } + async fn call_runtime_api( + &self, + method_name: &'static str, + hash: RelayHash, + payload: &[u8], + ) -> RelayChainResult> { + self.rpc_client + .call_remote_runtime_function_encoded(method_name, hash, payload) + .await + .map(|bytes| bytes.to_vec()) + } + async fn is_major_syncing(&self) -> RelayChainResult { self.rpc_client.system_health().await.map(|h| h.is_syncing) } diff --git a/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs b/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs index 6e282281de6..381f4a046a4 100644 --- a/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs +++ b/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs @@ -148,15 +148,13 @@ impl RelayChainRpcClient { } } - /// Call a call to `state_call` rpc method. - pub async fn call_remote_runtime_function( + /// Same as `call_remote_runtime_function` but work on encoded data + pub async fn call_remote_runtime_function_encoded( &self, method_name: &str, hash: RelayHash, - payload: Option, - ) -> RelayChainResult { - let payload_bytes = - payload.map_or(sp_core::Bytes(Vec::new()), |v| sp_core::Bytes(v.encode())); + payload_bytes: &[u8], + ) -> RelayChainResult { let params = rpc_params! { method_name, payload_bytes, @@ -174,6 +172,22 @@ impl RelayChainRpcClient { ); }) .await?; + + Ok(res) + } + + /// Call a call to `state_call` rpc method. + pub async fn call_remote_runtime_function( + &self, + method_name: &str, + hash: RelayHash, + payload: Option, + ) -> RelayChainResult { + let payload_bytes = + payload.map_or(sp_core::Bytes(Vec::new()), |v| sp_core::Bytes(v.encode())); + let res = self + .call_remote_runtime_function_encoded(method_name, hash, &payload_bytes) + .await?; Decode::decode(&mut &*res.0).map_err(Into::into) } diff --git a/prdoc/pr_5521.prdoc b/prdoc/pr_5521.prdoc new file mode 100644 index 00000000000..564d9df58ce --- /dev/null +++ b/prdoc/pr_5521.prdoc @@ -0,0 +1,24 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Allow to call arbitrary runtime apis using RelayChainInterface + +doc: + - audience: Node Dev + description: | + This PR adds a `call_runtime_api` method to RelayChainInterface trait, and a separate function also named `call_runtime_api` + which allows the caller to specify the input and output types, as opposed to having to encode them. + +crates: + - name: cumulus-relay-chain-interface + bump: patch + - name: cumulus-client-consensus-common + bump: patch + - name: cumulus-client-pov-recovery + bump: patch + - name: cumulus-client-network + bump: patch + - name: cumulus-relay-chain-inprocess-interface + bump: patch + - name: cumulus-relay-chain-rpc-interface + bump: patch -- GitLab From 515fcc952cd52504ab7d3866a83adb9bf0f8e56b Mon Sep 17 00:00:00 2001 From: Liu-Cheng Xu Date: Sat, 21 Sep 2024 02:27:39 +0800 Subject: [PATCH 086/125] Avoid unnecessary state reset of `allowed_requests` when no block requests are sent (#5774) This PR is cherry-picked from https://github.com/paritytech/polkadot-sdk/pull/5663 so that I can maintain a smaller polkadot-sdk diff downstream sooner than later. cc @lexnv @dmitry-markin --------- Co-authored-by: Alexandru Vasile <60601340+lexnv@users.noreply.github.com> Co-authored-by: Dmitry Markin --- prdoc/pr_5774.prdoc | 13 +++++++++++++ .../network/sync/src/strategy/chain_sync.rs | 16 +++++++++++++--- 2 files changed, 26 insertions(+), 3 deletions(-) create mode 100644 prdoc/pr_5774.prdoc diff --git a/prdoc/pr_5774.prdoc b/prdoc/pr_5774.prdoc new file mode 100644 index 00000000000..15aa64f5410 --- /dev/null +++ b/prdoc/pr_5774.prdoc @@ -0,0 +1,13 @@ +title: Avoid unnecessary state reset of allowed_requests when no block requests are sent + +doc: + - audience: Node Dev + description: | + Previously, the state of `allowed_requests` was always reset to the default + even if there were no new block requests. This could cause an edge case + because `peer_block_request()` will return early next time when there are no ongoing block requests. + This patch fixes it by checking whether block requests are empty before updating the state. + +crates: + - name: sc-network-sync + bump: patch diff --git a/substrate/client/network/sync/src/strategy/chain_sync.rs b/substrate/client/network/sync/src/strategy/chain_sync.rs index fd0e3ea1a76..b0e28d00f64 100644 --- a/substrate/client/network/sync/src/strategy/chain_sync.rs +++ b/substrate/client/network/sync/src/strategy/chain_sync.rs @@ -148,6 +148,7 @@ impl Metrics { } } +#[derive(Debug, Clone)] enum AllowedRequests { Some(HashSet), All, @@ -1714,13 +1715,14 @@ where let best_queued = self.best_queued_number; let client = &self.client; let queue_blocks = &self.queue_blocks; - let allowed_requests = self.allowed_requests.take(); + let allowed_requests = self.allowed_requests.clone(); let max_parallel = if is_major_syncing { 1 } else { self.max_parallel_downloads }; let max_blocks_per_request = self.max_blocks_per_request; let gap_sync = &mut self.gap_sync; let disconnected_peers = &mut self.disconnected_peers; let metrics = self.metrics.as_ref(); - self.peers + let requests = self + .peers .iter_mut() .filter_map(move |(&id, peer)| { if !peer.state.is_available() || @@ -1819,7 +1821,15 @@ where None } }) - .collect() + .collect::>(); + + // Clear the allowed_requests state when sending new block requests + // to prevent multiple inflight block requests from being issued. + if !requests.is_empty() { + self.allowed_requests.take(); + } + + requests } /// Get a state request scheduled by sync to be sent out (if any). -- GitLab From 6ff41dcac7c8a2fb9f62e68a70c9a214e42d2bf3 Mon Sep 17 00:00:00 2001 From: Erin Shaben Date: Fri, 20 Sep 2024 19:12:37 -0400 Subject: [PATCH 087/125] Update README.md (#5780) # Description This PR just makes some minor adjustments to the README. The main goal of it is to consistently refer to the Polkadot SDK as such, without a hyphen. I noticed some other minor inconsistencies, so I fixed those while I was at it --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 702c853684c..b8ddf8427c9 100644 --- a/README.md +++ b/README.md @@ -30,10 +30,10 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/paritytec * [Introduction](https://paritytech.github.io/polkadot-sdk/master/polkadot_sdk_docs/polkadot_sdk/index.html) to each component of the Polkadot SDK: Substrate, FRAME, Cumulus, and XCM * [Guides](https://paritytech.github.io/polkadot-sdk/master/polkadot_sdk_docs/guides/index.html), - namely how to build your first FRAME pallet. + namely how to build your first FRAME pallet * [Templates](https://paritytech.github.io/polkadot-sdk/master/polkadot_sdk_docs/polkadot_sdk/templates/index.html) - for starting a new project. -* Other Resources: + for starting a new project +* Other resources: * [Polkadot Wiki -> Build](https://wiki.polkadot.network/docs/build-guide) ## 🚀 Releases @@ -41,7 +41,7 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/paritytec ![Current Stable Release](https://raw.githubusercontent.com/paritytech/release-registry/main/badges/polkadot-sdk-latest.svg)  ![Next Stable Release](https://raw.githubusercontent.com/paritytech/release-registry/main/badges/polkadot-sdk-next.svg) -The Polkadot-SDK is released every three months as a `stableYYMMDD` release. They are supported for +The Polkadot SDK is released every three months as a `stableYYMMDD` release. They are supported for one year with patches. See the next upcoming versions in the [Release Registry](https://github.com/paritytech/release-registry/). -- GitLab From 8735c66393978fec6fd811ef7832b08355a2885a Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Sun, 22 Sep 2024 18:29:54 +0200 Subject: [PATCH 088/125] Moved presets to the testnet runtimes (#5327) It is a first step for switching to the `frame-omni-bencher` for CI. This PR includes several changes related to generating chain specs plus: - [x] pallet `assigned_slots` fix missing `#[serde(skip)]` for phantom - [x] pallet `paras_inherent` benchmark fix - cherry-picked from https://github.com/paritytech/polkadot-sdk/pull/5688 - [x] migrates `get_preset` to the relevant runtimes - [x] fixes Rococo genesis presets - does not work https://gitlab.parity.io/parity/mirrors/polkadot-sdk/-/jobs/7317249 - [x] fixes Rococo benchmarks for CI - [x] migrate westend genesis - [x] remove wococo stuff Closes: https://github.com/paritytech/polkadot-sdk/issues/5680 ## Follow-ups - Fix for frame-omni-bencher https://github.com/paritytech/polkadot-sdk/pull/5655 - Enable new short-benchmarking CI - https://github.com/paritytech/polkadot-sdk/pull/5706 - Remove gitlab pipelines for short benchmarking - refactor all Cumulus runtimes to use `get_preset` - https://github.com/paritytech/polkadot-sdk/issues/5704 - https://github.com/paritytech/polkadot-sdk/issues/5705 - https://github.com/paritytech/polkadot-sdk/issues/5700 - [ ] Backport to the stable --------- Co-authored-by: command-bot <> Co-authored-by: ordian --- .gitlab/pipeline/short-benchmarks.yml | 21 + Cargo.lock | 26 +- .../common/src/genesis_config_helpers.rs | 1 - .../chains/relays/westend/src/genesis.rs | 4 +- .../src/genesis_config_presets.rs | 66 +-- .../assets/asset-hub-rococo/src/lib.rs | 5 +- .../assets/asset-hub-westend/Cargo.toml | 2 + .../src/genesis_config_presets.rs | 169 ++++++ .../assets/asset-hub-westend/src/lib.rs | 5 +- .../bridge-hubs/bridge-hub-rococo/Cargo.toml | 2 + .../src/genesis_config_presets.rs | 163 ++++++ .../bridge-hubs/bridge-hub-rococo/src/lib.rs | 5 +- .../bridge-hubs/bridge-hub-westend/Cargo.toml | 2 + .../src/genesis_config_presets.rs | 163 ++++++ .../bridge-hubs/bridge-hub-westend/src/lib.rs | 5 +- .../collectives-westend/Cargo.toml | 4 + .../src/genesis_config_presets.rs | 130 +++++ .../collectives-westend/src/lib.rs | 5 +- .../runtimes/constants/src/westend.rs | 5 + cumulus/polkadot-parachain/Cargo.toml | 7 +- .../src/chain_spec/asset_hubs.rs | 132 +---- .../src/chain_spec/bridge_hubs.rs | 201 +------ .../src/chain_spec/collectives.rs | 107 +--- .../polkadot-parachain/src/chain_spec/mod.rs | 30 +- .../src/chain_spec/rococo_parachain.rs | 3 +- polkadot/cli/src/command.rs | 12 - .../0001-dispute-valid-block.toml | 5 +- polkadot/node/service/Cargo.toml | 24 +- polkadot/node/service/chain-specs/wococo.json | 218 -------- polkadot/node/service/src/chain_spec.rs | 492 +----------------- polkadot/node/service/src/lib.rs | 18 +- polkadot/node/test/service/src/chain_spec.rs | 3 +- .../runtime/common/src/assigned_slots/mod.rs | 1 + .../rococo/src/genesis_config_presets.rs | 119 ++--- polkadot/runtime/rococo/src/lib.rs | 8 +- polkadot/runtime/westend/Cargo.toml | 8 +- .../westend/src/genesis_config_presets.rs | 459 ++++++++++++++++ polkadot/runtime/westend/src/lib.rs | 7 +- .../xcm-executor/integration-tests/Cargo.toml | 2 - .../xcm-executor/integration-tests/src/lib.rs | 3 +- prdoc/pr_5327.prdoc | 43 ++ .../chain-spec/src/genesis_config_builder.rs | 1 + substrate/client/chain-spec/src/lib.rs | 8 +- .../primitives/genesis-builder/src/lib.rs | 10 +- templates/parachain/node/Cargo.toml | 1 + templates/parachain/node/src/chain_spec.rs | 4 +- .../runtime/src/genesis_config_presets.rs | 92 ++-- 47 files changed, 1406 insertions(+), 1395 deletions(-) create mode 100644 cumulus/parachains/runtimes/assets/asset-hub-westend/src/genesis_config_presets.rs create mode 100644 cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/genesis_config_presets.rs create mode 100644 cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/genesis_config_presets.rs create mode 100644 cumulus/parachains/runtimes/collectives/collectives-westend/src/genesis_config_presets.rs delete mode 100644 polkadot/node/service/chain-specs/wococo.json create mode 100644 polkadot/runtime/westend/src/genesis_config_presets.rs create mode 100644 prdoc/pr_5327.prdoc diff --git a/.gitlab/pipeline/short-benchmarks.yml b/.gitlab/pipeline/short-benchmarks.yml index bc6dd04264c..ad09f3f7cb0 100644 --- a/.gitlab/pipeline/short-benchmarks.yml +++ b/.gitlab/pipeline/short-benchmarks.yml @@ -26,6 +26,27 @@ short-benchmark-westend: &short-bench script: - ./artifacts/polkadot benchmark pallet --chain $RUNTIME-dev --pallet "*" --extrinsic "*" --steps 2 --repeat 1 +short-benchmark-rococo: &short-bench + stage: short-benchmarks + extends: + - .docker-env + - .common-refs + needs: + - job: build-short-benchmark + artifacts: true + variables: + RUNTIME: rococo + # Enable debug assertions since we are running optimized builds for testing + # but still want to have debug assertions. + RUSTFLAGS: "-C debug-assertions -D warnings" + RUST_BACKTRACE: "full" + WASM_BUILD_NO_COLOR: 1 + WASM_BUILD_RUSTFLAGS: "-C debug-assertions -D warnings" + tags: + - benchmark + script: + - ./artifacts/polkadot benchmark pallet --chain $RUNTIME-dev --pallet "*" --extrinsic "*" --steps 2 --repeat 1 + # run short-benchmarks for system parachain runtimes from cumulus .short-benchmark-cumulus: &short-bench-cumulus diff --git a/Cargo.lock b/Cargo.lock index 7b3e54ff1fc..89e0b87c864 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1035,6 +1035,7 @@ dependencies = [ "polkadot-runtime-common", "primitive-types", "scale-info", + "serde_json", "snowbridge-router-primitives", "sp-api 26.0.0", "sp-block-builder", @@ -2293,6 +2294,7 @@ dependencies = [ "rococo-runtime-constants", "scale-info", "serde", + "serde_json", "snowbridge-beacon-primitives", "snowbridge-core", "snowbridge-outbound-queue-runtime-api", @@ -2479,6 +2481,7 @@ dependencies = [ "polkadot-runtime-common", "scale-info", "serde", + "serde_json", "snowbridge-beacon-primitives", "snowbridge-core", "snowbridge-outbound-queue-runtime-api", @@ -3156,6 +3159,7 @@ dependencies = [ "polkadot-parachain-primitives", "polkadot-runtime-common", "scale-info", + "serde_json", "sp-api 26.0.0", "sp-arithmetic 23.0.0", "sp-block-builder", @@ -3167,6 +3171,7 @@ dependencies = [ "sp-offchain", "sp-runtime 31.0.1", "sp-session", + "sp-std 14.0.0", "sp-storage 19.0.0", "sp-transaction-pool", "sp-version 29.0.0", @@ -13082,6 +13087,7 @@ dependencies = [ "sp-blockchain", "sp-consensus-aura", "sp-core 28.0.0", + "sp-genesis-builder", "sp-io 30.0.0", "sp-keystore 0.34.0", "sp-runtime 31.0.1", @@ -14885,7 +14891,6 @@ dependencies = [ "people-rococo-runtime", "people-westend-runtime", "polkadot-parachain-lib", - "polkadot-service", "rococo-parachain-runtime", "sc-chain-spec", "sc-cli", @@ -14895,7 +14900,7 @@ dependencies = [ "serde_json", "shell-runtime", "sp-core 28.0.0", - "sp-runtime 31.0.1", + "sp-genesis-builder", "staging-xcm", "substrate-build-script-utils", "testnet-parachains-constants", @@ -15695,22 +15700,17 @@ version = "7.0.0" dependencies = [ "assert_matches", "async-trait", - "bitvec", "frame-benchmarking", "frame-benchmarking-cli", "frame-metadata-hash-extension", - "frame-support", "frame-system", "frame-system-rpc-runtime-api", "futures", - "hex-literal", "is_executable", "kvdb", "kvdb-rocksdb", "log", "mmr-gadget", - "pallet-babe", - "pallet-staking", "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", "parity-db", @@ -15747,7 +15747,6 @@ dependencies = [ "polkadot-node-subsystem-types", "polkadot-node-subsystem-util", "polkadot-overseer", - "polkadot-parachain-primitives", "polkadot-primitives", "polkadot-primitives-test-helpers", "polkadot-rpc", @@ -15758,10 +15757,8 @@ dependencies = [ "rococo-runtime-constants", "sc-authority-discovery", "sc-basic-authorship", - "sc-block-builder", "sc-chain-spec", "sc-client-api", - "sc-client-db", "sc-consensus", "sc-consensus-babe", "sc-consensus-beefy", @@ -15770,7 +15767,6 @@ dependencies = [ "sc-executor 0.32.0", "sc-keystore", "sc-network", - "sc-network-common", "sc-network-sync", "sc-offchain", "sc-service", @@ -15779,7 +15775,6 @@ dependencies = [ "sc-telemetry", "sc-transaction-pool", "sc-transaction-pool-api", - "schnellru", "serde", "serde_json", "serial_test", @@ -15792,16 +15787,14 @@ dependencies = [ "sp-consensus-beefy", "sp-consensus-grandpa", "sp-core 28.0.0", + "sp-genesis-builder", "sp-inherents", "sp-io 30.0.0", "sp-keyring", - "sp-keystore 0.34.0", "sp-mmr-primitives", "sp-offchain", "sp-runtime 31.0.1", "sp-session", - "sp-state-machine 0.35.0", - "sp-storage 19.0.0", "sp-timestamp", "sp-tracing 16.0.0", "sp-transaction-pool", @@ -26485,6 +26478,7 @@ dependencies = [ "sp-block-builder", "sp-consensus-babe", "sp-consensus-beefy", + "sp-consensus-grandpa", "sp-core 28.0.0", "sp-genesis-builder", "sp-inherents", @@ -26997,12 +26991,10 @@ name = "xcm-executor-integration-tests" version = "1.0.0" dependencies = [ "frame-support", - "frame-system", "futures", "pallet-transaction-payment", "pallet-xcm", "parity-scale-codec", - "polkadot-service", "polkadot-test-client", "polkadot-test-runtime", "polkadot-test-service", diff --git a/cumulus/parachains/common/src/genesis_config_helpers.rs b/cumulus/parachains/common/src/genesis_config_helpers.rs index d70b8d5b9c1..cd98c3a729d 100644 --- a/cumulus/parachains/common/src/genesis_config_helpers.rs +++ b/cumulus/parachains/common/src/genesis_config_helpers.rs @@ -14,7 +14,6 @@ // limitations under the License. //! Some common helpers for declaring runtime's presets -// note: copied from: cumulus/polkadot-parachain/src/chain_spec/mod.rs use crate::{AccountId, Signature}; #[cfg(not(feature = "std"))] diff --git a/cumulus/parachains/integration-tests/emulated/chains/relays/westend/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/relays/westend/src/genesis.rs index 172e6e0ac93..f8d43cf4648 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/relays/westend/src/genesis.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/relays/westend/src/genesis.rs @@ -84,9 +84,7 @@ pub fn genesis() -> Storage { minimum_validator_count: 1, stakers: validators::initial_authorities() .iter() - .map(|x| { - (x.0.clone(), x.1.clone(), STASH, westend_runtime::StakerStatus::Validator) - }) + .map(|x| (x.0.clone(), x.1.clone(), STASH, pallet_staking::StakerStatus::Validator)) .collect(), invulnerables: validators::initial_authorities().iter().map(|x| x.0.clone()).collect(), force_era: pallet_staking::Forcing::ForceNone, diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/genesis_config_presets.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/genesis_config_presets.rs index 41b7e622b1b..1dbd92d6bff 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/genesis_config_presets.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/genesis_config_presets.rs @@ -15,70 +15,58 @@ //! # Asset Hub Rococo Runtime genesis config presets +use crate::*; use alloc::{vec, vec::Vec}; use cumulus_primitives_core::ParaId; use hex_literal::hex; -use parachains_common::{genesis_config_helpers::*, AccountId, AuraId, Balance as AssetHubBalance}; +use parachains_common::{genesis_config_helpers::*, AccountId, AuraId}; use sp_core::{crypto::UncheckedInto, sr25519}; use sp_genesis_builder::PresetId; -use testnet_parachains_constants::rococo::xcm_version::SAFE_XCM_VERSION; +use testnet_parachains_constants::rococo::{currency::UNITS as ROC, xcm_version::SAFE_XCM_VERSION}; -const ASSET_HUB_ROCOCO_ED: AssetHubBalance = crate::ExistentialDeposit::get(); - -/// Generate the session keys from individual elements. -/// -/// The input must be a tuple of individual keys (a single arg for now since we have just one key). -pub fn asset_hub_rococo_session_keys(keys: AuraId) -> crate::SessionKeys { - crate::SessionKeys { aura: keys } -} +const ASSET_HUB_ROCOCO_ED: Balance = ExistentialDeposit::get(); fn asset_hub_rococo_genesis( invulnerables: Vec<(AccountId, AuraId)>, endowed_accounts: Vec, - endowment: AssetHubBalance, + endowment: Balance, id: ParaId, ) -> serde_json::Value { - serde_json::json!({ - "balances": crate::BalancesConfig { - balances: endowed_accounts - .iter() - .cloned() - .map(|k| (k, endowment)) - .collect(), - }, - "parachainInfo": crate::ParachainInfoConfig { - parachain_id: id, - ..Default::default() + let config = RuntimeGenesisConfig { + balances: BalancesConfig { + balances: endowed_accounts.iter().cloned().map(|k| (k, endowment)).collect(), }, - "collatorSelection": crate::CollatorSelectionConfig { + parachain_info: ParachainInfoConfig { parachain_id: id, ..Default::default() }, + collator_selection: CollatorSelectionConfig { invulnerables: invulnerables.iter().cloned().map(|(acc, _)| acc).collect(), candidacy_bond: ASSET_HUB_ROCOCO_ED * 16, ..Default::default() }, - "session": crate::SessionConfig { + session: SessionConfig { keys: invulnerables .into_iter() .map(|(acc, aura)| { ( - acc.clone(), // account id - acc, // validator id - asset_hub_rococo_session_keys(aura), // session keys + acc.clone(), // account id + acc, // validator id + SessionKeys { aura }, // session keys ) }) .collect(), ..Default::default() }, - "polkadotXcm": crate::PolkadotXcmConfig { + polkadot_xcm: PolkadotXcmConfig { safe_xcm_version: Some(SAFE_XCM_VERSION), ..Default::default() - } - }) + }, + ..Default::default() + }; + + serde_json::to_value(config).expect("Could not build genesis config.") } /// Encapsulates names of predefined presets. mod preset_names { - pub const PRESET_DEVELOPMENT: &str = "development"; - pub const PRESET_LOCAL: &str = "local"; pub const PRESET_GENESIS: &str = "genesis"; } @@ -118,7 +106,7 @@ pub fn get_preset(id: &PresetId) -> Option> { ASSET_HUB_ROCOCO_ED * 524_288, 1000.into(), ), - Ok(PRESET_LOCAL) => asset_hub_rococo_genesis( + Ok(sp_genesis_builder::LOCAL_TESTNET_RUNTIME_PRESET) => asset_hub_rococo_genesis( // initial collators. vec![ ( @@ -144,10 +132,10 @@ pub fn get_preset(id: &PresetId) -> Option> { get_account_id_from_seed::("Eve//stash"), get_account_id_from_seed::("Ferdie//stash"), ], - testnet_parachains_constants::rococo::currency::UNITS * 1_000_000, + ROC * 1_000_000, 1000.into(), ), - Ok(PRESET_DEVELOPMENT) => asset_hub_rococo_genesis( + Ok(sp_genesis_builder::DEV_RUNTIME_PRESET) => asset_hub_rococo_genesis( // initial collators. vec![( get_account_id_from_seed::("Alice"), @@ -159,10 +147,10 @@ pub fn get_preset(id: &PresetId) -> Option> { get_account_id_from_seed::("Alice//stash"), get_account_id_from_seed::("Bob//stash"), ], - testnet_parachains_constants::rococo::currency::UNITS * 1_000_000, + ROC * 1_000_000, 1000.into(), ), - Err(_) | Ok(_) => return None, + _ => return None, }; Some( @@ -177,7 +165,7 @@ pub fn preset_names() -> Vec { use preset_names::*; vec![ PresetId::from(PRESET_GENESIS), - PresetId::from(PRESET_DEVELOPMENT), - PresetId::from(PRESET_LOCAL), + PresetId::from(sp_genesis_builder::DEV_RUNTIME_PRESET), + PresetId::from(sp_genesis_builder::LOCAL_TESTNET_RUNTIME_PRESET), ] } diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs index efe4a4052a9..d495c78e5a2 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs @@ -41,7 +41,6 @@ use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; use cumulus_primitives_core::AggregateMessageOrigin; use sp_api::impl_runtime_apis; use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; -use sp_genesis_builder::PresetId; use sp_runtime::{ create_runtime_str, generic, impl_opaque_keys, traits::{AccountIdConversion, BlakeTwo256, Block as BlockT, Saturating, Verify}, @@ -1765,11 +1764,11 @@ impl_runtime_apis! { build_state::(config) } - fn get_preset(id: &Option) -> Option> { + fn get_preset(id: &Option) -> Option> { get_preset::(id, &genesis_config_presets::get_preset) } - fn preset_names() -> Vec { + fn preset_names() -> Vec { genesis_config_presets::preset_names() } } diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml b/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml index 77130ff846b..1434c3e3b60 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml @@ -14,6 +14,7 @@ codec = { features = ["derive", "max-encoded-len"], workspace = true } hex-literal = { workspace = true, default-features = true } log = { workspace = true } scale-info = { features = ["derive"], workspace = true } +serde_json = { features = ["alloc"], workspace = true } # Substrate frame-benchmarking = { optional = true, workspace = true } @@ -233,6 +234,7 @@ std = [ "polkadot-runtime-common/std", "primitive-types/std", "scale-info/std", + "serde_json/std", "snowbridge-router-primitives/std", "sp-api/std", "sp-block-builder/std", diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/genesis_config_presets.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/genesis_config_presets.rs new file mode 100644 index 00000000000..b287dcd5621 --- /dev/null +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/genesis_config_presets.rs @@ -0,0 +1,169 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! # Asset Hub Westend Runtime genesis config presets + +use crate::*; +use alloc::{vec, vec::Vec}; +use cumulus_primitives_core::ParaId; +use hex_literal::hex; +use parachains_common::{genesis_config_helpers::*, AccountId, AuraId}; +use sp_core::{crypto::UncheckedInto, sr25519}; +use sp_genesis_builder::PresetId; +use testnet_parachains_constants::westend::{ + currency::UNITS as WND, xcm_version::SAFE_XCM_VERSION, +}; + +const ASSET_HUB_WESTEND_ED: Balance = ExistentialDeposit::get(); + +fn asset_hub_westend_genesis( + invulnerables: Vec<(AccountId, AuraId)>, + endowed_accounts: Vec, + endowment: Balance, + id: ParaId, +) -> serde_json::Value { + let config = RuntimeGenesisConfig { + balances: BalancesConfig { + balances: endowed_accounts.iter().cloned().map(|k| (k, endowment)).collect(), + }, + parachain_info: ParachainInfoConfig { parachain_id: id, ..Default::default() }, + collator_selection: CollatorSelectionConfig { + invulnerables: invulnerables.iter().cloned().map(|(acc, _)| acc).collect(), + candidacy_bond: ASSET_HUB_WESTEND_ED * 16, + ..Default::default() + }, + session: SessionConfig { + keys: invulnerables + .into_iter() + .map(|(acc, aura)| { + ( + acc.clone(), // account id + acc, // validator id + SessionKeys { aura }, // session keys + ) + }) + .collect(), + ..Default::default() + }, + polkadot_xcm: PolkadotXcmConfig { + safe_xcm_version: Some(SAFE_XCM_VERSION), + ..Default::default() + }, + ..Default::default() + }; + + serde_json::to_value(config).expect("Could not build genesis config.") +} + +/// Encapsulates names of predefined presets. +mod preset_names { + pub const PRESET_GENESIS: &str = "genesis"; +} + +/// Provides the JSON representation of predefined genesis config for given `id`. +pub fn get_preset(id: &PresetId) -> Option> { + use preset_names::*; + let patch = match id.try_into() { + Ok(PRESET_GENESIS) => asset_hub_westend_genesis( + // initial collators. + vec![ + ( + hex!("9cfd429fa002114f33c1d3e211501d62830c9868228eb3b4b8ae15a83de04325").into(), + hex!("9cfd429fa002114f33c1d3e211501d62830c9868228eb3b4b8ae15a83de04325") + .unchecked_into(), + ), + ( + hex!("12a03fb4e7bda6c9a07ec0a11d03c24746943e054ff0bb04938970104c783876").into(), + hex!("12a03fb4e7bda6c9a07ec0a11d03c24746943e054ff0bb04938970104c783876") + .unchecked_into(), + ), + ( + hex!("1256436307dfde969324e95b8c62cb9101f520a39435e6af0f7ac07b34e1931f").into(), + hex!("1256436307dfde969324e95b8c62cb9101f520a39435e6af0f7ac07b34e1931f") + .unchecked_into(), + ), + ( + hex!("98102b7bca3f070f9aa19f58feed2c0a4e107d203396028ec17a47e1ed80e322").into(), + hex!("98102b7bca3f070f9aa19f58feed2c0a4e107d203396028ec17a47e1ed80e322") + .unchecked_into(), + ), + ], + Vec::new(), + ASSET_HUB_WESTEND_ED * 4096, + 1000.into(), + ), + Ok(sp_genesis_builder::LOCAL_TESTNET_RUNTIME_PRESET) => asset_hub_westend_genesis( + // initial collators. + vec![ + ( + get_account_id_from_seed::("Alice"), + get_collator_keys_from_seed::("Alice"), + ), + ( + get_account_id_from_seed::("Bob"), + get_collator_keys_from_seed::("Bob"), + ), + ], + vec![ + get_account_id_from_seed::("Alice"), + get_account_id_from_seed::("Bob"), + get_account_id_from_seed::("Charlie"), + get_account_id_from_seed::("Dave"), + get_account_id_from_seed::("Eve"), + get_account_id_from_seed::("Ferdie"), + get_account_id_from_seed::("Alice//stash"), + get_account_id_from_seed::("Bob//stash"), + get_account_id_from_seed::("Charlie//stash"), + get_account_id_from_seed::("Dave//stash"), + get_account_id_from_seed::("Eve//stash"), + get_account_id_from_seed::("Ferdie//stash"), + ], + WND * 1_000_000, + 1000.into(), + ), + Ok(sp_genesis_builder::DEV_RUNTIME_PRESET) => asset_hub_westend_genesis( + // initial collators. + vec![( + get_account_id_from_seed::("Alice"), + get_collator_keys_from_seed::("Alice"), + )], + vec![ + get_account_id_from_seed::("Alice"), + get_account_id_from_seed::("Bob"), + get_account_id_from_seed::("Alice//stash"), + get_account_id_from_seed::("Bob//stash"), + ], + WND * 1_000_000, + 1000.into(), + ), + _ => return None, + }; + + Some( + serde_json::to_string(&patch) + .expect("serialization to json is expected to work. qed.") + .into_bytes(), + ) +} + +/// List of supported presets. +pub fn preset_names() -> Vec { + use preset_names::*; + vec![ + PresetId::from(PRESET_GENESIS), + PresetId::from(sp_genesis_builder::DEV_RUNTIME_PRESET), + PresetId::from(sp_genesis_builder::LOCAL_TESTNET_RUNTIME_PRESET), + ] +} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs index 712b5c0ada9..ca832a5e47c 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -24,6 +24,7 @@ #[cfg(feature = "std")] include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); +mod genesis_config_presets; mod weights; pub mod xcm_config; @@ -1860,11 +1861,11 @@ impl_runtime_apis! { } fn get_preset(id: &Option) -> Option> { - get_preset::(id, |_| None) + get_preset::(id, &genesis_config_presets::get_preset) } fn preset_names() -> Vec { - vec![] + genesis_config_presets::preset_names() } } } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml index 9fa1f3b1602..9a76e61ecb2 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml @@ -22,6 +22,7 @@ scale-info = { features = [ "derive", ], workspace = true } serde = { optional = true, features = ["derive"], workspace = true, default-features = true } +serde_json = { features = ["alloc"], workspace = true } # Substrate frame-benchmarking = { optional = true, workspace = true } @@ -191,6 +192,7 @@ std = [ "rococo-runtime-constants/std", "scale-info/std", "serde", + "serde_json/std", "snowbridge-beacon-primitives/std", "snowbridge-core/std", "snowbridge-outbound-queue-runtime-api/std", diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/genesis_config_presets.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/genesis_config_presets.rs new file mode 100644 index 00000000000..e5d61598564 --- /dev/null +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/genesis_config_presets.rs @@ -0,0 +1,163 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! # Bridge Hub Rococo Runtime genesis config presets + +use crate::*; +use alloc::{vec, vec::Vec}; +use cumulus_primitives_core::ParaId; +use parachains_common::{genesis_config_helpers::*, AccountId, AuraId}; +use sp_core::sr25519; +use sp_genesis_builder::PresetId; +use testnet_parachains_constants::rococo::xcm_version::SAFE_XCM_VERSION; + +const BRIDGE_HUB_ROCOCO_ED: Balance = ExistentialDeposit::get(); + +fn bridge_hub_rococo_genesis( + invulnerables: Vec<(AccountId, AuraId)>, + endowed_accounts: Vec, + id: ParaId, + bridges_pallet_owner: Option, + asset_hub_para_id: ParaId, +) -> serde_json::Value { + let config = RuntimeGenesisConfig { + balances: BalancesConfig { + balances: endowed_accounts + .iter() + .cloned() + .map(|k| (k, 1u128 << 60)) + .collect::>(), + }, + parachain_info: ParachainInfoConfig { parachain_id: id, ..Default::default() }, + collator_selection: CollatorSelectionConfig { + invulnerables: invulnerables.iter().cloned().map(|(acc, _)| acc).collect(), + candidacy_bond: BRIDGE_HUB_ROCOCO_ED * 16, + ..Default::default() + }, + session: SessionConfig { + keys: invulnerables + .into_iter() + .map(|(acc, aura)| { + ( + acc.clone(), // account id + acc, // validator id + SessionKeys { aura }, // session keys + ) + }) + .collect(), + ..Default::default() + }, + polkadot_xcm: PolkadotXcmConfig { + safe_xcm_version: Some(SAFE_XCM_VERSION), + ..Default::default() + }, + bridge_westend_grandpa: BridgeWestendGrandpaConfig { + owner: bridges_pallet_owner.clone(), + ..Default::default() + }, + bridge_westend_messages: BridgeWestendMessagesConfig { + owner: bridges_pallet_owner.clone(), + ..Default::default() + }, + ethereum_system: EthereumSystemConfig { + para_id: id, + asset_hub_para_id, + ..Default::default() + }, + ..Default::default() + }; + + serde_json::to_value(config).expect("Could not build genesis config.") +} + +/// Provides the JSON representation of predefined genesis config for given `id`. +pub fn get_preset(id: &sp_genesis_builder::PresetId) -> Option> { + let patch = match id.try_into() { + Ok(sp_genesis_builder::LOCAL_TESTNET_RUNTIME_PRESET) => bridge_hub_rococo_genesis( + // initial collators. + vec![ + ( + get_account_id_from_seed::("Alice"), + get_collator_keys_from_seed::("Alice"), + ), + ( + get_account_id_from_seed::("Bob"), + get_collator_keys_from_seed::("Bob"), + ), + ], + vec![ + get_account_id_from_seed::("Alice"), + get_account_id_from_seed::("Bob"), + get_account_id_from_seed::("Charlie"), + get_account_id_from_seed::("Dave"), + get_account_id_from_seed::("Eve"), + get_account_id_from_seed::("Ferdie"), + get_account_id_from_seed::("Alice//stash"), + get_account_id_from_seed::("Bob//stash"), + get_account_id_from_seed::("Charlie//stash"), + get_account_id_from_seed::("Dave//stash"), + get_account_id_from_seed::("Eve//stash"), + get_account_id_from_seed::("Ferdie//stash"), + ], + 1013.into(), + Some(get_account_id_from_seed::("Bob")), + rococo_runtime_constants::system_parachain::ASSET_HUB_ID.into(), + ), + Ok(sp_genesis_builder::DEV_RUNTIME_PRESET) => bridge_hub_rococo_genesis( + // initial collators. + vec![ + ( + get_account_id_from_seed::("Alice"), + get_collator_keys_from_seed::("Alice"), + ), + ( + get_account_id_from_seed::("Bob"), + get_collator_keys_from_seed::("Bob"), + ), + ], + vec![ + get_account_id_from_seed::("Alice"), + get_account_id_from_seed::("Bob"), + get_account_id_from_seed::("Charlie"), + get_account_id_from_seed::("Dave"), + get_account_id_from_seed::("Eve"), + get_account_id_from_seed::("Ferdie"), + get_account_id_from_seed::("Alice//stash"), + get_account_id_from_seed::("Bob//stash"), + get_account_id_from_seed::("Charlie//stash"), + get_account_id_from_seed::("Dave//stash"), + get_account_id_from_seed::("Eve//stash"), + get_account_id_from_seed::("Ferdie//stash"), + ], + 1013.into(), + Some(get_account_id_from_seed::("Bob")), + rococo_runtime_constants::system_parachain::ASSET_HUB_ID.into(), + ), + _ => return None, + }; + Some( + serde_json::to_string(&patch) + .expect("serialization to json is expected to work. qed.") + .into_bytes(), + ) +} + +/// List of supported presets. +pub fn preset_names() -> Vec { + vec![ + PresetId::from(sp_genesis_builder::DEV_RUNTIME_PRESET), + PresetId::from(sp_genesis_builder::LOCAL_TESTNET_RUNTIME_PRESET), + ] +} diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs index 6c6e2ec7efd..5158349cdb4 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs @@ -32,6 +32,7 @@ pub mod bridge_common_config; pub mod bridge_to_bulletin_config; pub mod bridge_to_ethereum_config; pub mod bridge_to_westend_config; +mod genesis_config_presets; mod weights; pub mod xcm_config; @@ -1448,11 +1449,11 @@ impl_runtime_apis! { } fn get_preset(id: &Option) -> Option> { - get_preset::(id, |_| None) + get_preset::(id, &genesis_config_presets::get_preset) } fn preset_names() -> Vec { - vec![] + genesis_config_presets::preset_names() } } } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml index e0ad1acc047..a0233bf2ea4 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml @@ -18,6 +18,7 @@ hex-literal = { workspace = true, default-features = true } log = { workspace = true } scale-info = { features = ["derive"], workspace = true } serde = { optional = true, features = ["derive"], workspace = true, default-features = true } +serde_json = { features = ["alloc"], workspace = true } # Substrate frame-benchmarking = { optional = true, workspace = true } @@ -181,6 +182,7 @@ std = [ "polkadot-runtime-common/std", "scale-info/std", "serde", + "serde_json/std", "snowbridge-beacon-primitives/std", "snowbridge-core/std", "snowbridge-outbound-queue-runtime-api/std", diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/genesis_config_presets.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/genesis_config_presets.rs new file mode 100644 index 00000000000..4c948656d9f --- /dev/null +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/genesis_config_presets.rs @@ -0,0 +1,163 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! # Bridge Hub Westend Runtime genesis config presets + +use crate::*; +use alloc::{vec, vec::Vec}; +use cumulus_primitives_core::ParaId; +use parachains_common::{genesis_config_helpers::*, AccountId, AuraId}; +use sp_core::sr25519; +use sp_genesis_builder::PresetId; +use testnet_parachains_constants::westend::xcm_version::SAFE_XCM_VERSION; + +const BRIDGE_HUB_WESTEND_ED: Balance = ExistentialDeposit::get(); + +fn bridge_hub_westend_genesis( + invulnerables: Vec<(AccountId, AuraId)>, + endowed_accounts: Vec, + id: ParaId, + bridges_pallet_owner: Option, + asset_hub_para_id: ParaId, +) -> serde_json::Value { + let config = RuntimeGenesisConfig { + balances: BalancesConfig { + balances: endowed_accounts + .iter() + .cloned() + .map(|k| (k, 1u128 << 60)) + .collect::>(), + }, + parachain_info: ParachainInfoConfig { parachain_id: id, ..Default::default() }, + collator_selection: CollatorSelectionConfig { + invulnerables: invulnerables.iter().cloned().map(|(acc, _)| acc).collect(), + candidacy_bond: BRIDGE_HUB_WESTEND_ED * 16, + ..Default::default() + }, + session: SessionConfig { + keys: invulnerables + .into_iter() + .map(|(acc, aura)| { + ( + acc.clone(), // account id + acc, // validator id + SessionKeys { aura }, // session keys + ) + }) + .collect(), + ..Default::default() + }, + polkadot_xcm: PolkadotXcmConfig { + safe_xcm_version: Some(SAFE_XCM_VERSION), + ..Default::default() + }, + bridge_rococo_grandpa: BridgeRococoGrandpaConfig { + owner: bridges_pallet_owner.clone(), + ..Default::default() + }, + bridge_rococo_messages: BridgeRococoMessagesConfig { + owner: bridges_pallet_owner.clone(), + ..Default::default() + }, + ethereum_system: EthereumSystemConfig { + para_id: id, + asset_hub_para_id, + ..Default::default() + }, + ..Default::default() + }; + + serde_json::to_value(config).expect("Could not build genesis config.") +} + +/// Provides the JSON representation of predefined genesis config for given `id`. +pub fn get_preset(id: &sp_genesis_builder::PresetId) -> Option> { + let patch = match id.try_into() { + Ok(sp_genesis_builder::LOCAL_TESTNET_RUNTIME_PRESET) => bridge_hub_westend_genesis( + // initial collators. + vec![ + ( + get_account_id_from_seed::("Alice"), + get_collator_keys_from_seed::("Alice"), + ), + ( + get_account_id_from_seed::("Bob"), + get_collator_keys_from_seed::("Bob"), + ), + ], + vec![ + get_account_id_from_seed::("Alice"), + get_account_id_from_seed::("Bob"), + get_account_id_from_seed::("Charlie"), + get_account_id_from_seed::("Dave"), + get_account_id_from_seed::("Eve"), + get_account_id_from_seed::("Ferdie"), + get_account_id_from_seed::("Alice//stash"), + get_account_id_from_seed::("Bob//stash"), + get_account_id_from_seed::("Charlie//stash"), + get_account_id_from_seed::("Dave//stash"), + get_account_id_from_seed::("Eve//stash"), + get_account_id_from_seed::("Ferdie//stash"), + ], + 1002.into(), + Some(get_account_id_from_seed::("Bob")), + westend_runtime_constants::system_parachain::ASSET_HUB_ID.into(), + ), + Ok(sp_genesis_builder::DEV_RUNTIME_PRESET) => bridge_hub_westend_genesis( + // initial collators. + vec![ + ( + get_account_id_from_seed::("Alice"), + get_collator_keys_from_seed::("Alice"), + ), + ( + get_account_id_from_seed::("Bob"), + get_collator_keys_from_seed::("Bob"), + ), + ], + vec![ + get_account_id_from_seed::("Alice"), + get_account_id_from_seed::("Bob"), + get_account_id_from_seed::("Charlie"), + get_account_id_from_seed::("Dave"), + get_account_id_from_seed::("Eve"), + get_account_id_from_seed::("Ferdie"), + get_account_id_from_seed::("Alice//stash"), + get_account_id_from_seed::("Bob//stash"), + get_account_id_from_seed::("Charlie//stash"), + get_account_id_from_seed::("Dave//stash"), + get_account_id_from_seed::("Eve//stash"), + get_account_id_from_seed::("Ferdie//stash"), + ], + 1002.into(), + Some(get_account_id_from_seed::("Bob")), + westend_runtime_constants::system_parachain::ASSET_HUB_ID.into(), + ), + _ => return None, + }; + Some( + serde_json::to_string(&patch) + .expect("serialization to json is expected to work. qed.") + .into_bytes(), + ) +} + +/// List of supported presets. +pub fn preset_names() -> Vec { + vec![ + PresetId::from(sp_genesis_builder::DEV_RUNTIME_PRESET), + PresetId::from(sp_genesis_builder::LOCAL_TESTNET_RUNTIME_PRESET), + ] +} diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs index bdac664ee43..65397f038d5 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs @@ -30,6 +30,7 @@ include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); pub mod bridge_common_config; pub mod bridge_to_ethereum_config; pub mod bridge_to_rococo_config; +mod genesis_config_presets; mod weights; pub mod xcm_config; @@ -1301,11 +1302,11 @@ impl_runtime_apis! { } fn get_preset(id: &Option) -> Option> { - get_preset::(id, |_| None) + get_preset::(id, &genesis_config_presets::get_preset) } fn preset_names() -> Vec { - vec![] + genesis_config_presets::preset_names() } } } diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml b/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml index e98508ea02e..170d6d22605 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml @@ -14,6 +14,7 @@ codec = { features = ["derive", "max-encoded-len"], workspace = true } hex-literal = { workspace = true, default-features = true } log = { workspace = true } scale-info = { features = ["derive"], workspace = true } +serde_json = { features = ["alloc"], workspace = true } # Substrate frame-benchmarking = { optional = true, workspace = true } @@ -54,6 +55,7 @@ sp-inherents = { workspace = true } sp-offchain = { workspace = true } sp-runtime = { workspace = true } sp-session = { workspace = true } +sp-std = { workspace = true } sp-storage = { workspace = true } sp-transaction-pool = { workspace = true } sp-version = { workspace = true } @@ -218,6 +220,7 @@ std = [ "polkadot-parachain-primitives/std", "polkadot-runtime-common/std", "scale-info/std", + "serde_json/std", "sp-api/std", "sp-arithmetic/std", "sp-block-builder/std", @@ -228,6 +231,7 @@ std = [ "sp-offchain/std", "sp-runtime/std", "sp-session/std", + "sp-std/std", "sp-storage/std", "sp-transaction-pool/std", "sp-version/std", diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/genesis_config_presets.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/genesis_config_presets.rs new file mode 100644 index 00000000000..30a23d7aaea --- /dev/null +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/genesis_config_presets.rs @@ -0,0 +1,130 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! # Bridge Hub Westend Runtime genesis config presets + +use crate::*; +use alloc::{vec, vec::Vec}; +use cumulus_primitives_core::ParaId; +use parachains_common::{genesis_config_helpers::*, AccountId, AuraId}; +use sp_core::sr25519; +use sp_genesis_builder::PresetId; +use testnet_parachains_constants::westend::xcm_version::SAFE_XCM_VERSION; + +const COLLECTIVES_WESTEND_ED: Balance = ExistentialDeposit::get(); + +fn collectives_westend_genesis( + invulnerables: Vec<(AccountId, AuraId)>, + endowed_accounts: Vec, + id: ParaId, +) -> serde_json::Value { + let config = RuntimeGenesisConfig { + balances: BalancesConfig { + balances: endowed_accounts + .iter() + .cloned() + .map(|k| (k, COLLECTIVES_WESTEND_ED * 4096)) + .collect::>(), + }, + parachain_info: ParachainInfoConfig { parachain_id: id, ..Default::default() }, + collator_selection: CollatorSelectionConfig { + invulnerables: invulnerables.iter().cloned().map(|(acc, _)| acc).collect(), + candidacy_bond: COLLECTIVES_WESTEND_ED * 16, + ..Default::default() + }, + session: SessionConfig { + keys: invulnerables + .into_iter() + .map(|(acc, aura)| { + ( + acc.clone(), // account id + acc, // validator id + SessionKeys { aura }, // session keys + ) + }) + .collect(), + ..Default::default() + }, + polkadot_xcm: PolkadotXcmConfig { + safe_xcm_version: Some(SAFE_XCM_VERSION), + ..Default::default() + }, + ..Default::default() + }; + + serde_json::to_value(config).expect("Could not build genesis config.") +} + +/// Provides the JSON representation of predefined genesis config for given `id`. +pub fn get_preset(id: &sp_genesis_builder::PresetId) -> Option> { + let patch = match id.try_into() { + Ok(sp_genesis_builder::LOCAL_TESTNET_RUNTIME_PRESET) => collectives_westend_genesis( + // initial collators. + vec![ + ( + get_account_id_from_seed::("Alice"), + get_collator_keys_from_seed::("Alice"), + ), + ( + get_account_id_from_seed::("Bob"), + get_collator_keys_from_seed::("Bob"), + ), + ], + vec![ + get_account_id_from_seed::("Alice"), + get_account_id_from_seed::("Bob"), + get_account_id_from_seed::("Charlie"), + get_account_id_from_seed::("Dave"), + get_account_id_from_seed::("Eve"), + get_account_id_from_seed::("Ferdie"), + get_account_id_from_seed::("Alice//stash"), + get_account_id_from_seed::("Bob//stash"), + get_account_id_from_seed::("Charlie//stash"), + get_account_id_from_seed::("Dave//stash"), + get_account_id_from_seed::("Eve//stash"), + get_account_id_from_seed::("Ferdie//stash"), + ], + 1001.into(), + ), + Ok(sp_genesis_builder::DEV_RUNTIME_PRESET) => collectives_westend_genesis( + // initial collators. + vec![( + get_account_id_from_seed::("Alice"), + get_collator_keys_from_seed::("Alice"), + )], + vec![ + get_account_id_from_seed::("Alice"), + get_account_id_from_seed::("Bob"), + get_account_id_from_seed::("Alice//stash"), + get_account_id_from_seed::("Bob//stash"), + ], + 1001.into(), + ), + _ => return None, + }; + Some( + serde_json::to_string(&patch) + .expect("serialization to json is expected to work. qed.") + .into_bytes(), + ) +} + +/// List of supported presets. +pub fn preset_names() -> Vec { + vec![ + PresetId::from(sp_genesis_builder::DEV_RUNTIME_PRESET), + PresetId::from(sp_genesis_builder::LOCAL_TESTNET_RUNTIME_PRESET), + ] +} diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs index f22feb70382..ceedf4f86b2 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs @@ -37,6 +37,7 @@ include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); pub mod ambassador; +mod genesis_config_presets; pub mod impls; mod weights; pub mod xcm_config; @@ -1149,11 +1150,11 @@ impl_runtime_apis! { } fn get_preset(id: &Option) -> Option> { - get_preset::(id, |_| None) + get_preset::(id, &genesis_config_presets::get_preset) } fn preset_names() -> Vec { - vec![] + genesis_config_presets::preset_names() } } } diff --git a/cumulus/parachains/runtimes/constants/src/westend.rs b/cumulus/parachains/runtimes/constants/src/westend.rs index 47ba8f7e97a..8c4c0c59435 100644 --- a/cumulus/parachains/runtimes/constants/src/westend.rs +++ b/cumulus/parachains/runtimes/constants/src/westend.rs @@ -185,3 +185,8 @@ pub mod snowbridge { pub EthereumLocation: Location = Location::new(2, EthereumNetwork::get()); } } + +pub mod xcm_version { + /// The default XCM version to set in genesis config. + pub const SAFE_XCM_VERSION: u32 = xcm::prelude::XCM_VERSION; +} diff --git a/cumulus/polkadot-parachain/Cargo.toml b/cumulus/polkadot-parachain/Cargo.toml index 383e0f158bf..ad88be60d74 100644 --- a/cumulus/polkadot-parachain/Cargo.toml +++ b/cumulus/polkadot-parachain/Cargo.toml @@ -45,14 +45,13 @@ testnet-parachains-constants = { features = [ ], workspace = true } # Substrate -sp-runtime = { workspace = true } sp-core = { workspace = true, default-features = true } sc-cli = { workspace = true, default-features = true } sc-service = { workspace = true, default-features = true } sc-chain-spec = { workspace = true, default-features = true } +sp-genesis-builder = { workspace = true, default-features = true } # Polkadot -polkadot-service = { workspace = true, default-features = true } xcm = { workspace = true, default-features = true } # Cumulus @@ -67,9 +66,7 @@ runtime-benchmarks = [ "cumulus-primitives-core/runtime-benchmarks", "parachains-common/runtime-benchmarks", "polkadot-parachain-lib/runtime-benchmarks", - "polkadot-service/runtime-benchmarks", "sc-service/runtime-benchmarks", - "sp-runtime/runtime-benchmarks", "asset-hub-rococo-runtime/runtime-benchmarks", "asset-hub-westend-runtime/runtime-benchmarks", @@ -87,8 +84,6 @@ runtime-benchmarks = [ ] try-runtime = [ "polkadot-parachain-lib/try-runtime", - "polkadot-service/try-runtime", - "sp-runtime/try-runtime", "asset-hub-rococo-runtime/try-runtime", "asset-hub-westend-runtime/try-runtime", diff --git a/cumulus/polkadot-parachain/src/chain_spec/asset_hubs.rs b/cumulus/polkadot-parachain/src/chain_spec/asset_hubs.rs index 233ae986696..699c0b5ce77 100644 --- a/cumulus/polkadot-parachain/src/chain_spec/asset_hubs.rs +++ b/cumulus/polkadot-parachain/src/chain_spec/asset_hubs.rs @@ -14,22 +14,8 @@ // You should have received a copy of the GNU General Public License // along with Cumulus. If not, see . -use crate::chain_spec::{get_account_id_from_seed, get_collator_keys_from_seed, SAFE_XCM_VERSION}; -use cumulus_primitives_core::ParaId; -use hex_literal::hex; -use parachains_common::{AccountId, AuraId, Balance as AssetHubBalance}; use polkadot_parachain_lib::chain_spec::{Extensions, GenericChainSpec}; use sc_service::ChainType; -use sp_core::{crypto::UncheckedInto, sr25519}; - -const ASSET_HUB_WESTEND_ED: AssetHubBalance = asset_hub_westend_runtime::ExistentialDeposit::get(); - -/// Generate the session keys from individual elements. -/// -/// The input must be a tuple of individual keys (a single arg for now since we have just one key). -pub fn asset_hub_westend_session_keys(keys: AuraId) -> asset_hub_westend_runtime::SessionKeys { - asset_hub_westend_runtime::SessionKeys { aura: keys } -} pub fn asset_hub_westend_development_config() -> GenericChainSpec { let mut properties = sc_chain_spec::Properties::new(); @@ -44,21 +30,7 @@ pub fn asset_hub_westend_development_config() -> GenericChainSpec { .with_name("Westend Asset Hub Development") .with_id("asset-hub-westend-dev") .with_chain_type(ChainType::Local) - .with_genesis_config_patch(asset_hub_westend_genesis( - // initial collators. - vec![( - get_account_id_from_seed::("Alice"), - get_collator_keys_from_seed::("Alice"), - )], - vec![ - get_account_id_from_seed::("Alice"), - get_account_id_from_seed::("Bob"), - get_account_id_from_seed::("Alice//stash"), - get_account_id_from_seed::("Bob//stash"), - ], - testnet_parachains_constants::westend::currency::UNITS * 1_000_000, - 1000.into(), - )) + .with_genesis_config_preset_name(sp_genesis_builder::DEV_RUNTIME_PRESET) .with_properties(properties) .build() } @@ -76,35 +48,7 @@ pub fn asset_hub_westend_local_config() -> GenericChainSpec { .with_name("Westend Asset Hub Local") .with_id("asset-hub-westend-local") .with_chain_type(ChainType::Local) - .with_genesis_config_patch(asset_hub_westend_genesis( - // initial collators. - vec![ - ( - get_account_id_from_seed::("Alice"), - get_collator_keys_from_seed::("Alice"), - ), - ( - get_account_id_from_seed::("Bob"), - get_collator_keys_from_seed::("Bob"), - ), - ], - vec![ - get_account_id_from_seed::("Alice"), - get_account_id_from_seed::("Bob"), - get_account_id_from_seed::("Charlie"), - get_account_id_from_seed::("Dave"), - get_account_id_from_seed::("Eve"), - get_account_id_from_seed::("Ferdie"), - get_account_id_from_seed::("Alice//stash"), - get_account_id_from_seed::("Bob//stash"), - get_account_id_from_seed::("Charlie//stash"), - get_account_id_from_seed::("Dave//stash"), - get_account_id_from_seed::("Eve//stash"), - get_account_id_from_seed::("Ferdie//stash"), - ], - testnet_parachains_constants::westend::currency::UNITS * 1_000_000, - 1000.into(), - )) + .with_genesis_config_preset_name(sp_genesis_builder::LOCAL_TESTNET_RUNTIME_PRESET) .with_properties(properties) .build() } @@ -122,77 +66,11 @@ pub fn asset_hub_westend_config() -> GenericChainSpec { .with_name("Westend Asset Hub") .with_id("asset-hub-westend") .with_chain_type(ChainType::Live) - .with_genesis_config_patch(asset_hub_westend_genesis( - // initial collators. - vec![ - ( - hex!("9cfd429fa002114f33c1d3e211501d62830c9868228eb3b4b8ae15a83de04325").into(), - hex!("9cfd429fa002114f33c1d3e211501d62830c9868228eb3b4b8ae15a83de04325") - .unchecked_into(), - ), - ( - hex!("12a03fb4e7bda6c9a07ec0a11d03c24746943e054ff0bb04938970104c783876").into(), - hex!("12a03fb4e7bda6c9a07ec0a11d03c24746943e054ff0bb04938970104c783876") - .unchecked_into(), - ), - ( - hex!("1256436307dfde969324e95b8c62cb9101f520a39435e6af0f7ac07b34e1931f").into(), - hex!("1256436307dfde969324e95b8c62cb9101f520a39435e6af0f7ac07b34e1931f") - .unchecked_into(), - ), - ( - hex!("98102b7bca3f070f9aa19f58feed2c0a4e107d203396028ec17a47e1ed80e322").into(), - hex!("98102b7bca3f070f9aa19f58feed2c0a4e107d203396028ec17a47e1ed80e322") - .unchecked_into(), - ), - ], - Vec::new(), - ASSET_HUB_WESTEND_ED * 4096, - 1000.into(), - )) + .with_genesis_config_preset_name("genesis") .with_properties(properties) .build() } -fn asset_hub_westend_genesis( - invulnerables: Vec<(AccountId, AuraId)>, - endowed_accounts: Vec, - endowment: AssetHubBalance, - id: ParaId, -) -> serde_json::Value { - serde_json::json!({ - "balances": { - "balances": endowed_accounts - .iter() - .cloned() - .map(|k| (k, endowment)) - .collect::>(), - }, - "parachainInfo": { - "parachainId": id, - }, - "collatorSelection": { - "invulnerables": invulnerables.iter().cloned().map(|(acc, _)| acc).collect::>(), - "candidacyBond": ASSET_HUB_WESTEND_ED * 16, - }, - "session": { - "keys": invulnerables - .into_iter() - .map(|(acc, aura)| { - ( - acc.clone(), // account id - acc, // validator id - asset_hub_westend_session_keys(aura), // session keys - ) - }) - .collect::>(), - }, - "polkadotXcm": { - "safeXcmVersion": Some(SAFE_XCM_VERSION), - }, - }) -} - pub fn asset_hub_rococo_development_config() -> GenericChainSpec { let mut properties = sc_chain_spec::Properties::new(); properties.insert("ss58Format".into(), 42.into()); @@ -219,7 +97,7 @@ fn asset_hub_rococo_like_development_config( .with_name(name) .with_id(chain_id) .with_chain_type(ChainType::Local) - .with_genesis_config_preset_name("development") + .with_genesis_config_preset_name(sp_genesis_builder::DEV_RUNTIME_PRESET) .with_properties(properties) .build() } @@ -250,7 +128,7 @@ fn asset_hub_rococo_like_local_config( .with_name(name) .with_id(chain_id) .with_chain_type(ChainType::Local) - .with_genesis_config_preset_name("local") + .with_genesis_config_preset_name(sp_genesis_builder::LOCAL_TESTNET_RUNTIME_PRESET) .with_properties(properties) .build() } diff --git a/cumulus/polkadot-parachain/src/chain_spec/bridge_hubs.rs b/cumulus/polkadot-parachain/src/chain_spec/bridge_hubs.rs index 754bd851b40..af399be9eac 100644 --- a/cumulus/polkadot-parachain/src/chain_spec/bridge_hubs.rs +++ b/cumulus/polkadot-parachain/src/chain_spec/bridge_hubs.rs @@ -14,12 +14,9 @@ // You should have received a copy of the GNU General Public License // along with Cumulus. If not, see . -use crate::chain_spec::{get_account_id_from_seed, get_collator_keys_from_seed}; use cumulus_primitives_core::ParaId; -use parachains_common::Balance as BridgeHubBalance; use polkadot_parachain_lib::chain_spec::GenericChainSpec; -use sc_chain_spec::ChainSpec; -use sp_core::sr25519; +use sc_chain_spec::{ChainSpec, ChainType}; use std::str::FromStr; /// Collects all supported BridgeHub configurations @@ -81,14 +78,14 @@ impl BridgeHubRuntimeType { "Westend BridgeHub Local", "westend-local", ParaId::new(1002), - Some("Bob".to_string()), + ChainType::Local, ))), BridgeHubRuntimeType::WestendDevelopment => Ok(Box::new(westend::local_config( westend::BRIDGE_HUB_WESTEND_DEVELOPMENT, "Westend BridgeHub Development", "westend-dev", ParaId::new(1002), - Some("Bob".to_string()), + ChainType::Development, ))), BridgeHubRuntimeType::Rococo => Ok(Box::new(GenericChainSpec::from_json_bytes( &include_bytes!("../../chain-specs/bridge-hub-rococo.json")[..], @@ -98,16 +95,16 @@ impl BridgeHubRuntimeType { "Rococo BridgeHub Local", "rococo-local", ParaId::new(1013), - Some("Bob".to_string()), |_| (), + ChainType::Local, ))), BridgeHubRuntimeType::RococoDevelopment => Ok(Box::new(rococo::local_config( rococo::BRIDGE_HUB_ROCOCO_DEVELOPMENT, "Rococo BridgeHub Development", "rococo-dev", ParaId::new(1013), - Some("Bob".to_string()), |_| (), + ChainType::Development, ))), other => Err(std::format!("No default config present for {:?}", other)), } @@ -129,27 +126,20 @@ fn ensure_id(id: &str) -> Result<&str, String> { /// Sub-module for Rococo setup pub mod rococo { - use super::{get_account_id_from_seed, get_collator_keys_from_seed, sr25519, ParaId}; - use crate::chain_spec::SAFE_XCM_VERSION; - use parachains_common::{AccountId, AuraId}; + use super::{ChainType, ParaId}; use polkadot_parachain_lib::chain_spec::{Extensions, GenericChainSpec}; - use sc_chain_spec::ChainType; - - use super::BridgeHubBalance; pub(crate) const BRIDGE_HUB_ROCOCO: &str = "bridge-hub-rococo"; pub(crate) const BRIDGE_HUB_ROCOCO_LOCAL: &str = "bridge-hub-rococo-local"; pub(crate) const BRIDGE_HUB_ROCOCO_DEVELOPMENT: &str = "bridge-hub-rococo-dev"; - const BRIDGE_HUB_ROCOCO_ED: BridgeHubBalance = - bridge_hub_rococo_runtime::ExistentialDeposit::get(); pub fn local_config( id: &str, chain_name: &str, relay_chain: &str, para_id: ParaId, - bridges_pallet_owner_seed: Option, modify_props: ModifyProperties, + chain_type: ChainType, ) -> GenericChainSpec { // Rococo defaults let mut properties = sc_chain_spec::Properties::new(); @@ -165,86 +155,15 @@ pub mod rococo { ) .with_name(chain_name) .with_id(super::ensure_id(id).expect("invalid id")) - .with_chain_type(ChainType::Local) - .with_genesis_config_patch(genesis( - // initial collators. - vec![ - ( - get_account_id_from_seed::("Alice"), - get_collator_keys_from_seed::("Alice"), - ), - ( - get_account_id_from_seed::("Bob"), - get_collator_keys_from_seed::("Bob"), - ), - ], - vec![ - get_account_id_from_seed::("Alice"), - get_account_id_from_seed::("Bob"), - get_account_id_from_seed::("Charlie"), - get_account_id_from_seed::("Dave"), - get_account_id_from_seed::("Eve"), - get_account_id_from_seed::("Ferdie"), - get_account_id_from_seed::("Alice//stash"), - get_account_id_from_seed::("Bob//stash"), - get_account_id_from_seed::("Charlie//stash"), - get_account_id_from_seed::("Dave//stash"), - get_account_id_from_seed::("Eve//stash"), - get_account_id_from_seed::("Ferdie//stash"), - ], - para_id, - bridges_pallet_owner_seed - .as_ref() - .map(|seed| get_account_id_from_seed::(seed)), - )) + .with_chain_type(chain_type.clone()) + .with_genesis_config_preset_name(match chain_type { + ChainType::Development => sp_genesis_builder::DEV_RUNTIME_PRESET, + ChainType::Local => sp_genesis_builder::LOCAL_TESTNET_RUNTIME_PRESET, + _ => panic!("chain_type: {chain_type:?} not supported here!"), + }) .with_properties(properties) .build() } - - fn genesis( - invulnerables: Vec<(AccountId, AuraId)>, - endowed_accounts: Vec, - id: ParaId, - bridges_pallet_owner: Option, - ) -> serde_json::Value { - serde_json::json!({ - "balances": { - "balances": endowed_accounts.iter().cloned().map(|k| (k, 1u64 << 60)).collect::>(), - }, - "parachainInfo": { - "parachainId": id, - }, - "collatorSelection": { - "invulnerables": invulnerables.iter().cloned().map(|(acc, _)| acc).collect::>(), - "candidacyBond": BRIDGE_HUB_ROCOCO_ED * 16, - }, - "session": { - "keys": invulnerables - .into_iter() - .map(|(acc, aura)| { - ( - acc.clone(), // account id - acc, // validator id - bridge_hub_rococo_runtime::SessionKeys { aura }, // session keys - ) - }) - .collect::>(), - }, - "polkadotXcm": { - "safeXcmVersion": Some(SAFE_XCM_VERSION), - }, - "bridgeWestendGrandpa": { - "owner": bridges_pallet_owner.clone(), - }, - "bridgeWestendMessages": { - "owner": bridges_pallet_owner.clone(), - }, - "ethereumSystem": { - "paraId": id, - "assetHubParaId": 1000 - } - }) - } } /// Sub-module for Kusama setup @@ -255,26 +174,19 @@ pub mod kusama { /// Sub-module for Westend setup. pub mod westend { - use super::{get_account_id_from_seed, get_collator_keys_from_seed, sr25519, ParaId}; - use crate::chain_spec::SAFE_XCM_VERSION; - use parachains_common::{AccountId, AuraId}; + use super::{ChainType, ParaId}; use polkadot_parachain_lib::chain_spec::{Extensions, GenericChainSpec}; - use sc_chain_spec::ChainType; - - use super::BridgeHubBalance; pub(crate) const BRIDGE_HUB_WESTEND: &str = "bridge-hub-westend"; pub(crate) const BRIDGE_HUB_WESTEND_LOCAL: &str = "bridge-hub-westend-local"; pub(crate) const BRIDGE_HUB_WESTEND_DEVELOPMENT: &str = "bridge-hub-westend-dev"; - const BRIDGE_HUB_WESTEND_ED: BridgeHubBalance = - bridge_hub_westend_runtime::ExistentialDeposit::get(); pub fn local_config( id: &str, chain_name: &str, relay_chain: &str, para_id: ParaId, - bridges_pallet_owner_seed: Option, + chain_type: ChainType, ) -> GenericChainSpec { let mut properties = sc_chain_spec::Properties::new(); properties.insert("tokenSymbol".into(), "WND".into()); @@ -287,86 +199,15 @@ pub mod westend { ) .with_name(chain_name) .with_id(super::ensure_id(id).expect("invalid id")) - .with_chain_type(ChainType::Local) - .with_genesis_config_patch(genesis( - // initial collators. - vec![ - ( - get_account_id_from_seed::("Alice"), - get_collator_keys_from_seed::("Alice"), - ), - ( - get_account_id_from_seed::("Bob"), - get_collator_keys_from_seed::("Bob"), - ), - ], - vec![ - get_account_id_from_seed::("Alice"), - get_account_id_from_seed::("Bob"), - get_account_id_from_seed::("Charlie"), - get_account_id_from_seed::("Dave"), - get_account_id_from_seed::("Eve"), - get_account_id_from_seed::("Ferdie"), - get_account_id_from_seed::("Alice//stash"), - get_account_id_from_seed::("Bob//stash"), - get_account_id_from_seed::("Charlie//stash"), - get_account_id_from_seed::("Dave//stash"), - get_account_id_from_seed::("Eve//stash"), - get_account_id_from_seed::("Ferdie//stash"), - ], - para_id, - bridges_pallet_owner_seed - .as_ref() - .map(|seed| get_account_id_from_seed::(seed)), - )) + .with_chain_type(chain_type.clone()) + .with_genesis_config_preset_name(match chain_type { + ChainType::Development => sp_genesis_builder::DEV_RUNTIME_PRESET, + ChainType::Local => sp_genesis_builder::LOCAL_TESTNET_RUNTIME_PRESET, + _ => panic!("chain_type: {chain_type:?} not supported here!"), + }) .with_properties(properties) .build() } - - fn genesis( - invulnerables: Vec<(AccountId, AuraId)>, - endowed_accounts: Vec, - id: ParaId, - bridges_pallet_owner: Option, - ) -> serde_json::Value { - serde_json::json!({ - "balances": { - "balances": endowed_accounts.iter().cloned().map(|k| (k, 1u64 << 60)).collect::>(), - }, - "parachainInfo": { - "parachainId": id, - }, - "collatorSelection": { - "invulnerables": invulnerables.iter().cloned().map(|(acc, _)| acc).collect::>(), - "candidacyBond": BRIDGE_HUB_WESTEND_ED * 16, - }, - "session": { - "keys": invulnerables - .into_iter() - .map(|(acc, aura)| { - ( - acc.clone(), // account id - acc, // validator id - bridge_hub_westend_runtime::SessionKeys { aura }, // session keys - ) - }) - .collect::>(), - }, - "polkadotXcm": { - "safeXcmVersion": Some(SAFE_XCM_VERSION), - }, - "bridgeRococoGrandpa": { - "owner": bridges_pallet_owner.clone(), - }, - "bridgeRococoMessages": { - "owner": bridges_pallet_owner.clone(), - }, - "ethereumSystem": { - "paraId": id, - "assetHubParaId": 1000 - } - }) - } } /// Sub-module for Polkadot setup diff --git a/cumulus/polkadot-parachain/src/chain_spec/collectives.rs b/cumulus/polkadot-parachain/src/chain_spec/collectives.rs index 865a2a91708..227e15fdff8 100644 --- a/cumulus/polkadot-parachain/src/chain_spec/collectives.rs +++ b/cumulus/polkadot-parachain/src/chain_spec/collectives.rs @@ -14,23 +14,10 @@ // You should have received a copy of the GNU General Public License // along with Cumulus. If not, see . -use crate::chain_spec::{get_account_id_from_seed, get_collator_keys_from_seed, SAFE_XCM_VERSION}; -use cumulus_primitives_core::ParaId; -use parachains_common::{AccountId, AuraId, Balance as CollectivesBalance}; use polkadot_parachain_lib::chain_spec::{Extensions, GenericChainSpec}; use sc_service::ChainType; -use sp_core::sr25519; - -const COLLECTIVES_WESTEND_ED: CollectivesBalance = - collectives_westend_runtime::ExistentialDeposit::get(); - -/// Generate the session keys from individual elements. -/// -/// The input must be a tuple of individual keys (a single arg for now since we have just one key). -pub fn collectives_westend_session_keys(keys: AuraId) -> collectives_westend_runtime::SessionKeys { - collectives_westend_runtime::SessionKeys { aura: keys } -} +/// Collectives Westend Development Config. pub fn collectives_westend_development_config() -> GenericChainSpec { let mut properties = sc_chain_spec::Properties::new(); properties.insert("ss58Format".into(), 42.into()); @@ -40,27 +27,12 @@ pub fn collectives_westend_development_config() -> GenericChainSpec { GenericChainSpec::builder( collectives_westend_runtime::WASM_BINARY .expect("WASM binary was not built, please build it!"), - Extensions { relay_chain: "westend-dev".into(), para_id: 1002 }, + Extensions { relay_chain: "westend-dev".into(), para_id: 1001 }, ) .with_name("Westend Collectives Development") .with_id("collectives_westend_dev") - .with_chain_type(ChainType::Local) - .with_genesis_config_patch(collectives_westend_genesis( - // initial collators. - vec![( - get_account_id_from_seed::("Alice"), - get_collator_keys_from_seed::("Alice"), - )], - vec![ - get_account_id_from_seed::("Alice"), - get_account_id_from_seed::("Bob"), - get_account_id_from_seed::("Alice//stash"), - get_account_id_from_seed::("Bob//stash"), - ], - // 1002 avoids a potential collision with Kusama-1001 (Encointer) should there ever - // be a collective para on Kusama. - 1002.into(), - )) + .with_chain_type(ChainType::Development) + .with_genesis_config_preset_name(sp_genesis_builder::DEV_RUNTIME_PRESET) .with_boot_nodes(Vec::new()) .with_properties(properties) .build() @@ -76,80 +48,13 @@ pub fn collectives_westend_local_config() -> GenericChainSpec { GenericChainSpec::builder( collectives_westend_runtime::WASM_BINARY .expect("WASM binary was not built, please build it!"), - Extensions { relay_chain: "westend-local".into(), para_id: 1002 }, + Extensions { relay_chain: "westend-local".into(), para_id: 1001 }, ) .with_name("Westend Collectives Local") .with_id("collectives_westend_local") .with_chain_type(ChainType::Local) - .with_genesis_config_patch(collectives_westend_genesis( - // initial collators. - vec![ - ( - get_account_id_from_seed::("Alice"), - get_collator_keys_from_seed::("Alice"), - ), - ( - get_account_id_from_seed::("Bob"), - get_collator_keys_from_seed::("Bob"), - ), - ], - vec![ - get_account_id_from_seed::("Alice"), - get_account_id_from_seed::("Bob"), - get_account_id_from_seed::("Charlie"), - get_account_id_from_seed::("Dave"), - get_account_id_from_seed::("Eve"), - get_account_id_from_seed::("Ferdie"), - get_account_id_from_seed::("Alice//stash"), - get_account_id_from_seed::("Bob//stash"), - get_account_id_from_seed::("Charlie//stash"), - get_account_id_from_seed::("Dave//stash"), - get_account_id_from_seed::("Eve//stash"), - get_account_id_from_seed::("Ferdie//stash"), - ], - 1002.into(), - )) + .with_genesis_config_preset_name(sp_genesis_builder::LOCAL_TESTNET_RUNTIME_PRESET) .with_boot_nodes(Vec::new()) .with_properties(properties) .build() } - -fn collectives_westend_genesis( - invulnerables: Vec<(AccountId, AuraId)>, - endowed_accounts: Vec, - id: ParaId, -) -> serde_json::Value { - serde_json::json!( { - "balances": { - "balances": endowed_accounts - .iter() - .cloned() - .map(|k| (k, COLLECTIVES_WESTEND_ED * 4096)) - .collect::>(), - }, - "parachainInfo": { - "parachainId": id, - }, - "collatorSelection": { - "invulnerables": invulnerables.iter().cloned().map(|(acc, _)| acc).collect::>(), - "candidacyBond": COLLECTIVES_WESTEND_ED * 16, - }, - "session": { - "keys": invulnerables - .into_iter() - .map(|(acc, aura)| { - ( - acc.clone(), // account id - acc, // validator id - collectives_westend_session_keys(aura), // session keys - ) - }) - .collect::>(), - }, - // no need to pass anything to aura, in fact it will panic if we do. Session will take care - // of this. - "polkadotXcm": { - "safeXcmVersion": Some(SAFE_XCM_VERSION), - }, - }) -} diff --git a/cumulus/polkadot-parachain/src/chain_spec/mod.rs b/cumulus/polkadot-parachain/src/chain_spec/mod.rs index 82aec951704..adb3eb2c0e0 100644 --- a/cumulus/polkadot-parachain/src/chain_spec/mod.rs +++ b/cumulus/polkadot-parachain/src/chain_spec/mod.rs @@ -15,7 +15,9 @@ // along with Cumulus. If not, see . use cumulus_primitives_core::ParaId; -use parachains_common::{AccountId, Signature}; +pub(crate) use parachains_common::genesis_config_helpers::{ + get_account_id_from_seed, get_collator_keys_from_seed, get_from_seed, +}; use polkadot_parachain_lib::{ chain_spec::{GenericChainSpec, LoadSpec}, runtime::{ @@ -23,8 +25,6 @@ use polkadot_parachain_lib::{ }, }; use sc_chain_spec::ChainSpec; -use sp_core::{Pair, Public}; -use sp_runtime::traits::{IdentifyAccount, Verify}; pub mod asset_hubs; pub mod bridge_hubs; @@ -40,30 +40,6 @@ pub mod shell; /// The default XCM version to set in genesis config. const SAFE_XCM_VERSION: u32 = xcm::prelude::XCM_VERSION; -/// Helper function to generate a crypto pair from seed -pub fn get_from_seed(seed: &str) -> ::Public { - TPublic::Pair::from_string(&format!("//{}", seed), None) - .expect("static values are valid; qed") - .public() -} - -type AccountPublic = ::Signer; - -/// Helper function to generate an account ID from seed -pub fn get_account_id_from_seed(seed: &str) -> AccountId -where - AccountPublic: From<::Public>, -{ - AccountPublic::from(get_from_seed::(seed)).into_account() -} - -/// Generate collator keys from seed. -/// -/// This function's return type must always match the session keys of the chain in tuple format. -pub fn get_collator_keys_from_seed(seed: &str) -> ::Public { - get_from_seed::(seed) -} - /// Extracts the normalized chain id and parachain id from the input chain id. /// (H/T to Phala for the idea) /// E.g. "penpal-kusama-2004" yields ("penpal-kusama", Some(2004)) diff --git a/cumulus/polkadot-parachain/src/chain_spec/rococo_parachain.rs b/cumulus/polkadot-parachain/src/chain_spec/rococo_parachain.rs index 9f4a162e67f..251926838d2 100644 --- a/cumulus/polkadot-parachain/src/chain_spec/rococo_parachain.rs +++ b/cumulus/polkadot-parachain/src/chain_spec/rococo_parachain.rs @@ -19,9 +19,8 @@ use crate::chain_spec::{get_from_seed, SAFE_XCM_VERSION}; use cumulus_primitives_core::ParaId; use hex_literal::hex; -use parachains_common::AccountId; +use parachains_common::{genesis_config_helpers::get_account_id_from_seed, AccountId}; use polkadot_parachain_lib::chain_spec::{Extensions, GenericChainSpec}; -use polkadot_service::chain_spec::get_account_id_from_seed; use rococo_parachain_runtime::AuraId; use sc_chain_spec::ChainType; use sp_core::{crypto::UncheckedInto, sr25519}; diff --git a/polkadot/cli/src/command.rs b/polkadot/cli/src/command.rs index 2947867c516..89e21bf135b 100644 --- a/polkadot/cli/src/command.rs +++ b/polkadot/cli/src/command.rs @@ -109,17 +109,6 @@ impl SubstrateCli for Cli { "westend-local" => Box::new(polkadot_service::chain_spec::westend_local_testnet_config()?), #[cfg(feature = "westend-native")] "westend-staging" => Box::new(polkadot_service::chain_spec::westend_staging_testnet_config()?), - #[cfg(not(feature = "westend-native"))] - name if name.starts_with("westend-") && !name.ends_with(".json") => - Err(format!("`{}` only supported with `westend-native` feature enabled.", name))?, - "wococo" => Box::new(polkadot_service::chain_spec::wococo_config()?), - #[cfg(feature = "rococo-native")] - "wococo-dev" => Box::new(polkadot_service::chain_spec::wococo_development_config()?), - #[cfg(feature = "rococo-native")] - "wococo-local" => Box::new(polkadot_service::chain_spec::wococo_local_testnet_config()?), - #[cfg(not(feature = "rococo-native"))] - name if name.starts_with("wococo-") => - Err(format!("`{}` only supported with `rococo-native` feature enabled.", name))?, #[cfg(feature = "rococo-native")] "versi-dev" => Box::new(polkadot_service::chain_spec::versi_development_config()?), #[cfg(feature = "rococo-native")] @@ -139,7 +128,6 @@ impl SubstrateCli for Cli { // chains, we use the chain spec for the specific chain. if self.run.force_rococo || chain_spec.is_rococo() || - chain_spec.is_wococo() || chain_spec.is_versi() { Box::new(polkadot_service::RococoChainSpec::from_json_file(path)?) diff --git a/polkadot/node/malus/integrationtests/0001-dispute-valid-block.toml b/polkadot/node/malus/integrationtests/0001-dispute-valid-block.toml index 43e55402e68..fe1836bd71e 100644 --- a/polkadot/node/malus/integrationtests/0001-dispute-valid-block.toml +++ b/polkadot/node/malus/integrationtests/0001-dispute-valid-block.toml @@ -1,9 +1,12 @@ [settings] timeout = 1000 +[relaychain.genesis.runtimeGenesis.patch.configuration.config.scheduler_params] + max_validators_per_core = 1 + [relaychain] default_image = "{{ZOMBIENET_INTEGRATION_TEST_IMAGE}}" -chain = "wococo-local" +chain = "westend-local" command = "polkadot" [[relaychain.nodes]] diff --git a/polkadot/node/service/Cargo.toml b/polkadot/node/service/Cargo.toml index 216aa10e8ac..89f8212bf9d 100644 --- a/polkadot/node/service/Cargo.toml +++ b/polkadot/node/service/Cargo.toml @@ -18,15 +18,13 @@ sc-consensus-beefy = { workspace = true, default-features = true } sc-consensus-grandpa = { workspace = true, default-features = true } mmr-gadget = { workspace = true, default-features = true } sp-mmr-primitives = { workspace = true, default-features = true } -sc-block-builder = { workspace = true, default-features = true } +sp-genesis-builder = { workspace = true, default-features = true } sc-chain-spec = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } -sc-client-db = { workspace = true, default-features = true } sc-consensus = { workspace = true, default-features = true } sc-consensus-slots = { workspace = true, default-features = true } sc-executor = { workspace = true, default-features = true } sc-network = { workspace = true, default-features = true } -sc-network-common = { workspace = true, default-features = true } sc-network-sync = { workspace = true, default-features = true } sc-transaction-pool = { workspace = true, default-features = true } sc-transaction-pool-api = { workspace = true, default-features = true } @@ -50,22 +48,17 @@ sp-block-builder = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } -sp-keystore = { workspace = true, default-features = true } sp-offchain = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } sp-session = { workspace = true, default-features = true } -sp-storage = { workspace = true, default-features = true } sp-transaction-pool = { workspace = true, default-features = true } pallet-transaction-payment = { workspace = true, default-features = true } sp-timestamp = { workspace = true, default-features = true } sp-consensus-babe = { workspace = true, default-features = true } -sp-state-machine = { workspace = true, default-features = true } sp-weights = { workspace = true, default-features = true } sp-version = { workspace = true, default-features = true } # Substrate Pallets -pallet-babe = { workspace = true, default-features = true } -pallet-staking = { workspace = true, default-features = true } pallet-transaction-payment-rpc-runtime-api = { workspace = true, default-features = true } frame-metadata-hash-extension = { optional = true, workspace = true, default-features = true } frame-system = { workspace = true, default-features = true } @@ -73,18 +66,15 @@ frame-system = { workspace = true, default-features = true } # Substrate Other frame-system-rpc-runtime-api = { workspace = true, default-features = true } prometheus-endpoint = { workspace = true, default-features = true } -frame-support = { workspace = true, default-features = true } frame-benchmarking-cli = { workspace = true, default-features = true } frame-benchmarking = { workspace = true, default-features = true } # External Crates async-trait = { workspace = true } futures = { workspace = true } -hex-literal = { workspace = true, default-features = true } is_executable = { workspace = true } gum = { workspace = true, default-features = true } log = { workspace = true, default-features = true } -schnellru = { workspace = true } serde = { features = ["derive"], workspace = true, default-features = true } serde_json = { workspace = true, default-features = true } thiserror = { workspace = true } @@ -93,13 +83,11 @@ kvdb-rocksdb = { optional = true, workspace = true } parity-db = { optional = true, workspace = true } codec = { workspace = true, default-features = true } parking_lot = { workspace = true, default-features = true } -bitvec = { optional = true, workspace = true, default-features = true } # Polkadot polkadot-core-primitives = { workspace = true, default-features = true } polkadot-node-core-parachains-inherent = { workspace = true, default-features = true } polkadot-overseer = { workspace = true, default-features = true } -polkadot-parachain-primitives = { workspace = true, default-features = true } polkadot-primitives = { workspace = true, default-features = true } polkadot-node-primitives = { workspace = true, default-features = true } polkadot-rpc = { workspace = true, default-features = true } @@ -189,13 +177,11 @@ full-node = [ # Configure the native runtimes to use. westend-native = [ - "bitvec", "frame-metadata-hash-extension", "westend-runtime", "westend-runtime-constants", ] rococo-native = [ - "bitvec", "frame-metadata-hash-extension", "rococo-runtime", "rococo-runtime-constants", @@ -211,26 +197,18 @@ metadata-hash = [ runtime-benchmarks = [ "frame-benchmarking-cli/runtime-benchmarks", "frame-benchmarking/runtime-benchmarks", - "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", - "pallet-babe/runtime-benchmarks", - "pallet-staking/runtime-benchmarks", - "polkadot-parachain-primitives/runtime-benchmarks", "polkadot-primitives/runtime-benchmarks", "polkadot-runtime-parachains/runtime-benchmarks", "polkadot-test-client/runtime-benchmarks", "rococo-runtime?/runtime-benchmarks", - "sc-client-db/runtime-benchmarks", "sc-service/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "westend-runtime?/runtime-benchmarks", "xcm-runtime-apis/runtime-benchmarks", ] try-runtime = [ - "frame-support/try-runtime", "frame-system/try-runtime", - "pallet-babe/try-runtime", - "pallet-staking/try-runtime", "pallet-transaction-payment/try-runtime", "polkadot-runtime-parachains/try-runtime", "rococo-runtime?/try-runtime", diff --git a/polkadot/node/service/chain-specs/wococo.json b/polkadot/node/service/chain-specs/wococo.json deleted file mode 100644 index 0ad7334685f..00000000000 --- a/polkadot/node/service/chain-specs/wococo.json +++ /dev/null @@ -1,218 +0,0 @@ -{ - "name": "Wococo", - "id": "wococo", - "chainType": "Live", - "bootNodes": [ - "/dns/wococo-bootnode-0.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWQC541JNa6dguvifYYjwPnviscJHqbwvoNDMX3WBubPJZ", - "/dns/wococo-bootnode-1.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWG9v9Aexs6EvBYAwy9cqLyw25BRi2U1RQNQ2r5QJRxfFm", - "/dns/wococo-bootnode-2.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWNza3xSzCbw6phggjKD4QyqF8xvVpDFk7ctkoM5c1PQz2", - "/dns/wococo-bootnode-3.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWJ4ngb7S1Lkq5C4ZYqfFuJswxTE3UC5zjui5TLhAULTRU" - ], - "telemetryEndpoints": [ - [ - "/dns/telemetry.polkadot.io/tcp/443/x-parity-wss/%2Fsubmit%2F", - 0 - ] - ], - "protocolId": "wococo", - "properties": { - "ss58Format": 42, - "tokenDecimals": 12, - "tokenSymbol": "WOOK" - }, - "forkBlocks": null, - "badBlocks": null, - "lightSyncState": null, - "codeSubstitutes": {}, - "genesis": { - "raw": { - "top": { - "0x0595267586b57744927884f519eb81014e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0x06de3d8a54d27e44a9d5ce189618f22d4e7b9012096b41c4eb3aaf947f6ea429": "0x0500", - "0x06de3d8a54d27e44a9d5ce189618f22db4b49d95320d9021994c850f25b8e385": "0x0000300000800000080000000000100000c800000500000005000000020000000200000000005000000010000700e876481702004001040000000400000000000000000000000000000000000000000000000000000000000000000000000800000000200000040000000400000000001000b00400000000000000000000140000000400000004000000000000000000060000006400000002000000190000000000000002000000020000000700c817a80402004001000200000005000000", - "0x1405f2411d0af5a7ff397e7c9dc68d194e7b9012096b41c4eb3aaf947f6ea429": "0x0100", - "0x1405f2411d0af5a7ff397e7c9dc68d196323ae84c43568be0d1394d5d0d522c4": "0x03000000", - "0x1809d78346727a0ef58c0fa03bafa3234e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0x196e027349017067f9eb56e2c4d9ded54e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0x1a736d37504c2e3fb73dad160c55b2914e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0x1cb6f36e027abb2091cfb5110ab5087f4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0x1cb6f36e027abb2091cfb5110ab5087f5e0621c4869aa60c02be9adcc98a0d1d": "0x10006078f6e6a00db1f40097f0d07953008b04cda71ad831e70f37e93eb2b40431010000000000000022371e9715d00b3a21c9a899ba3eafd11f5143b821b159b864025ba1eabdb6310100000000000000e6b8162c3e767f8e61892f7fcd06d27041d806e5e0335c59dcdafa5c8e181c5b0100000000000000585a72774ca9465ba0e7407e4e66d239febbe906cbf090169b6cfa15dd44e5770100000000000000", - "0x1cb6f36e027abb2091cfb5110ab5087f66e8f035c8adbe7f1547b43c51e6f8a4": "0x00000000", - "0x1cb6f36e027abb2091cfb5110ab5087faacf00b9b41fda7a9268821c2a2b3e4c": "0x10006078f6e6a00db1f40097f0d07953008b04cda71ad831e70f37e93eb2b40431010000000000000022371e9715d00b3a21c9a899ba3eafd11f5143b821b159b864025ba1eabdb6310100000000000000e6b8162c3e767f8e61892f7fcd06d27041d806e5e0335c59dcdafa5c8e181c5b0100000000000000585a72774ca9465ba0e7407e4e66d239febbe906cbf090169b6cfa15dd44e5770100000000000000", - "0x1cb6f36e027abb2091cfb5110ab5087fdc6b171b77304263c292cc3ea5ed31ef": "0x0100000000000000040000000000000002", - "0x2099d7f109d6e535fb000bba623fd4404c014e6bf8b8c2c011e7290b85696bb3": "0x10b691bfd2cd584abd1531b7deff6d0e34893960b59ae550348c33abd76af4cb490e93248544c963f34bb9cde63c97f85ef7a1939d3c9075907b26edf368fe846e5ed9fdbd8dffeb5324935a7fafc536de96d62abee0a05d7eefa961c1cf3de266ca24971e2ec596d510c673f4f8d36d0a8a407b59ffd0643f621369973a335656", - "0x2099d7f109d6e535fb000bba623fd4404e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0x2099d7f109d6e535fb000bba623fd4409f99a2ce711f3a31b2fc05604c93f179": "0x10b691bfd2cd584abd1531b7deff6d0e34893960b59ae550348c33abd76af4cb490e93248544c963f34bb9cde63c97f85ef7a1939d3c9075907b26edf368fe846e5ed9fdbd8dffeb5324935a7fafc536de96d62abee0a05d7eefa961c1cf3de266ca24971e2ec596d510c673f4f8d36d0a8a407b59ffd0643f621369973a335656", - "0x26aa394eea5630e07c48ae0c9558cef734abf5cb34d6244378cddbf18e849d96": "0x000000000794e321d00fb2d42000", - "0x26aa394eea5630e07c48ae0c9558cef74e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0x26aa394eea5630e07c48ae0c9558cef75684a022a34dd8bfa2baaf44f172b710": "0x01", - "0x26aa394eea5630e07c48ae0c9558cef78a42f33323cb5ced3b44dd825fda9fcc": "0x4545454545454545454545454545454545454545454545454545454545454545", - "0x26aa394eea5630e07c48ae0c9558cef7a44704b568d21667356a5a050c118746b4def25cfda6ef3a00000000": "0x4545454545454545454545454545454545454545454545454545454545454545", - "0x26aa394eea5630e07c48ae0c9558cef7a7fd6c28836b9a28522dc924110cf439": "0x01", - "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da942cd783ab1dc80a5347fe6c6f20ea02b9ed7705e3c7da027ba0583a22a3212042f7e715d3c168ba14f1424e2bc111d00": "0x00000000000000000100000000000000000064a7b3b6e00d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", - "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da994dc96e49150ac7c3ab5917a8d347ea0aa7ca70cae6201086232336a1535399c34f372320c0aa15d68c4cfa493079f27": "0x0000000000000000010000000000000000407a10f35a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", - "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da99a0d9ba64d584162e7d1fc85d6d19ad1005203395fd92c2b411136466e88ef74dd6327c0a6f32b3be7e38d56a2e1324f": "0x0000000004000000010000000000000000407a10f35a000000000000000000000000000000000000000000000000000000407a10f35a0000000000000000000000000000000000000000000000000080", - "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9a1e0293801ecda3bccddad286cfce679fa6a6474ec1a9234888f7b20d3978a706d386d0f16344765faa48cb376db331c": "0x0000000004000000010000000000000000407a10f35a000000000000000000000000000000000000000000000000000000407a10f35a0000000000000000000000000000000000000000000000000080", - "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9e39abd9d6d25130391c9ff6fc64a35ef18caf23bb6b8cda7f3f90c1dc2b2f6c2b7143b13c956ef0a12dfe486e83cb758": "0x0000000004000000010000000000000000407a10f35a000000000000000000000000000000000000000000000000000000407a10f35a0000000000000000000000000000000000000000000000000080", - "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9f4c6172605184c65d6c162727408dc0be4c1d81c7e8df384e1ae9667a668825c56e95b0f7d3b1ba2f7539d4c470abb1b": "0x0000000004000000010000000000000000407a10f35a000000000000000000000000000000000000000000000000000000407a10f35a0000000000000000000000000000000000000000000000000080", - "0x26aa394eea5630e07c48ae0c9558cef7f9cce9c888469bb1a0dceaa129672ef8": "0xb9921c77657374656e64", - "0x2762c81376aaa894b6f64c67e58cc6504e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0x2aeddc77fe58c98d50bd37f1b90840f94e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0x2b06af9719ac64d755623cda8ddd9b944e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0x2b06af9719ac64d755623cda8ddd9b949f99a2ce711f3a31b2fc05604c93f179": "0x104a611c52c43142e11767e4443eb56b908babae266b4f446271d11ffaaafbb16ece83a2b5c733f98b4018856a1fb0bdf0138dd883cc93a883f97de48b762d6b12ded28f03696a0c9f9dec223f3cbc44c4895d8b243ebe5cee12f9f02bf0c5043c9e3e67bfc0daed31db022fce484b2cf0d757e9aafded1988293da74301275b38", - "0x2f85f1e1378cb2d7b83adbaf0b5869c24e7b9012096b41c4eb3aaf947f6ea429": "0x0100", - "0x2f85f1e1378cb2d7b83adbaf0b5869c298ef7dc060436e4ed803af07632b89b65153cb1f00942ff401000000": "0x481a2bb5d6b9d282f3597f76299e767b1bbf06577a886d6def364451d4a95a5204000000", - "0x2f85f1e1378cb2d7b83adbaf0b5869c298ef7dc060436e4ed803af07632b89b6b4def25cfda6ef3a00000000": "0x481a2bb5d6b9d282f3597f76299e767b1bbf06577a886d6def364451d4a95a5204000000", - "0x2f85f1e1378cb2d7b83adbaf0b5869c2ff3ae12770bea2e48d9bde7385e7a25f": "0x0000000002000000", - "0x31a3a2ce3603138b8b352e8f192ca55a4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0x3a636f6465": "0x52bc537646db8e0528b52ffd005894a2041e01468413521028ee4c3a03850c0b278ae1c05fe510045e30c88789ed5828071801ae10048d27c39f9eff086b9b6f23fd1d87552800107259f316bc3f938f9f81ae1d78d1903cc5c228dfe9c050dc7b93bdf796322599028915a61196126d79ea34800c255ac677be934ced1499782df3671103be5364d28fd4930a906ff7cf6e8bd4b5393500e74f1202e3cd66bb25bdfdde7ccf633fe29e95e39d5f8e97ce19c5a7f7f447eae9eeeef3dd67f77bb76dcc493ffd9e19cc6f5bf67bcfa9057250f9b71fe4008c202b19f9fd5964c23fc52622d86cb62b972db7acf46e3fd821b7d1fc36e296f523b593b465fd476339deee07616dc4b71f9ce1d846fd3c6de4df0f6ba379fb5964e294b412c185aa7977be77a78d39994f7f3eb551f3ba6591c9b4e5e4a0b64b7f19a8e6735a01f2e5a072bcfda0d74625100aae8cf3cbf0f697fedc327ed0c924e37c05cc5fe29695f4f223353849bf212f799a5bf3fbc1194e92de90973c4daeebd45d2b3deefc7e91973c4ddb75b2e05a09effc7e1c5ef2346dd7c9826b85bbf3fb7378c9d3b45d270beefc7e1633a036db9d8fd46087cc4be697fedc327eaf8dca79fdf94127bd96f1178dfefc2c5e405bc6efd66b193f52831d4eb26fc84b9ee6ed5ae1eefc7e91973c4ddb75b2e0ceef9fa20246db9d1fb611df7e163398b63b1fa9911aa9919a496e193f07b55d1a2f03d57af6182037c85c13e37c7e7e25d1a27f40d3e695a489246e24d94ab244122592dc923891a44cd11b49b614bd507447911a454220f9a2e88c22348a42509446911c48d214f9501444111d455f8ac0147900091792358a862882a3680a24531471a0a805246f14d990cc81040d247420c903091e48d61495510404923b8adc20f1a2c88c2232908081440d246f90c081240e24669090416205923690c881c40d246c20d12aba02899b242f247121090b455b90d851c4a6284dd116456314c581a40b2468909c4112022475146d21e1a2088c222a8acc24c12a12a3488ba22f8ac2282a53d406899a222c3f27f870116405e48b4c0b912000d0449028826c395744741c01e3481547bc38b28634e288185cd6912fb83a9ecc6b7930381dc0e1008e961e2d7ae0003a1392f14f78d66c06c70188866f8aefcc3c63aa31e77818de082138cc1626b063ca118a11e4871f32260833011113a099e0cc045b4ca0c504464c70c4ec61080e212708c9d236d0db04369c159a061f5bb40c568b69e342d03708a11282c6871600a2f82a8ace04e182134311acdfe1ad78153e88bfe157f8241e8b17e251f81f5eebcbfc97e7c0d0d6bf30d4c5501443594363866810fa3244c4501148a818da61a8035fc39f301447cf143d65becbf7f0576058de09224d1059828812b32366453c1244789885a95d33232640629685238790aa1f567e35b44576466685ac0a1c15e81c436010a9304447ad6288d6903543b6866831248c2169867c3104cd102b867831648b216a868831a48b21600ce162c815e11a4249082121f48690182130e411424f08951182c26261b5849c1022039484101540480845012446480a202584a6003a42c88cd018a02580c2e4a090a3454809a13c84f010a243680e203184ba84e410ba432804424c0875114283d00e423e70250835d1d3069011422c0861cdad203588b8217246cf193d680455116486c81a3829f84843223181160e17223b10f152ad009aa2c895213986cee80941112c3331403600d500644565b06f9c89a1376618cfc6bbe1acfe8c08ee184ad37366e80ccc03436e43a8a074f4680d9102b683c5a3c78a508d908d212d863cf1d3c64c882155846d009d300404302086b6f859e3b70c19d383054c871fae19113f6c005d195266081343cc0c89a2a78a214df4a0015f18320550094026c4de0c210384a5278b1f356279f49881fd40e5a03b0c71e26db02f301f864001f322e445880d1c08c912fa41880e3f050c4c111b28e64b781e46aaf7020be32088208f7721880786e6e81963288f66c189a8558d058435abe1278c190d311d462484d4308af1f1c30f184fc6cf18432efc6cc572188241a807211d7cf4f023c69bf165fcb0f914be852229fc1213042181580fbee6a9be0a081e0038c1d5781f22c0e38b8009d143c79025e81943b21863800d21f40656c40f1c436aec1d30227aec80bd016b031b03d605901b8ab470573d6e0cb9111a6202218e702114840f20261842c80c426e98d930d4829007848410f2c20718528c8f21c62992702131a3e7081f5e90a4e1a34b511b3f667c5570577ca2f0c1e203059984cf989f363f5f08754186f986f819be309f98a13486c6f826be0344c8980d31f4450f153d5bfc11301e6c1e40290055c9da7c60da04224504b501e2d25d7c404c31b80e10ad80e40cd81148b2f8be3417afc35bfdb851d7f0127cd2206133040bda45b3fcd657401c780f5026a80f4031046d016d01d2020443510a8d03100b3d5c00cd00d4012246c07aa8b61f33640c3721888ca03282d87c37a017820881440bcf010805ce8e1e2fdc8aa88e590c45687ad2e821038914456a7abee8b9e26789cf123445cf9a1e36453d14b1f0f3464f9b216d884040c404d18da889b103130dd00af54acf56671561d1a3a6c8861e307aca40d206280b123186b001ba1a42c6c7d0d385102c402e246921d68107025ca2888b24b61e357e941872a69a415206500b40321459118b014914496010fac0cf1a2120623c8c4cc476f0e183100f3f5ec470189d1893f071c5e8858e31761979105a414805a11484b408a120748277a9587c98c7eae9ea71f357cf042582f6e05544cce89903266602377ac2187263e88e1c3886d0bc14435e501ce80d425c7e082015605d80d4e86163088d2036681b1f85bf41b982cc781b7aeab06c0c750dbd19b223e88ca13080e0701bfc8608de0c893114829e3886d608c500fb006dd323076844cf1b435cf4ac010b6288cd0c852049c08c182283560d7105b9a16c1e049f45101543cae04ce8e102e2ca39c10716a2d5d00b6411454aa86e0051f598d123c6509bca06908d4819f6cc58424f9abf62b4fd2de4a23c80508036803380348059a00ea013e00d6013600de013200ee00ee11aa22d360dcb65d518aa63888da13582b882e40872e3080f477c38d2c5c7161f2dc8e29075c9e438d2c2110e1c7161c8367445f66656c2cc16048d206b044983481c940c227208591169038819d40d206e828c1184cd11218e147164082279fca0f0a325e887202282809099217343e606481040c4001162c88c213c86ca206283680ea236666d6668ccc8808d8191814101d405f406a80e225510d9828815416e08e243101c80e4000408203ac84a90a120c342a4070743c4072220202205112680d4b40f405c0ca9e12c0cb5e12b10214174055112631651184468106d11b540d4012217884210e211de31011e445413e4f139bc12ff8117f3543c11cfe55978135e86a7e1a178338fc363f92cbf52c5f8dad427be313e2b3e293e2abe321f990f04df125f0e5f0f5f97af86cf876f852febfbe123e2e3f271e083e17be1bbe1b3e1a3e1dbf2b1f0b5f0617d1ee0b87c42d4339f0edf0cdf07380e7074705ddc1b9c1b5c159c190e0dee0cce0d670657064706d78663c38dc125c189c19de1d0705770487062b830dc119c115c111c11dc109c105c101c18ee039c07b82d2e0c0e0cee0b6e0de705d705c705a7864bc36dc169c145c141c18de19ee09ce0a8e0a6e0ca70645c0cae06ee06ce068e064e062e0b9702a785b371287027702b703170581c0cdc0bf54dcda3e251b92a9b9a4645a39e315da83cd426aa13b586fa43bda1da50b5d4126a0a3404d40e1a079d81a641d1a0773817dda26fd03aa819b40c2a86b3e16b20e9a2705027681395066f837aa1587405da855ed114280d9405da8297416ba043d02f9403f4054a4583a042d4351406aa023d81264191a046d030f4082a866ea139d01f681114086a03d5c1dd7034aa09455c1e858f71285c0a47a26885ea8307f1055175f00eb80e3e43bdc2b5780a9c0b5c0bed45d7d173f417cd469bd170349be6ea33da8d2ea3c1e8375a8c5ed374341a6d4787a0c9e8aebea3c3e831ba4d6f751b1d47cbd169f41aeda6d5a85e2a1deda57f681dba868ea1b934077a86cea13b507b987974d56461de314330ed98757c2ecc35661ab5cb44639631dd9874cc37a617138c59c5e4626631cf7c66e69aa935bff02fea0ef387e9c3f432393061b0124214d4531981064cac07cc2a077ce0c1930b5880011b2820013306240468e0e40018346162800258305fa00089d191a40f0d705e9e3b467ced4a5050309054058ad2074949e074294d4a4949af181e0c258a4a084e49504d4953aa5c0f9aa62c41896ac04fec0ea612951402568ad20485090a0692a844254145354da11214b383a1a43c41c980d2d3942a178a890a950d9aa6804025013656479791181d44b1399898a0446162825200393e88f2a04989290e5652ea20894a5452d4071e4c51cac02c060743314d8112a524254949a90309626f30950e9aa6248df0a4148253d2942a37aa49098411a06cf0313758294a1395a62428519aa82420ca0d12e39242258a929408626d30d4084a4b5023283969001963036a8a0146b8646c0d0a626a04c5d26011949a7ac4d0e033f8832854aa28c5626ef829040fa674d044254ad2942a57090350683d3133248895a12446064b999214a504f51442941fb1360ca534256aca13950fa2428092e441d2942a174a15294d509e2e101263c350504a4c416263ecc4c4d8e2a8a610a84449ea008a12534f2c0c7e0201041010901303833ba042e5a94a5295291f403d293565804ad2084a4b42b12f588a52930753a09a428842c5ca94109c9492a29ea854517a328aade12825a8242f186a0ad4081d28296120694a953b42074a4a349f58170ca514154215a829527c625cf00750503eb03135acf4542549e9694a084e4a4f49501d58a902144bc3514d4a207460a58a2cb60543294551b19294d444e527a6058f80812434504c4a4d4d503e8892540584a70f92a434854005034d53ce4435518992e40482942c180a04284f50aa243d51c182a9740024a6c550514d4a5694ae600f9aa6486952aa92a4140545ca14294d50a2148959c14a52a8440912ab829fa87400a5046586a1a242a81215029521312a4008010a0a8a506c0a861205454a521395295294a2a0a6403d295591205686a1949ea844491a216a0ad3140c40619a02c50a948f9161a82a215079f22049484c0a867aa21205450503284f503680c5a2e00c4ca9729f94969ea8444001640c0a56520a4129840538408a92071e4071c0506c0c432d4551fa20ca130c15d5a4f4a4f441d3948f39d134a58358ac098e3205aa69ca5394084a49233055a90225e949490ad39410684b51a830d17e62378602126382a124fdc440c0202441898222054a94120274624b68600a13942951539aa6f4c494e0a52854983090a4141542142511947ac492e0272a0508792a3161004a5453081d244545517a9a22a5694a52d28f263b591dc867cab09d21a3eb720de49bcd7667cd7f705f1a5f1ab3caec684c285663b5c66aadb50a4da0ee6ee4aef38e763d3277eecdccccdc51eeee66ee3a6606996b771cd31cf6ca1dd3d0f30eab3b129feded1030e2a0bb3bc88ea4411ed01fc003823ddd7bce6ef72110e4ced199ed808f7dfa00290fc875ce5c77fb9c5f03dcd9c13ec25d77c43b4a3d2fa49476775d73c8ceed3977cd4e2973ced42975dade75ba7bbb7b5d1006e2a0fb6cf68e015d7b17c21848c83427877ae8ed79d4d3d1e9605de7dcb5bda3edeeec614e377bb6c75d91912e80739a67d8711ec8cd3edadb79d28675be41b7d75ed7b9b333656705544e00d8b5334936f51c012c343570ef3aca0eb213a20988ba09dc3fe6cf9d9939ce39e738e6f873671de73866e738678e391c6ee6388e73c6e1388e63678efbf8e3188763ff98a773cc4dce999bd971983f9c8fd9793abb73b3e7d15a996b4729f53ca6ddde5c6953ea850e80d8ecba7bcec9516a01103a33ed3a6f721fe0a313e4c9a041c76036051dbcc079dcfb0765ef26c8ee3983dcddd81ee49ec7ee79edce1eed3af62031a849539fee5c698e7b8e91168570c784727777eededdee4e475a8436a5a304eeafaedd3ba75d7b03754073769d01e6ecba6e4eef66e798326d8f36b357bb4177aeb53aed997b538f9967773b13e18e1974f77e396de6d9b9778e7b74ba479d763b1070086477afbb69d775dd8feffbda81747b7beeb9bb33b3b37b17a0eb3aaf0bbb473a76de799e4723e2ec5d77eda60d3a77333373d77533e7ee66efbaeb983d9e2137737787eeeeee733ab377f3f4ae767b77b367d7753b3c3cb6eb3acbc37d4366666f76a7b4760c32651e42ddbd870fb377eecddeeded3d643fdcd99d65ee3dbcdbf33ccfbdbd87f774f7e93edda7cfe993b677d7deeeededdd7993ddbd0787d9dde3d9b39dbd2773b777ee3cdbd99ddd9b6958dd27fbecf6e9dc4e296d66e76ed1bbf6c9ce4dd9b9eb3a77666fa79fe7eed51d67327b74a7bfe6a8736177c779dedcf5e8e9c13db9ddc7bd63ea4e43cfa39ce879f727b677ccdeed941964e7f666e7913bee98c97677dadd71473def80b477dc75dddd5402ea1dd376caccb0eedc7b3ad85dd731e8d4636ea79d7bedbaa6ee4e29a5ee79cc5dd74d29a5cced4cb99d3265cacc4c29336577da75dc31779d3be5cee36e6f88d79dc7deb1333ba5eedcee94b9e3aeeb9cdd9d99b9e35ad9d9a937927b3b3373e7eedddedd59d075ef79dd7973d68e0003a8e05f0015d6755dedba9d2501e80aa3d5ebba9c6e6fefbaaeebbabb4ea7761d775c531ad2a6d49bbb7677e79cd6aebd634fa2459234dec33dc21315109440e03e26252b4a4c4922400121821f1e50614af2a0698a071e5061fa815305042851504a204c89fa1f0c84003553824a02818a9426284f16270a2a69e9894a06a2949a827e40c994c88ea4344149527a825205042a497a3041894285898894294c50ac2441011949517a5292f24425c98802244a539ea28848f00309152a4f4096a240c10013139527221440517a02f223e4e7474f14d4141194b8a82850a09aa03c4525454d0101042a3224533a507aaa627d624d4a55aa40997c040421aca4f4d441d21395282851a8303141f9e123204c896a0ae1c9fa7410154210104268f21141e983a4a8a6294f5592a2404d01e1a72789511011a864204a881105487a94203992844469090a1306923c689af20114940f7c88a024a5694a52d314281f44e9a0032a9f044a8208949692a244506a2a124194920712f87882a2c4142549c903284f54a2a092a4304df9204a52145308503a509232e3a29ea04441498004a8a8c9832950a40e1f811a41692929ca14260c845045f60425f462245294a0a0949eaa8040254ad29392152a51949e92a0404989a2947464c988022455a228411d7d1f4489828281a40fa0a6344de9402664840ea63c295901b2d2a4e441f84852139529504f4a569294a2a03c2935297990148023145080448a521295a82a233c45498a124149c84a93921fc911a33faee6dc547d3de15a8deb6ab529ef09d7a6a65c7c529bb5599b3953535da3fd646ad6a6a6dc9f4c4d6faa3635459fd4e6d454835dadd63de1da543fe1da54ad466b944e4d71ed7bc25353dd93da9c9aea27b5599b9a9aaad5dca7a6fa094fcd27b539359f706d6aaa366bfd646a6a8a6bb5eec9d4e427b5c93de1a9a95a8d3e999ab55a3fa94daed527b5c94f6ab356ab798d3ee15aad9f70ad56f327b5599b4fb8369f44a0663199798200058503f4b9dfb2e672df6dc0010b574efbe1b2368a71ff41b2271f6ce15269417ed4b8b5481b95dccbb8b2f78e41c8c19336aa4c4683369ab73e110b636efd249512ddca68c8dcfadc463f6ec96d6e151550e576dffc51dced0769501bd57fea293a49badd7bdd377796b9085bf0e35edcf397b3fb263ddb911c59d62eb75476cfcf891974dd77b624e1f6f3e56c396f37e90fcef66f2793b34ee677fd1f106beb72649d576ee93ddfacc68c204f1b715f9f8a17785f92f02683f99c2dfbcaf51ee4b6791d89e4dc953bdbb93c376059c1e5a39ce71fdb487c7e9e367a3df3ff7c501b79cf2f6b23ee6bf7458aef913510041fa94332e6fd7765bbf4c513b807b1409e5723dfb0b22e7d903e08d666f8df073e38c177f20b3eb81473d27da51d4ee7a4c3e9c0f71efc5ecff77bd926de7fff695dfaafbf5dba54ffb3e5bcdd87ef91f37e2e3679bd5e2fbb54bfa40f55530054ed86bfc43d5500fdce96dc87f67b24bf9f07d690c5d09652377c8e2ce9e53e87f4b6623e326954396fc57c84fb4943f290046bc891fe3924d33cd2c96797c4a72f3e8b4c92bc19c5770e7cce96e28336067ef7d5097dd1c640ebc47bee3d90235fdf2d795ee7c4ebbc0ce87fb6e41e7c1024cbf063dff3059f23639f6d521fe49e2f16984b1f7cb0c3a5d783b69cb7e43e7cd0c317197e396fd862133003d0321b29c1f7ff2c1b29bf7705d0076df97aeec179b979e7e56699a482cbcfee8553007de5f2e3f012befc444aa666edd5e10a2b90f12e14c66451418769420836059b9c0f5632569fbbeb4f8e8cd59fb7e34e87be04d7bf7b26eb3773cc31d7cd676e4e92d7dc49f67724f748ce91f582959c0fce89659c7fc5c34ac35ac326e75726b384713e58c526bc86eb6438b39244b7d98e2d93e07a8d9b9e05c8538591beaf30f6072edf60c8e1b60d976f2e505dd9e55b0b4464108585eb963525b85b86c04214973f67ae30ba13976f1d60e3ce31976f31f8708b5cbe71c9628a17bcaad8ba4e4f2e570dd7690ae79651638001b3e196515e6c00c7b865140a63d45b427151c410125ab7840aa375d9b3d9f8cde5bffcfd02e8342f7d9092f56b4d732702fcdb96371be2b8f4e9cfef49c69cd434977e4d734ba8daa55f49be4efce6cd9bb276fb6b1c773ec85246beed60e69297bc60874d46702b59d3dc295ef0567aa68d6a277ffb9d04bbc50be69beb34df541bcb60beff05f3dd46817cab81eb46f5adb6ecb732af7fed3a799d246f478197bf5667df01e64e7bfd27f7cc04e5c8d9936ae45b0a59d7bba0fbef975eef7dfdf0fd418eec0771c818cef32d6b17e743b2e438eb9473ad44f5c5f916c918cecf9bf3200d9b2cbdef079b8c65406db60b3ebd7ec16fb26f8b17e0bc48e67c48e23c477a2892f482340c4996e38aa45fb08621c921b822d917a43db97694cbf9fe90643caec87536c58939e127debcb9fd7ecb9a1236dbed90e43597239be43e87141fc945b2f252bff7ae418d09efd9f3fab9c9fcfe2966d0ef3d8843820fce108764362ed8210ec96f5cd0431c92e3b8200d39127c1c12aabb56fa7af0342ff8f5ca051fe4c48965049fc559c248c526bce6820f72f53969cf3939201cf0798cad848b43125df07b7d83e2331bf940f1c38f2cfdbe3e7c85dfeb41f2291445eec3d77f6439efcb321779d98fdfbb5cedd8326aed750b6b9938bb24c011467af996820b238cf5f22d052ecc468044310c45f1270982288aa2f81c247681e0eb05823f4910401004c1e720f08deff3bceff3befbeffbbef778c9f71cf4d9a571aa415acd014eb1b95c58cbd848d7dd246d59e7b191ee27090208ddd38f9730f1784919202b8d5df760ce057c8340ce9d737776e7bae7c8908d74d4631af99602d5addfd147ea8efbae7b8ebc80eb5a07f002be390f32c95b2eac65640b97d908cf6772de29b2919c908de47c934c23ce8b1f3ef84ce3ebbff7be7ba691fb5a7a3727e7435ed29ff3222fe19bf39d0306e80f3a9993417781b7f871dfdefbf7de73ee1ef7819df74e5a99d7f3be9bdc8ee3fe6eff7a76c9fbee594480f7d9b276bff94979eeee3d923b49dd9eefb593dff6fa913a0363ff0473989f8cf9cff76f32e6ef24064897b948d97df74ef2edbef39ea2c22ad77b16337872bdf7f8828e2cbd5ff2ef1ef448fbe4c45c36db8dea715fef1e15ded77bcfa202aadcef91da239f9c2cb8cd46fca3b8ebf5f5ffc827efa3b8fb7a169d24ddef23b8df7ba4930577b291cf3fcb5cc45f6c52dff3c8f95d392f7f25cbfef92c5e30af3b39b68cbfe6cd8e0a90ab8ed707ab3f38c57e1ad8b52d6bb7a754c7ccfc1ddf493a49ddd939f965713e528339d3325952cf9b4cba3b756fdebcb9e37a4d3af9f5ac93d767ee3bb2ecdeca7899a8edca2dfb9dba3baebcb956bcf25ba2ef3ddb72bcfc4d86fd563aa7272bdd85aadd6999f0233993dc7754dfd773ffb24ef38e4b3f32aa2f7d1bcb60deae7f8b4cfc3992cbf9dc3b69a5b92eb9b7a593d4a55fcbdaed66e7d952ea7adfe4eceef67e42d52e7fbf9337e1efe79cfcd24bed92ff7c169d5027f3dd2ef1377fd3dbd2b2287880e5d8a296db3f194b5e32ffde449fd436daee9cb6cb7380f3c11cbee1f333d1fdf8f5bc6486e4b311a2cbcf65e091a4f79e23f524f94ef105be9a7c39f3822c32e14e676cd9ebbf077f8a4ca62d6b777ee48b043dd2cabce5bcdf4fd2cabcdff71ec99782ded31d675a27bfb1267ee74ffb6c847f5e8ffcec92f7e053d1c9bbdc832198c355277cfadf4f5184ee3fcb3f418f2cbdff5eb6acddd764f2026d2975c19fe4477a4f2e32bf3c408d89cb5c64fef748ee2475fdf317e9e4d79f3f3203e378fd91ba49be1d69e472a4478233f4c8b2fefc8e8c4deb342f67798ccd76876e37391e40c7baf49d741a97ea4b70cb6aa79696f1e59bd518706a6923bef49578c913d1edc25fe3327e3eb3e52c9595111af7573b342d6dc473e90349d235964c776a69197d223236a0e27e3e525319cba9e5d22f998c4b5f8a7ff4991c331c61c31160b66c361af79436491bfd41851c4ac061091a959511dafcab1d1a3f9555d3babf22006d3e475e118026f5c501f3a536f019fda975e9732ff5c501dd73560ac967f43b2bf5c501dc4f2bf53ea3cf5922bedde697acc5739850a6cb1a5df82046167fe95350c2c8b4da0b6696a8a18626d40801078d69fc25d1a5cfa203de66a34d2d6d24c1a53fb1ba690d20f39292a8966394c18b7ea574ea7a6573a9ad25d7d1ef6c13674bee993c08ae5b2df7a5db52ea5277c256ac7039dbc5ab5f4e5da6da0c976b1a00d5edd62fa92d45b8dec45660ddfaa5062ee56c13b7b9d56a7901c104699ca05ac0dc9fcb372d2e5cb0c34a7691e29fca79f3e6cd0cb4af8bcd078c20b09c61a33f9dfccd9bdbc51624851d5cc87a12cafa00195b54156e73bbd82648e384cdca00a86e22d8d80a2c1a7fa9815b9f5900cc13947c3bc1cc8d9ab74b09dc86f6591bb7a1b195e29f6aaae1062eaedc61b3d13c6ba3b2d968fca5d4bb2dab8034f4487e4afab7088109d234f186a62306183ce481e5cd1b1a3fcff5e696009d52b2fe9c9d57ebfb7be4582bd7e11499740a2ed69f976f27c470612dab4fc979410fe97395ce4bbbbf00f9691bf17bbcc4a77befdf3bf7cebdffc7bdc7f93bf7debdbb831cc9b732e91790ba9ffbc97d8cfebc7d639403dcd78ea3249dd76323dc6b805c9ff212eef92b2fe1c8242dab285ca296d567d14baff4de8f5e5f5fe647e0d7ff6923da32efeb2be125cf46bcaf0f9263cfbc7f915ecf3c1f9e1fc17ae67d7d581b7d5f9fd5b865925bdf072f99b73ee7bd5f712477defb6751737f5ae67dfffc297a1febde6fdf58679de69bdb3ddf4e58e1f2ed84142eac65de734ef47e965be65536c2cf226523fc48cd91f34e75adb60701721b75fed496f3d2b023e7ad17d4a7df95749a6f9af8d34f618b4504f8d3f79edad2bbb4fb293a894055c215db752a3310023cde5ceea16a97b350f5b9659d2de97725993caa12ae5ca7f9a614a145279e2d450059bca03e7d2a36e19ed948593b725e8e749a6feefcca84da5806fef4fda95deaa7d649ea967ebbce7e02ec1e9c2213f0c3afef91e2332fc179ee411cb249ed96e183cf62130f67c97bfe902cbd9f0fcecbb713a86e65235c8b4dba5f24bd7f91f382208b08e8ffbe17c9ef5f24cf6791c967cbef3bfcc879c1476a90c9f041922fc8e2056ccb79f9c3073b042f785d6623253f08ce100c1fa93904498f0c5be6bf34860ffeeb965e06de0dedd2f70d92e5f7f3919c4527fddf149de69bfb7a2447ea17c9f723f97a7c39b29294a42de3703a1790a18cf44bd8ad9e9236aa44b77efd24f5616b6efd9f3e6a5aade0d622b7fed846fdb5d6fa1c54ed12dfa640e6c93ca580dce9842da33c1bb451f92ec46ec9682efdfa9d57ff76dc97ace67630a4362a3d0cdaa81c3f68a3ee2b68a31fb77b0eda886f59c2dcee2bd04794e6ac86c7dceeb98d80dc92e7964a6e571fa97fc0263b33f3cced1eecb0c9be302d6ef720f764c7719a6f2e77cfd346f4bbeebbe7a0ce2e31977b01b983916f5ae2b8a577ab41cdf5e7eb955e7b4bb23632c14dcf2f613f3ce6368fc3aebff33c9a34ffd1792e009963622c4322262f09ea11641c708491b68ced738f3f2561e414d09f834abe997087fbed1fdbc87fdcfe0f6f39dea0b0653006701c380ca1668e225a60210d1aebe84cd97d256d548e976f573adcfe227dd4b4fe0a9c9c776c1fb73f9c36bc7cbb72e1520b6bd9e49b1d204719db0b8540a6c0d80fe274012fdf4c98e1c27e402b4770c99286096bd8d042e3109ccf41251139c1495b36692b80b60c0459bc80b3e55fee3dee7468cb6c19e5af87b511f8fe3f6dd481ddf7d9f2fbd77f5dd8b2b18dbaeffdc18e9c17fc5a16fe142ff0b9a12dc37f7d47f20dc928bfdf83b3793a4e47f20549beaf0f76e77b7e94e569dc336623e5cf9db6e4bebe3fe87372245f0be67b6454bd33043b14e172cf64e9b9c89dce7ca622579db065412df3effe5bd65924679292e5bcfc1c19e5977f9263cbfaa7c8846db55ecb7abceff911b5d96c361bcd5fc64bfc7ad07dcd4a83bf930f1845088ca5574d70e1765b8db9fd3f9584b56c963f773e8b0f18e7f3bc745601b94190c39a036111640c8c7cb322e2d6cbb7aa3597756e93d2283083d165393e1730b27880daa5d47a764e03185180a41b1877e6fe0ef95bd4a07697d83a4d5dfeb27f9c4954dd2edfaab26ee95dfa43976f54715c0a722438aba841edd20be6e5beac5dce7a4d5270a53e484526d5450d6ad759e43ec6d926f372cf59af65f3795e0ea27669e4026630bff4ef4926c9634e6e02f20246ae7a7f5b7f5ab90bfafd919adb28d6a4dfdfb9b2fe923f7d8e7baf8dfa7237901730d23badd732faf4c19c0b9ca62e656a3100727b8ed746fefdf4b98d28d822133a450d6a949c9745af00c86dd41e3209b29291def082394cbdb08dead35f7327f9346f7fbf93f45964e2342bc9372a37179ce124f936e924df4a8a34ac24dfaab42eb751d39fefb5d1bcf35b6432c3cb62136add7a4dd6e89c50801c446526bc446d5456f06d54ca2a6823fafe45daa8bbfe631bf18d4a89ebcfd3474d0bea6e99e4fa8333e43e1a7de6cfcf4cdb68a97f3e4f1bf5fb731fb1cf9cea70cbc296cda70f76383f9ccf7dc434fef076f9a7d8cf62937efa19f4d3b08d9a6f6d05c80df27350396db7795a8d7c3b21cc9db6230f99741a97fcf996d3d6976f2790b9f3ce9fa202e695cbb69c36fa4c5aa1979f924e722ebf93cc4b4afab10c5e975aa79ccbcf692ed12df9cd2dfd9b2f39b78cf3e7e5a6807f8a4eef345eb7d3d654800c65ec2fbdebfeeeefee58bc5c7777ca41ce8485aa04354a00f36dd4df2f6ba392deee0fba5db9e3c8695da46ff973fbb99f0ad194916f5794b87cbbe2c22d65b7bfe8f2cdb67559640281b11c917a1cc336e2effa971fac245f27e70559c9382fe76125b99f2213e79e450d6a9703a798417dce96f5eb832d6630defa2cd296d10769e8e4bca0871c597f8adcb292e7ce074116395bd62ef74e965e7d77b2a4977ba4069d2c0d70fd41163b5b8a70bb77b2e4dbf5af64c92d13a1ca074f38b8fe25b7acdce0fa971583eb8fe495e4db914efee6cd9bae5bdfc9b2824b1f9ca193f42bc997450574d739d2a9bbd596f3ba759a6fdebc7973cbdaf5a724b76c5adab2f94497816adca45740268232f2cda6753d32973feca349e387b511df6c5c2effcfe5e720b600e425e37c7efe4090db88e79dcf0fe6701b316da37e7e708234e436e25b7a9759a7016410d132be4edfbfdf452601e12dc77b99244bd87d91e506ecb5b1a90c5dd7755df7484db9e3265d13abb94bdd7bdf91fef4bb0ee7f98b1025e125e0f3fff491cf98d3438700574febe1d9797e591db24bcfeff08c3edf3a3b0f6b59f7fd3d48dab2ee77489e9675ef437ecfba7f91b296f520f9ea907589f1fbf7a31e9d570fe9f5ac7b1d726c59f72349d4b2ee73c8242deb1e873440cbba17490c5ad67d486ad0b2ee41720336d23dfb91929e75ddcf9f6293be2fcb2debbccf862de3efe8dce9ca033867b73ba515e4c2f99dc8040666645bf27010235b6e8a19f92b0e7b62870ed257fdb8d0f35ac6d4f3affe4b95a77799993b9c52844bafe75dbe3c69fc92835207bbc9fd6023954a39a5e11bd12fbd861388efbe20835d05823cc01474e97313f4c227ff817ae9772213fa3f2de3e78eabd47bb287036323fc55c624a2682dbbaeeb28574639e8c20cfb04b6250657c9a53f8718e7334f268a1031ceffa15fbabb331b296597614a5cfaee3f8b18e73b138fb9742ae1484ee9bb130118c167ad5b2629791a899770c046c42f1fe74bd99192227d24f39987dd336def3d3b9918cbf10523fa1eeca0fb7d8b4e34a0850f5a605d2622341ab44ceb4e9cd167539c2139af48d625c65289123fca797fa41c927d36c3cf8f787ae65df8201895dc97123fa2fae2bdd5d372dedf006df4bd67e4bd3f066dd4afff3eb4e58fbfc0772625f77b179d4895d9a20398cbc46a5e24bd2c7eb665391da964b256d8dc11debccd57732bac8f30f0597d253eabcfcf498cf4a55a562b12632d62fcc9a58dfadb46cdcb648636eab28068a3ce7601d34661da28d653cbadcfb71b7d56bf8e97b92c65aebac4581f030e78491363c95bb7fe13296de47dfd0f6c555aaa8d4a26d99ddf5f5f040d3c5b321a179970368a4e22d848fda6a0e4e9c8ba04fbe4d2f33696cc756bfd2a51a51211a617aa2ff4ad9ee65f7fead0467366b511fdfa15b4d1ec8cf8d60e03be9e65e2314e36b043166eb62e136f79f47224bd9464da14cce7e923a679341e6fa59545ae7f9db76e055a6b65e798f9613cb37d0b121ba9404905828c600aaa49f86541b3fc9f89c4e45a49ce0d90861388d2fbae1ce97931b6f5287ae7f7876d34c7a9c5dd97eeee1d13c980f87e7002c17d7f254bd8e5ae563642df7d7264c9c3b7926578d94bc246ec58e500e9fc26a7065b6ecb0daeff244ba23be79cb3945d0d5ad6ef241311a3b98dd4b2fec963ae920ae65473c928a164c9d4c45b7769da1a9d2e46c9edf2bbe781558f49e879484db9839e954c7e1e58c9f1b48c83fde5385bc92ebf299d5c71fa5964a2c49b350d0882fc4df21721bacd632a1074f9a538aafcb94c419793dc1e5bd63cd3d6971579264f279392cb4fbb9978cc9dcf4d4f81be3bf3d72f398e9fe35fe298df997d3ac13f8118e797f4b677a713e3fc19067801572b737daebf44bf6919e5b73efde1a95f524a2b69cb27b73e25e707c6f934aade5a2db7aca45fd6ba54e7176aa98fb58d4abf6077777b7b9b00f697eeee4fbd79bd96cd257e4b66667a998dfccbeebbf4d4d7dfbc611600fdd2bd979c924ff346f56527fce6cdbccc4bfccb26dd8b71deb65e543bb1e0d67cba0b39979b60c3c3bc324efe014ef2029e4c7ae19a73766bf1920758432a3261faf4071be9a7e20564155964422d4fcbd848bf93208c7ceb0188cbf363142fdf7a9821e6c49f3eb5a506ae5bbe7407907bfa7397f3b4e574e29bccbb4ad6a2b694b92d65773e7fffdcd20246e7bb1fc7d7e9dcf851cf8fb2b948e829bbcbe708e6e3c3fcb0cda56fdfc146b1c3f8acc1f8acdb8bcf3aebe685cc2d27176bddb26ff4cace2cd8f7f0f99deff9f93e4a0fd6c396ff3bb684d972cc6a2e5c3e3d5d3e226cf9f8d8dcf85cf95c71f960f96075f574dd925e91c1e30a1a101f2d1f1f981a18ecfb7bcb8f7cfc88c52ccb8000b13f7efcf061a90f1f21ecb1f44c988fe57efecc9a59647aba19e7d6f3effc54f39cf34cabe2f9a9f539e487669c36e727999e1f3eb42619355a55587adec7c2aa7c1e4bcffc614980591f1f3bdd40d56edfd848378f2dbd1d5b8e3d6c09d3b1a56cb4e5f7e7907de3aeae33626ee800196ee4409335192b35e838618d37b4cead0853060db63a92a07d5d471c3cd84cb0f54003d1a8c2a2468b2f96a0b1cf705e7cdff271738587125c58dec585da48a05924502f4fb44193f1a0e696f44aebf60d47261b5fb6847db68479b6fcdbd9dbedf6cb6e593e632f9c2d3d30d59663189ff1a5b6842179b9f98cafdbf22f989dde9101e4a092deaecffb84f1f1230c981f3e7e4ccac567fd3ee89c5a5d3c3d3c3c3e3e3f7c7c7cf8f800e9e9e1b1555878be0af6f33eeff3dd13fbe98000f998f7ec473eac8f07f2fdb0e0330d6649f0b124f4fceb418a058a4fafc49ca7b61c9df7aeefaeeee2ea5be7613c07f33dbb1722cf6b37ee5bf4ab7aacabe9792c3c3bcfb4aa1eeb5abf43522e538b8bd6eddff9726a79b965dbb888bad8bacb8fda8bcffabd11d65e6e3f68bbdad63bb6bd4c5b7b193dd838b5a697d94566d2fa695683e2e73c1799db5cb7b558cb8f7a78d4c07e039fcd2d1f3ddcf83cfb6cfc9e9e679fe13c16e779f659f8cd63d967e0833ddef3bce863d967dff77c8e0fcb3e7bbdcfeb48c12cd1f7532e7ee4c36ee0b3f93e3d6cc96aee7c1d5b7a3da32d79ebcecfb1e5c863d96738b6643777be684b58684be6baf341cae5f613f9d1cb96de67cbd123ea88bc6abdf9defd6a3dbac71b200781ec47dff3835d5c93f69193c65b97b5a8179fd9bcab118b9fdafcc83f0bc665c4babaed8dd4d580d1f223a6a9d9a26ea81730bdc55c7ee54755533b34ef3d2fafd04b56f8cae2f2555221172c3faaea1e0be759bff2597f671dcbfdaac3dcabdbef6a2cbf774f9ffbf92fcb6ebbb3cd59f6d9c4ea2d375c6e7fd9616e7f63f9d1e8c1467735306abbdd58358ca583994c7bd82dc7fb914c9bb7e9c64dc771cd1b98d945268c4debd6576ac8f8acb9b9d5d4cb6deac58f1a8828c020a3032a4c41eba760fcc85fb0e38e2e3ee0e004ad9f86f1a3698502176fbade6c41eba7373f3a22450c63bcb11a43eba764fc681e51470b57726cb1d19a4bffe4e2475293067ebfbebf3f0ae347dcf75709fb45f023d062e0b3c662dafce86537e89f577ef4590efa2796679f90c0d90f7cd6df4d5d0d1be97735b7dfdda77bff0de3a45c5d8df60885087ec4a0f77d39b95cb69ab4ef73741e879f5faf073f143ffcf173be9c5797df2375ec0ce366b4138cd5a4e588767a19f937b09a349d7f795f32992ff9067ea9e472f825d1e597f991d406cfaf434e32640e39d5e090738b9c6ebee7f744f099ff063e139f7f14413baf5e7662b52d617672f1d9b4e55fd6d22233bf9c59a5ecca3cf0fd89bcd07b7f259e47bfeffd31f023107c99cfe683f63b5b32993bbf7cce967c837d76acb65472e797a3e7336a4ba23b6b7f25801c54f66dbe7fc95d4fbf642d6a4bb6a5ecb22db9ebfae73096330bcc2db9ab8bcb8fd4a8d9da72e3a69c64fc48eaa77668fe33cb8fa4c4e7be76e1835f3299aebefefb926fb5f3befb52c92d67168e952241e4accc672d4502f7e5cc0aed83646e2f0bfbf816c6b363a7040c673df6594974fb55eba64f802307fb765a3bee719eff43efbf17188a3cdff3fc9ee3e48c3a3b3d3e7635693c3d3e3e803c959547fbf130a7b222a2c52c901f240f8cdcf1e1d343c278c81f7c64fe0ee975df8b8f802190d80f1f092224023e8223e68c3a3d804c772ca991bee8fbad6df5e9872fe5e0cd766b5b0e16f7be7b29f7a72f155aa9e6f29983a942347e38e0f87a291f5bcb672e35b5a4786265813abdc5552b35b97e82c9f95be8b6efd636db2d24fab8c07081f9beeb2d707ca6815f7696f89a5923ce973ce6d8c7b19e96185a1868c7c6faece85998d6d40eadb39ecffa99c659a651cfa9cd7e5501725039b9bc8eabcfff7e449f9f232b89c52989c53b52aae73d52cac74fedd0a8ac441ae5de6ad2382c4abfd297f2de6ad2bc97aaaf8feb5b4d5af557cfced6ebad26edd563a5a8ac441ad59505b49eb79a34a92f0ee0f99e97f261a57cacd5a4f5f8f3f8ef58a9b9e533ff1ed66ad25efe3ab68acffc3f2bf5c467fea3b59ab4ea9f635f0ac75a4d9a67a5443bb3a4d867fea1b59a34ce1fb413cb675a1ef7dc58b3e68d6a71cf5b441d735d7f7d39b75e766a8756ffb3533b34fa4cebbc679af7e504e3911dc99195a4e4fb6c4e9edaecaee3ba23fd1256fe2d651f9af1a93ef7fc2f9cf7ef7b4b82087ebf5ea025e10b9f3efb51684978e5fc7cf6a3aaf043333acddb91a155852f755544cbb12480389684b0e3bab82e3f126d95285aaecb67fea1ad0ac3ee2799ce960fda2a109cb7694bd8cb56bd1e8bf7552fcb617ce64f2dd7f5d9aaefb17896042cf30ded0b7da6557d967dd63fc1f8acb37d68c627eebb8b87abf642c60bdf78b8ba1ca735bd78f123292c3eb543f35eff2f0f06f3fe358eb79c5ea4ae8a681f1e5a342c4e02fbcc022e9a970fcdf8b76d399de87f68c626fd654766d2304e1ac6b2cbe2baca2e4bcdf4d2b7ecc87464fc687af199bfe78d230c76cb8eccf567ffbfb62eab23d365719b5b725a5c17a785e4969c8df3c2d92a1835150ccf2de956bd7add9262d13025c5aab7f49bbb712e7e6b2ee742a66caeceb291f172bbbc94b38b68dabe643544a5777d668dd38671defe727ae9b82e034d79b5da396ec01f13d0b0f8168d008bdf01a4439e9fa06e07090d4bdbfaad6647f31c9afba07904349780e6568afbab225a535d19a161f12e9b1547ebaba6f58bd63d680d84d696d644686da5ea5f15715c0115968e764544ebb7729a579a8b34efa179109a0bd1dc4af95f15b5d3da568afed40eadeb9ae31e8bc36ae5ece856eaaa88e67f554493da71cb3e9b3fb543bb2aa27193dec0f7a3abb19d3e95d5c368dd53598d34eec1e7f7fc88ea2a098d8ae7ad94d05e6f15d2a8ac9ef6bdf7fc303fa2bafa09c51c9c1fdf2a88063eff487525a3515929a1e93c9515116de7ad64b41e6f05a38d4f654544e364b4ca47e653593d8df291f9cc14cce7df2171f8c8fc1ea4c847e6eb90397c64fe488e4cc1fc1cb2878fccc72179f8c87c91f4e123f343d2071f990f92302e9aff223b3e32ff23433e32df233da6808fcce748207c647e257ff8c87c4a06e123f39d14c247e63719011f993fb533db0a90a36fb5c315505d11d1fcad28ad9d56fafc9df7bdeaf387d57a3839a34e0f7f7e1eaee08af6700557a10f5770c5e3832bb8fa81710557413fb882ab22b4b6b0185770e5fd7005576310aee00a26842bb87a5a3f7f045cc1958cc61550cb2257e07604c215b47d9fcde79fa2b3b7400e2aab960c6359bd5432e263a1b730df63e1334bd401e62a098dbe14162f7e64a5dc9a60014db4525236ce4a716f8205b4cf4ad58070c2846aa546a842e22a88298878439332c1021ab5525366ccd005172f6a5aa039fdfa5456218d0bf21ed7edbfe2a17d5ce584fc8b666d551f4f79de074ffd1ead08be8716c5a2f65d7ca92f0e20dfbe14f8e44bc51ea47fbb0d7b20d4f60f7b29dbedbfb908f4413e3f2ff5c5013beff352bdf352dd4b5df1d0fc430ae6b67339981f8ff352606e3fd7ed202b253e907d6ba546d24a2181e0c7ac14bd01b1521466a5fcaddf7c5829e6e1f91e2b45b55c2b024bb17460ffb2525488a53488b52a42f3fefb72bc7dde0f908ff578d1c773dd7ebd4ed66bebf67b593dff656df156cc4a890fc43ef159bf8f95dac067fd3b568ac867fd9d95b22a42eb3eb452148c73399806f3c336d7b3161107137df8bc8ff1b99d9c1fc1dcc6f91cacdbe2e33856f8229696cd8795e2dec7ce1e56aa8acffa75acd4cc7a5929df92f2accf4a75568fed2dde62aeaccac3ed58a92f0ed0f91e2ff5bdd45512daeba5be38a03ea775fba5be38c0fbdab75b0f2bf5bd8e9da3959a607cd69f63a5268e95722cd14a3556685bcb36c1f09925eac0a2e067a5a8ac42dacb4a7d71c0f7f4a5e6d6edbf4a42a356eab373cb67fdf4392b35b57cd65fad547bb66f93b3030a3aac28828a2b536b7261b3831969ba60e9ec544b4144105bb0b843061ab5936bcbb9e85616d5c2d2a2376abb791605c37de95c60b81c0c672b97adfaa5676dbdc9aab6623916fdb2b5ba376ff2e022cd143260515baffa36d9d8a20b363690e982c634ffb26db7bb2c38b0851813aa5883c634b7d5e6b3fe7272ddfe39c338bfacb6db5fb52e034dd9da6c9f1576b8e302be73ce39e79c930cd948efa0c60e655ca6916f35a8b9bdc3181e7f73976f3bacb9b0490711ecf6f3cf97b511ccaaebf6ff1469a386b58c87c90ff823b0426e19b600b6ac5ebed5e0c667fc8cc34728259dc625fa12dcb2d6ead4a55f7e086036b7da2e5e7d26bdaf2425ab07c145f3ac8dadc0a25f7ed71fc92929c105278b1390e04ecd5aedce82798a3beff42bd76d396d974527f352eacf6459bbfcec34af3f33b311fec902c80f088740a779bb051d6ef8f59b849ab7bf450cf87e7669fed74f938dd48ff2ae13bfb9e0b7d824fcd70bb421c06e14ecd6ef8fe42bfa610b2edcf0bf07c11af6ebbf169dd00cbe0fc19f24f81f19be8b4ebc9fd3321b9965dbc946e67f0f9265dff959f1ae93e7618b9ab77f7e8d9bbdc664fa4337dd8f6dc4372eba6ed77171c7edba8eb363cbe8fb0923df912d65616c7fca12e459886e6a6650639b210d17561a30b6a4c9424b9a301d88e0f22d4d0c17e7f22d0d0a69ae9c60870973506db106165ae4c1826fb1020c545bec0003d516496871aa2db6d8420ac71254448d30f028a32a051e8ca8e3882ddfe59b167268c1058d07dca6430b4d04d1460d31bcc1a34b0b2f2be431e4f24d8b2b2e77f9a6451926dee0d178108d9d1804a79da787a3f57decf478cd10f438f6f32347084c07c8e7130187a68b2d0b343854f12d0b342ea8f059a059a16acc020d0a545e1668aeb0d02cd060a9ea2ccedc61421667daf8cc5c61450eef716fc1d5f7ea1533a470860d3ca810620e3a50b0ba42a3e30c1be399326e397e0a200795ac4544c437240c0c8081066d5432055d5993480638a375c6cc35d3c42d89c88c5f18ad33429439d365fc86028711043414c1e58a16c4a8e0c4992c469ca122e20d1376082147155c41a401a60c2ac4f0c09a21d21c91451455842193c4134a8060cc58c5003156229a00f3050824bc48a1c31359b719ace0b2840b1fc0ba3d45165a90daddb3b8baeece17a92d6b7d4760d175392517cce934e314934c4771e5363905c063aedc36aa9ee884b3612593748dfefef53b91499d7ffdffba9dcfdf894ce60599c379a3da39272fa9f391dca9bb9f5daa4f4bfa50b55be79c5a3a6071758512578cc1e16fb1895fb64e7eb99f5ea3b92fee59ac8fd42293cee36a061c579f726037c57a39ebb191f948cdf463235f69d0028bc6020b2c80da1012811d47b49186082670a8620e8ad3849823b2ba60c14518397c08297187095b62182166883884782cd882861b34d08839230738668d8918b884d1410e2754c87ac3b611d4060b9d1455407401430a255410c38d2da310369aa0218e2460d042838ba4c20133a6f88289243820a60d164221388083184250f96045171b5bb8a63138d0859736389801c61a2cfcc8c0c4095cb080e8620726d478615187981cc6d80a030c34696ca92fd070460520b276f0210c1a5b7e9c71238b2b9cc80185338a3863cb1759f3851757e8f8001a17dcf8019c08e30a398ac0810b0f98214249424c9b2a8630030818cac0718375071a14d098238f26c8d8e25150c7950c3cb031c10626dab0108406ad1956508335039a366cb6e46c91214d550b6cc6546d31c6161d074c61c21a62c8e8e1033088c1428c871daa60e80281196cd26c6d89a003319480e14a1a6adee0100690019c29238c1b5b43c0e104183d6eb719dc50820520b88ef8620bac6675a3e1035b361adc4063cd169f1f2e9210e384aa3ac084c08b1e266ea860f3a14bab0c25ba60a18fbc18e1810f98218607d470b1c3682491031a798c49022b0c355bbe342c809d0504be943122cb863a4030038ddba2cff06b714bd8eda4ae159c6093c51d5d7c8036fa116d4d0d5c64d1f0450d1968fd7f59ecbf48cd4133ccf318ee2bad7506132a0f8cab473f622b60b8d5c6f3b5d66ae6d63d587145adb0b99bc1c34c171e17cc8171f37084444597cff8de10e6c83a630bab336668a071672e67c676393329a4d05dfa0ab07682177668c9a005870c6b689c05d40e3b30f76c2cc8c441464bc3904287e58a32545c9913b0b8e04d31c3145a6e4f81c5e7f2adcc1a655ca06568b86ac1ba2db3c3550b65be58b550660813ba051a74f956a64c1924b82bc0cf051abc1dca7ce92edfca0c41ab0e2077fbc8c17b90a03c5754365c1b17164e728a1080267861d7759de7519145c596d7cee51b1560c07092543cf1c28939e1ae7fdf72042a05d83438f779bc643e7bd8e3061136040e79f90605153950a8318693fcb97c8b02850b6b59adb5725d98b1cb2bfcbeef7b81516cf9b87c8b820c4e288aa2889323850b40976f5170f1bc0e3de12423b87c93620c4fb8e3beb3c3bfc3d3e3b111b652dcf1e3f24d0a36fcfa3c1027e624bcfe7e4ba8ebf63303e4fec08ce10b5bb66c1913e6092b7a055a29ad0a88a3082caea4a1421d4ed068994bc790a1ea145e976f63e000c1182e2c5ac6a841a565cc1958b48c3973bbcbb731665a85460185ce12c45169f127b0b43c31c3094ff090a54fe07c2edf9e08733f50b100a942acd70c39dfe5db133c5c187b4fa638b30ae40e46faa557d25b9f6f4ec471fdbd7efd00e4067f765963e64e3277b6b925cc89196ee939b1e6faff30d1e6fa24a7ed5eb64ca6a52213a7a03b27d545ead9dd5623df9cd0724bd8edaebf97738aeb0f53e2faffdc9a3073fd833c0a4e6c32a760232202babffbdb965d86975072966123b3dfe3256d9d7809517d01e4204a29a5944ec14bf8b2a5e0ca38c9dcd2bb5d23ce3c42ba818067d308e91605cbf8963ed7a711976fd9e322ddc27038058074538227e03bb58ef07a47adf39dd908992e04394a88640f1baf3c9ad32efe6428a5d4bb94524aa92d29a5341ca37aadb57aad95d65967a5b5ce699d66a5b5d65aab2d6badb536273707e84d1a36defe9b1a779299fd0a98649a6a9cb7c7e522030e236b884ea6146d38cd3797cc14005ffeae917f76e1255093eace2e6c847f09d42cc3031be16fcac3a5960c2f81e236efb6f49f525cb7930c1be16faa916f8fcbdf5846f0f28d0935e1104c90b9acc5addffeb1eeea91f7f58b2831401b4defe7d69ef1974447af2f2b789163cffa3fd2ebd9acbfe35ccb7baed1205fa62037f87ef4f3fc1854f1237f8e3dffbcf9d17c3d3fd2077e347bfe89e547113cdbe71f95f8133f0af21c3e073dcfef3c3f077ed4e379c8f34f2fcf2ff323d8f36fe047429e5f043ff279269f882dbd4be49b8cc95e8217fa4afed82a20fff31c6981defe7f474660ab38a197bd47ce6c55d79f3d4806d9aaa07fa017c920fdf5bdd72187d821cfbd04df4392b6ca921ff43e4821b66ad6f3deff20ab625f25e497e8ba449ea7082d8890fff91cb24a88cd3244ab125fc803f917109b058716c1c76cd58fcff91f9ba58756d53d9087c59ee75fbf4356c578fee7717e1ee7796c16a755f9f89cffc8aa1f9bc508ad4ae77f70fef594acc2b1590640ab025fc8cb66f9a155bd1ec8c76c161e5a55ece9f720ab7a3c9be507adca7b20df368b47ab8a7dbfcf6c9649abe2f99fefb159385a15ce0bf96ab3f4a055f57ccefb9055329b2546abaa1f93c06609a255c13ee7877c48560db1595eb4aa1e9ccd9243ab0a5fc80bd92c4a6855dc03f991acc25255a305d92c12d0aa62efff36cb8c56e5f339dfff933fc92a209ba56955e3ff3c96aa1acdda2c04a055e5bc105bf5d9aaf1ab74beaac757f1fcce679934214fda2c47b4aaef81d82a209f65d2de56cdafeaaff2afaa4f3f02b22a66abb24c5aec85d82aeeabbaaffa7e7e10b26ae77f6c55cc56f57c95cf57f9f8aa1f0ffb2c9306c456815f157e95f855398ff3484e849cbadd1f248c94f98cdf07b981cff87d48117cc6df43ce2c9ff1f3909ecff87748229ff1f72039f099cd67fc2339bdf88cf97148253ee317c9273ee30fc979e5337e909c607cc6ff22613ee3ff48249ff17be4073ec3f2193f47ce303ee3afe4fb8c9f9218f88cdfc92a3ee36f7272f1d9cd67fcb539356baf0be7f3fa175a2d4f3692d8c2d55a9350e1d65adb502449a490c3c85dbe2561c22d7f72ed5a545a2ba572bbe3073b6278c30b17ad36c1061d31f8081beb582ff0450e611071060aaa386855cc15a1182f5a60e3f95cbe21e1c62dc7f733aa5377ca468511e6f3ba2391e6ba169abbbbbbcf36469ccb3724ca84976f482c71ebe51b1261e800cb2ed4edd417fe2f4b4c4cfc6d9beefca9aecd6e027a6dd44d7af399e79cb59e730a0210800004d0825d30a71b4b25d7c87f727194e32819d4320f7cd532115d8ee338fb3e9b39801ef56ae5b830335c8e0b83c3e5aed038ae5236d4b97e1083e58e1cd0b0230d9a5b701d8923aec32e5223758744d61d2fdf90c0aa68c8e00820c6cce53819408e0b2f37398e7bea3dc7711cd766e4b88ea42da31cf7238cc65dae4e36235ffaf43d0ea22c705367d868228b356dd26c410027ae9d2a3e10870976ac40c31d021083c327e6871b6aadb5221113c3f7859598155e47bc500572109718135460336b1e1fa51485365668038b095814b1240c1b310c970a6cc25c8113e60b3639976f47587139dc8c501346cb113770d58af8e28b13a2e0001848b010804b7f3ce2d23048bcc086e7f2cd88372e78f966841964cc18b7fc887333b9b8a9e533ce92c0d9d4705d5b57136b7211c1cb15911f75df5f2aa94fa3e4fb8cd3aa640c02f4eb574b82f794c6341a8b4c3658c0a4755fe977768a7b5aa5be4060b251c414369af7f4a5a6b8efdeb353dc9330d5599baf49c3051b8d3eaddf0ff3234a81dbda4bd940dcf95e6210a8cf7d7d4ac620409f7bfadca49123696badbba30124f2a3a6f183395eaee73cce1307afffbe7bb0c8819487d40c101f7cd092d0e34507693aef9609ede1e9b8b9f3e9d69ddf7179cf539fa73a5d8f17bd07c50745d0c6fabfb669f96c7e0f35b1eebaf371faaab31aabb9dcf9957caa2f82def7f81e96049df79e69628fdff91d4b42f73d9e69e0b765d23cbfa3f33ccf2207ddef3cd6d59d2f72b9f379c818f7351acf83957ceae720e7bff77fe2e0f538efef040129f0c3cf790848893ffeebc12972a0f3de4f9183ee7bfc0654def708dffb1e3646dfc78b083e9baf43da627466ddf93d160c0f49e6cedf21635c98db9d2f7224613e7b829ad7c9e989010c78a7270e6210787dcebffa9f388841208704f1739e693dbe2d93faf475be7b50e759e440fcdb8324d22163fd359af7201519c034aad7e74000fcd7b7c881f8394f3908fff52c7230da1c1bf3c7d112431224d9672f32e676036e0ad47864ac6b34f6d9d458abed9d01b9c71823680c2222e0b822a20d2aa0cb3722cc5c269e2bd133a571441877bc49c1863234a34b7f24a2844b4da0d10f72cb44348a976f44f870bdcb37228e5842646626828819de00b941fa54866bb992948db4378d7c0343c465ca4beaf7575ec2b30317369da4538b9ca3c9ac00d1498e15ca3e27e0077b16a501b98d687bd7fb8e97741c2fe12a2fa9ce4bbc79494f5e32d908f71ec9dcb44bdcfb73b439d0eb7e4ecf96def52ef75dc771b552eadebce47acffdcca0bbd563efb9f63aebd4dda57e2ffda16ab76d7793ca007a0c5baacf7d19c26477ec5e6cd25d66235faba51e4ce6f96c723dd198930c07bc440af8252cfc92b54a465326b93a9ff3aff1f93568a3eec8f359ebd4af7bf14191c47910f4beeffb6c599f7ba4f6ca0dee12597edf971b5cef91fab325d1fddeaba577716cf95d912c3dcb44743f269d0d496ed908e690fc6aa3fa738ab1fcefe3580925899210895d63008f18673fc28af012ae497697e8d7eecba0db0f56f27b0eae7fadf43fb274cb24bbfe1446edc83ee3f96123f367995abfab2572d2ebd632ddb84903ce5b59c1e5247de4d16e4390b90901c624337e501b79fdf945cae96450cb828869410d195edca0c695247378795051cb44746badd59f2b6381040a2868c1b505133a14b5a967a4f02f83e283de8f463feaecd7c260b199a20937e8500303adbed746486efd51cde58600e3f2ad7e902d99882eb50fc538ff83c22c6ef944f58a1b613c65b2f4082bc7cb030214aa0d63c5893f502c401a96e31d61f5cb221318b9093166aecee526c2b4a95fc6f9e37cd84f4999822e0a251c511a6d19fbd1d8b20fe94d88142e7d8a85462fb52553d0e596f98f63461efd887a4d8e3c3d9b5f71187d5cbe09e1c2a5976f4260dd72bcf369bb1620b38ed7b2222dfb52e63a3c2d8b515dfa31ce36e9cb3ddf40c075eb2319208edb5f45aa8ffc66b3d9aed0fa356823be8159739b68cd6d5a395e0cda686c2a6aa3ba25bb39a20ab96c369b8df6856f41e8704b8ee3f6736403c8e0221b3006d98031b45ac697a88d7a5e2569a3090070cb0a1a40461c4ef2651bd4b2eed2fdb4ac751ac066bced614d0f70b9f4bb07733c8f965dc73db5657ff71e39bfb8b9de775fd45ceebb69c5d83f29dff0f5fc2017fed42625fdef395b76ffb2b10cb8ff9efbcfa3cfd9b27b4ecca07befbf8d38491e6367973a9e0cbae7de4504703f9d7d1847563bbacf00727f4138df0357977efdd275812edf8098e2821e7ed84663cb380c5d6c52cea7dfd9d18617843a0d6063c6867689fb7e6acb9fb23e0f7dce2e55fb2de3ef2d353142e000868b2e38e0008266e4323f524de27d4469fc1c3447983e84f961861eb47a5073fbc11c0e9a5d8ce5ffd77d831fc9538af1f57c5f3ffbd52dd1a70f5691c9a741edce09ba9841f7d3964bdd4f917e472e711f39afd7d16791a3f39bce06076b7ace676666e6c9fcccccec313119256e7c0073b9efb807bfd65a6badb582e0d767b149fd7ae4d1e67b7d346d369bed8ae65935b7f494f8d1cef773c04b7ae87ce9ddefb98f9e5419bff4eeeba79ab19cb6696b23ef73826e15eb87f5b98fea83df83dc808dd4df21897a560dd0556a1dade795b20b3e0e59fe055f244bd8053f24cbf182f689de72daf856b219cf7a9ca5168c4ed338bc3bb7b4f007bda7a447bf92256f6ddd3ad38c257789e047ed7dd349279d74d2497fd22eda45bb6817fd52762d51cba82d2710b79fb6c8a4da699978eb723f45272268e1831657b77a7ef479b7bb89dddc6a97a8f5c296cd2d8a830702499287d326a2bbe41fb68c1fecdefbe69e7ec94474bdff1ef448af65518cddf3adde653ff268fd9ea7002bf34e4b6d13d1e5ec12fd6a476f360a58b8bbfb7cf7d9dd4f1d0c22cf6f063d3e68f103d17ca23e650aba4e6f54df0c7ec2b8d1e6cea7245310bdf3e95c33452693c559df4526ee64d73fc5f92d7acf390758bf6466f66a994e346477bc38cfef2d31bee8e190329fe17c77f7e1731f853debbe7bb07b1c5264225aaf65dd23794896de9db6e47991e5d83d4c21c6d2bb1c67270557461e73e70c337affa4b611af91a17933301ed3557094a467f53bcf1b4718ec5f46d4b23a7664395db89ce5b15dbe476abe75ed5f445b12a151c3662edb52fc293211dd26a2bb14feab0c6d4974c30fe279fe6f239d92e7ee7c8fe70a5ad6cf4326e9d98e057fec41becfe6e704c178ae28fee8473d46f6d968a72dc3ef1ec973c8f2aff83864395ed19601a03da8c409954b94899a24a5530acdd00c82800623156040402c160d482492a6e8f50114000e94b04e5240934bc324c63114648c3106180300000400200446866cac003eaf82cb8762cecfb5d69ac2b5b976b3a285b3e016443f2c8212281b4d9a62047eb73106fceb9e929881cfa5c24c2e5c64699e1cb73040233b3f120b4f0ea9a64ddafda0210d0dad697261b0131fa73ec8ce8bb7741d1eb8e40067e26f738d2a23536ae20d0bb48bf66b7723b79b8b4355592db0a1048cad7565ef4d6dd2262a4fb3efe6db2f69eafe8c7b55f1f1f2822be9e0471579b298153a6357d17fb35900ba581d6035b772a40c9a7d57ebe026f1b85b8ed51c69f66f6ae8f1cabd79ea8894bb73574af906db9db503bd2250fe2532581d15eea62d5130ddf849ef45aaa84fa3b544dc4e716a8d16611fdcd957db746d2e2b3a6b8595de76b3a0db565bf46d2e2b1a6bc54ad776b7a0db565b7436372b7a6bc54adf76b1a0d956597436172b7a6bc54ac78e4bf767aeeef5c3706ebba5b0154fa92bebbb98b85d222f26aea7e03c9f20db9b43bb8f5cb1cd9a06723f121366eaeceb3666c46d56b38dcdd4da751833629b1f8d8537dc4f9e29c471623c6f04b75e2b8913f1a61285ad81f9124caad0129b59425997f331e0d8cf0e0b4344ebe0496bf73473172f5c18b0ca0c8a8a0d9408a4ae8ad146228056dbaaf084e1759afdd721488fa5ce01a0213a4f7e33f8b0cb535feb9d2f55f43524f67e3500d36bdf17c06713203ef960fdf9049b9729bb7cc294ad6d482509a0c7f268272c0f8635b6fdc948e319d0d6b009d24c6e643b444caa19d0e07369032869986d0506df0b1358e52170fad77c0d56b6412f38f0ba98c0160f9153866c2a84785dba002a2f88d3b17636f13264d7dba058e8dae9392285d8cb329ede8c6183d0805000170205aa83affb536497d0e251ae2cfbfaa8daccee07cb8eaf9d1a0c4cf9d7cb5cdf66d210ab1d9a397f1f4c554997e5788a4c5b9919bf6d350644ef023efc4150cfdeee60fd6c8d0ac70d3172f0034401a2c1065510439da39aa8f21751b2f9ea4524a21bd0784f10d9bc63e754f2321dbb36a5d9bd3e6184a82557e2b9f311610cc117d731603e8efd8d3fa52fb4d5e6c2745abe22118535af8084ada365cec08d000717973990641229e184ed27a4cbf83993c813b4e308dbe6095d7251a81dad02f93c53a4a95e86c39d9d232486a20f46aa36cc3805112b9f5e04a4c0b253dc26cf40abd127871fdb9d8c5a8152bce49a16b005120fc2df87b56e6d515694c58529564a25f306311696deec25d0e055564eae4dcaab8e5fd2a9aa492ff1734bb1731481358a86ac6b6cc7d4f36b3d16530db734263dcf0ebade076df71228f8d86bb9aa12d737f7f7167345d80fbd81b5487ff53c0a80f95a5bf43f60afb286d4afde8002305ecb36e8652193cd0f7aaa67ef9f67d512fd2d9ef749eec406fd21304cbfbe0db4482dec7fb68e152a053d9237b10dfda185202cc7ba45574a8ff48f7b7f5700c272ac5ba02b5d7c97dc99dc827e158a40d88eb545477983f4996cd17f8282f1f3f691ae90dfe8f1fdc426b93fd98e8ed222004cdfda0c5defc516b23fb1195da88a70308e650bf487161b64fee46674a55184c276ac5bf48b37a47ff44a06c1fcebdb5fb25209f3df4d6215aaa84df226b6e817c3c1f8f94d638ab542ce771b2b933e921ff7aac2c70dd4b138117c4b8b3eba6deeda3ce55a10f8b22d7a89926243a7cabd0302df7d953462df25e92e45d1490c96b50093fd37062b000995c5604bae3551d9e08636eb0e1f2922e0ecb2649a79863b40ccfed4382ea62e007674278a7853d42ea2e05716537610f31156ad7187703accd757039c5c4659554e62e924180895ac23442acd0cab2ee5891fe750299157088e6fa42dc2c2e90d331a4b4a0eeb1e465d67530d898a1cea28104953428ba6fdd52f7f42d1caa05d766eabaadb45f048047cea499ccb6614ac0a28ce668b0058d1f4af49e8639de41b9fc38caa2e6dd2c95fae219ce658b826ce3091fa2f2ca9b92425137f2d925cc5141b07fb82b7c9401211ba46ae8f8073f3203384fda9d21976b6159fce446dde30b099d891a5f804b478dd0f88e5c1c795aae44d564b51c794ffac07ee7a99bceb5b391f8869f2c319c7bbff8bb2cd6705f41f0471404f8417b489f4da895e1c90b2df62b2a41f1e37f2818e13a4d021f818f940f5b6eaf49fb503649c650ce3e3b95cd817257518f41d7fc4c15550e103677051f7c88ea4ae6304751fc110ec9b9cee01b463f26c5ee776ea245dafd98598397cef2ee10be32e00d09c780e5b4584db773d0bd6f43c7a16d9e57ec27e80c7be749be9b6bc3ad47bec109535220eb6b4f4b2a11688b4d8e467960e9071e8face4cc436dc564be2057c0a4f8564b43525f285730a351f51ee38977fb6034ee773f562b878bbf1c8023f097c0bc15d3d2bd643616b5f066c14fdd6743b7b411dadce4b2cf4c3844db5048bb2264042119ef6ef98e0400927223b05a2c85be30ba66f999503c1551191f26afb72463b08c3a77260a7cc26f6e95becf0d014c7778e036a8c6cf447c019f8251aa8b11adb1c97f28dbfa081a96f3081ecccbd7e6082875109ce1f536db93e2c933d5a49f8eeb5afcf1300fa5d5deba0643ca31e885230ddfae9c545af766e187883f673d5b9475b43aae2ed0e0bdb411edb410816311d01cb4abd370d8e62f766e2a9831a5346a455f3cd8844fe67348588e9fea6f845f45c24850efdf382f77c583e243edf7b787d1898dd1e0db84ae2f0c9f19a33227b56b14d6cde4098c3fe8f6796a113a6f214d9c424bd6e907cd4a96c3de522d1450f5b3a69711fc781fde320b5ad0d3201cd60826589bfb98129ca8bf7a50130bbc254b964e2299af09004d2fdf462cd05d382d9ec4a6f73291850da4d9cfceae618f6b9fc6f54ee0e22be96033a95c54499d4f0b9b5238d307104f2ade1e5121f9739a1141ba2b4a52e8e4574fe6612859878f81d38b43cbb71c6b1caf219d1d632c41843eca660ec5b8f1a206d8ee4c93494370fcda6ad43f91e5a068f5e5f5457e44e265a825baab93dd29016a8cda7cc5b85db0949ef68c9408a2389182b71139087aaf806bd86baaac6efa846e49da0646f4f8de5793ae29ad1d73fd8b8c6e9fca0136ce8b941a8fd029c0d4e1afff42bdbf6c6c270152e92cb8ef6c66114e69f132a90950b9b3293bab2e7bed2f2c55119de913760e42f5c9868d13c28b2b544c4cb55c570b371bbf45fe4292bb6ea073a7195719dd04314497ab71152c69fbcdb0e211dacce55dfa1fade46b1f4a7b4ffe62b797a994b063dd72a002573e687b0961b88f1512c4a491666d6c2ca23a40ebdbc6ffe06261f50df138d50c4a69c50094dbaf21017203329144aed8dc30713d1edb5ca11ece2527e10bbc358c415be3458c0f48ff9ef1015af9d961aceccd875f163c22ce46a01bf79c674cc8f5be9a90324f39c0ea699ddcbe503653aa34ab53777edda9dbcb17e8660f3e09fe8f4f0e78ec9cb51054a5ebad7c5c7457f39b1af6e1f70a2a0d618d00d613226c85b4787bf192bfc40323bf502f65718d1a4be772c4e5e19b9d48ab2ec8df8b37f545dc10267028842ebb5045dafa5582daaadc0760ba805b601b72c6c55a3eb0fb40761fcd34a67cfc7fa298282fbc2a3841bd9faf5fe2665694aff3d4cde122ce2464ce5b5e277eb561dc812d6909745589f8b7023a677415b5a21e0442ceaa0b6f2d0d8b532bd571683ad5286f8372049a7b6d7ff4d695607db7173f317c2307207634140ab4697ad91797f2a05f0b01975876b10d9ae616009012a50b070c3a9d35baa0877864182c7d729fd13dccfb84f87f420dd92fb54d97f66dda04fb2ff42aab61077f8b5ac2d40f345d343904f6c99fc34afbedeb1bc2ef4356737e894414bc52878ffd111af22984a3b0e8d834ca37b7975608bdd45a751e10816e65cf7a958744c9c4a0471f9b336d28782df1c4b4a074cdfc7cb1957297b23f74b2f6ceba491ef61d496d3e9d763cbca2a852fb8504df1724891755056e5a33141003f6de2521d109812097dbb4c4bd0f0adf5fc28c5e8cdd1931af507f4fc7773eebf8ec17e0c835dfe309434f8291f8bd2292869fe086ffabe8dd9833178a58e269af51296e089d21b08e925757c46edebe58463b34141f00bec9b7d9f57893b561206dc3aa391c9d26e76ef0df8ccf24495f6d701851e382cb81f74f18d4f1718249e0267fe1bff021b4cc7c08bb8c24c7fe76ed3565dd854c7fc15c0dee13c9476bc6dfc086435288787bfb982a776e9aa290b297b9e8b7c834ab24ff2626e811ac2186231edb0114d7373dab53b98c8f8c3f27ab095321349581d9ab3f901164a3fcd6058ddb031dbe1e3c56d500eb7e727a00ab793e42d04b4a5b6524d25c3b543a9a89cec89c351aadd423471cf883d1a7147b7789a1be71cb97143512431d586f3d950bc068ac8e624faf03bf67374a31e4bb3b9c667e70d02c449c9c0ef604d2f3181fad84ebeffcaf4a856cdef3670fa7c6b4e8f436705c135bf04008939d81cfd9fc1344836281b375fc075bbd8e3629f4c4a1e1d164f14c3c84cc2d683a2be1724d2c6c6ad3d69b84398ca59ef9de1d4c08cc5a1c15808c026cca8bc7c1b894d35882fa6730b9df4016b1a74d86691502504dc18188f6d2ef3e948169ef43e23af8e2388890179df775479bafe7c39715ae747f0c1832462f6d6f9688a0d201a9996d540fb59dc8e0517d01ac8ea50b5a49ac2a3947b6c564aa146e1d802ae903d04e504e8b0af2aec4d2c86ec62f7e0722d509e5f388e5a472d00c7a889b1c86656d70ab81263a8843c35b980809e77d684cd9b18fce0507c6227e7664f823bd5b1c4bf50a0b93431009aafc1be750d4e94931d8aad58ca2d0a67b99d388a50799bac45e277deb2c158a40e1cd5292b2065d2818dbf0e7ec144381c72598292358f692c0cdbb18ead17433d90a2902211623b2bb2d3316c0bc8fc073c87792b2a0834d056f6be63862368503007fabeac5a6e37d8243d42c62f49a60b3f702ff8adf6ae0b7d1103c0192a2dfa82f3d0d7af7e32579f4205a90fec8a36be6f645ca416f1a6f2b09dafc872202bc4d4f65cc1ed55638028508991c6d2988fc954c71ab40ec46c04834f958c8ff241b5c0617ff93df3610a39264aa9526e2ee336975134b99bace08d179b640858a4c06af0f93b0b9978420856b2dc02679649ea078eb4c7c264db17e358a6b2fd94d599176c6da5b801f22785e364936e34f3d17574c47395015f21b36dbecca695c57852987a0f212b70c234effd22f16d588c1c665061a2a890f65ff16602e0a417cb0f77f366cd19ea1bb2152df36739bb6ba51a85b9e10a75149e17c0cc426ceb59f3edff614cf4962fd529f6ee374bb81bd1ec352231ea41cf99e5c4943c12e28a5c9ed5de708413d3ad334708769836d837606d09af0a1aabae4adfccfc135b693f59dd041ad92ea50283b71cbdc1c89ca61a6b1289c1c7930db16fcfba1988d5b208e5228b7da9df858c25ce86257336736f8397be46d18aa9a88283108eaed9ab4b92f6df953ecf5b80f359d6124a3c33a1bb7450eac8014c2e6c6f23314ba1daa20679a275e5b675f6e97a34ae9973b735d8dbc43b057eb89475f8286b185b50071bb64041867a9ec18cc8f0be06051fc1255ecf67c0840a408da017e464a580d284f9132cdcfd27c890c2ba717c0394b546a4dde92938c19ab2a47daddc6c9850667c4da0727716b54025a7e464548c0f2cca7cce9f4b87f8f1c9ce107c9ecab899a11686cff8281179e1c9c172f69e91a73d2bbf76f20412a14d31327b8cb820c5e10e82f0112b1d373182e03fe490a3081d0ba6b67977c43d68d6ce478295b7e0678309448ea13a0f12899f0a6be27f8e08efa36d177088568e1dd4e2ecc53c53c6ce28e730a6e2f0a4ac6d9d555afcc3805638f2200214f2b8872368e8db1ec79f3707b4765ff0a1abfde7a141b736cafa428742465efe61cce10287227448280d932d4bf37a996d398ad58f4546d4d776428b8a409019f824244d12d26c9a8220209182dcb93b10bc2cfc7c3d1129af59899c015f1724489c7b3be79f37824c6427dd9ff129e671f36fc5037ecfd74832ffd6119f6248764d0c27125e23c05d1a20f2cde3fa0e58552312c24677a2e31e9198fba43a6aabcf66c358175d133147b41fc27abae6ab291202994fe056f1074f4132cd094310fc7b3647bd41fb20b97e6f4696a2ceee219f2035ac7e1f5d184a545fc167f7eb76a794ea94106ecf0e46d0cdb4d712935ac3c5acdaa9ede4ec3ad662f4f78f4e8e10417bf47303e58543942b24cb457dfe6d04f54d64ddcb8a6a19add0532fff8bd29f9d306bcc00a151766821325916511747e1565f29aa1bc0f65ba8fe6bece2abcc0b9e745b3a624c0364ca8d377dd234575dcd5a11854014538a91761961f546f7db96638bdedc076bc69cbb40d6835111cc9cac3c94bf76b2c6e76fcadeae6ecb2b6a4a050e3c4d022c391191200e2331b555240e54cce04c407e9029d0579e35bcca2e03b4d4b5a5c0028e87990e0fd511318c998a6a8d0573ac5268a2c43f8f367b88c6a527204485f2eb30744e32a0fa618778abdedfcd917cecb480b04b36ecaef6752784dd7e812f4dd69ea941b5247625dd5383c0dbbc3d970aeca28153c98b063a6bfdf075ea90553099499631bc39910426bc4df15e05d4b986e19d470deed8add9ac6a2e5822c78fdd7fbacaecf93444e5380f66a04280312d0a6014adb33e0eaaeea300dfceb401cd57306eb21f48b7ce3bec03ce82020b03751c08a0330f2363b0661c7990f6ecaef03935f9deae0558b8612c6e967abe56a986ee8cc7c8e9ec64c86c8c92056b5a966663e995538d88bfbb2368a14d824b1cab3412b297420dfaf94f53931c303b16397a243b5f4e9020abe2eb3987860f2e4314baeabd039cc2c799270fcb0c83463d6139ec2adab5e06ed873f2d74c63988c837d0a1b2b686e3b966813830cebc6e003c5c20e61651f982dc24a39455e458475af067fb9fb302f601974b7156b0fd345722fcff232932608262a63b12ae4d82571cf36f28691714877f141a2b36ace71478046c960030d5107834d8e6b4b1e523c46be157c3f08a937eac4f647ce1efea3dc1c001a950b5877ba75a0666dfdd7df6d1b5489397a841db6d7f6a7424afb0617246a3f6cde24995ee53538b2d4248a420fd1d1004f9c4780e5a28379b65c8bdd3300e2143bac63d1bbf3653d0d90cf3e3aa318dd4638bd5df39340732d58fbea060f181854359a6d324f8a9fb91a0d72a5aa69f1d54e25578d80734df34860ad6de4aaab51961528afa8fc199e0985a0a55fc0e0cf5213a5ccaf2c19ae216e625f7bc19decac39a8a4e4c9a8bd6c99530a9a159a9828315f0d0de303dd19c9084f4f82ac7cafef3824fe76347c63a78f3bd3ca748d6181b3aae4589f3dd9a158a31354fda8f23842950fbdecff517af1abdc582d3729655bb3d62ebf82b2d76202b030ab3edd6b3bcf9872a21020d098cfe474131d9f8881b8e320b1d2f92a1e6a6155ac7c825afaea89859cb6925f8db13a1b368bf035f966c361322d16cc9c7c432b89e3fb4c9f5ede8060703ba617e0ee3e1be41e8878e61e691dcd91971a1eab52bb2094206f3ed31a90a9c7425791b93596adb98188dba61a0ae98257d82925271625a1561e25fe62a5f4ec490cb33f1f84f291e709047901a3d230d647ae10e81eddf6059a3a14f3f0aa1dcba6c58ad04fbd54c860925e988a20112329eb460e6b766d7c632a0c995e75cf2525126e0454b7c703cb30ef606c5cbe3a620e6254bcb7ca8bb31cf844bda553c800a5dbe1fdeb65a9827a40ceb129e8cc8c64857030d5b2ace7aaa7d6448771bad1ee3eb5d33446ddf86c5340cbc30eed683ce3154670352da535a4ae9ca877ee99d01da38f76b0761796b9ab45fc4132b95dfc91eaecac6d3641156fa3af3e9d7e5d58686c8fb1dc59e312c5f8610c9a3f461c14c33a8cc29d3dbca82fa0905d291fad098fce9327bf3532eb4e80e41124c7f7d6fa25969148e92796844f79b9b14a1a775ff7679d81647cdfbac382f3478b3b10c861c42d1b9eaa7998aa9de41b83946f682f8aeb0dbcb1b7007df86e6fcc6a4cb5bc08562bae4233c01a32dd8e2fc98cd8e2da5162b48ea349c46318c13d941b79f7b9017a1abd747550b30ae18250f4c9ee2569f77ae15411261d39c1c4d8cff22d71ba34249f7805cca4e0357178e9fd05bf89eb50775e10cd84868345c6c19246c124a82535508004e488ccdee4287b70e51d1c48cf2cdb65925c82a54f6d2bde84654497bd64446a74890674443317ad4bbef9e48e7acf7f1cac6a7add9cba48340aa658292fff206192b2378fff77e64831a0247b663aace9094902e6eb7c8320db16c9af5ff806e02ace02bc30a0e22dfb0a0b1a457b10fc1cb1e90c2f215ce9cb11882650895dae92d4a29cd625c6401713c8c2c8805f00988201352816c6039d3e6940624e396af85bc5b2134e0fed64a53847d6be900bc525e93b1627789b4e2a8d94986eca33ddc4c2ca4c68d895d5c6d1bd70cbe2683f9d7603e67570a04604593c6e14f003064186e9048bac1342ffb4f09d369e4e42f3f816e1122b211b6fda741efa7a66ac9c44d18999f0ef7d1599f3446fd4f543d63d1326271b94317358cf39f19a8d56a0cd99a323b8f1cf3dc9505705877e4ef2b042e12e2a7e5c04a4e40487694133e740fa73127b9e371fd5ddf549a7e242a405e1af5ffa45a71a2492f73b840a28bb9e5d6636664e06fe53ba09dd289b400292d86def32387d22d79a4e63ffec3a170a95cf7fde59a8ce47bc1ea19794da82272661351f8826e261fc9cc4a4a748a8a88e0257772d98c821f2643cdfddd7044c4cdef8fb4cc22a1222286e49c69638eb59530400a4112a5240cf8b74f458ccd6d989ed13c52e16f0052a5a064bd78415466aa9a88aee0f0c936439d7a0c0e36749b66f8ced871d8ca8aae5f9c0ae7e7c5f69b1e42d70dbbea1321e922bf10e92d5e5713a5cfbff493459395622337344fe1f1c194a02dca875cd68446816d23401f3b45340b9795e33b6f5dd26b22ec2d04b6c20a0cebe12e58c3631d323007d6dbba7717d602432eca6c06424342e21373b78f45585f65c466783a445fe101750795d558f944d2c2b2bcc618562cd8af6207ec66713b460b523020115bbca4838b84077e0c84c8264dad76ae4048c29671554ca45da725d2bb84bae4b6ebbce50ad02580a28a2b460b8f1de23d99e7152204bf5e8e99a00406b08af4f3b70fd25c3b63ef07f5a148904af9a06be3649702f69bc8cb594034539c8055f06f7de2f988e0a0fba5996630ff34f0628fa3398515e9888d72125991169b1c957d4777445eccdc86f00c871ccb28e68d5211b3a9d97f910d5ac17451bea92b1371d4423956a3670743e0fdc94fc15d668e7f67dc9fa77755663d2ba5bc5289e839bcf36ee0a7ab7c0c7b3b6273b0b95ba65c30d0057e4272a3dec8670c32b03176ca5e0c0564a5c682bc10719fa01d0486eb8ccbb65121561aecae76102cb1fd130f39515656430584f2eaa8bf83a31cb928a59bd0c5ec33084323ae99060bd19bad9a6ff4db1302b8d23d957c21d256b11b73fe9c8442a46a54e1a194406a7655556b34d7551d4a873a373e98336b12b7ffd5f1ac623bc89efaa95227444d9402902da3656e2dd42224356fecbb90c100896b103f18127fe06348828f7e95f138c1dda1bfd3b80af0eecdb44c0fe55373848d88bf6773ca637f18331816c4e1f514adcf5888885c4e82cbb5aa6981666dcb083a28a5528abce836ba58cda4ac12866c25a53b439a35325571cb418c59cbd5f4b0a14435fae728a6c22eb63ba21f0b02ea2c88fc6931185faaa8bfb170bd997c49fad6c4c10ec4e85af1a8c0551b2c071552727596e7a4c6383ee81591c8d51e04b0348e40668cf0864db44c470607b66bce09bbd9d49aca8af8422db542406249450b70c2a88b5474aa05ac6f02541531b1a3278774a52ef50243b7e684e5cf89e21967a60f66f59d95f665af2a46132cbb3a40df1fbd2597b46d04ac644b14d8d44edbe6ced96b750c35d7ef65a705cdb35782dd7817a771cb830a69582b64ec18fbb470b69de10b744205b2b716c1a45f84b35602affa802c781ecf05a855512adc67601ad6066ed965e7d09c66d4aa3f07c1b3811ab615dee36719a67fdb391ba28c101822f0e947fe6c40e29a2461f883045d5236c6acf98c0d4e516d92bda8ca55b8e86305f984477984b78ee01a071e50aadbc6d8ab4e18b65b40a35963bae13d0d8d03567c6265cd69087cb48255b55c8d084eac238b3217c5ba8f431d1ee73956585bd296b339df433356d3cdd003708daad999fe7548a5836ce2cb614013208e2710719fe7d7cc959e5ad28f9b2d2fa8e6005044f000c636eee67ce69c2c488f9059dcbcd6445fdb0582467c26e8529c410a3a35dacb2d4f909c21033f090c5909f5cec4aceca0f84639f6fa8cb5592bd2d98dec5c945fc4de62a410d4684fb0cb10745872293a626bbb63fb4d5cec198789dfca975030edc7303d840b4d270da1e501a54704f93d6ec1b760d006e107ddfa245070f3049f423f512795b690beb048cac6ab104e6d8110a3d95668ef1ddde484f24866e74c6d05234985483dd58a2883896e0a096d090b2ccfc5743cf49bd84912e521a93345511cce6733dbc0cde1447a1f17c7d970279fef0113a656fd0c67090c154ecf47f79c15d315f5bc5f4431880d4bd71f733a54e1f72374db709cc62f6fe04b38e6fd57cd18ff31c08c4904180dce7972eb2819181ec8033f3cff01bb91f8193a858065e1d784f1258a2341d823856b5540c34f8bea03c9f184648040b4e15a404f17d7a4861e0d3d06472b05904e3a09bf5c84baa6be6db017b810fac420fa7593d0fc364d40726300897a58b4bf0379d470245036ea5842be219028d27c120ac55cb4dd390543216ca982372eea7599706757bad8b70de3512cf63e37aa5bf7f18c017c047a3a40db8949ba7c75981a12cdb6232847eb244bb58e5addaa71491a1c38d7873555d1195ba9bd300bfd56d5fa4a39b577f4e8768b08c820f17b6e2c83f881998e108488ffc03be9e18ceed0cb1e13f768d62015d10952be1042d612e023d189e7261c2fb4ff6d155e563a330b5b0074dc8c2c702cca6257435070d63781ca0f9e6cd9707485ce13a144ebe98a8671d8cc4e03be923d102034a14d5b3bae298de44a9a3b98127742014a90bfda0eb4c554c4125b5165c8ae33bbc8b061e37383a01311d55669b99a4900ee76b2557c14caa389dc69fb56046612bf1035f44aa8d7d4688b85a2f272c74120f667e81e696af47b9dc0da3c1df39fdb836d60bcee929b5775a45c799fded0457f4fc00be748648df675fd1601db4122ebe132aecc9842e16fec36246f444adad8618f3161b71ac1127c22300d35a323ae0f85fac514c2e2cd319f2457965303b24639825e55c98463ea021693af1df30fde2c8ec6affadd3864e0f0ad06b9168b0d0c14c7a5073490150ea22b9f87a9f36981a3c68e54a9bc47066681be04a5dee16d6d4aecc8bbdfec2aa01a8d6159c7b99a1f5194d3eeb4c6fe818d6ebdc5343ce0fc379e6a00b9a1d5e5b869480eeb868dab03167ccd0a55ca4de06879308f49daa2f715d0d6c97bf50f4f42708ca49380172195a1595a672380633b6cedd1b05ea060b6ff7049f67352aed578b16161345c4eb0fd44a42d55566234916a2d84a079065aec1450dccbe353ec376275fc8fbe3856e95a799701755d821cfa047dc05dc734c5b62acc58f0523fe0f68effa2755d9329662c1c67da38fb806348d9f06a9957d84a7a7d905efa582ce6212b564d0fcade56ec6e5cd899bfcc1f6960cc5e12228a757992bad2ecd074b3605e912c9033c595d0825ed60f89cb9cc2fb14c03d3e5865db4bf2ed76db903c3b8849797558e53497a6cacaab99016b29423b3d399297a093806c6a4b385f75c2bb2781582517417de24e744c3f2289395784d549e3366b99fefa9edad9b67691dd2d1bcc990091e4e786ed8a1bbc12b45a48005d83dfb439985f39e983a89a4b52ef6b82592e26affc2145d50f75110a3f4e84b0484b9fa67c8ca1b323f66e9b1b28e73e7f673734a42263ca57fd68a3f1efb29952f6a68310ec53a18c815fab46a52a375f07253763788165256f45940a4386d9563b9bf00a009b42218848a4f3e77d23735971b941462a9001807f46c8ce58293a5ab526443391d6d07befff21e20ae805fcaa225d336dd0142e9149c2a63d883c6c1aca1789b76defe3e2ef3571de63dda4eb1ec57b86477a5b8d674ac2ee963193a804b78c73b9c5cac05eae8f6b819bd574a4d94d731b6047b2b24684b72989be179e2b241de3c4fc57366424ed7877ab483b1be00b912d3a38fc262bb00342f81d94e1d8c9b432bf09d6fbb8429b73abe4ce58b3ee5d352c8d68fa96aaef56266a0f0b467defd9c0adcc0884683b2e6276280651624dbe5c107a6535980792f472ae2e28490b9c14eb309346d2ee2488d9890a2a4df78172ccdf422ad98502a333e1eb7c77d01b8885a03a13bf84bd46c05b899b0ae87b50f2e46ca2566918feff7bb2ba06c244d7974d9de6d3fa0c86893e3aa91ce4d3ecf223a4b931b6eec80374ff3306e23a8dbdf267979172a8a4d8cd918821926d3e93ee2598c3d03ce99ebea59523b2f578ae365860f99a3ec9b1f11267f878f279d274c0b740a6b3fcfc2f74e60f50a92d0189b9a36511ff3db9c8d28bb643595a5adbbc87cfdd213422562cf307ab0a88c8e528c52612b35cf27c8fc03e41ee9e40a7353d333a1de661a7cccd91da7e5ee1b660aab410a3d35ba4b80b95ac67c4d1bed1e3f8e41be022a76e48f290cc5b7ce8f7b3efaa74136271262b8bea133efd87b7b8b0e40df52f220b0f4bd6dca9eb30c69ea89dd187e94a8b5491c4f2376a740fffdb4ee5c61c202e01f903e8de6658c6fa97ed23ce494472da69e78da422df06f92b944710a521d648d243b2c31df30bea187b2a6184e86b6d9174a35a888c25419505530a81d89cdf0e53284215b7994967971a2a3f288c1b09061669f595e191ab202e53576d5c7445cd42e2876e393ed1d7ef2b074e2f344afc1566fb7d10854ba1e0207b52d13bc235cadf0c77afe27417881ede993174093ad11804c8016383e8021228543858594d17ff2c0bc8850e52d2a6ff939caa8a50fc96d4d8d7294be8715a405219c85f607540038fe8da71d73cb17954001bd0d6d64e478afd09dab1831278e8400e075aec38effc78f819f5f9a9b9626e10c29e9acdac9f3cbb3fde043b5850af8720662b1b2a3d3ec2dad1aef992798e91403c8fc5d004b85075e72cdcfd17277332c73bcba6a5008271196b006fc34b4d4b86d5fa169131f4b0d970a26cbcb16d5ab44635c2110c16c49dec6ea7c61a3f2a4c2a0b8f495b795dc704c8a7a40f7299f191e400004ffd0401221295210ed78537d53fa5a197865acaf5d06c271ba08f664ff4a72a40bde666af7db6122cd1d2ebc1a08ba0d8dc3b0688091d626b7039513254cf367c90641b65f017d654ecc1397cbc380cfe89a615024bc8d979fe4aa3c11ce5073a204279a53f437068b33386c6e6b583ec8a5c538e68d1614fe2c5d1cfda510493ba803162360a98d522644091a30f11153e8123c9d04cd166233a0172062231c0b85c0b0e810f5d68970b3a3fcd4443587d88778e07800ff0fc8650b89148ae7d61e62cb89135f25ade7e4f80612ef765326564dfa6c8a458e6319dd0e2e3713422c52742f2c27ee814bbed56f037fa4318c6ca8ca406bd4119cd983e0368ec684bb2262c3dab68ce27fe52aad9237ba6543b6dac382ec64e3466e2078b5824aa5a03d0c66edf71a70bc8e4b0a6d7d32f8a93ee70df2601cb3ca4beeebbdc0b016fe8c2f29e1998ed8a9a7bc67d7794ae275bc0379ffbe2e2076bd8d882374a52dc20d49220d955009c01dd5f35fe3fc5b0f0f26068747e74e6d410e8bb7c258998e2e1351d91261232e6ab8cb7693551bbdee21f07dac98bd4c2c06f96689410d4871bc4a33aa4f42e4c871c9c067643a19880be554c3ddb2b91795987d6f4f00501f24cc87750508966be3c27f681c35fe4aa37166557431ae96e21d8db35c6ef8a4923aab921ff83fb42f8290e3aaa97eed961bcd54b024f37c446f87e2d4bfe41c79767c6c4842cf002e4b94d438f9c7910c68e858e1233d204a14cf1e28bbf95ca976158f4a2f002ef6191d800840fbb3880eda65dd763c360e25b25c9bfaccb14a09116eaf8907e3a749b9e944f06daca1bec60fedb8940142cb596adb2908927d491a3cc00a5912936aa14eaf78bcd822058cc6b9bc81ea951cba07bccb33ca03d0f078efcf8f944e01337a2e3e1f740a620a9ee6e0cfc181068473be6008e410f67cd134ed81c81d219fe0d58dc7810ea1bab2b3f37bd46ea7e0df1d8a2e22d944c05e13bc09da9c1554ad12043f5e4656e0b845fa4768ad419061f862a2f39677929c1e2b140fa471e01fb5c8f8ddecc13dfb6477940b444f2708325e6518837631837d07399ba49802db594ba2fcde6957e8b1e2ef01beb0486f55834f58a12a641b4f3c1e5011db971be73dfb351116296e0b6d2ffded4ea717492ccd3d4ff85c584bde21b6ff38c77b09ab9517ac8f6c1adba5fecd8e69f76573332436650c19a6830012b822f585dc0e754ad9d5ffb80736f17610d02ee211a1f4b691242831405c4da0f3bb63ccb0b8842c679a9844e1769b406afd9df8f1c185f7a5cdaf27030a8089abe04a43bdfee115689cd5d49709e73baecaccd3c35065e4c5016bf02da8635da9b863921fa74694198797d1f4c476fafb62acdd9cd6ace13811a1e7698817981a511990981c94b263925093493cc42c1324f437a505f79f91c8805a087f6c2bc4014e09d46155f1a07f5bcdf1655e42622f119d89442cf2f04e3e302ca8bdf706f68c6b1544f7b4f0c1f873f767502402bdc1a9e6c4b57fc0c7b8a532e63d858b1402d5342916a118f47b481aa82e62954d6b1fa79ac1ca6e16a1ea4a81ab6c3bf2d6fef8865f04ff8a5fdc9de2167d223ed628d9502292b90708cf3b9956bb3863130d4426b43ad384ffa40d3abb59f05489ae7b4a4a15a03ba654e996213e493cb471a5c2694832ea0735ca94c7f9a2e11a2ee5c27db0ca397367947313c24ba8ca9da0d5202f0879487ac3a0938ae7ab8f4f12c3868e376e283409dd46d7a90a680199bf1cbd0c016a9b1f3b3890559697a64d1f36b1549a718491efe5bf4821ae0db4e7de2c86948527e62560332b5de90b3131f2207eecf27de8e248defc14190016b621045225f40403a16846be7465520144315583cc7930dcf928662522c1c542111268e5c3e5eebfbdfa50b0ba25130525db8a30fad0198c02598a4041a1c3a28ae934d3152c182d08ca21302b81a29f93234f0882c50f4c2aead4fe0a982e011ca49ec43dd89ca65e9fd4e3d8ae4d7e7043d263d349593976b8af67cd9826f28f49649a7682b9e7901ad1e04249174ed60a39804469cd95a9d10d81ed0f2e6eadf5157d10c91bd661c1a805fb18050cca10edc8002c83c12ab16bfb787fa354a71b4cc7276adf4e9be5e474adfd2b76b5ac02dc8e17f325235ad142e4f3d890ca1789b1534bd4800f421fcc0e1fdfee96e7bab63302e20781aea6dd03bc6981d488612f279d149bb2f84b83eeca1a5c1a32cf9791991f6e38c06608db697be973dc1bf697783906b156747a42e02bcec59564899fe6763a52d3cd1a84a776a4675e695bccede27ba13a60ab79e7aa6a6e509fd5616250527ea86a00a3bd609c926a4bc07f4c20ebae18ea8e5ec2dcd020a4025a3049743a162e90292f64adadca2933f8ff4c4496da1794aca21bfd3f19f8eea592600afd480c95ca5a7904745a9561c6b733051e757232b8920fe6b617629719f7e867fcc38d9dc76c41e217bc9cc92443ddbc6650a11bd6ce36e3b20a01c314d244d52ae85bb1d0ecb36ad42eaa5193986eafa5d216ac67e483b1d4c21e6fdc730f81200c5ebcf3ef30da45fedfc2965e4d6841fb1121542222b4fc11d168a2cd6edb567eca780a9cf5c21f9c624686dbc9737c7f5e3339a8442a11446ecb6cd2cc18d0946a30cc4157317bee4e162061afe05d49bf6bdae800e1e3053f01e9438a507801c53c1b59acd5d5c9c9a10bf2fdd369fa574b591c6c7fade3b8f5fa830730b3e37b82b1b4e92b0124ca5a0a10c15915417a206c056744823bbe521968388bd8e980922d25df6f431dd1b3dc3e80802dbadbb6afdaf8b22d5fdb25ed7d8bf5b5b8eeae646da02873b2605eb86785f75c38cfca59eddc9787e5e0500e9eae39de9ccb5166b9818e6e5478f6f7b542703a4356bf004e5b41c5a7cc6efba5ced0f01aef42bfa2aa0cf1d941e833205ae7dbca6f5e232788f00cc65035626c0aa0e77e381e952b544757a815141c5eb6524f4e7d54cbbaaead0aca33bf3dba9ecfb58259107636f517737d3f8501c6410749d8583b8bd11708f7a007929040c05dd1bc763f06899e0540e26715fe3f88bec8d2dd0feceedf8cf7bdc0981296e1fed5ebdeb581d9d85d24fff1dfee8cdcf9f85405405a1d63e7fe4226dd132a19975198d9071affe8541a18b1ce3665d6464f94bcc356210d7cd24e8db6ac709a8a5d17d2f52bd9b05234901c962d8314f4d077a709a0e7ba526852ebc61908b48d3240bed75c769b8895e0f14fc880fdd07e5229373971a4ecc463a8e7130d5bb1dd3f2f42bb7306b6c5e070a849ccbc5dab181bd1a2f3d4ddb73ac1399e133feea459ed8d555a0add025cd11634b39fddaa61b84b875805e3857d092239f4f5873f4fcc2a93a3952131a61bf821231172893574ae9674f94c7ab0a8e061cae60bbb56b6024b51e14b7d31f6eb6aa3a491058502e0d0f4f0572b85100513128d02c046b6d56cec21c8c9aa4a411a32a42bcfcb49d57319ed71a29cecca6c0f6de8862390d7e37b78a77ec196ea8239d1d1b06c16816e58ed2711ea9ef781d30770fdad5d9d16eb0788d718f55863db9fefa4e03d7a213f5b897189c2974ceb06fad5bb6944cb88050a6713991adc5d3f511f64bfc4195ecca2edad235ac6d39b431881bf145b88fab84ec5bcb77872773e783b9eb7646d474084518cafea8f906d0d93dda09e89b4fd7a246d0f36a68448211c4b02852f042d4b783aecd0482a0d70e6180fe838af8e9df78e2bb5c304a9af4d44a80973aa8f03a7325170da19f0f4650ca02e0950dde9d3d12ff0e94090c373e5344ee7de32add94ada20a30c9579bae908d89fb9b4542a730b784e2dd6d878c2852e566a01de3e98465d42759ae35447bbf10c8fa4809f93335aa1b01b0a10b363687bfcf4c55e7995c237e35a1bc9ed7c4e78a9166b38517ed17176f94215b63128c613141f48f11afa0380ab1069ad336cb7df1096234add69ca6ac5251fc48306d53f506426eb0c7d6c21389818a3196efc2448ed07ceed9eb602354a44ef682b4e1b60865964f54a3a601429cfc0e30d2226cdf3ed3236e850fbbe3a2833862fb2562379d215f8301dc6c3b15806a78f41def7bae83827d7e617d80b55a5b11e929376e3917da90471f30c928f0b59a8cedfdcd225bc9f939394bd00cfac11cf3e2c59b368d340451af5684c87cb6239e3280c137ba7f84d04a53fc6829ff65b169cc3384e326e385a1788884ddc5c12d436fd6699b863e1a73242a01b349277207b78d53622521c069e124a2c942bacacd7f65d663cb1bf16d32f63329cd15e81306681e87358473967cd33ee8826e632c05ad2d53f85c73981c9260a01a2c8a89a79007dc1b4483e144ee238ab52945020a53da023945fe074f500129126aca5a5b6036b0705b4c4b6fd9c6ab03a2f91286aad0400cd5db4ba28faab50a7e7f6c4509d413f73e6652a5b41d36b27ab08fbc216ffd7cdc1337363b0a96baa56ae00c04af90fb7cbc2b2a79964e0940c9e86095cca56b40d3931a98f1fd5d8d2e283f3652afb720efc78883582b9c3ee7612e54bd5e0cd2f10829b77f9bf93a7e955f30b33fef351f090600045f5e7d8048e65abe2475817d70a4dc824558dafb7dcdd813cc9f9dc7315177dd7252908faaf23419d14c19159a959d562751c26160e760718d905dae85b95b17690c9162cd6ce71bbba576ff5b8bc0c35245b3c5aed8b63f3d2e8358dbf3e396e71de18af3e934e5660402ca00f0da9701fe9a9f8a0a2b48521f3becbd7684410ffc6138e073aad52c8237a2d18691a7fae91b780186c8a2e2c1d8a614f36994620cc9960dfff9c64cf8d9f653e84129ec7483550c43e09191c4a711496b165fee52f9703db2bd9c7827e74e54d812a0e5b11fbfb0f47842475abb538b7d68b128e95469d5b53fda076b44bc08cb1d0ecf0d56cf498257e8d611df02726377a3e98225981bda288e5c830be82eff9382ce3baf58829b164cb941d2788b4869eb2e4d726c8c57a372c342d50672508cdafca699f039dcb9e9976f9135cf88d24915b1a82505eba34adb37dad3743d1e21f1cf69a8edfec93e9ae17b8eb50e72989e36899bbfa8ad67388fe954c987a31625699fc358348d7379cba9899cce1a9f1fa0edfb01a39ccd1db3b0515f929705ea91cb0def5061a6f146cc56a8f92147401c8aaa7afbb96b2f8b02bdf3e28a141efe8e080b9f4c90187c2aed72d33ff8270d2ebbe915ad3fe19d60d67710ec7caf0e37d0ef451ff76e00f9abee414e0cdd9d150f2fb2c846c9057cc0bea80a21a0dbc10aacfb1e15877e5ed5bbed932d34f5b53aa29990294a4436dbbbedfd3a14b92042bd7311a8286c55821c71bd4238b54ab46b58052c850ab5178734a8eef11a0d5834e22f617984fe6bda844a90182b1847a14547ab03b18e552f28f1ef885f21c23a44741a5473b1157e34753bdb9eb9fb53cae4fc5085ed9f34a8c03e597d3f71ded7ea05262a0c29bbe11c303a662ea23597ecbb1d90e7e87014efbb5eda763c60d7e00de3c11a4e05209721227894a4607aeb0d345293f540abe61af357523dac4a8183211c93c88be70d324c390ed0fdfe15ee18144fa11016c5c2f5d6920edbfd8d12f17423a5ace94b535ed840b6985fc80a68a14d3d5518ac970a79973a990a87d4aa664e87834aa2766713ad41edc85ef9659e504cc99626012ec0e2127137096e4904edd1e7eef44a0ed16f3a55ce1f48ad06b78054ea818a72ce76623fddb460a4d0dcc056b5300db058ec0451ffd91624d047e6f804aa8c1621265d84263f775209f25e6a04be2bfe3dba127dfdd2423ce673b187f0b2af791025003dc221aa1a0ffb180282e55b6bd028bdf6919b6a64154d226f7c24a3d14cbf25ec66d60aef96bbabf19820d0f9f935c88bf2c28b2a6d29ab96d194c5a6e02a39bd14836629d0409d730f32fad846ea64d5c9adbe9b6a41cf302826497a2bb605836177cdd8716435d01fd6bcd4d3cf2bcbdfb16dd5b8dd94c72faad3e1e06fea6065015abc3621e71fececa9f359c608a85085d84a6cac8c78fe7993d23a75f37c390b86b1182f92987ef3c8884caf1d71da327dbc8089ce2d578aafeb000013cd7c38ca879c62d442a25523627420c0509471ccb3c66543780c962cc8d7fb87b9d8730fd00efe21f806b5252a41838bb16e2d6454f747da4c329f03f27fe1ff34442da8b899602ec124c19fca9423643b09e487fdd10ed257f7a2177bf7afc9ad1dbbe18800791a965e55a528c433063a4dc9f90dec4a51128f7900bc90b1bdca0dca45cf610b657dd897b055a1b478e79e326042ca212eb4b314e6726db888e3bbfe407452d201e610c2bca69d580fe55fcdc0e1f90676892b1255c5a65d716222145c9ea4273b3e1b725cb33708331a3f3d7350363bafa0c24e0330d92d7514c3633bbcc7b0e45abd0a957cadab2f65a56fa8ccad50d3d2f86091d392123d9684cda1e1ef7e27e162913547206d70dda236a302c8a104837b1e4a6f22a8102527d3e4869db0ea03fc4ff34fee564b71bf784a6c3b0dab7f69ec34e8dc629ff62581830e6290684041acfbea00c8d04176ba0a8127ec1a5c66584594eb51604ab753b9240516e14a563e75f285cf2b093f422961400683daba17e4926bb9e2c18b928f02d39a044599b0005c0a25230b6122024e1059a02094f8792eef5d2bb4edeb1fd6647dac018586e76feb2eba6db812aadfec394c7a50ad3880512189d9f3b26f30ef33771ed3a826d65286d39eef9b46c409bd150769e1da02b1f033603af6ba75f1d09f02dfbe1d3a2cb32bd4e9cee44fd435f7a870c28b1633290fe4ba8b1ee4ba1195109973315015d7cb7001c3ef242f22a5c5b0412adc938773559f7439cdce4356f901cfff034648b76cb9485b4dc0baa875182062627e15703e36cf1c021e5796f7b7f5948de6d711eee38d1beab86e689b08c562ef263da3b0256a090a01cc7f29986ad2465984a82d2285b29ffb8b4d1609b6977c313f389f0742bfca305e96dbc52b9222c2fa96455c4b81c6657e0cf32b5307a4bf84dd7ce2b78f69341434ba5c73af70b88d46ec9d9ccb8aa311e40e9499f0641103a346531e0e705a4b00d7eb6024096e47467b2fd340cf009d0244931a3016cf4f3f299ad062130c7b759ec0b455d8819532bbdd81e619ecd52ac946b791cff8395df53d0c43e702b8f5bc7fa23e2113f6df8aa4a260a3c0c78773c43b45474e2b8ad21721a90dddf1938bc2ae2d673932d854a82aeb2f2f9e109c0882a224c2a29a1f29425616a970fd6b1171bd9369a14a541a943f6dedb97d523f9b76f8c094f18d9d14d908b641a74a175b94ef9176411bd3af459057e7169a2ebf0837c465b39b1bbe5b623a1f51523fa5fa8422f4ea8b0a0d2a810c238b0992cc0c17ff41e38b80490b987f0a4bd35b0b010283d61ae8f8aad7325e1fbd949be78c3fe2829178b047fb31dabe020c816996f3833636093f50a58c74398f0b2096bea02e75268c0f3875da06528ceacd992c09214e9cc2b5172b010c1c7c5c4ba648de01e7a9a65e170a4ac6f9076a129b34e367b8b61e2ce0ee576b620a34f4aeb6a1e0c5a0f8cac3f166ab29e930b0d0fc3f11a46360b8e6ade77efa3874da75fca5abd5d7783e253082c5c7d28748dd17e6820ef342e525c48c4574bbe38c371d80d6a7ebeba9064219687aefd1a0a8c71655642203793889aecda940ea85b96d1dd266a0900e06bf6ebe66e027ace0017e9a782805bcabd5832fba43c4511f1496962b1cc531ea511d2f434dab7f3f1728813d7959f65533f878b1c1677e65700cd0f886605058d8ef71a43ede73ef200f1965cbcb5970920e77dc8d9db47bfee212044baa074fc89b337fad2afdd509219b61104f4db903526344cb1b000951ea55e99780a0b177cc0fc8623130c40e044174d6e055a4dd6b2fb42de21d647f39fe081dc22a5f5577c8260e031fefc54323b8887c9baae08c4643b0a8ea7f2073ab3ef8bf556a0e9864e8610bd2633b411f00d0207d90503cd684c2c079b47bac495f0d2ec6e0d1188a23645e91df49a3ce4d59922f25e4d4bc3e4e34ff965db18f78bff91c27e2d7dc8b33bea9186bce80eda2f072d0f4010126ad4527ac06cfb100f00da150022fdd9a552acff0a5398e38dcfb85bd364eeb87087cce3210b2069a61fce68fac169d1e3974ae5e8b871143cf16bbbaffa7e27857c077db17a8fb0f81abd21db1b3f952d848983d995023d2510ce92a1f7a1d10d40ea4141b426acb2cb274d3a051fd02f04b010dc0b5521d5519eb2bb1868b4f3449ad131e0b93f8e67b11cfd88da7d00ea7c4d80596ec2cfc26bae226bcd6cb5aa7cc828c8a17b0c51f35b320ffa711f0bc19b8fb87844d5479904bad7cc0131b9965e90585529015e128723266172ce89d40569709989f34d0bbfc80e466b7130f679cc2d66a318c9428e619dfabdcc2c116539333ebd7d7ae5d8034626e7d872879628bafae12cc12b9e4107a502c5dd344d6604e03b1c9f20056bb255a2fc972aff986b3366dbcaff5cb4a3dc27d6c2972e18e64b47760c46c731da94c7f6427950ac7975ab2c4137a5a38548cc16520b79bdc74320c0c4ee5d05c8985fdbc0178e46291829bc74212af80158020b35b46acb10f4bc1fc1aec672e169bdfbabb2480973455af4b98e0eed42988cf5db15dc4547b94aae7d0933a58e7e0c62f1e1447d255bc564ee1a6e2aec9fc222b69149cf45500bb19a086329d780b442a24ac98e5d9a98593f2142c97713fff18be0d33f69a325fb7a8e09888a0d411b5dfe1daf57ab1da01b21f95991d9ca3010bac9420123142f3ed9aae8bb3533b2beb0c6cf5a47266de9f013a19e19ba8b75e052272838d81becdd34c488c088ec856200c94db4ac655f98e475f98b6d0d667fab96bfb474675bba5b7c348486cc542ae8c314f522d3e6a648e0da2dbfcfc727636a34484820de1b1245fbdba5284254dc704dd140317a99ebb209c3c4190fe837e0a9bacda2bdded31681c1976a56912efc9aad35f5eb945fdd341d94bc06ade6ee631243d59aa8369c8ad8275a5b2637cd7ec522a3b99cba7dfad92eef7119fc33c68feb71949925ed195d5140b47e9c2c0debff9e4a1a96ed25005889a5a3dcc9d0ef912f74a98a71024d3b57488d6f467798efef8004f17335f905a6042b27174cb0f3ff70e0ff4347bddec9100623a069e82015359ad1c8b008ecf75c02359571a64348caf4177da4d75b743b3f10bc24776711c0409f8b4d40ea5682a0e34027f3e75ae85e786882d7747b7e5e7012e32430731776aa14f05266f43b375468a52e83e926d2660b5e8d299249dd197aba85bef1e634db6ebe9eaefd0ed3a938cbdd48166c6cb75be3b3c9a49ed2c8076da9406ae30b964f3eedf92eeb97e17d8e1957292908997a90c34117fca37eb6f309093acb80a2c957d60ea26bd8cbe1e3b06e79ba99a18306eb0ed8152572847c27187878c6564e6c834bb6cc59849aaeb814d0eb22d33fcd193fde201d383afe17138fffea71605a4a30578b1a2b5cb0b0a4405720d6568fe3379bd68c58cd907941000b6bb03255988ede0148d1d0c40af65c804245f20f62f7f5a4d488f80db71b18f97ee9d592ce3c96982bea82fbfd08e877c22702f980b55428796925ee10281095129c9c851fb642c2ed9c9583474e25dbfaec645c13dcc4928723cc59471b9e8a03ec98c026a2e24b75dff0321693dc8a947c43a4a1b00c4d8b0d132aab4ddbccac38fbba37b3c0854e0e512d263eeecbf1da16bdd1b9b0c10294226cf350621bb37a980ecd37e63ceaa308c3c2b9a7701800818d607227fd02b1fc11a5bd2a0a468d807ae337133537db20f1e9b0bff969f241dff4fce387b4a9984750b09d9ef2fd14b3cc06934a9d874a8ef4201fa1f45a2927834246fba5f825b164132add32b5f824c0e9bdbf8ad95da535a9b392e3fc1d476f3617f58f34e92783dfb34ac33dde1a6a223aebb66d2142098775d82788e5e30f4f08f46385718d3676b9b8291b924079e15cce146ca885007d263bad75b17290a9617dfd5bd32ad616edd80eae20b2682b2f12aa8d24a84cb48355c2998b235edcc0abfbcb938ab128ac40aa043218e22035952504171dafae1e772e6442c7a4aa2adcd4066c61920d3058b647c74685cfa6b85e3a24c926dad2c25ec945ce0a849cda6c0642aea96a4f39f5197db3828ecd3f1b6970e14acb17195c7dd14a8ef5e5d5ea33e2c1d8ab4ab400d69aabb49e21c665d0a8d7a3f6b6d34b9babb6eb61712c098c1bbf99cf86f7d4a776129acd8da0e3505b3c44ce91805b7c85b9f7db3e692b358472a3184b1a8300a7c872a593320d87aae2604dc37dd141f8dd30aa8ee696ec3a0ed87860af25b9815024e4d623116af07dddd840ff6874c7f9309b40e605a9117799462b03fbc6009eb7b2bc0c80e31e89398393df1371c2730a9da598230c6a33d74ca46586e6e4a15e8581ce6c758cec6c13410304a6ababca34b2f2222e833996066ee30c15885a0d209d4539d5c1649a01af7c047d96b06bcf179eac6ac84c40e2a7525d072ec6cb3cf1afd4277ef8cd171eed848ac72c75a88590f18537597dedb298cf0d89efd2b544949ea6375cec96dec66d1b64a15ff7961844f6c5da0be5be9c75adfbbe8c5ebfcb5dd9e7008938357a00c0a97a55b105cbb2e901f9d42e61d37ead65f6edbc78ab95e7ce644ecf479ad6da4d0d3a2c037417e96aed421103d1481b3bde161ffcb7d717ace7224ef92ae8c7490a840741bce7edf8f4ecb8d73b9e2229b1fea44c725dfcf608c911a138516e9be057c6f672bc6d5881abe68ae211b2765c753195ae9d5f836f000a7ca2376c33a4b96ac606946159495a6a7eb206567ca49d361211f684f3730bc483507c76eb469463f34717047f581ad9094688c0a4e805c066bf6ff4b7496ec25b3e7654e4503ed2cce07a173330d79d3ef439c5e24b485ff918766966c3f0417e2b269a42aec98758ed262824461b2461190205a0f64b09ad0d5cbcb8a5475b3a102071c788f28d299d5ed249e8a31191483cac336c9b669d4b588cf6ae8cf6c3428bf9ef121ba781490bc3067fe9524b49455d9f3c63040fc5aa21e2ee1a9c4cd5a4db1e62a5d6096e6767e9d1306ed4505529badcb592ecfb02f535651991028c04684024cfd83ec6b70d7a2abb0e2b809dcc3df9b4030b16c1d271061418328008b51cd13869c8fb8e3dc55fd910cf60756f3fcddb187406b9c936e2b328c725077cdd3260d6965cc4e625a4f7537cb4584c7111c7769912dccbd83901e1acc0e85f62361b5931353991cbd153a8e651e3cd2b7e0d79aef4fbef7d7ddb866042d40dff4a8438c8de185c7bd9fb07e9fce07c58ac019b1e3c1815b755b2d7c60601d90d4ca6e2765429028c79ce84b2c45afb8270bd8b8923c0b0e3c67797e283f0d7ffff3b18f3141e19d502c678c06fdd096e27a72a4c36983deaed3955a897916c87a25ec2e9fd4bcf51f81fa70203863a747f21544824184cff1031572338a6376404799a05a81accd94eab18aece2db6cd112c615dd05cb14c81c80c049450d67884a07f2dc27c4d6bd11533c258fc3acbd2220745e7866626ddf3de4d738a9f4c7ab4da342b0c0b1fbef982d8b378d68f9c63bdb1a12d5166d7ddedf57ea1e258da88517a17409f6dfbb7d93d35408db035e7c395e34214e3d2dc14fabdf7fde8156c51036a07279c5b304ec29274bb8a9f70fa3bb5581935bedc72750d77cebe5f39e3ba8e1bdce16d9e025e32a8012709ee806172d9f10f45c0418511326320c4fa324dbe4880612983341679a985267fd89707d8527d1fff6e8d10d2c82664efbdf70e280a1e0a840930c78c59a80b62a4b502608e79230e608e1f2feb80d6c6ebcefa0af3acee619e1eb1bbc7193a4269e8c527cd13a1b779383a217ab7ce457c42fcd16c1e311b695de4ddcb45db6d6720c0c6b9c9df968db4160e329b3bf57ebcbfa4ce6cfeb6c96a90f46ccbc2f0177fda09d9b97c346bd0883572d3876a1cc21cacb3a8d038f478b0bcfc25b5f72f7e75ae35a7945dfee257e7dec246483dfb4b8a469387286e546a4098c3289e039863c66c03e6983b4e30a2b2f47ad387740e610eef4628243595576efad0eb10e6e8de51d1c93fd3a7cb545ef9d52361f335300c7b2061f334b090c763c56375e871f93a244cb3363cf1d149f36cb98795a1630c317f20c036d2ba3ec8ecfa4f9b41e69765238d87c836bd6d9a35b803f6b41defc295ff7e3ac8ecf889236d64786f2da59411e390b028a594b2fe7b65ffae264f218456be5ad2fc6d1d6f83b621b8b5900cc3d58830e8421f1804a70f4f4730a0be908236a195d2dac319564439a20b233988d1240d16309c2963c9059c9a346150a521635cd79279860d4dc66d358c22e387fe2e65818c2858c32832ac20430568220c5022d10a92b4e0c4922a59c06012060aa048a92a94a182242790218a0d3ec07084909aa72c672c21250a2e5e384305031479840c22b36a99a6699a96a1b92e4dd396ce6c9b1018509880c203d5192918d29c5a4b03050c67b274c185a9cb134baa34350d654bc21862c9942890e8c0c517225050040d4ac0449168c0e862e9099a23619041e40c4593c688a5336184d11306174d6c2c90a9b77ae1620b1446b408a3045431c9174a645f988005321998a1f2810b9ac4926c184575c58c172e8e6c2172132509aa245fdc400535b940f24510934c060da3cc983133a6bffbcab4c59831669a78610506108b1880507a6205438841128f98b981c8ccc28c1066aa68f18217fcd46c3ecb9ed45cf5448855d100f25476b1034eeb2c0e2263ee26984b69853bec2b26a2364308fbb26bbfd5f3317d8ca0b357fc11d12eca1e44b63db775d123a26de3a471b26ad7ac5d4866f788b267578b330b072150959202510865072744d882114228699465347921a55bb4d2e811510873c4493d1f1ed1942f9af952096b0a77403aa394a14c94324e554899a5c7434e28e3a39705dc31e79cb5c619e7a49389c639a38882461169a54bb668a506219416c2ac07ae669add2c948a4f54326a3acd298aa5669ad5a49ea864d4749acab40c4a3d51c9a8e9446b56a59ea86414ad54ea896a4221a594500a0a5751cb4cb1c01cf016098ed64cb39bfd718a5154f2494ad19a6956fb7ee054748aa2924f35d3b2efa7e154748aa2a235ab59748aa295fec0a9e844e7cf07a7e4e5f73363114a99a594fc548239ac1417532f1b301c29b830aa8c527f2faafeeccb060c07cf08d96103914a43dbaf663111261d43d99becd0b6652b34a7090918d33053e1a1478251432a645741a34074ca7c7aca03f308da699e001cd2324da2b20ac1c5a04c0c9426970ba3b890eacf72a1c4059718343504229586b467a7a2e5ecf84ba169d1916c9a83cc86c769e8f91040c34c45050a44a79c555512425855359451c90ebd0a7c3094e5532b36082184509b19ad9566134208ad0da2b75a17cd393728a5b666eb0f4208a99cd375a3a89d84c10975b709756d47a8ab7584ba5947a85b7b425dda11eace8e514ca82b3b36d1f013ea6edd84bab0e39c1342281b465a63765d383a68ad74ce154c08218410c2292d9c70bee69c52374648e7dc5622b8351031030b6450a206254f3811450d468c4832658816889043087141892c5166b85ce1c450e6c2163c2c21020a0729276503a8c6508d99ea32c605635830446c61cc146354101404617d869bec509651207728ab95662a197c86e1f4d122280852f8798e074f7648a4c90ed96fd770931dd20ef1270488541ab2df30938e212d08ed7a1ea2f834f3278427cb2368870de40ec11d490d2f34a3525b0c4df8ac4f95740b375b70e1a59431cfc9820b2f63532c30071052167167116cc195873b64cb4f5c95608e08c54d2f930a0be1e76a0821a4ffe69c738a39a71415fe9b734e0a735017048210f6dd9e70ba7ae274d1e4e69cb4e10aa8a45a67adb5f77eaf20087de6617e430dbac805371e526abb19c58df0a3dba684fb6174523a55706dc3a8314b3c31e604ef197384ac32c68619269d4e8c5902c5a4713284182c9c0c91d3304a4c97179021434669cb16366c5165891e46c47b5ea76494d2a9a527622045142da0d03054896cc142dda2880c4454326932326b881071d3304a8c11bdd3304acc1315a7969f26b85fa6be9421f365ea4b94724e4a6bd53129ad35cb344bad9d32da9a659a66eda659bb6dab15d7d1ae9b3276db6ac5715de7756151166bcac8e2b2eab8aef33c16abc56241b57c4b4bcbdfdb4d6c23addcd09b9b29e38dac2261f26ad44824b2498d54529346aacb172f5da4ba708956629528a54629f18954930696a9653ef079203fd8e5f3408784c97351ca3929ad19cdb2296316e59c94d69ac9a8d19a659a66edb6a2abd59471a5d96d5bad38aef3a8e74d19bd15d7759ec762b52ebd77ca783d56ab75af8dcd0d0ec5c19932e2dce0b85c39393468d478d1d76bcaf8b2e16e7070ba895d1e08b15f1236f109b4733c1072ba8969c01ab4468d29630dcbe2f1317b3ef687d413e6582bbd48983c08379a6a81394e703fdb5ae08e9c96af4d7007ac4e0618829387102e408b10420821860459c44861218b1825629880f89159f4e1ce865162967ab8b261949821dfc54c1854f5bcebf593c1808cfefe339f7ea815187431afdc551617062d4832b3689aff45e2893cec3d302df233e81e065894e8e07abd4069f2706fc328183c350c064fe6d4b480a245136d880e3be8604455c60c3334712a628cb8d48466e125cbb2ac669183992154b0400c5392a18c052d94322d9894ec20a5699a0e57d3b0b841fa402cacd84b801228d9e18521674c118338040ba88a452cc90e5946352c82a85814b1b4c3902a3f557aa68c602187a91f5c9839ec348c9a7232b504879e1965eac8549139c534a51406cd9130655c7012468b262761aa30e2248c1461a24ca9b4560a258732d6c8d04512481c31a404860b0d0c18d7752f09392c010410506081e24915982a565a1461a12006532aad95c22996f8e209314fc4e8a18535608c5430501ca9b75ea1a62e69baf7bac210b1c31565ae60614ef14087261bb27c011304d4503d03cc11158c0992541e6e28a50c0053821598254d0c00e3640566880b538657c328306284514225108f0f696357ed45ab67ae8eb948fb8ae29a8ba6b77ac51b2edabec23ee4370d7f49add94c63cf9c646344c17c22e374c282294306cd40d1f20512499cd800859a39e73c921d3f67944a4a4a4a4732cc8c21869e187e60f881a1c7e9ae1a467d49f385ca0a30ace8e26659c6e46699154a481fd89b155da40feccfdeb6620a2bb040329b5cf1735f3f2c5372b586515570e9ef875511357da07d4f1551aa90a1bfffbcae69dad7dd9fefd5dae1764dd38e5ced54cbd60553d20792913e3048c80554d207d60d5bec9230146c80810d450cd9e28392195528d92a9a94c05e195d9dd63b7de8e9675b2ba3036761eede06e3d89c758524bd0dfe6877f9662a61347f498d93bfc8800b633520e9f91c353def3ac7c5aecedb1d0ed6dccf843e9a5de4fa3cf444c8b9f7e1bcc667fe681cba8a5aa76fe1da14df2ad3ad7bb1127ce9f99c13b13e6d3e5fbd1ff4ad1ff7f8a3ddd970197210c0f9fd6ce5ee75de9c464f84ee33f3709f679d621e2e13dd73b739f571cf9d953fa49e36b9c53d0b2136ce6fb202865c9abd1f386fe5229cdf5ce4bd7524bb958f64e3e42efb886f3d7a445e86af7c343be7f4adfcc1fcd1c847b35da739f98bed72e14f48cfd6bb731d6ee1e811cdae037d278d9e081ff7d6bb4c25ec3b9aedcadf0a6dbbcee3b1fac4728ad0ca54c2ba73f9e68ffe687697bfa349654fa0ee337f47b3a513775e5e4876e9d2dd295e7de20fa9e727eb1c0b7f2974eb38e720073be9fdb867dd06b7f247dbe62c0c25cce6d1eb7193bfa4be394da16dfed9be67e54ec25a17923678fe067bdd5918e7acdb642b61adb3f047bb75e8fdb0c954c25a792542ec2e17ada447b4ca5f524f1cfc659f581361eb8e420964f39bbf23798ff3a223d938ff5c876fdd0643096be5ef48f6fd77248d6477e72e2eb2396c0ec6a6782e5602a8e6b8d9410f049bfc09699bb37ef19742db6ee9fc1ecafbd67530943eada2351d24f6cd61db60d8f6d3eb111b270789ddfd62d82e7c24896477b988fa88e7ceddf50ee3dc85897058f94bea5ffcc5beb90deb2d6cf31b7c59d8bb8dfdc5b2b9d851fc71eff0179b87cd3b7cdfc21f52b76e83896e8612d67a47bb0e73671db6388fa8cb5efe843477d87538f68ae655fc3c4aa1436a4a0f3d4a673642eaa3d831e6747c0d79bfa04f9420899a31c87c212589288e78a23f30a9b706608c22ae009344ccc816a82369e8113538c49e5ef0f3ba659eb0e8ba3852a728d19242093dfde2e48c2bb09082e2a2ca962bf40726515346a8b8a411828b17461c69b24d71ea1ac69e5aa0c36d8d7452fad40492d85394112ef4e98a12b7b00c19acd8912249bad45b8d4c21e9628086311ad6f46d18a3c10b321905f3a5bf7fb9374d84b9341ac6680882aee0b61ac668c081b2204683136ac5bd6918a3a105155caf618c04699a0454596c4a98ae0d6353aa18e21533221573814c9922ce50c2d444ff9edf8c1319265b921091aa41cb54cb8c121a502021860f6188b1c3154395882e5d2c899726daa5d53036c50922b22935c450a7286942cd6032c12063246629608c70c90d6352a0da6b1893e2837d2226e509c60eea0538eaa48b9329b4a56ce93f675cd3346d8634358a528dd254a338d5faeb625aad344d5b729f4463f284c9d38f8553df2bcbb20c8a12d20776b153dc2f2f2da40f845206d69af6c35429a5558b413993699aa6695ae682c6f49f6c053186a718aad417b8bc2095314d891509154bdb8603932f8c0061354dd334a9e903ad912e90ac564c61e212271903550c69cc60a23ac24292180a4c30840c4a456ee68299e9d4249748fd4c19a6a40f6c19c8d8fb1e09d3344dfb7b7eed9fed980c544cda2f61308dfe5ed35cf8ffb346d3340d0625a40f6c18bad8ab691a5396d11f622f78a182ad844b1225524c4b44cac54b37998a4274166b42c605da650acac49719a79452ce4b39e315386ad470c6071c4cc48a90e923d945aa348c3191e9988d3e6814de10e6c84129a551b2c22139a91377524aeb51a8231099978f5fc498bcf4775bd0a2e75db116caf4ece969b36c0585a486431991fa80a463387deef49947c1892b3fbf4b75a505466c7152521a8243578ae851a059041a48266ac37960c488541d89b5f0c40b223122638ad0238844b5ab618c881538d5f4f630d2dba802a4357d08406450064e4a4a4a4380608c8527fd71a0a3e9c4e2037dba02634b4d34ace9b31ae9d253d3209a66e9cfd514ce202a7de2e9ed855332090ad52383885022d9895c8967820d3da9bf782984e95996e5a2792ae30438288130070f6f70570611217285e967a81e66c8f4a12bf4490dbd1eb6a906544262e40067255052cbc31d19dc611bfe1e19697d64a47552438f47aceaa4183d408615306c482021c30a183624906c0045005d74128882d185ce6c2da4b1214c74d6303684c94d42911420c22a85522c95d35a09460baaf1e4c51866383571c61243110817861c691a42a408185c14419345139a98521435cd94561a2b4ab49ac6c9437226960228549090811233c60f638ca18db10686334a6374918d110611a52825aeb84bb5d65aa5bca85be5a56467098e5164694c12413cb992c5932358c4e872c5f8c2033d4dba12c391303698a98229053e8861a525c6961766e882091b9cf0b266cc0d3d88d182184ca0d888a1871c4e20a50a1dca5c996285060c15344bb090504a29b582260b8706892629689a5e689e98700d634a42d840b98e0822e6c30c3b249182072938b0e82284124b9ab26061071d4d9b64e00a2a33be280286142774e88a8605a6a720a6c494b24c21a9c59474e8efbeaaea0a5dbab6614ca9043125253f02380060d0250008b808a302a83252c4804a95333cacce08b185bed6334fb433342039433d8882c3162baaa4a0a162a85209e30cd6992349cee0348caa32a386269e8651555355524e9452baa64a8934554274fcadbad28486ea348caa6a32cb58f3459135655a580306defb9a73aea1a2089a1b144811d740b930e79c66ac01e2755d5d0efddd9fd1fabb61f8b07426022a50aa12c5499a1f7cc8218c285d9f9638a12d545a2bcdf9a10a8c2643b23c85c1218c178c9c0963090b67ec9a26aad59a0d8d111dae6c11c30f4a88bce00630a86870358c3a038b0d9a6b514dbe5a5ee62fc2182d75dd2c7f2f1a63cc166ed0f0ff6e34fd97d3f49fd7f415066ddfa0cb9fe5329cab0ce796219c4111fac4cf53b883a7a5cecbe6ce8d1a51840c2668ba41c30dfa99a437503a339a61f86a6affdded9f9d59a6356f56a3b235258d3566514a29a39410f6e4ac71a12f50c38194f247fb47a9a438e8912b8fe5c6aea7266d099d8c138c32aa920ca96a5a930607ba74461367405111a77cb0a4276ac474f981c9174d4031246d68f92bff9f1f29c44882da920311246862c904b11ae01429c80203314d5445bc94008525630c81260c1250a0787942010044c85c41021ec814e142c3093d989902e6092926579c48808207b62471450b66920881c3949b2866ce78610c274a674429f5abb5d619b3fa2ffe933282a12ec42e6e861a0bfb39d3f31c0895d184a7e7cd68f2f33ac34affc02e3d3fd70f0424d07b9e030914a3f4fc4b8d969eb7315f1ef48cf387edf86d8712b6610bc2a6c11c47b3351fdacd5938bff873fd9eb3f966730873acbe3a17e18e20b6bb2077c21d415edd3d3e9e4220d842b3fbbcb79ef3ae0b627b7b6cb5aeb58783c4c38e39b7bd1d85a4ee8cba1f19699d9383d8ee320712f6e3adb09084cddb600748d83cda1e3968ad1edfddfbfdf77342eb5c766ee5ad56ddca6be1a3d9ad67b828bb4dce5739b9a8bbedd585a4c7b93aeeabb770d1eaadd79553ed13821869dddd2856b591eb473c446fefb0bd5c6414abbacb416cc7cc810bc2e21ff38794b3e11320cc71647bbbd0e4a2d1ecee42b2bbf71cfc25757721e9c23fd8c80101f1b13e1151a515d0303604550f11d5b086b1219ef41052cd75774ad5f45c0fdc61737821074820ed9f0d570e0534f420e82bbb01b7c2f2f224f0fcabb1f3ef7ef6b334feddc971ddb66d5b878b56d06e5b7cd7e93c1acd5cd47df5c3469b3fd972c60f4e7dd00573ce4f478edffccc73cb45dd7734fb9b597bc5dfed9a25210949a16b8c95aae92ffb912ca271213df137bb5e7a44b3b789ebb57ca6bfd8db8fed2eedd9b9ef48fe488a304f43368d47fc51aace221553d70551c4289882cef907b998f00373d634b4472d3e9ec68564d4a24c9841bf9c8512084784559ef8d3c95b5e5d7b46c20c8a4e3ae7c95f8ddef1fecaafd7f042237b692a0251fdfc513fe36999d8c17cccdb9cdfceb37f94ea8bf334def27e745d1fb57b9ddccaf7b2ce6bd4f82b3bcdd7b7935bf52c0f84f8a22d7bf762ade772bdcd3ab9cebff2f7d3d96be4569cf7bafad45c57b36eceeef0a421243f5a06843a2f3473b0cd5d58c3c1acc7d52ffe6accacc30942a0af46d37f96be07ee084102dd8060731df4b6cb9fdd7474cc5f8dfe74348d556a748af71fd4a2b7b8dd5c54df7dfb273bea45ec20bb512f2e531c703b1cac89f9fb8947b51341fbd61175f1f51bfe8e64c7cc63af3dbb09332846c52a356adc7b2fdd9e95c04177ff4ae0a07f5a3b92ddddfb475bbb0d7ec11cf1d603a17bd1cddc35ef47d79d0ef489a73f54f85cdcf154e6af040e7a7b2b7f3f2cbcddcb1fedecd48bd841973f14927a3b778f87962955af3057b3b69f0a5dcfc12825eb1b41d7dab42dd75aa65e44036479ea155fa51ae12261346481caf2d45c8b936fd25e4fc4ab8f1cf75a3daed6ba4247428b7217b29d72b8e943dc3dbcd462d2cebde21584ac4eb3d2eaddbb4c84493b3dcf7c933d9753e878382434271ea17e859b3e947dc34b2d5b0f8734bcf4c1503dc530cb4c35ab404f339c3e51c82aca32b5a936fdc482082296a54b55928258104a3455177ee8d2b4610c082dfddd7b81ad41064d81c0d29489f938a90b5c3c8e3f94e1d24af626d1772f8ae10a574e0032946519a96624ed40b8d7ecf4ab8ce4e548b5ca48d46a07e29dbed6af0ee4e6f51c969c73df72bc02c4f5ec3723d9e49b91329b91b48c44e9b97b4f36f7ceca518af59bac94fde6393962a9c7c94837198995913c29efabdbbc012d236967dd26c727fa564662ad32d2eadcbd3c02d21520f65d46e232d22ae76bc27cdc7274a28a727afd4c61894af751ea54d6e6d79496af5496ed3f18759314a55d524a6a2e762f1df7470bccdaf8246a79327de6e123ae97988876643a9a3e66cb9678b68e06b7514a870b4fd4ce9bf06fdcc83bd6f264387d7432eded1c8fed37b16dbec25f0acdf3cff52f3e290201f6f6ed1ed38683afd4ef481ae7242ea2ef4e0092c1770e04c7750e7532d2959ddf388e0ca74f06b625d28d8cb414ab86aed4efe4781d0cbb633de7acc3865e8fd8f13458a771d6699c8b5de52162bd3beb1d8fc7fc3af478d4b8907ce1cfc5c1162271c71380e8dcc68164709d2b2700b9719d676023c3bc824e46d2f90db8bac43a45461f5455ad691ea29c73cf3977a883e150ac818d62c7cc43c49dc6b9d3c837bfcfc146d1e6d2e3c143c4bd3bf72e07995de4cab08e1e8fd6639a1944052f998b740588ce77be9375f2a7cf84dde7df1dc0bff31c0e11e0d7c13b8fd8023adfc948397a3c12d8b9ce2db093756e01a11b02381ce2b9d0bc81e1f44162fa10cf77ae839b00f09d13b9113d1fb463fe84743cc7edec9c931e09446eecec7c6705211b9ce7366a27b54ec3d80f4e4d420be9d7390f464a1ae239573d1288ec9ce9433a171280f31c7a1568e9e01584fc3c59e9b7711b99081300ae739e1100f0a69ef31c0eede0a60fedfc065e6a3101e03c1b5c48cafc69df2c90afc383938662fe58df727cc4de6f0e32fb8375917c7c0b1365ed9d0078009884e933ef8361f803d3675e0029f4f741a9e3a60f6df000e0a556555555d5d006990900cfefc14b1f0cad0080ac42ce7048fb0a7f45311fcdd62e876cc4c284bff3f9146690ce27cfa78dcf0c3ecfc23630928d33e11fcd8ee7c1483c67c247d23913fe0d8c757093ce7f1edca4f31bb7819b74ce730f176997e732c04d3a87d327030c878e6fe02d7f4793e76876cc3af80acd461f74cc45f630aaa39719b4f309e947989f56626ee9839ab9df78065ff1e41534381cd2f9dfc119c4b4f4c19006e7f977347bbb6d08633f7cf94187e668f71914bd5c1d3ff5c8fda2162a133c10a6d107fdcca4c16dbce3f209b2e59934f88d7fdba3971904876666d2e03a5fe56fcb47b3650e92b5cd5434c82a6460af979ecf00c3e993693948d623cca01b59c75a1b7927d6b59ee6222a3ba754745eab8ed4e1d1c13cf985a5527314c3a8dec12aec601574ce456f049d6cbd09c0219d7ca74f54438fc7083a592ab554a221779c903009f45d255ade0610481a4764023617bdd80f50585a1e07f48ce8920e456ec0e2e415cb0b4c5890b4e0c3181fbca0103b09abd0b3a1801656240cc22aede15618624fb1ae42f38351332ac619ee07a38222f499f25b179b9e9fd7e9adbf974d7f2f9dfe5e33521f2e8fcac2133028d1f50ef8800492b74f5c16748557d0292365659d65aa31380e7a705bd6defb7ac15af2a81192e9af7ef5ecabec8910939e7409904cc77c24916ab5f5f35cc5b2ebb755fee68fa48d5d8d84d80bc988635bdc437e491dbbb831536ac54a9ac72b52b687277b5f363a2b61d3f640f59eee9140966ea150c4ac54b9e1880d312b53fafb4f0b9ea4fcfd8b00e18c2c659499e28297344bd4292fb4e062069918f8305451d0b5d66a9fac48996225ca0fa45694b8ea82bb358cf1102340c3180f643a6b18e3e1052fd42653b6263918a99f0d4770e0014a4b539312b4c91225f4b4491353520b5f7028c2f4248910349c8089199449950b536071ebad5bb68022028acc1157c29880091395499718ba58170f34f4772fb7f090d0c3def32f9139c598952443ec10eb9c86b11dacfcdcfac3323a299dd4cb5d358cf1d0448c8718e5f2f7f090a549052650dc52bdb532a952995c59b203193b6cb1c5153a1489e2de31aab82d3254dceecb14b79392e2ba1ac6aa40d11654d941aaca932a4e036818cb618c1cc0682249149b2215085654117a6a9b9a8e9c8006335600262a49119b6409127ac6eacc4699b8f4540c8b56d151b1c48de19888612dd52a3a2a96e82913978aa58c04081692a822f414490f1409554252820dc90e44febf07378ce5b044d36818cb014b7fffb15f3858c3996803177983163adff5777b5abeb3742acd0993e8af87e2e0454688632c87272d8fc303fb751289fe6c646a2677cee69c4df71b6c933fdaf7147fb6efa3c7837528816e7e2f346df0679bfe2677f2e256dfbba721ac714cedca0ca965d1566bce09e3b80e6b1dce7aabede97038fb8ee6f1d8ce6d1c27b36f9be72356d11eda1aeeb06c4eeb3a1c81b818bf248cdae839ffc3a55f61050cb9f69986b743cf6e767a3e60156dc844d3f8b8fcf534f70ce7df6d48a0968532c87a39af38b9cb48c0daf57a07e0fce6f53f1208e7c3590175af34f26737178d4f2f04d834f251f53182aaaa2aa92ee1a7bb2e07df8569e374bfe96e835bb83beb6db1edb84dcb5db649377196b17ac430c218e36a750ec2c715f7898de08acb2bbc7a9176bbcadf4fafae7dbe751874836c6036f8f8855b2cce831c6bb5c2b023ed819be776aa941bc20c62dd0fc64502499140b06f90401d0fefe7e22d8e31bbb6d2342dd3f20a1b7d90651677b7d923ce628c51b3efbaae7b66332139bd1eb6bfbb65efb68d0c439843cb9f2b3bd7d95b57401f8e8ee7380e56ab7bd5e3e19deb70769bbff855b7bad0f4563a665074faa9435cf82f3e7985307de45bf9b3f65e2e5cb81e09a4e1ed1f88a712887bf41e9f61792381bacbdf649d4bc278acf29530a3687109afce1ebd10a80d228d91242c2ad95809b33087ec88b8f04a4f884b1b4a685711de4804cc2855d727f80cd6a79b655a966959ad35a35419c5d589938f506212242c52aa84c4f510471ef9e8d431aa85244782f4e621862d53e82a65c542d348eb2aa4e57fe098424d8a4e12162f1f9bd2355aa620424b7f47f22b8183ce3e29b594d20be92aa9643dc7cd67194603d04b4ccbb4f478cc1fd48b6800f9ec328b414501111487e26d17b91424a654129664a4359cac4b20994fb37035eaa9b58c83210835cc284c5ab191f6cd4803743502cd5f6c4fcb3dece785267d12443b9d4443c3305d3384396a3c0a496de1f47ac4dbf9ee5c0d5c4f03d7a4bef9c43854ad9dc7c7bc769d6b3c5eaf18def5ed2d1be5d4da6b9452739faf4f9cf38a59f5d9b49f337f499c0782769c5b8c731b5cc4dde62d7c83795e874d63ded76b4c1d9d73d7c93cafdc23a96bfcbec6efef8e07c2bcb5af816f4e03df3c077fdeed5db8c8e6da6bfe921a277fdced85e60ebeb1c145f65772ef700fed36f39c1ac52a6e9b1b554fbbc5a99e7935bf59eb3d2029899e19421cf69a2702bd263da3d83de8b57f34d34f99e11f09935158b88638eafcc41fa5f3f007cd1f124f0ff81692124218a4b9087ebee42a08cee5da70ecafdbb08bc9ddfe759d5d3b35caa53618d0f421c900f9f9eb3a5dbd66587a3cb2735de6a980bcf709c87b39a937fcd9debee2f017b757ed96835eecccea5c496946ab7fc52c1ec253a8696bd5f2d7f576f86a21dc3119b0eae4fc0adbe9537db8df7bc6b8c588a98459ad8370079cf61589fbc1a8a33a1f820370cca9e18b5f32dc78a865db495885e1c28e19c21c529a129628a10ed3226e0831c61863cc1fbdd00cd7040993778084c9f7e8de14ebe80e771206a5469841d6de2be58010b8fc597bef96bfd7eb30d84fa740e365093a7cdcdc900218e29374c42630877c8dbaaeef158bb8dfebf57abd92b8985171ce76360509f421b596bf791362b8b1e351d719764998bc094732e84a2aa957b3dcef2da5bcc49270ea9911c11db17b64d772440708b1e08e0eee881d3fedf3161769405488da20aacfcee160ebddd890401991aee7f1619fddf50a7b7a37ffb2b3b4cf3ee7b3cf6e40a2496b5de6f1b8b9c5f7365acfdee3d2fb51afe52ffbc1dd66ceeb7a3caf4752dbdc8604a2399e7aa798e7e6382058d773f097dd0777d7b9bb32cfcd797ce0dc3bcebd1196e250138407f8ece7691c07dbfc06f7486a9b4ce3e2a25666e1222f6f588784c5fc7559f60eee40814aa02c476b351cabc546b16a6e266cda8f328b4fd87e6c6065bf6956a3210845af47f68c76300784393e160f2298235e48b69098e49256697eb6ce7acbbaea3f1b3bbc7f76459f9dc57a77da43cf51d65b871eab5559b5b26a8639e23b2cf7b318aeba7a3cb647af87bd76a1b9e1795b424fcf98613a1f69969e0f1b5abec862b2e214433f4f37ae9b56c222dc3e4ae97744eb6114fd808b52090e76e0c258124a2d2fef5b2debb97ab23ef3a7bdbe851530e4b6befdcda3f57be8b56e08b05b997a3e608be561bbbaf651fea852296f692bce2bacb5d65acd5eb3d67e9b9cc5dcb76b78d3364d4866759ba26ee7b6c32db397b0ace727b3d80856d5aa652b7afb8a43d0d345dab343099f75f62e890b25ac669a19f9f98162ed4f4f8f134a7b5eaf27af1b3ba249ec8824360d63506a8eb8a15f1226e5122256b7b2590b1e70500645a5ef477640a1caf4777bda2662505f62d5f2d1a65446a818949586b12d518d8086b12d4a9a6b5979eaf1a85a6efc8ea6762189a9846995d2cc515c9b5cfa9a6d0de2d6118090103b885d1f935cd930b60549d363590107250793301c3862ea7a6556484e6dce995dcb2285924a4ae9948ff1f58ed4e883aaaaaa4ec2609d210a387bb8af1933428d11696214353ed9ed96769c734e4af3e76a9abf98a3069b9e4aa0861d4fa92410bdc5100861360e98038f70975c2f1f98d6defb7afdb00f9221e1fb998279b9148d92ff9174e2fb947969ea378c05d1458834fdd3302604136d04992640c358115f3a5a5b3ff805da6d1e7a3e563de1ab0782760a3f614f2313ca6bf81b8655a71bfffdd8634002f5c4ae9946c7cd0d2960a2e3e7350016ecd082973caaf4a8d617669c61a8bca2be10a7f4e4398977384873e761d3476f1e7af247b5635555557bb14af628a7e65adc59361aceee1046ac9de3e2773ba336b3f17675211df1373be36ca49fd9e7b55c2ee266ab88fbca7b9c5efe6473f63ba2d1e622eeabd34af3479bded624a42c66df0a337ef547b2685e4853fc4da2d95a96d5db78fbc52699968b28f57e7c124aa0d6bfe814b97fdbb32c7fd129e372feb47c24bf2d6759f62f9b998df45ce659ddbe12c57b3fe2e94990404db103fa2ccf0bcd9ef4b379b5d730cbcb2cb5c947b2b35f9bd1db65f98b9c5d793cb618d51c8761d38cc3943687299d343675462ff1cd3ddc3dc3f52d9c91206135c695d56ea35dd947576f187634123ba8acfa1dc95844b3dbfcc52a8938fc1d4d1eeddeab7d5d79590143ee7734b7dfd8d777199757af466207d17ef5ed24508fc7e6d4356b0dc3a694d2d6b21c8d44035421318925ef1053dd759d4ba8c46b17ec9a9864cca119010010000316000028100c098542912c8f035d1f14800980984a6444150e235992a32888a120066218438831c600640c40062968cc06c7d6aa6cf15efb8212c56a10b70dd666790d283b4401ca2d5ad676b843b80de83885b0fff6b496b450287d2fb03940b9a5edfcd1235cc7eb090f5e05af9e0e80ddb55e9221b9a3b423cf2fc1c6328a41f1a53ff4ee92cdef7efa5dcb4b5255c905eeec7ca8243e146cf16c3333f79bf7a1968581ddccb18897661dfc79258478454a2c27754b26a6f52e7eed08b25f15b8a75270b85059415056ff99310bf578b27c600e3c18cc0f5aa93a6041e6b68afeb8da1f184e13e471bac2c836be96d12bd748203b38c2861359581b8a953c1991c0b085cf3da0c241959072f035703d45d0d3afe19b71a616b552ac40ba21863774ff0616d76101903231a510d0c32f2a0f3c54fe58e97ecd14dd1429fcd5dcf9dc03dddc1fa88f45bf649bab3317d6ffb410096b3a7fca0b7038b50ca820b929817433ac87d5260bf59041ef8457a20199e31525084fdf5a0305e21128005afc71ddeba585092a6cda5565411fd3ced687355ef39e86b03a70a18d2bca2e429fc8503af4018aee5bf68cc3cbd44d0d4569f3a79d1d077b1045e4939676cd9171c8cb5376a85a5ac0f7503f20ea8caff640513468d1940ca5eaa12173c98e063d93e7bc1cff26a0dd9028c3dd11e7b5670064a90fe5e6732977f8a76ff15db05ec89ed03aaa246e353087766446ce3beb75f1e06fa206ccc10d61ee400d053c0ee279e8a2d86984e6343e4933c72f388362f511bae7097493d0d17343d5d4d1e01a043c8864a50c48269072030b0b852ecbdd622e845695032d1bf2d95c994a7a115420b9694e78140fd269b917c41eec6a7a623be660cbceb563a9c023f760cac14b83d453682a248ed7d3ca9989bc80783a3bc9f3baf59c6664b648daf094e48690f30260dbb135730cc152d34447d3a218b3af0c3af77b91f184d3896c989200528d7c688b33eb27e133cb029d58d5dee8a95684dc530cd8a166220277f8852c30e78cb196c1937639ae73fd2478eff23af9b20cdc2332a5e9af48db22b123d9bec1c13be74e309e179c22ce70a19c995079ec30beb41a592a01b37990768069b164a4ef7643b3d3a3e5128cd29cd9fd002af84eaefd9bb517daaa32b5931b8284bc9d3c2b1d2d8d0915812c6684b9f212ba5a37cc15f1199960db6e486ce20a88b8d0511c8da23688b226cdc5c0842fd096f67c10774ef10991e5ad44cdfd2db77bd0ae5e92ae2aff8d9be7299fd8c98ca27a9cf05aff568510c1165f8f05c154b6eac195ec18149777b6a14bd73454b1c8f8b0a0465f15487331efaf87f0f75f97ed2f04845712c40484e9d27f6f0ef423045421d49201ecdbdb903db2ba846f79ebb293cd76a32734738504ed6c7fe57523da0ae84c619c202e41b8255a45a0bdbb6001cd3d75cae76d38fd6ebbbf6acb90cd3a0f306a60b2637a1a33b4e7926b296c5b8ee9844cb92371f4a4d46d14e04715d6c9797ad2ee088ffb74e2ba397d8d06602833a6a0934b1f74c7249b0d0007095488aefcc0832c8a4e1b16ef74885157804d6e996423d424ef371aa3d2d341b5de1582ce3cbd25b6953ad7f05211a8ddbe159838c161486adb51faf2d41eaa08911afd5d88c0e21e4ae48ed424cba8212f5e2c7048576010fb9ed429bba1c372c8cb8525afd111884dd3ca64fa99e1afddb87973a922d6de849fd2ba5f1f28f043151cdec077ddb1db82a0608f88dcf070fc3ca8283ef86422001f54ddf2733c956ba9716a33ff21d214117d0853f5a053148216e74d1dc3776586d5b67b327d8e45442a369656c06c414576dfbf7f6019f79df604145f407be3fe8a793024bb92804d6a63fe1874441579857d8d5c8be3153ac73ae402d7fbbb61af1e526840a2fdf3e46d3e83390453e0c4b6f0e65c76711e860080486ad7ac862653889da7594261ac6b1e971aa8f088466f225b11f9fc932ca31ddfd89fb6c93f02cf95cbf5e1b2c6f1a808fe360fa0ae1dc9e713f3840e7b21b61db2fe3cabc5989d0207b90bb1f4ab296ed1bffda0deafb8d2f8e333eb6290d59d8f3b6975b9bc7b4e80e059d0510b0a4fb47edfa89cfaf8e281397a8bbf8a243074259a9a05776ecb98a8cdcf3b71fb8ab137061ec2270b698884763e1e2f9870eae07b5bea0046ba15bcbfc16b9e71da2cb7f10c88acbda5448ba2ae10caf364cd645313e585b2c37814c189b15265276d3a13d84c8dfb4d09d61f3870deb64002e656d6dc1e00907407f74b29677e92c251914c1e5e188d085c4e436e20af21de19853cc69f3ba600b2078851142c0349791b9ea6338ee153cb7405cfa3de9ef6623df13e033ef52fa5cc29210a6b66b9af3f9ecaadc62f36b80550cbc800503fe36b140b19b72b253039b6dec6689bd1b6346ed6c3ed6186861a57b352b18e13833fcf35d069f315ca6b8d82b6d7fde34107b56efb7d5dcef698da71ed86e2fc79a2bbf426d5df1530a5f576e4e00af49181fa4a8f70708a2030d8670238b9d50b6d5c5f0b68f334a797598d5a142bfc373378523c571b820844ce86dee05c888add0942038628497feb52cfb2c6276b889f6ebcc4515c07339c573723f77490b247b205d62dd64f613abc4e546343ecc333635b2ecb8d653946937f29607e3fef560dc8ff222a3d71a9f3c1b4a80786e524a347d1cdfda5a3ea18208d1e5586a70d57d9d156237c3cf65d0486b3e904e1a6b34302e2120387b3e8e99ca21f6cb3692618727fda2d6f7f57160d48e4677f19db9319b2c894ad0535a2b5cbd7a22a211c49c99cd9ae76edeac5adbca24525e56ca667b234e154cc7e2a411658f057093fb954bd3d90650f64809518bf5b5c9c40da1560652ffb1b7af91d0b46644a6caf5f293e68fa2e150a2cfdbece235fc112800513052f0970937a57b57dd8d9bb4d284aaabe8451d2b9d604c0eb6cbb1619b67665c4ab74f91759204001d55b98927a22ac84b2fd0810a3af83aabb50b07079bf1649d43d72b8ee827da3a28ebd2fe268bbd6a632052abbfc271c99ffe3cd92a4e7c3faafcdf2472009125ec29821d6a4a66395a6a0e5922f29024d92e7c099d8888490bd304117be10c16e38e57c66c7c4e2b85a5eeec889779caeeda6e15c6acf4b9186a4579c9924133ebf74221f9d370999030f4d129fe52d6d357c3a625ec56e783d76d77987644ddae926e7220556bfefff2d4c268857f87bf2b9179e99680f3d2829f80576eecfe383fcc64acf15b6f24a206560c62e310298ee20c60638a44e486ff1684a3603a1ad1602bb52407f67721f22aa2a3755d5120de44c0da0c81f4cf9ec307ee388b2f702b959d0f7c470fbfb8dbefcee814ed7fbc79325207274bfa142da1636ae51f6eb6db2fad33da00fb904570ae90a32e162425fdd6c235ead51835169442eeddc9f9ced91f6f625a5116b1441b1e3bf2a690925de4982e16ac29c2523a35ed106ac92252774002df30f2ee8c35b39015bb21a1eaad635ae9040008aaf56f30032920ae4955d902bad3262067993fcb0dc509475f8b8558d5ca5103bf684bca77acc2667473993ffcd09e80fa3c43dbd80545688d674024ea496054e6f1ab6d999882c6fe8c6e195c1af35be01284b510475ba4226c9deb8970cf6563e820b9811cbbe45e9ae8ea29a1ea4488b197e9969c87d8b7717b73c87598d4a6f91318ff02ebccc2408147c1f14263bd4f82b933a90092e618fc39e4c7e4ce4736504bd4475ae9e532aa731268a8d463c0ff6804e25e2cc12f346e45fb3594b275146d02484177833565c598ab765d3dacbe24bb60cb7b8ce27cf6f5b905e420160ba614330d35a1f7d6eb3c0d23f074c4b5d81b768e0bf59b278155333b07958bee4977d1400efa4daa7b78e0eac6ef48f308b3042ae36388d2e183dfcec1663c7766475e757fe2d449ec9c14de72bd5aa033bb623fafe0c7cb7b07d4d34ccfe56ab3fffac847e6d08d1596976ae38663d0bea0d220cd1c5ae4555b312ec59ca86322eb0eaa52e5192ee224fc822d2d23258d3911a0cf8e2295deca0d9ab23fcd08e00305422a0d5868a555b4d5b3faedae3e1e935864f3b701a13f9ab16d07d15b41a08104c60568bd4f2e951974f8b593b1acce185605c1239e12b2a361bea9983c56651a44d97e8a1b024a9aec307782ee8d3d94f996236a845d810dd1e88986df5c87ba9d581b46403e2aad4534bc3effaff6ff316a24c7c3d5bd958975097e404a537a761f40809207a96aa3ce8b2b0df2aa4af642e5c462dd1f7ef64342ab5547d34f7f40dd9d42cebe246628c443014fd960bfdb40873ecdbb67c9f1f933163e6a34b3a05d3ea58af89ffea5cce1d057a80c62d939473bec8004d51194782f9cef79f20ab64b56364bcc1851bf19f9100229921fb25f26d0ec44f5d8e903c19ab41434ff4184e1a94fdc3730a34e57d732222a0db7348723428c4f31e829819092f0eb4281f9cad903ae8e8bdff57c9c72ea76990a0aa685294c14df494aa96f115b79d5443b5694ec77c3881519db863f5f7b5327f61db6b6b63b72c6e7ddbe1d1bfd72967db3ff9a211067783d5f16070064400a3252859059e623801720fca17df6a434762bfa5de879cd2b24fcb5225f43d970f08468865f21a5a20cddc4811d472d2ec5926eb96a0173bca4c1898a7777149740b553c00f291c8816524b6f388bc5d946e70777a972aa6243865a36790b9120fcc1e91d4a7b8e88627ee80dac6578d0274bef7cc20f93de209cc92e88728080dbf47b2f746ea874aaeb32978b984a05b50fd6bfb9848eaa7d90e254fb2d1048a25181f6dfd64b2e2b679576d73baeba9b1e3687eece1e5e4ab9e4955b196112b34061fa24e067efc76f3346b9d644a12b2cc0a7e61101c0533bf6601c97eb6ab51e4ea3bc4088881373198e5f1b60fd08a5105f8a67c4f144fc6199b7588de0fdb3a31cefcdd2f225ce7358f9d16cdfc7d4326c440a1779e5f93edadde2e01d6fcd0eb91f74581927d11fcd694d685172d13c971ae385c23ed65dd78be06c07dfeff10fd2d44d419b6b7bd4c7c22236061358383d5db8b0aa56aeafc406115fcce24ee778c4812b5b0b1bc05bae35ab8a009c4a59b9d7ad8131d64a4f261a1c12ff998538971ccbf748ec0a0dc5881893b62082e9d118f2d1a64eee150815b45a096a96611f22beceeb71245af0566886f249fbfccd8ea1a8836247f552301eb461f52f281606b7e40a0e44de2fa8880174862031f612754d839e815b4d0d386802b0781c1004b2278138005e3dcfdd0d85746ad253531daf2a54fd1f1af4b7eb3696578684ae94b509d043a64d2742c07ede78a20c2a7f8cc2be6832e1d39dce86d1a3e3c97f324f3836bd6063432a90c0e6f9b320bae1ba8a0a9934bfb9046c48191e496ccd817b93177357e33885bb246dcdc1d3f6a0a3323f18b9c8c0d466f6571c63866157b143a0406a8e7388a63b5d0af7088441f9a4b002955251a462effc72c27a603f8b5c0f4f418c5573e65b4d7de19e699f03a436d142b08b4d84b315d34fd324b18ed0d23d4f01a742c9a1f3e6075c1964b2a56afad1128555325d29cc101dc80a9a3807d3f93dd3253cd780ebfeb7530bd06d7bbe271c0c86ebe3d8b57c2d4f0ea2c677e349106028a75270c67250afa46909252269dec56ecd8e0189dd8f1cd661d7629eb7549c51b38f299ea49a4183444193fd1bb99989b252f09915d7d010e7c058e3220f1d53171352b9bca134fbf2a9a576764ef8897f040ce936e7b2da421933f93115ebb9517b6a96464432f91b31cad710b53a0417d5679e32c3d814b38fb02738f19ae0f6abb374ccfc9ca7def3064df14605eccb08752281826f9624544d5d55539bad9d580aa85790a13fbeb33f0128229ff0abffc1b48c5d7ca38dd600074c5b692975bc1960d1cc82758aa6a5e6a9f85c598a5bd9b8e6e90a21a7f27382a334e0b09b7816a5cc856b99218fa0dd16c1b9486842ea5c0426d8b3fa52478684a6add5a4daa9157f437589f1c864763eb348bdb6ecd4b7b678269eb3ea69a1a1dca51d2a8816875908a13eb65e8135cf7a7effa0e63363655a11074afb52d5650a2be9648242e164bc1ea69a0a492a08c9947b0fe1bbeb7e640f09999ee8108d4c3a2449bea5d221bba05b8c67065959a86d8bbff24aa89338687d0002711bbdbc6d87cbf8d013f177b05d9a38cbaea5164dc2d5e97909b15069a3b86e2aa3d28a541d05717e5938871f79c0c2e2ecc7cee6a9c1574ca9c52a12dca8ba140b80b999122b9a963e4bcb601e56e26bb9492d0eb7496d0e2d2b26d4529aa80265e3875870fa78ad215abdde436bbed15a81eeb9a9eed20a38e4e1476d01413a0f3ed4251a41cbd5ca54df10758a5265b150e56114c5ac001c93d4c61a3a8fb2bcbc9e7532ce9bca75d378af6c7375024462ae80269b7cefb5cb7cdd890f7ed2cfbdcbedcb2d9371106fd8ef349cb93b6884e10706a87d794fb3315dc9231399b0d21fe75c35a78b05f59e1a609256c6a31014d3a6c672272ff44573ae043e220496792514e0e090dec069edcf3d2917a14b1684fb7668cede95f7fc2ed009ffaf96d1bb4ce5428f92fdb66065b9aff5fb3007fca851f001e6a9d7a6c2fee5c6ff000a52443a164db3a2b64b652ec191853ed6913c5d09a5c331988c31d5946dd0b56bcd60fe84335ae49b61b0d04301157be4ab220245eb6d33a6f60b1fcb37ad3e039b1265df048b8b2681deee6b5a50c8ed94d33febcaf2b246e8248fb5782f16d47b4bec466d2e9014ba7844c386630655d361eb121495f46078642c0891266d3c4548f8c52fe9aa1f7ce2954bcfcb3fbbabf783bb014c00c3fffc04428002950a57050a7418c3685339a05be3e5e231683ca87770ec4a2aa8f6655739475b774780d271d145a5d58c43666ba27c369199ef2f503ef06fb041a5c7ec4bea4f90bcd5e1d06d2f97c837095922af02391c526d3ebd03b7ebabeeb288ef398b6489477e96f58a9df4e15c7d960596e83f0620ed14be7540df0673f4adb1e74ca547c088ae3f4ac0c314fbf1725466392e543488e0983ad25bde5b1b0c412d9db7dc3d21f87c0ca55e39d9273d187407ab7c9f1f7a9b6c8be763410cff6eb522966808110a80ae50057044280aab96fe4a8e58823b0a5adb5f503b0b1a043a2859bc565315503b76850a642ef36204c5d7b859b645f7e3a99fbd4b72ae0017ab66c519a2d54507698b74517698be03d5f4793b9e01469bee052d3ba790fb1bc71d3a0b7d37c06d14306a39aeceb3fcb34225f4cdc494abe71a461e83c07badad06adba1c650532a560f8491ad02482c11e56518a50ebce273f222974d61555e75efc7952bb19f5f7473565e8ec7f29ae192b586ef27c3496ae02f54c27cc76224f0fb084d89566ebac2377d7168853167f3b00e812e2a4b7f72d9223e26f53a51274f5e169b4f4f269d6d8241e58de388d2377ee9d00aff5030cd75e86a6818a4c4c2509a3ba1f12c5b45cb95b1e7f577791f3fe016571a1cdf5409993010f52fa9cfbd2678a88657192d10a0b12a63582d0dcd130e863a900885ef0ad2952e8de9958fef33dff29665051e8055571099a52faa8afc21021359b8455183e281460ac3a31ac47a2d018e36fc72c05b9b368ef13487880412c648db879bdf8510baaf55149c24efb70ce0ab8064971e03f79040df765536d8b1561316b0c964c109b2ffb1e5d58184b1533f0a2210d90b4b91df5dc7294a848225b0c213ff407f121a5a25ad9882d80b9e48e5ca6776b7ecb2096e3f0617c9394d08f598b4978525c632af99c26bb17c0e03f4d50944e147382fc6f76d805d03945188b3962478c0b6343701296267089a3edba182cf3ca5ff9eba65a56c41bcc11a4291febf35da25c58c2fc24e44786a3177a18d8920850ccb523b5c4309a9a155cb45b3d6a2fef4dab2eee3f42c0308b9a24505020545f1925565c336bc9f080ff80023fa13663492614e41db01707102a8120ad7395021bb11f23e81a55b81751c569c9e8eb80010a8b4188d3b1314df973255b311c5cdde162111962a52fd9c4ec1106b0fe7583f0bb112a84fa260da185fc83f1363263a13093f3195133233faf58a1376734c82c502da5c0f0a6b3d17a404eff93ce504190f7a30664b1a9d10b45547d4ac6200ad998e5a8ce398123e18a658548f3f31e2d04b7e62cd2d5e44bdc926c060d0097f7ba8efafbae641bf3e561006ec366f8d6e1e581b72185219068a6996f67410378e712073eed8909243cac8e35a34b5b4af2bb1d9d2c2c30d87c02f340f7a215e4f01dd04beeb6467213b8286c87a1fd43e4094c40c34fd4ee791a4062e7528b22b8164c8c32b8156388aa65a291a99e5bcb33dc195b4ca9307095c0a81b7e18cf45652432b763755279f3a605a51fa3975f30066e1749c2c2ca4d0210bfe34cc1073a6d93ecc5818cd8bb071300c794ff761e4084867592ae2f041f4c25f30ed08e15bfe7152e4a53bcc3c62eec15be40193e150dbfd02bcc3e1c1c110737fb476d29ced5c40a86cb476e46c724ab1b25d1e0bb9c1cb2ec3026efb8442377048edd5b5710e7f2188d8524623d15d11285bf86fffb6f03988356825dec96ee6bda8db1d3e433ffed76e4807176043939e1b9296b9e104dd59eac28c7e12770b6019598f78e33befa1bb606bbca0b9eed3059d66b3f4a154b4372db1327e75cb0688554905ba9c5cad48d3513d72c188c53ed9c945790622d1a427ae6f277ee6b519f7d8af822b7ee50d647c5bca816ffed298a345830dea5ba6cb860091cd6ade07dc43346ffd508204d72d651f98c33a4ac5310fd9777f518c936aec46f643eb42dced05017f0dc17964a6c9c3212501f81d7c17fc9cb2fdc9051e8cd004fd91afcaa2d86568c7e952e9b3a2d9791325a58b95382d6ee12f5aa3063c48081fae17f0f35d062fad442059c640650442c46390e0eed547613ae7bd9eb59b1c56526dc4d9d94678fe3d9de1762fb34896e18a80e4706a35597d4a6388cbdd5f4f957bda701f117cc1a94c3417c01b1ac2e827c3090f220e223a48c921f1fcdd4499b895d8c3e54f099747760d8e92ff5749b8f30317c49aef2874be7bc9c56699e96d82b235e5562181e3b6de50bf1cffe537451226c9f057eb43ef2c60d1084e80a89cc2ed4b73845bf16c800d240c7cc5a85ca7d8cd51cc1cbe7eb8f1fe860f4a5d41968397d871e1bd59c67b2b0ea0f5f8f813867f94b4504d2420b094200b43e690616abea555beac01fc87dbf60314986bffdbeb7397072b7d8b1a8efe3b5404557cc80b61abb0c52592c75eec1a7067aff372d4a55ce3384640925c1a3d35ca1d0eef0e81481a31c8ad4bc0e3fed25905130130f02bdb885a393fad88c94ce861772aaaca8c1ddd8a665554cb3d8722b25b56bd92fdba74ec847703a4cc7cc28a68f01ca7351b1ae28e9055e3d8cc603afe9b312d4eb66a3ae26921a751c8427031cdd3fb3137ffe0006c92066ecc0989160560e48170fe89710b6c708d348b3dceb2d0c52f8522285f2c14b8ebbfacb17ee13ce8702c2aea21dc099512ed4b448224e49096ff72e4db296f693acd99cc96181bb380a8491a0caf81afa24a6e42b626d7f3785eb08e31cc0e035968a34e33678976ab254bbfdf05bd47c7c0412489d33b1f53a23fbf4f257164a5a7a7baedd20e535da24153b1b5a4ee7acf262e3ae10c724e0871981a9fc85c49f0ca6770854002ddbcb1bcc3ddafe5899e98b11bf6e12217211bcac19de9785448127e4baa876cb00dc273cfe34d93f7c11cd970a6a05810beb10a5f71c58ca2f622428ef1ff03c7e8df8dd0a90bb4714cdf7b2e81c7e8ae41e66c3dda953515410adb0ee3bbe15495c34bc83097a1ab1703e16e31922f48f32e9df65e71adbdcf98abdeab3277a4c93ac8c58965d674603b79ec5f11ae88ce92869c0fe3a214aa7048f5759db4df02d0a0a167bfc6ad086c53c82abd97e88b0dbe9bb7c2f58d5e3f8ae8362272895351b1cc5c8a80de2f76dc4b9daf2215661521fb54118fcd03c21f573a67980c1f46978ba1366c515d8b360bea577010fe9fd2da66fc83305b320086e30e3a31edf6ac4ad71a93a0ad79716aed6671e189f089f3603a85f605319b0ec31a58a7d731eb3aed37cf59cf18e34223abed453845ef1002b4e4fc41d37840c6c79f8dc3ef144920f05e191e8d1bf5d48e15ddc16746a7583688061946d68b6bb027cc5ffb39a279a6565f30c6ac0c893b1588013fced2582554a8d312058a8161602e996b435e05cb7c3f8ea7fa5e0ccc7b17580b72148dfadbca34dc7c2dd3a9d97c4203021de590d34371f917c3dcce11270200db35699a461c73262b1d2760b7199711cd07bde1fa1d2797ad1150ef6f5b944a414c44e33d1dc71dfa60b70001f9e535b35eb3d8f53a32e10c82678fbfe2a5008a24eb1d5806c393a55499e58b0fec0265df7a2dc67c16acb04ad8f4ea3364a1cec30025a4226edacfe4614f9b82c2393309b18924bb77224b7bc2052c06f39388428cc1361377f23850b460a39006c714ac9aaa1cf6a80cccc4edfbfa71f10b940828b971ad6045985206b05260481c36d283d2c23b022fa6fef61e87799900d5254c2bcf4d5d9f06a6f729184fcab5ca2384db8a5b626c6178e1b49d815c1d3ed11f9014b12273690bd82de336a01f5f55dcacef1fb31f00cc8f001d228312c7fd3acfe77b19fbe2222f56f445e57ee1356d3e178a89b3e38fb4b925bb561064c9bc819a9b22f468bc9d47f0913ca812ee284e3247c8214ea5a6c44501738789c15b841e462c319cba24f951219203cc53e9a104131c1020868060f0467a4242a08e33b178b5c73e9d7755425795eca72431b03443ad05fa408832535f277a6e9becf0b30829c008a21d3ef669666587d607b465dbd521d2ab4a89728023fd0ad9c1750a0d1490f0e83513028ec54c0d23a15588af731bb3ceec4fc452d5d93674e5570f9de939998e3d424d5e49eae634a4b312269e12bc0e54ba2ec36605696c0a00035d2130374a12d150228b081f350eab00cd7f4e1565d020dd28c247b8464d316b709ddff78a9f92c5ab03653e352cba4e70d94aa09e70b9be13ee3a3988ffc46e09072f557d32a14e3a18a42487812c5cfd4453c212b99bee5e9d2b6714816265a6cae3f9b12ff9b9543fd1c7995d226a65d6abc8e425d1a0aaca155a47d85b52066bcafca582dbe7869649db20c0a3f9494050bb836b23f23da39fb5d9ca634b95dd495fe0461375235d91b9af9fe87d2d2cfdbea89d3ed8390330718ade607c80d50ec049cb93c308d4ac0ad15a7fa2c79361c4c5cc2c5101b8fa94befb358dc4bc408b5778f1c1c658a583fc99331d7c1a9310ca9e030166499e4a616be4b17ab21345565019c2639652a7481e313041a51f116007527db0512e978a18cd009c7ff50a4fc471212a7086cacdb69728055c090aa2363fe19b931e14e421c68495ee01550d825305462db56857f5e5fc8e410a754153ad3c9021905b6bfec3c32207bc0e9a5e508f30e2a1408e7b1f1380fdac824e86721781b38b4aa1c7698fe45473fbcc68b5c06cc7c31f65a5bc23c89dd5f8e7db80dedf20b1c4b3f6a4d7f01fa505390784cd0bca3c3d80d5fb0bf09e4211b2d35bf8d5e5c8fadb38bf2205074096637e58def35484b929f290f24d02e1a3f5d008b7ad5a5864cb1f6e3b9d6c4061ff33563bb4fb13e7167ad7ae620d015759a0c74b8b68b685da5309df3e0d2e6ab112768c608191a45897ce2fe42ce288c023996ab748fe7520249fcdfca0496d7e977f1a88869141fc181d49936ba5d637f55f617bad72335978f3cd324140defc4efeb3a111cfaf60fef8a42bdf84ff1204eedea657525c155850f0265eb298bc4a4511cd82607d1f3e495b616f35b7255ee78499376c872fa5db0cc9198b1a33f1214c74d6e72c69a53c72e2e85036b841e7aa092d600db4ce3e60721ff9a2d110c80b56c0e41e49ef642098d1b24c23e852eb442293da81af607240b8dfd4300d43ae9f8c59ec7067240ef8550d3da90835032d132a3dfb2e911dbc1ca0590631ac66d247d7d25b625188d03b1a2663a2e32f1904909505a297ee4f7d144dd40fbc7394946f32d9ae07a552993a54dc25c753ee8470577fe5d92096925af781767b8e122d1665a4f9226716008e8dd158cc31813dce4261d6b4cb362070b6f508dd44d5f3b06913d4e3b6c866d0402170e06c82edd24235e02156c3b1c674d467d3700a200b664ef0517f9368091c292ba6ca69b18ae6df3ec8e5f7336bf3923b65278c95c615a327b4d2727f4786bb98b7c61aadb4184d559cb2b81c18c720e347f51deb82052dc75ca51950d305f33f027ac5a02f60da4d003d57c7dd41d8c526461e4d1fb1214b3be26359fb3009c5036aa34cdd8853511c6a37239b2a8f7c1867bbf2aa17cb34f430d56d15a00a0259c41019ef4edee4222e1c0000137253957647eebf0571f7a026671f174781c06b51d9ad0016bb621db66e44dd3b0b514b1f8dd766b22bfc766ddd6934a17b2e62426552c5deb30e018815900f3d3067414b12785c99f2363b9fe9b7599eed9b6cb7d809a29a784985c97745d931e747bd84a8297e1b79641e019cc5645338a591c85d33749e159bedf41353178d4870a78a63ac7fc2a9be8dd8249c64e25ad141b17ef426040efd19f5c8ab4f65a4b121379fea7548aefe9a280897207a05075a7f989f9676cc1f378afabfd6594830e1b8fd7ed923ad58baa8549bfda2743c4f14e5bb5c3773f0b8124fc1180c61d7022d058182058b33561b3b121bfa08c3bbb10509dc3b32bfb5d6afee9317ee61ffe6ad619132b8ab3b747b851977bbb94db951c4b9d7cc11cc8a95808e4c789d39c4c606c90de8b1b3df6ee04a879d3367e50afc53c9b5ea81ca0c864d078afa883b3e9f595d630980061d8fac0a531cd98bba02d3c33afeae8a2e5a7459a8a71d84bcf14876b08c603f38ac8163ec22ee8b15314be0587ecf6862592bf5a2399b19f855fb04e5da7d0fe5ac383db990fcd27a5763e8bb8b19a3d8a47331a5b8ec9c1a63e4447ce92e7b204a64a951c72152c730c91fc3213d380debb1713d10f8c54790b21b8fa484ebb1651eae7bbe3b8852c214b6c2163f2f5d4f60c44aa72cc0c13b4df1af4912b8cf2d966416ecb8f62de26cd793f4000680ec9afc1229dddea6ce94e9f413850c708d2bc332d1c382917b32f876d93cc76dc552c7c3788f0eac2bba57b2756eccb3bfacd017b2cd8a011ecf952ea59a79712878e90bc6f4f7f47d8de4f5910f23c2a2c836b28fce656d84eef186c2d780d9548b93491fa477356b89e30e27db37e005116a53946ccc07aa5f90c73c147c6acc0f12b3d1fe7198678fb5da0ad0ce011b86e6720bda4fcd5b2c9506c790484a93d127f8040a06bed9079bea637af7fd36f760a8da33d1c8cfc69a7fd42ca289b9072a858a092c0f25129fc6a54b948ef3ee61f0d627222870467b6b20fa045e1440a75fd3cb4f04224b7eaa522e33bc086833fee829696e09429813f5302bd0593f02c4f6104b47b6ba58f81a5e09fc0f75e4b93d1e0574509c90e884b840ccb7778121f5a6228f4e468813296e2c47655be4505f3657825ab3f31e4a1ccd39d91337aa09cb7252b31ad223defed728c52ee26bd8e09dff503750bf4d6ccb2718cb6720d81e5717e9d6fe17c08fc607a6b1da238fdcbdefe2c7f0989113b804db2881839269a7a6bb8e8f354cd5553fcbb1aae84e31e8daaebaa6f0c25a07e9cb4d3b3e6d59adfe15ca3d2b44a943c725f8e2721eedb9d2f1d16cc0d7792d30c4fb5de7607886f5740a4612e8f2bbb9c2a0c871819ca707ef7802c29166ea55624dc007cf36df4922f482bab26b03d9b8b6ce222b50fc6c8a1cce983d98ee58c36bdc189b1f1bf36942598ef0d8e104c80b05c659e60170c260745dcaf2abfa2016db3ba30822d1202e139dd46a2b281984b88554c6277eb87d033ec5ebe5e0c2308363fcbad7cb89745d485e398264dd918743ca9264a04dc244a5a5aa832850912a79f120a026418b205aed0ee02903098c0afd6a69899a8fccdec6542c894b47c74fa0c5c5c89b7b636edd87c4478154f947ee8d43ea9abb173bc2a9fd055a130f25948fd855f4d52151371e73cc8157f66c747406a716501d09efee0f8e9ab9f548b532164a64dc8b019791bf3b11166de98efc818c9d07d63b508f3395d4337b9286ed370ee30b9bca8a7da489799a66c2b35b8bae4fa3c54470388a08b0bd54887a4c0e7310186971ce8628443851d7c059c136d03ef4309b2c59d196ced534e9cd0a1b8c4ac783f2d22de95ef13c3846c5f0e21246848c5a5bb0fa11821e8cd4ddda6223c0d4537af5d0d0acfb7ed405a6e5a806d981df0211537b5f7e4b7fdb1766da1adc70ad1e256490ccea08bc1e756b3bfe6bdaf573afd34f483d342db89cf81ba9a29352736c80c776748ef67806cc810af38b274812c27f364366d475612d77b2a62fdf4785e72db5de881f7855d773616cc1979afaaee1b401278a2696441e1b2ec5ca6a31cbd76a672d24426c8db989ec839fa2426a8614bcd39b501af308e13ede8ffd7091570f9c813235f080fa68313a4bfd87d81d6eb5c49969c30850843229773f5c8951a697150d6fd9715422e8bdcb9b9deec94b06dd4376d3f2d749c6407f6a63d699142ad3bf76ddf228d04096f0ce29ecd8a7d1b580b37fc7cbc7ffad5d0f582372f3a19e2c4daf2c44c1a68818b46acfea1e034bb94d6b35b3d62a67ebdcc2cc5e4e027e74429dd9b582f049a398299cc8344261c09cf5bba1e4cefdb7b1465b7f0265ba9c47d4fc8b728b4854aece7e7732430e1c685641b4a2e34ba19daafc95b9b5d11470c2c474ef537de8558e9e9a195a7ad5e7221223b08dc480e749d5957771cd920c484d797411f3c1b1103a6682733eff6061cd47bbf032ad4156f0a97b16bb2a062adb76ac7d6377bffb4edbc40ee47d77e96d4a3e112e50d4b9a6b83765c3b68d89419cbf3a00d8c199df11a2cd333b64e677bfe92735537ff126df850e5035ece0b5d488abb723d910cd1bbd2c8df8ddb190614e5ea5921b09c3ac445621277ed7ebbd4a672041294fc9f51b03f3aaae4f33b2ab64aaaa9233279b1e57f6f1f36c09dc16684f302cd42692e92dfd9c422b946c652bba057108979edacb18509a1013b0b378885f46fe7af83ff939d045e44dc86223a62a446b7185136cd73dc99a69545c2aeb2d692daed276fb6be7243666105901ee2ec36e26a511820c178f8cbe9aee3e343366939a7a994948451822845a27288a7c88f4bc71eb97139b5518af8d1053f8edbed85c6130d8eda72430d68e6c842ae9912c7e6f3c88182587df144395013fae5d814494f077f526f6521e9bcbac44581a1f3ad523227e0d7d1327a45dd08b160d8385ce09bed4a6d9af86b278b23b0d31ff7b54a136e275dd07ee0304378009fe09bfe7276f9e815948a8f88b1d522a32ca64ef56cb46b5830680c57eb7ba1a7518dff8001f2973aafc1635c9caf583e90db0502bed409dc11dd65997fae8e07c50aeb1d6d303a00bb7a16b063592e66dc9ceca0a5d7301dd6b1257de7adb0b91113aa15c6f4742e23d310c4a44c30db3cd7e6cee70e6ec7e4f587f1b2e96dfdd225eedc1d0ad513705df13a56d641656128f34ab673873d04326cd1ed45f0edaf8e24dd8c2005958bff6eaf848065f06100ef124a7d6d1ee9fc52b80b59c9473dff0072a88def6a902d20d67b436a7131a9949988e36a4033f406a5363f52cb0aeff1999466e940ab9274ced4ef565c3e9e792ac3a4c57caafa0ee4ef62d5017199c74e0005d65433fc06906a262a17b24212d73c6adcd0a3e414cd994fdbf1bf36f562d3ab4126a56fba10b584177b545e1e19e5d2e82f201a95d1ae1f3b3599f94766a7abb59fa1e47a4c6d8e6689ed50367e12dab77e180b0d5d5606f5f5d3d92007798f2d1ca8e44006eebf381cdd918fa3a0b2c00c16bcfe6cabf8a7bb308724e3b854583a6226af7c53b4551dca41c4008fb145d83a04c9b44c980a26ea1522e8c45cd02d9645fbb85eeb6e9e0abb4285a1975ef9fb7f82a86359b9ddda2584894c58cce689ac0fd8060a92ac9e4fae9eb32f8bf4b54a3c469d9bd37e0ec022d73f7f61e5814f2363eb2d295de67e4f8c778e8a269734c2d6c351232ae614a72a261454815822e91276097f9f45fe28215c1de875ec2bfb518fe0383a446799cbab5346093e16a6a8037bb6066aab1d0a80b26d5ea1979215a8d9a1a865e633284daa888696b7870bfc6963c8c3bb537cebfb66b142508bc8333d296708edca5263e126ee82d3813a069a1b79875908728c11f29c219753961a7e0c1ac03ecb61303552207d2cc413c8923f9687337773b4176783c015734209fa9f55c2a301d69fc729c12556a42fec8b5fe36df6ca296f79f476e915024974baa2b34a820e7e3d3d9b7275c80eaa0030c8477f011bde9610115e3398774a129af37450a7408a5f5a66dcf1c2fc4185d7a85261443d286ac838ef08c3ae5803f5f274f48f809a8e8a887e36956416cdbae7cda4975f63732f60b295cedd6518b0ef9c01c7bcf5bffc20f5655b3cdd1448185da6a84bbf9a0099c6f68e96ddab22a80fed7f0445a47a53b380e7baa0ca7df3c0cd937d21ced1e66424215e8207b08a5dec4e4bf8efe680b037ba49424de2ed21c670f3f5fbfe96e10acc2bb0eedd15a1944fc59b99e9537188f3ee2e1bc8663f2b363f14561c342f016a284713ca4c28d9b9fb21714928592aa4f7157069a2fe587d592b3b454529c5025d59da8a4e9eaf44bbd11e2d38dc182d1a39d0d3b20d713b2bf2ec01b84f93f6889064038bd76208010eb550e2ca5bb9c712cf4edcf43b8b6ba70a1c0ce65c2c467252825a44f36f5a7b5016ce0708f4044a7357319b9d63f05d75d2f328d819c7aa81ee57d6e1d528c0ccfd65e27049423b65831ebe04740fe1310195cc7ae049588d8e72ff2c12bd3491e747412eaf4ddcab40b9490f2ca742e41f6cf75446ead4c47888376bee3f50494246b12fb60189990382f4110c34b8513e4bf993bcd4dadc74391c0fa05e47d0e18fd2ce8d4e79beb377526d845e7c4632fd1555e447021ad4138c59517ef2a8689dc34079163c01bf2205c1dc6f15c5e9d6e314db332ec252625894b3709d2cf9b7b4367864b54ef44b2463dfed4b286496d82cb7dccaf38905a89379132557f6bd0706b84d4ca751cea6adbf5962970d65322a4f2c473ea65dac6e464e1e2ffe24e7b7a9e59e9ef6f2b3602b87b4a201a2fd8d61f08e017520aa3008b4fb2e5647eab8cb53317c3904ddb0522b4a97ab86b189e678fbfa3e15b9a05fe8634b5528f5cf529e4c5ba9637c95217fcf3f68e56415ec1723d4cb96f4aa0d4015319c2267a417ebaa7e815d2d699a1ec310c70947e3c123c358ce6d3256cd6ba8f34f62da6ff67e5cd111ea0142038bd2232dacaea817f2a7af66a07dfb85ca91eb945470fcf9c3fdc1e3163dd61cefcea3634c3622c4ae897e4ac36b0a16cb0f6fd7b37de0ac567149d277d65dc525f7b07339c509590fde99fb489fe5d20ebd114839c5760ce3956867adf90700a9a3654a8b72668e21cbf71b6b03f5f62dee43f877fbac927a45064d4c06fdf551204b38e0b64e468b03605e59633653b94e6f1103eaa1748e67386db1a50a0c9d27eb487339bfee8243c3e00a688eaa2bab66fe557540635fb3be3eba80ce9ce13c0e6509f66bd0a6385d1c810c6221f0e6a980be45229f9f9b90b3ae99034332b73c99607f171e8ed69191dd6299bf128cb6fc05ce49aecb7e0e73da83aae60a024d491b4bd32e3fb5fc588743a40f9e84c5b229cccf50d7b9546dd4c26194d6a6426518fb0259a9133c2965e56b2a0fd76076b60cf5e780b80228c4db5b83c2ae77d5840993a361eb41de02dd815f0f9f01c69ca85dd0ba054fb93ef67d56f43ddc70052d1b50cdb38b216b850c2e8adaa696fdf4938093416e40d6e2c8b9a30bcf00c72d8325a6fe8503cd40250026a55c6b90a0ca855d6ed97198b45fea374982e0232ef23a5f560c855ab1c87e0b0b3f41d7fd2be210990c97905f22ffe7d88e5729604da2c2e0cceaa58b067e4e98170192081eea355341941cc007ac885f992c690017b030219fe0ee653b9edafcf90c55aebe6823f3c13ccca04bb6af869e78f50ebf893f78667cd50dcba94d93c00d8dc56ede45f406908f82abae471e91a6e55d7e887573603785c611007c743fa995f92901f2167f046424fd02715e15859d993a23fb06be33b59287297052ffa0649ad3b5573ec8a1e3bc107f442e88055f493f9f08233bef448e3a31e71dfb64f8070a36d826f3273678d731c26415099de10fc613c9f3328b49263e332bec8a6849e7d9944dd4a17bb870ce6eb6525a7f70fea139a465050f32236842e82506cc8573a669076ac27d8727d3019a199713e69033c2ca2bd47c584b090ee463efa420c7cb2a64534d389816ee4be8fc2f069ed8a1d5af85bb128b9b7dac14c42d8ea3c22da4486366a88d5180285af55910d3dd40d095a95629c1a5afa15ad3928fe8db55cb3669975fe1f83d327a14371943e91ca35b3fde43ea6b8eff6d9335e3e582bd875a168212e2be5bdee74b5567b0640bd8bc7057041550250639b3f10dda7ba720d59455274e5e0d788b9845481ed3a5c5327067c988feb0b626bce89f4bc0c47b74eb10a08364b7be65e69c8c8330c4f471c167a0458bcc4345725812640115ef9c13b5f4a039b36f51da68b338255da43a7e100c8cf8c11e1724b5739918897e82a378c534ee9d633549f54c6d871e1977f98e9a329f45647068277fac21e5ae41b0de697946557108143e8373b408003e49ec0d1049b6690ca88883d6c4863fbde0c7c88762d5800b00484a1dbe566d7964884cb8ed0906935fefa897c38621490be2cc5009db028d9dd37e4f29b8107d16bdee514e88de670a23b15d20f35d786d25d420d04736eb7f285dee5b88803adb9081463d9ba9c1a7e52f42a051e29c5c7caed8471f9336ff911e4ca4eb341776498aab4e5b4d7a93be347e8e002a8a3e071a59f79058a2ac388c40d833954e15c3780dc5071d33d0ebcc2a70051db367a63a5a03be0303df446d6ee182df467b4625ba4cc1932818b5b1cf029f7b88a4c034f8e90959d204de867aac866033aa704c27c846b889d3357655f3c018c22c6ad3c05005f7887451c64da4e6c81184e690c9df59074ecf6ef7409e84ad064c53985e75cc836868458321d84fd69473a94abe37c7b6d94f75ac23f09cedfc2e8bedba3cd37b27da476646ba547da11e6e3a713f6cc19c4d280e7ca4aa82f62b37d55c0fe9576c5d7439a0cca6a235e7e113f65f3e472a431e9511d00419c5cee4e4ce7b4e44c475d0b85be7ca17a48a36080adadef9d090ccc2021546019bdfe232a1a748e54ff9191ec2fae0e3dc6751148a0dbcf4fcc7a128321897958bd3402d9d4116864137e02a34325f04326062552e0244ac69c0a1a0f97955830f3e7130a773e59801b51f062ac1db0bc24476025133070a00a8fc6e30c61ca725f85a6267c66d3d361c4d3c605080946fd178ede29c62b618d3b454e4886138916112a051f9cc92599c8fe4373c17447e1b1c29b10895c7ee4d4dffcc3ee0098e3aa0cef08866c22811b994d096c353349fcc980df74335563a822ff00098420a40f67add5de7480946a7afda320b56f2020dcc11228171e200a5171a2ee3b0461c96d2992a931c5173a2ce86f9038dd68a78715f395310a942f8d9040f281643d90207a6f92cc423ab596c0013f15112b21715cdb33dac1b2d3b1262359431f17179d326cc4a1319ea2cd848e54027b257a473060a76ce3a09665a05e0aae208cb9fb16c4949fb381cd8ca52b083ebecc3a87611e2ef75723d97cc06f9e4a33a9ca436e06520486367444552a0ae8ead41007dff37f1bf9032416fd1b101abbd5771720d34ee963a498618241de0c23e9db5ab38ac6c3de3c6ec2c9bdcedba3472b78cce0f0309fbe63b9477dd4064eb2e854bade4885d906efee473e152cf1b3d73b4ea5bd6d692aca9d6523923b502f4115df15a31832935e03217e493158f1f25594e1d8969bda0dc630926da5b4c1375093e7021c01b8d4cb7430b83c6cc54103c2f65db6b3ff5018c3d24444234634a8fdc74db6bd8bb0fe331505b480a01d1926834a8d5147c52d2621c535dcfdeb525b5d593da377976fc94f3caee650b9ae6a5667aa48abd5e91e03ff7af89e9f41ee2388df4c50ab7b3f51d368f99487ec89f9af479fd624ea986a78678005a3b41affec490ed424742d98b3786ed3f2b8a8abbcd420b58a85806da8b4548ba15623cde091355cf42fae38ca03c6cdd6e1dc23648e3772fc81e82da65ad3272c106d6a2c568d9c8a44c925ab6350b280171db8180f8308873a7e018d9b460b76327deb813edfcd0e18e3f7a59255e74a56c9f3a51358385889f1476762faae57df924a663b07d4b0c593b59bbdaf48dbe11616322d7e3247750c65e4c2d5bbb1073eb6140645ecfd4db3ed6e7ac6511090af3904ec3abd0210180af84f8b3361a9b84ffc6a6e9882dcb0e9cf7068f97086ddfeef52022552da0c640a1090ca946864f9821434387a3150582c7ed6f22b42365a57ac4e71926b306b521c8251d090d907d65f29ec525241babf43ed9426e6ca6eeed209d8ca7c5e33299720193308ed6c6352b092cfdc089619a1457f0f8a6f5750617c6ad9e7cd0385dba6ebf8e55888aefd911e0fc0c0c4350d63a19e8e1e459b442a6f1c0315b6729f5184bab2b7f5f064831b5413e45072d9391973a663073c61c58cd00e014f133ea9ea482aa45d90090012d99438053fbf6cdfe37ced73312fbe43256483b0dd9f46af1e39bee4397a59b27feeecf8cb27ccb5cc88eb4c409879110533a159f5ac9f7a439290df8294cae6564e59fb2f487102be04cecbd3f52ce2e6abe3d0b0692426e035d08e2022e90537d9e67a5dd0cddc7c04760ec2908cbb2db5c587f71538c2def77521dddb71f4dc8168ee6b8a1483e8c98fdedea3ffd80adafa14150aafa6182c118bde0be8938808e25ba9d360e50bb6a53513698bf58de3af16c4f8c98dc750d42277a060596c568cfa97bec61275d1d6b469c78a36a11bf007ab13ba1acdadcb907af0f1f0df96f914f6e7566254fc020bbea44a3ae178a5f18d93beba6c9fb2eaa73416a817bb8f706b4f15525ea1710ef404ed5798e001779aa1dc7b728af181552abf577c01bc60320a5940d80a04760ad4e31bbfe6f301e76332f4997cbaa9ecb358809b30b8480954d8c7818289269d4de4d0b2467c14bfd423e8c138d0bdb883e2f1d2dd2c9e8eaacaede62057529f49d9ee02c3e2451c8b8d7b2df454ee40dfec170864268e8de62e755691115d8f23309a5c6a00370ba98328ca5070e2fa532257ba20cf03368ce3999ddf2bd102609078481a02554b5fca1d454348fd05e282d3d21aefea64610272aa1bb47875c21b8f7edaca793435791e9e4cfeb506a02f641ac5d2eb083dc23339ade6cb5c657de9be9b04d92416751ccd04662be131c84ee6fa1e0a38c94766880151f0b53037608acba22e7bb2b2a8077c5e5b91c8263a481694d385b0cee5cc38701a71bd2e9ac9771ec0e7b4e661f67acb2ad7a9a2cb0ba7da9d8a5d5f6b75fcfa6206a4c482ff049b7a3a04709cc083a1be54c7610a1f508822f73afa6611e9afc78b576484fed6ae822959cb78f69422b98508954ab7c79b95824b1482172258e88969377268fd1b348b619a76db8e9fdf0fdd8e8d80045ba87d9d75f5c5a9f64391ebd5eaac69c8daa3b519d5f7f023fb26d329c7ee932307fdd47d527ff411ba29a1b08a29635da8055d86e74ff59784695bc28abef3661a4898834dbd7615c28a08d9150cf64312957f9fa91cd9049937c8e5196d372a1af0f31c7c0fee2eb42f20e289f99b274f485d67b450fec3ab60715f234bb8c4e88c4f6fa6a890a9df59c281148cab187fd13cd746dbde2e34eaa1b8221b4491e0911b2fafdaf0fbbe7469869bf2d7713cd66cd64cff5aabb39363f924a4f35323787ae977b95e5ad71d768915834911bb1fee75cf6f4acb4ed0414d756eeb646e56bca5a13823382e481357bc8eae2815ed85fbf3708bb11da9921abeb33f76f13384d73062555ff33354b9bbe2ac1a195ee83bfdd6e9da9578de214ca5c72bcd87268a413adbf31bb4abc909d838f36e30a131f4e6ad133ba8798672fcba0843935ba1dfab93a478da81461854c9d91ddd87bf21391196e603f8b04dedd75e8de045315e40de25ed81ed24b5df8980cb752bbc3d5c242d8ecd51048d20584e61998f5101eb746fd392cadddff1da0ed90ba3d59e8ab4de9abb48489ba60ccaa6b46e065833be418eb7f5936c0c31291901e469bdaad6ca6ac3020132afcf0439d45ce5a44bd9b5a41d2be81af88051ef584a2f293c0a3cd2641c52d1a4b6c97426a8482140fef9a106e59f44ae74b694092956ff40c509d708e095f872743e36281d0f33270aad9a994a1555725e34efbaed519bb8be302ef913f92377e6eb60a63c5bf77679292ddf6582567e51facbb8b96af850cede7f5dbd8ad6efcaa0295f2cffb26e7e1dac5e3942b18ba32ffb9f4bd556e3f8bf7e81cd315231dac98c2aa286dfba488828e1dc9beea9fe3c858d0faeff15516c808b1519250de18e1523f894463ef93793dc2388d8fc56d25703834758037fdf304cefecd07c43029cc46f2550bc0d8be3aeb45050b97b62ad3750cbfe4abd83e1c8a646011b65db92ff612096e819e33aaff68b769590686b5e6fb683c02cb507df1359ded7f8cdba091db2258c0a53d06607c85cb84d0a507c5eb2f3a56c7fa21032485fe37b06ab50c0d171c283aba1e35a7e577fddfcf4d2958329e8f8aa2d25dac7c37d5ce0ce75550018edc35717705e614e76e15d9ffdc751c4425da2290c84d775736a74b6a50b2f33cf63f6ae54d9ba27d9604d3e3ba784f0978f8df4f308a28e0c1e46c6c66446dad54808e1a6c65937580aa8ede59396a57d5e182b76150b87151378968a11dd1bdd135ecf85ae055ecb44f335439f6a050f594723ed36570b3e3c5a55eac21616e8e87c55d1a60142b1875710314f90fc6dc682b8e2d3ee908cdf302bab5df7fd17e4422ff0c7b5810118997a1c602b30e2364637c1d161e8044a93abd422b0d8011ce092f93018f95296490485f6bd5305810d082d077760b4d36125ed811927cdf46955475613ee5a235fc7a2326abca841a03d7732f4741a55b27948349b14b514c97c50484518c0cb21df7878d02fee7785c707c618f053c00708c539bd8a087fc87198f876260d03ea35c4c4400e0194474a979e39ef9a835cb18f4a659923b5aebb3f36815eccef8bf07eb5dabc0292109f2e8b870a812f8ee60b7f1277b1c495706f98abf9d06e970d08fc8cd0019571dac46bc0697742f688bf43c60b9d328b45b17e67ac5ad46efb28876de3cfb87e7db43ed6ea098391f9960b0136225e44a24992aa674d42c94443e69fcc78bdd4869e17e62571ccfba5146de0c64c7bd9bc9bebdaa0a4884ed4badd217e8a0bc2d9ed00abe7530ac025f166c027fe54eb26ea9380601ae10f9021e734d909afe8bd40a4828ae476d784df2408376a827a27b5f41fcc70eb9449184c31794219f4e532fe70ef5e8bb0c85edadd9942a20e19c906d0b4dc3706741a5eaa714069132aa4f35e02d78f729e7cf261d8b9246792bf2e0e0024c9c2e376f72a5958776e6702a98c6a5aeafe04928dd86635b7f4ba980a9de4fabad617517081e6cd6a2cb3c756baf921513863a3a881a2444627ca9dccef87a173d36bfed09a1135f02b3794422d933fc9fa5814e0df6777d8f20f3b799f42f3df20b9363291f907b8ab4912289dea57a6c04e6b72e40cdf328d357cec17f878eb0ae192117d589cda5b81f123fdee172eb359285d7e5aed07e1a8e54d802565955cf31f462276cc53fcb984cbaf39c8712b618ec73ac0b837ba4734bec072f00b642082a8553fb061683bbb5398abdbdaf19a96348e89f8aef7322df731728d54f502321c55410b6cd92c9ff15bbd0fd4b10b4c7db050bed9aa0654af012a5e65485c30d0514a3ad532f37f2e47450000583da200024082214aa8d26e4e0324b2d1ac75c32a68eedd333e057f5d8038be5058e139ae8eeb3c8fdb89e380cbbb8f0a34c4810b5e7982c3c919e14d4a29fbe0dfa2b60921e5b78490bdf7de321a045f047904f7d36bd500bf3a0cfc0ac7a078c08b63dbe5bf7e6d61e72fec9794ae8967ae6240f234c32227f3e49ee140e9152c5e320da611d46fd7688e9ed7869deab5a81ea79e97a5a161a611d8d7a88a3f273dd2248abd22a4a7a74d9b2dce16a7bb08e92e42341390682620c92aa5a5073c49c9f5d48a1ca0107a30fd9455ecb4d2926b1ecd5915397956cc81ba06ca295321aaadd5de58e189939b35609cdabcaa17253a3428e7665484cc6e6062c1a498514992c35e11ca3dbbc242de72cfae10d9ce51a4f96675175df2a7483d5d839f610c0aa6eaba09fb4e714c7a13df4b89b66f67e154df83e9bb6ee21814cc44d04d040149f97a987cad97e7c130d65998069638664489b69b78103681632ff0f612b153a2ed40b83b187ef742158e41b56af4bc3bace71d8e41f13ca68281ff704c0aa144dbc1cb4bacc2b110285187278e49274ab4bdc3725ece4f1c7392db558f79bf46abf7abd3cc8bd2e9e7fdda7cde2f79f105ed09fb0586fde24291dbfe13c6a060dd7d704cbe3a26d57b7e432628986a7bcf9958e400b19481126ddb5938a930530c8a02fdb2d7deaf1ecc64bfbdbeb05f5ed82fd5bcac72240be59e559121773d7b7e4b6cbf9a02fdd20e827cd9570f6d58494aa1736b6265a80e59b1563eaf56d741d05a0bbeb360e87d077dac8d13d876df771004c1bbfa0eb6badb107aeb3650fcc441cf7ad6eb700bc35070e00d45affbc0ef20f8893783206ead70ab86d05b87091df679a0f87d608795541e0882e03b1004411004c1d63d47914e248117c267e5ef47e8365a5808db38819f41100441a196d0bd6fdd7b2fd81da8f5d5bd0714c25a58e83f3e610d0f7cbf75e92581f8bb5dadc2993d10045bde57ffde0a4f5c284401deae42d8891a2770eb5f08438185c05fd0de568dd64f1cd6c227304c08831f7d714dcc1f31ef42f146ee6afd7eefc5a2fdeabbd174dddbb053f22eebadf7febebd0f3ed6745dec894b2cafde4024d71b7ce45aeb69ad9556fafdfebb7df7859afc9d6227dfaba6c4c9d5e7157cbdf53eb8ff3e58bdfbe7590fb7402c63853bdcafb7de7d56df3d6cafd52bbd09d857dc5abddefb0d5bab77bf1c27defcb55658464f97f79eaeee1df6b07823d76ff3c47bf1122b7bafd7bc155652fd2eb13ebcc4ca3fb8e2abaaf6b7565bebf7fbee86dfadb7abd5fbf76b6bfdeebd0e8bb65e5a7b3c8ae5d6534ac74c1973bddfb06725deece373aef3094556aee02bbe1ffefe8282ffc2d56da8f2fe62de13d7e7d68298b5c4f2eeeabeaff79efb1753be9b822577bf2b2c36e5d56df885e28dfcfde7e6ef2097bf83170ac17f5fafc22f9c1e56badf7dff3b7823e962ef1604c39957a1d883975879aabcf0da7bf60bbddf50bc21e3fe931e0c7b7089057e788995559eaa090a8d1b5c4bbd20e584953dd0522f48c16062ae5c9899c9629dcdc0cc665b6633391548d7b39344b8eedc79cebd37086f97cdd5662b824c2f6bbfa3754afa894db93b85ec66cf5309394ff6daa0dcb328292da30f28d88143102549c87881c1cb131f760c919e164aa8b51699a3c29c147cf8a105515e568c1721d5cb90a71ce480391c91c23f14058328186cd390382cd071410e282b300993b7281d798b1202e44c2692430aee304142ca8d54de7eede4cd4b113fb05008f8886a52a5c7d4ec04306c52982f32427152021263643a6c51415a3471ea0c91c38720468058f9e2250642ec192369a494d288141d80644489e6c501a418ca111000071e4815f36298c5d398aee95980124dd91c842055cc8f4073d398471f982eb1edd4ec0482791be10421480ee411a9c2c88308d42cbac02c1a515c3929f2860fcc2359a65df353529944537220a54815f313cd1da9439e1765903ca553cc8bf20431a408d2c89568b63927a55a501625920074117d0d3be8227a8e22bd8928529051a6ae17995e4ac9743af1e0c8c14c173a6da28e9c997599ea22666a6a6ad605ca8c0b175ba60b9620645ab9675c58c0c5890d3b8c1973c4aa2c50d792416f1f83e2a67ced9d372d14eb95ec459ecb53194adb79de8395e4edad2743debe477b4f2893c8da7b249ddc371226ebc6ba5e86ed59cfebbaf7e9904af57defcf2f6cd78ae90a7345a34c912e758d567c9f4af5beb452f743d7c57d3748817e3dedf0de7dc777d5a5d7aae1bd3bccc31d867d3889dc4d154da2f1e4bddbf1e1a6443b54f8e24b89fa5a13cfe47ae8e49dacbdbfdd6e9193b55beb2535599e933073ed34557b31aa27c6a60bf0e276cdfb407b8bf4deb583a18fbfc0db30167301e2580cfc087ab298efc9e2fb40bbed7987aacfb03bf7f16cef4231964512a6d89435b96df69db94f2f89b35bdbc6ad19999bf588f6d992c7ed1917205976cd75629defe49453ab732ad94f5a513032cac271465ab09ca0258c0f2d674ab038685a66a0054ec7c5d88cc6b47089170c451e2cac9bb7ed60c851221e8945a09903acace14d62126af66ea95cb105cfb6852c70dcedf21cb7711b9dd10194ec7d3bfd56abbb82ad7eef731c6b7af9ac66ab55a39e1e04d683dedace7d0bba7c07864c2b28d8ea4067ba4038e673f0b19f7f0f0a5bda83de2f1ab634dcaa714f0fc2ea411833059dbe5f4cb2171432f97c751a3205bd55e3e7f7367cbe0a0aa142266e4b62851c25e2790d79b8190e69722371afd680f72c69ba4df5e158d32b0625ff7d25c3a617688300b96759e6642d44b2d7aa417f6f23e8ab7314e9ae5a367cbe3aece717c7a0b8ffc07c5e3e3354df3c1a4fdfc12d0c615e9479ab41bfba8da053a4d5e5e5bdda633fbf18b67ac5319faf30b7c4a2de0c158d12ef0c3371a0bf981ce56ccf61f5abc3e8ef6d95eb318107fa173205fd63058550ddbf90e97b094ecab649d936b1b11889acbc6d6f9e737293de0734ab5458844ea20b61e08178368e156a4bac1906907a41cac47fb00f7651227b164eb75f2630d47c09ccc4f4739f0361a6efe045178da71d3fb8c5f9e0d6b6e3c3ad9eb7380c83dd5310b77a708dd5eb5b1b0661f5fa7bfa1608f7f4176b5f18d335afbd00b3c03cd232a08523c8acdd4cee647b494be4b51a52f9399287ae67d743484f3d595049bb2a31372dbd3db598a32fa8bc06c4a5956a162fa9329d2fe8824c504ae98a2217540b97abc888530195b84874403fb7df5ae773bf0e5fe27339ba0b74ab218ebb2b582c10d6cdc6df5dd1b526e5ce4f9c7b362526f7065d37b7aa801b2ad3d5b3292f59662dcad56470c5b79847a2ca681e892d1c308f241df9a189245f2bb29424a05e12a51c5c91cbb5d65a25a5de6c61c99d2b9517d8346f8694dbf66dce229da7ba4f50d51e773d64514c226b9358d365574c977dd174d9bbae415dd1e5b2b7ddeb8a2e17daed8b8c387b953d50d5704f8f8ee73e414dfa27a8966f56c7ddae9b97abdd27285abf00344972cfce30c908c83d9b2a2277a00bd7872b5ee225f4f392627b5f4ca943ed6e2a5588570325f74c0d938c807c5b4c5710d7034abb978b7a7aa0591a3b999eced46cc974898fbf2aa672ce183a1832cfdb5b07436679e95f50253a8ebbb7fb3e41f1f88b355950a026779e9b427345029a445a0aae6abae6e5e4d4df7934e7edb685dd2ed15e9bf6b3ede9cc13c7d0a45cf383932c1ae55944631ecd0b4860de480e963cbf62ce1eb2d860f2ec54da2b6651226e68bae6f3a533acb4b8de16cda38a6f9a2b7fd453d239bfee1494dedea6d9529b72e7160c304a0051775fd2194d36f42c5725fb69b7a730709db9c9ca99a58a5a4b206ecb8b20cba95569ad2ca9605852694aac94714bc66e16738d74c52dec25d7528d07e3d69eeb5f504acf256dd20660d3704bc63cf680cdd5c889665597956b9db756956dd3b648db924a6372af5c79f152ab69784995ebe50bb5258303aaac74e910f3ce23ed12690450b35219f51a164facb073279d732ac92b1da7c5e02a5129cdd489cda6867cb967533cb2fc6d3ca717a3bd9e4d25994d3de94ed1d307ed08a575d36ca55372e0f6cc4c9b5c6b0d5d4f0955cd4c9989d363cb925551519a547043c3161de312a91cdab0c033abb6564b274b9112ac99fac10c10362a7c3340d990c4cc103327ac596266878f32449409224a7dadf5b59e709bf3922c9612f77469d1768cd05cdef0f8c10447a4941c5113860004b334427aecf084c6870f9088dcf590bb6a3a341d649af49715d891f5406448112767ca4cf1290366c8900c5f782c0903a58434536562d8cad400848332dbc1ce6c071334993453264e0fed6995258365a484808cd39814eeb41419213a8c20a30219148e28a2082644fc880194212e30e24299313a469e21c4b40973041133f8a092050a1b3b4d80903b88a943270c99303a8479626b28ba440b6497945cf8b07a49769e864bae3ca515daf2e6499eaf4b2ea12d6f66e0e6aca1ce1043586b2d105ba5b5d6fe30917b261525558309d0481559018d940940d0ac72cfc0cc91c2319dee1cfa854dee4f1c77becb3dfb3226f76512b7e69e7d0193fbdd803877daf005060be7aa72cfbeb060df7c91593777cb3dfbf2c37ec1e1824e4e39299247bbe5e46e93bb7bc5f41ac5d0069a685e883cbb081c79be872bc125332ad242a220cf4b7c63cd95f7a06e378558012fb44de465bb61b10399069786ae8ae625cc1a20ea1841040f241d0f131e2356361c385831f2a3ce953063b8cc2714cd0d3c26f8b06809b3810a0788913077b63cdcd48279ffeefd7b0f65564fd78dd1d9d5295c9d429ea34494a8674331d483c5a17abab1e691b8a4129754d2a962250dc8729346753de73d01c2155d4379bae014899c9c31e4295f513a99bb763deb8e9ce79cc0ec4d1d4979a65c794a69e87a7e060749cfe0c8a02487b1860102a5862751a48e308101098ab41d1ac62a6390c584acd1ac91693d8e4822e4383b40614a9a1ad89861f23497e81052447b83429015888c2a645bc6a6c1b8b59c27697a1053068e0a9458f951846b400b4a2823c40c737880325d70e3a508179f04f0303e4066c78c2b999d517bd34210233b8c6846997ce1c7d874cc8c0e9045913f7499156c9093024d268170e91ce4938ee2314e19bd5c2665304e194d30aa5818652518657208a0b941b3429f51520339839e49138c18629c1a8f64478c358c1b1c4026934f498c0b90a501e334ca6418eb141939902d2933ca1e258e1865dd430b9944a18190748a185b8751460227a391cc881690482a50903833fe304a1c58d66b84d0ec4ce226851edb4c41dedc3cc99ec8240a904959bf09e382b1875187232e64b2ca281bd271646d240ef28bac4da7d122b784e9199c30b21965438c6e464ef62588b103991063016431b494dc21c9b4606c1efa4a1799182cd285a632031c15481f1d4410306393b16b40345eb98214b24ee24e0a5de704b288f18651fa30caac8c329e2e467e54e141899311b385376a8c344046664432800c0b0ac639308c7072b46c85ac719077a48cedc38ccdc882110c80ec05515a46993432350a692f5d462ba34bf666446244800c8851eed059240c54181920333316c99888195b2764e19812ba8ad4dc0831ca5a8d948c3346b580053cb8d0650ac90a1305b3372b3032676421837223d53411a30ead069a96c49d3b94522a4fa95419dd015a40912d708042705745f0e49eb199937bc6264df674905f93468d1d6d0629ac82d47fe867038427485d33c75b834410adce87d84c6143038f498446c748a36324d3c4c93d431327afc83d43f3248ba0e2be983dd41d7408503366cb1d2832929b6a9b5e52925b3539570e75d4daea66b6fc517dd419334bdc9d62f366ccd36b1e8cf91773ce1918c039cc4af5d3fedacf6b5e52a594ca4aa0fda0fd2045a049dcc9d249768a29372cdb654550731d413b095c1e81849bed65abb035363babaa1ed054d49351b326e9b76d58046ae711b5a14b71bb76f333d4a26e5173d01de04aa0484a29ef80e9926f315d73ae072d281227258b355dd0cba3b89406b82fe68b3975744477835a6badf545ae6b50b785abbed6ee755dd3857d7d9151bdea40f3c862570c9965cdd1c190b95f00cce32f6eb2d05e9b5c7a133a02dc183f1d0c9907707bebeeeeb6aff573c9c6620d5dcca2445daebd0f9d006ed70ef774313f7178a56440f7dbb5d35a534d9776edcb0dc0b517394d0200a094dee95a51e482e18aae16f3a8bbaa36a04107436614abae75bb5ad574598ab516aef8dbbbe06eed0542c1e113f7096af3b25df651fded6e4053dca297d9664be7acd20949d67d82923cfeeac9a203811a2c5aa87272c57b8158281e042bcba43bcfc5e8fc0fe6aa55ef656e1eb5ea4790ad8a4590995ee6257df9da6ae6ccf2bcc074cdc3028e80a8d4eb570c7b523ae668040000005315002028140a07c562c1481829a3f80114800b7f883a563e2a1305e280300c08034118c3200083000000010080300882501008a1591ee2edf6dbefb4632431d2638457e0950f90e50fec5f3819971108be0694fb6ceacfa0deac733f030dd040acdf8de7c501c297974a675e52dbfab1762036ebf7e827fefc38fcc96afd7b4a85d254231da45b38712592239d16bdc0b443ff6ab70e4206ae8619aeccf46a64bfbd2d2f064c7a4de210a918dc964afa1ba5efcb55b2adb69154569056991759e5dbd4a656a81a986d9c93a788b2b7bdedc63c9f5ef8b21800baafa6a6f250a55a63b03aeaf0a24728e38cc8b162cd68b2f7e8f11ca84a44d021c43c346116bdcc4c067caa684048115b99c938071c6598e78904b10fc7146638d86eeabb45951d151385531182fce71929ca662aa680f74b105872eac4cbb44414299b4cc72ed9914d4bdcb86e40e98dee59934b466cc8a3338e280aef86583e6c88a6f01575e73e4440f4023b7d43fa98d1c7ded49d206bc780e22384a1f8d1989c489c3d39b75e76f25b1209737ca2599ced168126417b17fde707e1538cea3ea118036d6fbb9e3415184935a3d16126757e492deeabb9a7bb75b90bfaf126159675aa5cf90d1a9bee9afe04dea81b4fc78c5f8a3db935d964c330f2b7c97663ebf6bbfabc022bd6c584b87912e1d2a3bb82b8dd2ab094d736f72d39d159dea273d37eb14ee8154b8d3811dfc15fadc48515ea84c9b7d6160f30eb96f360e47c609550a51c36d8ec8ec1c01995a46eb0d758ebfe2bf07592f4050fbe6ec0a68306fbe74263ae99cd33de641742d5f3371904e8a1ad95cf1551b191415001c185bb5552a402602443bb6a1ed34ebdba9f2532fa261e6eb407ca16a3320fd5580e9fa7bfcf59fd5d24e9281ddf75cf1909638a975ac7b38c64b555414c73c229f7706d16873da1ba0b618fc50642e5ab548aaac919d3394891098f93246c6d4fa15bf17e0e0044c8e7a3e5e61ebc1f52fcf42ccccd03c6c3b4596c905f8a6c0f73108c7992d680504d9a3aa702c8637cb300f730e22e823c8a382d021226fe0e1a3dd3ac76fa5110a93bb3ca3d4ab703efe256f2a73b5ea0e229abb10ab1a2d623b3010cc2a7be67bb66e1eaf9ba071f3e3da041c0816020427d2e178c0715ac3d4acd30c4a287ee0f233db809e2a2c6632ee733e70173403ca8f8f6ac6bb1aaeb9b8369e8ca87dd23dba9d8e079b4fc43f42cd35b642fca4e55011a7a15aa1e3ce8913d53385684d0c74d957ab8e1f7170fb9b33e94f693be63113c0f2c68ba5e2ce7b3631021224fd43619844508af0fe33d38f5b081a01fc445ba27b26ef6601e4c358b404fa4ee02d803a983229007691b7cb0c0ce370e1c482ef2fb81c7ea36218414161ee3368bdbe3c31920fa3e40722a44f1666316328e8abbc1032eb702ce03778e84f0f9f658dd2cb2792e274d888b569ef566e8e197102a1e1e98ba9eabc7300df00729403c0f1a1f0fe63cb406e2e2c753b2b5c2437bb3621dbdee478fcb191e84591d8fe9cd02db63b9cd10ae108b3e78214610740ab182eeb18bf329f3c4dd24f1a082e3a1dbb4205ef981c1b40166d44a25bd568c7945fd6af6ce4941f001077f02eac32d8db749a3ab864cab0bf6aebee7d8600cc21abe0f3ea37a78dbd21e341089405cacf604eaa400f44c1dc007ed1af0a062c7a3bf81c5fb0474a5d3be3ce8a1b8a11f68f56601f7c8840df1fcb8e31ff80f7511817b03fef351c2d9da8371353f78218cfe7082b0f1a13e1017340f649c8a98c752c74c0179823a2904780c75650578366916829ea43b20449e753de69b05cbc31ecee7e181b76631f3c4b8481ede1f12f5b038889f2d0f480dff414220ca02abb778fd70f82b64ffe2503e26e2963d54370441f903570f628866105b88be0f753dc843581be202e2b1db9585d163daf83f98206a1ff87c20fd41855c4f6b530bf18118f1c00c62f310de8815681ee38e72f11e94c6405ec5ad7bbb20ba170fc9b1d27f74b362b9f5354f8f5b337c4172b57c0d20b131d0867fe4f3637ab320f370ed6a63e0b1340a3c58507878ee6d102b7e3c65772a44e3693d98130fb17f0837a82c24eeef27c5f303cd67e62174c37b90108829583daa8d1084951079727a70ea18573d071ae00f16201ea7e30f41138459aed1c808ca8e7f193f2f1ee71753e159b2d5023105bd67e6c0fa403d44283a1e4e3a15550f0b348bcca342ee42e6d9c0a988f7b0b059e43c2a701772cf906dce43680f748170a7650fb866c1f2b097f3797ae0ad59cc3d31d7481078763df4b6ed871442b1c784cbab2aee97db0d0fc10181c803ce43b3da565a9667e348bac001fd042385e5b1dcf8ce552e981e375a3c84e70f2978cb2de2c385215685cbd1f2b613a2dd22b2792ed87452c83d0cd32c528fa04384e8f480b80762fe8253145d7ae0d03b12b028e7b1634df60b60023b43a07ad97bfeb985ee56e6dada45630f3bfadb1443b81202cc98e0c0125b5ee2c17c40911f31fa8f70a2c4e7614a8f32575b4a66cfe99a8eec32ce74002557ca3a3210faf6bf365beb225250cf601e0e972b780b41962be7e0d21baea8575dacbd7ad494a21cc60c8e9953e3fd75220d9503a3e7945944e780a92f6ae417f1b37bfd0c1f1955221184e8c77f88749a708f5a8aeccae89d95b9c2eb8e6744f81a1d9a8e593271185d2e42c8789de397102a3538c6f33e16b41cbe84448160e240fbbe8d036bf4dc717c566030590c214c10fcee5d914d1170012a9d51f3b411e2ae1e7ed6a729ee390dca342ba4a9aaa7a14b01bf44360f3d9c8050d6da5f320ce4d189d38db0b09e2349424ebb11f0014670bfa6e8d47eb1a74c2bceb2acb8adff1e70f75d96ab1fa1e8ea08edd7b0dd95a88e03444af1b35aa58c4406026b083affa05fa7c8fc9a9da02bb8ce0c702e3fb4ee15a23ce8964e6a7f5195bd53763c8b48100f335f72e9ff09a08c3170134ece422d212a9498b410918f46e155e9c8912c8a68f24e2d1cd188991caa69f3df9e9f7229162a862f7e3bd65c21fa47d7d7b21a6e735b08e414111753275098fd5f89494e20f02163b0ab81e2a1131bd44a2d3dc637537e35e4a2e3ffc05a2428dd32402eede9822607c8476c5cdc7bff23a05b03fe24d7c99cee8eafed0c1933dd8f560c78669c2c692a31622c3cd4f9548c5cd5565916396f13c14d08dcddeb5d31600a1541c2b1f17c8936048c69640171151a0a28fc7784cad2f04d7255e794fd767a0f90e3ade1b4428dcf5928d4376aada0d0a153a481125a7eca2b2f22ca1607829a008ba378e129dac44d10be352c165aed0ff17e528a6071489f4b5910dfa2429580809188cf21ae9609997de2526be78f54b9431f6ece33538a58c7f581d332f69262513d5d3fa21a5bb71224b1e3dcab0545d9c94713013a71c7ac8028443a27178f83b3005ec4393cb8581dad901f9b57c70d03d27f7dfc1576c7d5cd3238f420bec1d81cfe41f64242594924439ed2f0ba5bc0e41ecae1d99aea454f0801da82477b1420ea9dbb9238b820e50a048e00ad9fbc29e26b5a3cfa8d8ec871e84260446db2b999cc3219fcfc86f0b0c0e93b9eaafefe75fb8d40fd7c990f09762df1a2be369be08bc0c90710304f87ec367b35127c16ea4094b78e10e54f8d7cf1eea287e3e64fd90a68e1a91af66c681acec05f0186cb325821552f19beb5652014ee277c539ab592f1642c17fbf02054585db2804fbd262a5483c5a9d2276ef4fe1990ff785dc4712a13821ea9d6419d72dc576279f3c6aa16dae5b3cd211c5146c4f1161031408c9c2bacba5a49bd2065106e837277a06623e866e17028d81b23b0585c7cbf0728c4226462e23fb4166dc421f3f4734b735ea00467adf44dfabe191cd0447a5754421e510d221203ea6b0e79d5f39a8ae1d851407c47fa09065cf59c7d446ed8fa49559f216ef2e600a3661a0e3e330c3e086a5f598056b7481b9d310f530324199d8707e19436616b00893021b50cdc80694103717e50a731d62602cfa9dc26bd016113f8c03c33555f90d322b3d8ce881ff46bfeeadcdeabce3c4020268ced7c0933816729dd66dc232497f4cb8ed0069bf7fcc9f9be7fcc8e069f6ee2c207a2ea0b4b79229af5aaa4b42e5015ee9024e2df142c04144e407dd0a73ca68c80966410bd94d77798338834d45045902fe282e80cec78b376cc2d1672430f7f370245491a171ba12ecb83adb1f4328cc3e6477ebb00d04c848ad105be62adcb5beb1a23846a828d6b2e6c927e4d7274bf88409643d801ad0c674b1e75b39449a361b1cb200221f3d82d74206764f421d05b35f13ad3daea5210f150d0bd893f864b85a4fa3667d830cc56c3056132ef55c2cc2a2ec8ef6109792208a17f928d0f8eef96132b7281a8add9ae1e97885a5682e7128373b86ce584489808e738f357b3b0780a917ba89af76e19e8bc0f1d35b52bcabbd963166dedad2a520afb7ab8cc32e88a4e156bd4e886354d3463ad9db35c23edb3aed8d70eea6c94a20dfdfa455d2f34219ee49ebee88658f9206137cd5d355b45ada842924868085011a0c9ac95d5e00ea3e33d8a6d42a0b5aca5bee8c43c0d9cb9bf407248c98b20c5235464e3910d75a48486c8a5909c416d20a77a7159af20be7569de6819abf6e9f81abf4cf4b94198398b5fcc5201492cb2c4e5b7beda7d6b70a9f920d3503d05763106c1a98f4af801d976993cf74d6a220f76224215884d083a592f2e9095ac330a2511b40d25c400babd1f040771b7101f9961926e20d0932a2ef86818ad4d408a8b040f368c02550b0beec27f1b7d140c6a64e30ab9cbd896a7bdfda80dfea4c81c96aa1b1fcbb130c9644ab40d51009a87153683e08abb9049a6bd7f055d4cd4b1c003539664ac68ca1faa396afbb6dbc80ae4c921826a167b94d7b99420e9e3e4ea7d3d5682bb2e5daa2b6188522292ee40e5d94f938493426c3eabe2531e3d26dd17c234363016988e703746a96120b9d8bbcbab4652e4afcda56e7e15b4588f2029ed83b44a24d2703d02206456bf859563044044f1e78190762d681bd3717fb4b5b86a512afaf1bef1ac607f8c9b7b01088fe8fe91ab083da6c5d9c7a716b9ae31c831a055313f35bdbd61df1e327bdb1d6a69ab3be7914ab4faa46f8ad563a4d99030de430f6f429258244ed3cdeae15157577621b586163110eb02b5574ff3a728e9719527d9bb92bf70a9b6aac6f793f201d263166a99682b9ce312559360abb49272f8c0fc17fd0ce063b2fc5c07dd2448903b7dacf784069785ea4673ed6c806b2d0308b8130892943748fc3241a620acea31d05c05f1ca77f05b05c6dded530aec54a12856393b14e8b36206947772ea412ed2fb40ff06ec7a3b3e66c36868fc5d0d47da604b61e0abbb4134680a989042b3825aed0ab179243a1e02e500fa7b1230730a81a5dca50c7e1f43a0c839b3f0312f462c741ccf8dd6b6aa1871619948a2efd3bf2170574af0a5a1023bbfcf128712a8cc2446745ac76d0a51a9cebb4644c6ff6fdabfb8e8674c8553d20ed48cf906f17190d14a0cc9011dea0414a1d5d97cc7b829f441694c9881de47969bc8cd55f11c9530cc127bef75d1c1c4f0c212b25d8b0a2d25ff9ef1a8bc99b02258001d309e96f609547aae9eb9aea7d66c9fed926a1db1f54810267901fd8d0842cf65500a4c308038ff42afc6e61e368eb44cf9785474c77aae1dceb08b1d9704c5224effb85568a41a24602e637bd4300d4b0dc38bb4f2ab7b82671e03e297a015c9e3e8a99f6c15595003bab193a835651b28a479a3bfedc6fe76728322274f11f1fba2a44883302af587a0fed3cf67a039ef998fcba4192a92c1427eccaa4223d670ecac4f4d8138e3aad52a16b1ec526621303b224e0fc81c14bb5b6dd0033f9f8844f7607c1ec6d654354ef604d52040bd1e66dca302231e1a4a8ba2f8a3f8706d653e3336cae37bb15f7d7e1bc3269f392ac09f859d432c4f7ca0dbfba0cb1cc7583f74089d021ae93812e01135cb58daf6c4f7e165868d4e285b2a265175051878b0c4ba168d8dcc19b8986ba346a4dc566211e832fc2892b227faf955e46fc1711faf70fcf9c2345e7ca22df0fa7a95b5b314f07ecd618d3f4bc38e195d2257c0b7c00af2ea78e6a4f99e532c11f9bce3c6ebce68ec60b99e72c70efbe087d97135ecf5a309a915e5ee12908b7182f8cddc92c48358025f8a80ddd27587a23b8e85eda6c09f3b548fa1ab35029842252e6482c10950beca836591af7739a217e137d7d3dbef6c18cd41197a261132f0650c48046ffdf18531797fd82a8444f899f04de2dcb95aed9c1f72bda9c4ba7f93c9fe8a4f52464c75fad5d029fa1b955793bf45a0a282f812726804e7529f2cea109afe5c4dd71efa6d9a4e6c75d7d3a597125f79dec530b0dfa53617f6d480e6b49f6df051d89a8a026d3e8c1b31e6367c87ee8ded494cdc56bddfb9f493b47f1bd472f8ad9a297a891bb8e54390ada64768845c17eb1f8918480a5f6cbe23dfa63c8faa491fec967cb708a8bb5600601c362563ff5169ceefb9a141ff5cf6b758504054fb0d599f80608d8b572f8f366950d3f954574b2607e0c8705cc53a5f2d684c3cec6ad0fd91075f745cbdd1e4d039fdb37ced06d8628c9fe820d3a5db08b6d904b4008f091d7e21c0305455edbbf45713f15ac93d5885a6989b55d105a9955aea4cea5bdfb58fbdba99574344b5e4bd708313b369753c84f4f7b795d9fb740a83dfb63aa56968b38fc54617cb74cd97129b11541635c07d6aaa2b40c9316e31449cdb86946dcfc64d0246d156c6e2cbe333d7920c3d0bd028f53821b4a04cb8a999484ca0081ee070123174b09ab04cb24543e1debd7ba1c1036de4972fd1a3a80351e56bf0d07c157530f00195c1555e8f71d988a54831e223f988d6c99da0fafe3511ed4847860dbeee1ce88640d5292187bff05fb592152c864bce893c454296e8466c575a85a725b87a5f1c7539f598a8eacf3c9693d28955ffcf50611873bbd24c7fe35979c86900dfd71329f167505efc38e8e0ef818412721583d59782301f36362029656d68534d4b1961dd25610a3c8338dee31b163b65b6fbc0b435f1dbed0289422cd1b8f06409df03d9ecc19150a224f8f90ba32536c073dd2bfc0c60897d9a2c020f0d4b003329d14106868ec6156cd820329a4b7d12ea45d49bc86e70acfd475c51ca8803913efd08f771dfd2c5b2ee10c725511d2fd25336651a53040efed31172ebd2a067a92ddfc9641486f91de77e3a90f6ff202e975c42e4ddccbc9763e1411d980a311dfe2e1b9114608f006a0ccbcf949584211b6666c19838e0c8f645c7882b2f050b9623f9fd8e37f5dbbe9a7edab58cbdf615d6aaeaa212837ea171a95189fa3ee9d69a5cb6c02441b18968204f30f28a9fd75b51a934600938977cbc4b583885641e54d891e7484863772246d94febe22589ebc204ae10b452f68d4534518288ded5f4edae8ab54b65878bc408d021c89e65cb3729c151b432fec9338c59bd129c359b4cd9adc9c3901d70dbfe8d578333d2fbdb0d20eefc1075c3503f75eb5b52cf22bce4a39587fea0c9bb52e8853ee1de60d6615593d6275adf804834ca00cf57b7c0eb6d32e1a39c659606cf45c5ab7c547eaefc9c0b826e3700a614bb393f47141d102238d9feb071ebf6ef9809bbd48d3d6f9635a30ea06d8a8bd399785f212bddc1fc52bda3afdc11c50ababf90a6171ccd6e202b334181ef1260b0f22499e03580078f63c388b45c20564d1f2fde7c5ae30c1fb2692c63517cb23420bb4c145b7cbd89eb175f30910be31343a2bc6d3e04e37c890483c77a07b43556b0b3a4c5f7ce607ae431fbc123e7031f0e54f53bb580f1e3c0340735af1547e0ba73254396ed03d9ebe92bc68ec300b417ccf32dea983bb313d16be65e34205032b9e7d4da6e580bff49d063658a0205856159e52297c4d6cc3d41d3f5e610bd32a9661c6996a315b6edaf48e4a304a621fb718c25af9ca321287d353a1b74c5ea167bec2cbbd0e82b458fdf6dc52ebd755abc4bbae65578206cd807c5a7c9456889cb9df7750040d6ed8319fe81cab9ab5ba5d3b18c94c4dbf45881980bdd9b36146a961333f444ff16def30073ebbe2422397b5859c63508fb77cb395e75d980848c489ae1d64b6e96ca722802591a121ac153026a2c2b40e7b73a845b9faed3a99e09a787145f78f9c347a54a5799fb16966980179c6d036b88dd06a110747610938df9cb15718d97f2ad6f5245019f2aea649c7d1d5c7681fdaff09077198c4304d4a24a58feb62d89cf6d4f79145ccca206ea76e72ec1aa63420eedfb9950c1a3e729420ed42bcc3e01b1b856f46a3a16f28e0a73e82ca8ef62283ff4f6207d6b830264f70081915381cb63c784e794a825823e0e94260b194588e304377b6def672e6d74b1836e5a81adb206572279b8749ff0598ba9f69f84064669a7c569fe340bf91def17a3257817cdbe1042ab9424471d01b75a207ab39e18f31c2d17ac4b840a113875397ec1d2b7798e0e52428f21543bf8ac29d78aeb251dd89002d5c66d868e2038eef9d41d03629d6be33812e2d0c651836b2226a9c0e7ec1fa211764cc30540f88850277c5511e3921d9bc556477e95c58bb76ab1b483db38f0dd4ee4b11defdec7fe538962ba95113c05d09541987df907d1869c16f8d3a1c6057674f174c3ddc7c7378a8864b9c5ecfb99ec8aadae183c9f69ee129672bf9124a6f2a7fd80be1f9910490af5ac05d197e706f3861a5ceff1025d573e28d87c1999fb265b963b348e70ef247e1953cbf045506c8c7408f7550943358e1b1b78907ad428e17229f23b89b3603f3ac2c86d0c74b02df6c18873655bc2f95b9012287ca08161c601e7584f773b74b4631c9bed3636b655f82fff20e4de1d9569a9730c87b3fbd23ff18b87c41a6dccc4141fd496a1a8679d4b247e0d1b4e6b9526eb20a503713465f96d29f2eb9f6ed7ce046c05edb27be3882bca1c9828aa5c02bdff02780801c4ee39d4d698e18f6aa09f5f327602a17ed5ed06812475542efa2b29e2786063d7193f37ffcd39d05d13d4a1332fe1878f7a7f334784b9e70afdd6b98806225d2a7e5eb08ffe3d761672bf6d7d86fd5b89cf652b7cc1c5c67e4a332b6da9369e6d68ea526b5d6756593050bc9f1c10777dbe79dc048a9efb0c5f8c607e014842a9d3dd067c0068c651d594a3b6211f49b250a265b9492f1e34732d9d87580e7bb20417b517c86a075213a312d4a71029b255f72447fb6b1866a19bc278ad525f0991ac7034cbc5d86690cc3d931cf370f093a187409799d85cb06fa7b03ad6d71c6a7324615d9fd134783544c624f2045dfd19a4d287907c08dd72fe958fa50f3ce37e6eec3375ae0ee5e985ef15268808857a2b6e991732ef98f6b9b3cdb7a3e57f8aa07ae71c6ffdb6f6297279506bc71e4c2434d1ccc5dbaa096fc96d780efcaacd2c9970a99b1cebb6d07a8d19e776c32589f0f390b20fa249320b310a42b37fda82f26aed5e8c852ce3c6812f7e3031efb9a7f3b55e6487096caa1001e091d033862c5f59949471fb7863128d266728eadeb00d21ab2943ed65d0924fe9bed4aac9276039a5b4fa2d331c51c943460940cfd8887368dd36c4106748101ec2771ac47d6ddf0fd42c09a6212904c3a4f3bdd24c8c5369a340e8230c6dff21c9b7bb8a3ae3781ecda7fdb150c4dd60fc2fc8a7fdb162b2fd654daae46fe9a37364aeabcf064a9cbd0c2207a2aa3936b5a83ea31d17c0d58b5d4fdf2c90949d37febe66c6b5dd14356a6fc8325e4418ee6be17c3c54842e8ae3737247bf35debfb03da16d748303b9484faca395a6ba11fdb47d4445eea2e6a0346a1dcf761c4edb0eecd4b6b0a982bb12ed4e592906312f1f7aa211561a8e0189b5d8866954ceb31db74c391162fb8e7ab4e65256e2f503591d70d423e37fcb6a4ff5fd15f532c156d72c0a1ee11ddb20f2382a762e8a9ed3932456de9df35cbd1f953ee3fb0fbe010165158661751d29e4d653b3110399a8ca03eaafa441030ae21f875fe2c731a747be546e6a059c7c87f64563afc3f98c897098ced67e9cad036e167c7f41aade1469a15d3cc047454e1d31f12e6e275c113a2ad8c2fed9505839c7da1781dca4d6d180eec1c9c3be3740d462a4b87ced13a61250750e2eba8addde2e64cc73e343777812f04902f886c373169f51149154e50863c9ca634746723fc8c7037b87aed78e669af242ea569c6a0bdc00d43bfc208f6b2d5bee0f2f45699107768aec7d22e4f4094407f745b93e97781fee3f1067487f1fbd783f18b60a721f08fe4097be6cce2de41c3a9145c1372eaf55480f2462f5803ab34d2cd1f5ecacad36dbf9af94cc75ee103abaf9c4e197e5e67169feb2a35fe8dfff97eedc88947419c83d70a8b8c6661481467413a7cb195e5af69f77fa6d27fc53d95f530ad5578e2a9c2deb32e87943d7b4c95ecd01afb7ad0afd73532f9965053d2a77df0aa3435120634336d51dca6c8d2735a413ec6886935a38ad1d2ee097a6bc6e0e333def699ba3b90baef2c9ad71ac92b93c1047d0ffabc7a55f856bca93f3ebabbbd21107b7149d48114a572456a2c55d68d5995ad55b2307720443712ebb502425d41bfddd742b359210d5c73eca955be3ff31f01ce8f54c456d8483b360510f5fe323f393becad2b39bce57a59160483347d2a4d6bc93b5ff018cabd2cc1322f4ef7bd2fc07f33f171663dd661304e1e4243a8f7a0e0b24b14390f4ea2c077685c6b9f954d45fd26ac4b0269276bfed04cbb86ca7ad290ebc97e208cbb4bcb3978cb97f7bbe74d1a615250a8b065d8bcd4aacde4e2b96e7a1dc3e3afbc6457894dad26c08076aa914f0ba01a6f61069a78a35a3f22c5e0a626211dab76019d512a47b5a06a17745de35e0298aaa520a2152bdf54175df99f1a97ce70ceb313ac54d6286169dbc7901127c456b5df91641c21a390650e19a5e4a9c49e78d07c8fd79ef0a3e6266060e498c7796016ccc1e6891c103b44cf6649fd1d0a8d80377e3751a55274d22c9d4590f708ca9938b015c0756b19fc7b55aaafe47f2f8c3fce9b591956202dd722dca77ff29fac3152cd857484fa2601ab51db913e397d529c215bc02f38808019ec3168d4115b7f049b9cc042b85687965e7121e6bc87184cbab6a41b73f227736698c6fbfd7b2a0e556820b3af3938c38a3fb0ae59515309b4c91b966712e540a43e68aaadf3c17028b752c2fa59a1eef5d5a09516dce5e8fc8923fc9398f7b7cf96443920295b633bf6b5584fa88ebf98ec4bd066ff1e220ccac8f56338c1c7dce599c2c7a14d6d52bc342d17ecd01d2a3af9736f4fa7d96444f038bd638e230f421c8662df253c827c92e1532e413d3ea1913e153f2e476dc3ce09e52abc3f2d47a5387ac5b774a882f87d784d822d5c74d212ed62eee95d077d14c3f8134b41698a9b34851ee75d2bd2560fd09d6213f58e40e7ef9a3dd6cd004c863b3c88e366305f6943482c4bf7b1608410e535c9a36aba1ac7debcf626c107302c4b99bf112650e9767603052d721a619a3c159305c0f1285b7885dafef140cb1ce773e21d115bfeb09866cd361d76ffe86addc517cb5a09d39652b0e8095f1e2db6d4db3fc19a5274334de7679198e3f74032e81fafbd2cf4cc8c2c8743634e4128260a8598773b0a87bf3032c4911e259464cb27e363355a4b720081925c38faed0515dcad10e6803c6f0fb2e1c7e12d6ccdac261b4f61b6ca0447f018aca8145809e51ca84be86a3729c509a1aacc61306f6107e3e8b037b8ca2751e4ac2b3535d150ad093ce4e3b4597a3138e2a3a677cf67f9be48de44cabc8a73295851a22d8b1db987b943e14c3d3c065c7097f9458fb3d2e19357bd0b3c6f5b888bc9774d8ba2fe3510e1725c70925ad1d70cf8544a9c6bfe2240a1ca24a5a2507672d31b6633d473bc20b380600fd355065d7a2847483e5c719ecd76e4592812745517808147dd9c2a87ab1a9108255d1c2e58a34e10b0fd85cc0e43a88c088d95a10f52a46601d09464cd58fd7b0c64972a27fedfd696066a2901c51c4493bd2050ec0e668a49c445c9a66051b6964ca3e7f2db35741dc4880cd3f995b1eea5d4ebc76a4af6aecc138d92d48587a69e8026cb2c0fe66ecb37352f35792d2a622db150d6f5bcc6308b05dd0e4a3b7972d05020cb570f24200a9f21d15970740bacc67119e2b77ad02094511c28616d5837bb96bb5005d0732f769ce59afbc511d7d99f38bec675ee1acb8ef60b32e593ffcdeb7268bf4cbaeeb2e533c283440bd3569297836b4f9d67ed064a37b9542f1474d7cb3445ad47580fae772893607eb29d64b8c6fcde5b543d0ea1b0aa72093cdede472832ae87a4d7717689e7df144906cc004f4ad8d6871c1b7b6e36e2d83eae122c330d3329035642d4895ce99c7820160f3e41932f0c8e649944a3563471e169bc7527491bc86193ff81cdf4375d310eeb2d6b4b777b98f8a4b068e7218024fee652d9c9d974d39b4367f819d990d9ec09ff1038d31bbb4e4463f0a44ea8d5b8974cbbb805998a2f94383e054d213c12f4db8eb9fc82657659123fce94cb7fa3c6772abe0ad55c9cef2ce36bb06c2c40a0875d716713deb7122c2ecf6c35d0244537c32fcebbb9cb1a6c0ab3acb4bec9a76c20bf7140f924e8ecaf337c4623e4d2cfdbb669c2972a3f0b7f8aef52152c053e235bcc0013ff671e5f5ff328d1a6d383029006bb00e1c5c57dd80d515c5e5351434746bdfef258e3fac55a1afdaa098fa9b469cf470e24eb6f8d29ca2ee3ddcaac5bb397309d748036853d1ff50e0138dec27a930b7360a3d452c4f03aa7b728058304c5a4a071711631ab52662bccb7ad70acaa50c92f2a50430bc2699cb9bd5ae999f297e27f1be956c2f8e2ab63eea22181e3e158747dea1c635755764cd95960bb1d3fac69ad8879f300f43fdd2dade7b6f29654a524a19b20881077b07fdb1f38704e6fa49fa40e4cf2cf6ab951f85342430fd08c43ea604ee8ffd32dac0bebf312fc84f7eeca517665f28e31da1be8d3288fd6ecb2ad484c9f6af961cf37d3639831ca560e0f24a326f1f5aebd2cac6ee5fedb7fad6dbacb52e6b1fccf7377bb5cc36c5a890ce12f4b79f4b7eef4202b3bdf602916fff7e28df75dfbf2728df7eaefbdd095afb6df4e38757c96a181238c84f96b148c6af562e4cc9d685a61c73cc5819fbec35a24923a44f947dd82d1f635c0e29bf1676c9dfbe5d52b6d47c7eb290cef7953afbd66541ddf685f2b9b751dac942b8ec5dfedbe705b19f8b1b21fbb0cfe58fb9631f66dfaf615efdf6b2daedb9faedbbfae3d77e30639f615f2c92616757bbfa430273134d3072d242d162b41144daf9c2f92ed77e342229296172943f3f977f4f3469d0100459bebbb42e24304720f233b094a7fc5c4860962f3fffc2fe8200913fe5cf8f68522552b4641b394a45a923058315189a3211510af2eb4f17fd9094fae6a81f97c0324757fdfe76d58fefaa0f66ff0f09cc4e148be2f3b03de6b427f7db5ab31b23f7ae4ccbb0cd5ea4fa91dbb42c04a26915054ed6fed6c9ad032b02cfffa4d4e1c2b2b731c60f2247b89f1049391efdf11322b39d1cfd7450195dd8db7f4d72f473611f1298eddf1df5239aa08c51931309285c3210fa6dc3bfbe7b417eb2f4c23ab911e8d3ef39bfeb5276d1289426b996b027fbcb3a997ec3c9f33136997ebf540b64b49ffb5af8960b0173059139be90cef283fc64f969f61322fde52744e6f8fd84fe29ecc03903f3b1b7af4d4bbdd05f49e6c69e621f100c04397a21f6b17b07f6f3b5f64299fb72417e7206622ce24179ccc7da132285c8dcf773edb04f5f49e6fa3590f94a327b109983c83c33d9337cbfb5d27fa55ae092efd77e193bd6185fee4202f38c09d2efbce5f01503b3c24164269a20fd07bbc7af4ce7107dd944997e8cb0e9635c8eea91878c9bf7e476ccafc3cd0315b0bc5c8e5ec0521ebadf7fdfb91d3588ccf6ed174e212328c9dc55158916278d2012abc261bffc3a4bb5d04488ccf4bb0ed07e6291fc9f6823fe0b2bdca76816b2706a0aa24d9b1715350e21f5a4082916a49eccc1dcd0c98c38f63d6fdc20012587208e1c0d0b55578a052b23d8d48b1ca59e483d2183f9e0162842c74d0c4d554cd1c41195caf48991dce116cd51ea09d3c4a24915132c1f8a8ce0e614e97fa1771f5c60c00c1a288488f29282a7233f33a6cd28b961c3b2196101c8c99391ee7692c4499a6e62076b628593344ea8c83e394a39b19272e745c09c528709b461c1ce961f9690c28c991ed0a0799de9260d21327206965a4548ad2047aa490f6646861f2e4fcc9ca953e68a16288a1c7999254a32a9252d34b9b2522be869828d915a0244fec9516a89188c0c0f9898172f2530312f2ad5e444d672946a52220a336384d51378ee3ca96192b1d24f2cd41ca59aa85a4a29a594524ad98f630ad18a5d2b864172b395a3f2da89ddfb4550be8c5c0833538fe648811eb00239394a35a5c95a46ffc3b81068a6af65514eda3ef4bf9a070383e7471adb9573d298f4a3d26e96b1ef74da218d3286f2bbf507a74d69532ad5b425532a4794524a293582bd0478705fc1963451690167394a35b1905b4ac955ce729452f28409fef532f2194637bad70c29256e32cd514a499b7cc3f890c3970f3cd8a72ff6cb965be68a0c542c104a9ca450a244852c7a98022b52e7e21027cb117982b850d43499b152e4604350c39593a5aa8afd014a9215ee144998965401270a3b022690c0b92abc99e204189233f648981c6eb8754a00830c760629b709244c6e0d488a5819acb0b273a7892594b84d86b04cd81481e6ae20c6668183dda2e5122102ecc90b728e38b958478e582bf4d82bf0604d4e5ca6aaaa226c560a2c961e6e123231783981096e1123b0384cb15e8cb0b084912497092cae1057d80a746c9515582e465c2653547eb04d94e1010cc684cb3d024b1231d81b8ab4104491a6db258b0e1516092892c811011263dcdc2780b04444d91774b836d8f00309ac15a72857052a2cb1a2820a9607ac5b458aa939764d122b10e236ad712365b5ec70a1a0b298218610b8808226f7881493915b822bdedc7962654ba0024b4584a50205174d9a9bc498a72e4ed0e094f5648fc882e172c97a72a9b2905827f6ec90a7c8133b2ce102d5fdd2c6b6f1c10ec1c3942f36055b4c90c5952b9505958b250b27ac49164af46461ab98c2481d3671aed5116782b050a8b164cc189103096ab82f64598345952e5840d182c50a73b060ba46aa1083b1289c608c093c1883b307636f2cd49a28686c93317abc6049b6a8c172a3f65499b227ca8db387c985628f91337a6c0b79ac0b74b0e2d82790b860de5c5963078d11632c09bc5c24b664c172e1e8a972f7e88962023d4cace83102464f993c4cd0b13dc4b12c20a1c41b356bdea04933c686f162a3d8729560b9575754997345947bc3154cacd41546a8e8b933e4b15174ccc4b14a90b039bcd161cd6501cda562cc0d81172cb68800cb94155560b022ca1d6305136bc60a2356083df64a9e3a744e10e7224162046f745873a5a0b150c60ce1a5862d3460218355850d561429584c6c142c2362f414c9b3023a9892385b90b82978e3c39a2ad0d81963dd78b1366cb95eb0e4a9124494364ce25461c45eedb177a6b874a2b84a348145954d638475e2063bb226877f69a0224d0e5fafd7192ac834bd627e4571b0478b930bc6c95ef550afa692f815114dfa6a054de855144dae982289d3142f646bf000c164c323c4120d8f122b64160dbe56e0105f81c338d53c6a98f89b16faeb9ce03c7b56b842d2e4aa0524779e68536a122a323c40e01162c3a304a5b41dcf159e26ec566badf63a6547b20dc3302cd3f268c9f3847118a04d7a795a6c32cf9b9f9f9f1f8cf3d4c92327cf1e24ad56abf5e2c5d594ab16362dc338ad083c5994e0d9d2240b3c3ac8b992b3042381339183cf298325cb997026be14a9599706bbc28d224bf30163c267ec133117883b5eb21ccfdca1e14e95a965c1b325164505e09192c30d4f933d39bc57399eb923e7cc1d387782c03e211aa7c50341adec42ef31cd191c11f47f39b7691976639aec5edd566bbd3f2a9caa51762cd336ee861acc7d1877dd3f08be5e46463da84c79df006a4a73a56f64a7aec1dde7766f75f7eeae39d09a83df0d1be5f946797e5d1331ee9a468ded938cdce0c8be10d7cd2b610e51af9223a5db6f238ba5b5be043712ce1b34699284ec2feb67d8fd91734cafb58db29d3bab97f7bf491b98bb5d2fc6a13be4d6cd8ed63673ccad73b5725b866df7c0d9fe4b7b652fec8757f75763b39bd06ac7ab2c9f29cb194a35f757adbeac5d3fec059191959535f1f5af3ffaaf19b386cfd321aa548b28610ed17f5a0387fd7cbff6ba09d6ef87121c39efe350ffe6c5d8a3ded7a2b2282caa6a750b7bc7e7ce31df6b5656d61552ed60d36ea94ea8fc7e2aa97c295b4af9c5392fdb9b7c8c79ad01d7aa65b1bae63b3e57fbb5b5b5fefceadf956bc9ed23702ade42eef715620d4c879aa28dfafd1941ad69caa11be55cbf4c4d2c0ae351610604942c73a6cc75a8cc4ecff084a4b13aad4778ec1bd968b8e5d5daf2c3b8eb1a77ad0ef3fa7ef5fbc5d7ece6312c6d6e47fd227737487271ab9313290eb13919adbb629996f432d3695dd661dded9eee86bba19e2822fa9d494162b07f5f8c5f5c5eaf202120a064099a92737df35bad5273a91d8803d8607bd7eadd995eb1ef34aa23583f563b69a451c4f62994a7d0fca2db0b7203e5c66a0d2c9426c3ae0c1d9eeb67148be86b8025f64d123a41c8b36c76383055a1a6a850158a4dbd2128fb57a35c4b6a548daa50f2694a4b85daecb44fef6e2aa750a8bb81004a4e4921a72e1b9753536c72fdb00566ff64d504e96f7364d5ddb09c6a2aa250c9294a8536e51eb2146689ccd297dc5fac53bf85727c2cea97de1784667d8d511f10602877fbbc176be037ea3e83bb49b046d567d36464ffec0b6564ec1b1206cb27a13b1b6bffc8ed5899d2343f4ad02c91e6c9f280b9edfd9fd6f0a9d7c8cd8c081a75f55e119c41c4c77b0eecd7e6e138e4af7938f3302f468c51c04646b9bfaeb34b5099ec6753ab6470fdaedfc51bcebe07418b69c0f2c3a826aa897d9366b8a04ac10a4e98c1d44c3acaae76fe06bb7774764622306a23afb55b88671a1c487bcfc0c2cda33fa5e95e4b579a48f20b42b3e078d507a664f92f050ee594906bc03c8a74e89b49d32af74fef7ed704ea34ad50b0618995eb875d9e4e0da047f6a3afa98915ede47062dd0d4fab598709d4b4f2aaf6924175c08e3bd1c10de54e21100eb9bfa192dce987a88286caa13f6590fdc3a606e0c69f1a871cfa53eeeb4ef287268cb853cea07c883de3635a837bf7bf5fa8e4461cc9de080a4c4c4c4c473eaf699a578ffca88f34ba691e3d9a475add344f1e71dbfd987f249e81855d483f40fc71e89f6b07cef2b1667d702ca2afc9cdbf06e27fbffd43eaec5f3da0407228fb03f2b97664ffbaf37625bf92e9edf4a6ce8e658c89606d224d6d56cf335a14129a207d70d2a060f8edf3fda24450f045372df3f1301da2336c9003961f5e14b0cfbf29d906bf7c14e8b5b57a765e087cbdb8afec4202e242f2af5fbf10c8d61fce665361a9c724305b8b06dbccfb38e4efd8ad60f6215e3d5b7fe93747ce4f00a9b4de2218dcdf17d7cf82e5874f635f7aa5be70d240541695fbbf0adc8f834e2f298b9a21501625e34b0c0f54c099995c3f04caf427a7e395fdcba66211fda021b07c1c34cca632fdcea2e2126892dc2c2abf708a027537902814caddddf21b4b3a44bbb91cf3c338fb30a6c97d63065118d4ea846ae0a043f4c3edfa7c48733b0814901d5c195078ab11270524083176609f7e471f7f21fef97cbe70e630c2b9dc175e8cb58fca26cbe590d9b36c924d2578264cb066ff7ec422fa187cc1f237eba520efdfad4635b95f72483e56c2dc896a724f5042d92d4a324051b1ca910a558eff94e3fb546e2919a272bf8d3684b4e572b862f49064964a7db911e4c7b7dc08f60b3d7af5e9ad9b679bf4427f97f48f55c9da11c22597ed1cde2e976c97ecbc791cc7433e752ab2ecb12c7b4c8a942836510a9165d4c5ae75ecb3b71c12fdec2bb703fbb2cf85fda663663c4139f3b42fb91dd7de2577935193c67d99464db635dbcf031570bc9ef40662ffeeb05fbb328e99c3ceec7ec4ff56b93ad244df66c9f6ef12fb97be5b9192fdab8b8a55ace50273d1a2f18b4db29934688e30381d322adbaff3bd7d5fb6000272df1f8843e276dcaf2778ffde98b34fc7cc7182dacc38a8d5b64ee87543041f3f92861c71b2a265d2f83269ccdcaf59e0dc7664c36eb7c5f940eedbfa9b643369704852caca97cbedb8b8b5a37efdd1d93252f791513f53347fa72a4c394ac590850a56fe4935249b95957595bd68a1d6f16b1d5a9c5168cef9d7b7cbf1f0c7fe6a3b341fee351f7c5b139ce187af792efbc5095a6d6a5b1099b5cfe77d7ce47dce0bc19cbda671a1f30092615253ea64ee43dffcb5d7ac177b29db2ff4a7dc08a1cb7e7b739fcb02c9b0f76f577f7d211ed967df7e9fa78c396255d89d680aab33a36bbe957a012b47a917a46401e428e58255bedb9efc394ac100276bbdb54fcffa332066c7ecc718d85f8ec750ae9be442983b865c48a0ccae1d33cf177281a51c73f3a83fbf70ee703d98fd8b6cb4385f7efeedd9c8e5684ab1eab6524ec7ac2f65c8e998efb28ff1a85ffdb1ef19a49f2a332ac833b9bfbbb4cdfa6462699f4b3079c5326d9b4166d332ec8a500235ad26f665a3750cf10d54958aae40a9144335d9f4fd9bd193ce395a767db80d3b537d5edceb0ae55ba713e9aea2aa7a9d2ec54e63396555cfee57ee9e5ffbb196f432a361158a8fa01005ac7defae65a2268724ebd7b1c897724c416f755ab51afc6ed84d557577f75a6b9d23a03d5c2cd336231d36dc755dd7753d66989aba1b36726a015bac6a9b731c56a9bbfdf68e92b1a55f3df0bdb39e898a6d42d7e51befc51d6f5f8cc14f0204635f6a85bcd85e2cab75c35dd7753306143c12b5567797d46badd475907de78c7aec547777f75a6bbd17e3aefb07411c5aa2bca913b1facd2cf68d712593b9db8bd92cfbb06b9da6ba8ed16ef45a6badd473f00c886c8c166bb595ced8d47a75afb556fbc5bdb6dbbbe4156dd4770777f0bc0802207bcdf5fb58e4fd3fbe5b09bd97462aabfc6299f7ecd9b3b1110c466d14fbb65012afd5dddd5a6b8974ed1384a86ed8650c3f88e623e84244b4acb18b310ad82725136badf47aadb5de08fac188cedd2eb6b7a3ba1763b006257d6f7577ea1fb9103008132b06619ee8396787b1db5627f45814789b52be9aa63ab04ac7be58d2f49de2dd699e6aadee3ea17a4aa982d675541bee2e7e7579d5971a4d66d9f3fba32ea4a66a3aca845cbb2ed7d851fd11b16f7717ac03bd2252bfd486bbe1f844e4823b3a44a7a72465d7961c3c42ecfb3bbcd0b535c3ea77336c7e999a7a21f03617dcb346d206e648923ff2e8813fb681dcd103f740bdde11193df05bdd566bddabc3d1be3541da94cb516badd5c60e6ad7ccb3ea6d39daa9e35627dbc818630d2fad0dfed7ebf58a7de514f7c936576033faaab5d638e4debc436e23f14a6164ffaa13004660a7a9eead6141f4c6c1825883cec7c2e6faa52073f8b16fd339e7a9babbbbbbd75aebbce285fa7037dc4ad45a659d357aadb556ea598c62df24ee9473592d02bb9d79f2e8f58280cc3ae23e02c421ff2004ef128007f62ae042aa9dfea5bacb2adbebf42b75cba54a8dc31d72ad7bbd5254510f3ddaa8565be264fbe18b21446211fd9f2811cb7318b1366c85d59901072b5985037d22061911455f5300a534ea540a707f5ffc3edf3df87a9752e453eeee3855a37c1213ab6416ef5baf70dcc9aa2064550e25a592589762c522fada172c9f52d1482fc5c2aa02024a1659953d2a86a8bead32d76e015b54ea627aab33f1bbe188a77a9d26f0d8774a3997982a21fb7727055ddb5dbb2031f7628c810a3c688a6659aed3361f2fd221ecdaaf443cc657c6beaf3ba91576eed56efbe6f5a04354f31eafa91e5353537d2f363b091294720fc97d9340796b74fd0deed075d706f510debb61b0bbd8b958a6b9fbf7c518638cc34b5454ecdb4426d02eb617cb76c061252aea6eb8c1f8e489fac558a66d9c52f5a1ebbaaeebba8e0419a8a8ee869faeee0ba397da0aa2a9b55afbc3da21d756b7eeb5da7a44f528a9727728d5c8289fee24973855adba68e97cbef0725d47353087dd2b8b8e99efd7690610c453776bc9217d9256fd45cf9a2bad9cc945d04051770bb5296d2a4b2943b7ea80940e4cc9b2034e59034c5963cafed30b93dc2af7cbe8148be447ae017d14e990b4ca52ca2cdc9f75a92d74acaa5c3f74ac3eb29f467505961f6a54474ba146956975abec502f14c95edd2a0345390ab4f8011e932b9a1c7639dac0d4406d9d91d1eb05821fbbaeeb2177fd8de321b91d314a666d46397dc2a5dcd12bd8b53dd7d003b9e5fc3ace16753777293ec59fb29c69b2f43413f4a926fb4bf7efc6ac9e69a69a59e37e7fdd1410b3cab9cc21fab99627dc13742dae254da6b4ebfc63d80fad9d6c0a96924db1d9944cb3e4d0b54cd1244da64ce58e2ab23d16a6977a111168d16badb5d65adddde90b318afeb1536df818fc26e2e16eaedd9b791bf4b877eafefdf0e69003086c2081eccd087ebe5f583b90db659c3ef793b1c8678db1c87f89c6be44165cb9b28128f75761b876eef6859436c9d42977966fb3fc9a727f3f1dfc622af717ceaf49142af7d70b4788dc3fa7be688742e50027fb540e69148c5cbfea3441fa00c042a3a61039a477c374def0050aad2d07b7a49c4ea5f47b6db531bcdbbbbf8d89eaeeee5e6bad93041b6e2a056ef9f4762f48c4dcffbeb41e63014bd9fd9bf385fa42dd0fdef042e00469f4e697ee85c08e83214c30c4edb295163fc03f51415d9452762b7778658cb1c3a496b5afc9f0e6a9798c45336ce1db2a73efcf0fe80a037ab70c4085cd9c566e298c5bdf57a973fd29e787e3ac5e03775c70ab139a43fdbd94ebfe1a428b1fe07edcc25882e04a348aa68075fbdfd7a496959543224c4cb9bf14b0ee16323356eb178416b5bcdb047b3629d1aca6b99de2540366769b0ad3463e60dcba715488037b2d6a778b5cfdc225a85cbf3dad65529ca237c77d1d92434a406e2c7e3e48f87a58c0c28c7da7b6395ea84c35b14c77c3d7cbce79a4511f8b33dac836ebe31df1f9247ff4f31de5b850d6b89b371441ff1fb5aaaae801d8500472ff47397db2efc6227f0cfbc20de4eee881dfea81ffac77c3d55a9f91fbd36675d395e9034de61511f32a4e2db97f6689555743c42cce6582f4f113da34b5e490365dcd2a2e48e655eecab4839dba41142d9ebe8d35623cc59e7e0ccfc621ec5b783482d80379d85fe0616f6564ec3beca3dc0818f68db54cdf028f3e0e5a2541b8b103fa1d804cff4746a6ef9365fa8549461ce7853387b26a48d6bccc8b2d3ba8f4b3fd5dff7c72316593279c7295328928637f2f51bed8c55edd927c719ce7630ed51034a714879f5b84affff15cfe3fdf7aecb91ac873f5e76a00d073ef03e8b91afaf381ffc547a4f540bf74e4e3b91af0f3ad77f9e7e3e75b2e1f3edfc273f9bff8a523179ecb3f57035c7c8bf7e1e25bbc7f8b8f480dfef968f12f3e222e7f4c87ea2f1db578222eff52f0f1e261bc03e2d1914b87eab71ec64784088c87f1bdf8968e5cfcd2910745f011c77df1a805baf0e2510b2f1e0179f1087bf1e8c75b3af2f1968e364fce4fa99230c1fad41bc0093036e563823d306883e9cfc7416b705f3f05cc211c13acaf55c1fe3d9decbc4fc4973f279671f6f4a0d3cb31230b7f9e1fca276f08add1803844df08a625327d2732a55e2c92d3648231872fea3399b27f036883697c2128986a5330a54734db21340d4880e58715a783246784b0a2085043022c988200bb817a0e4080186e609b065058fed00a583ea594524a69e490524ab38fd1831cc618a3b66160c700dbc6819e02705b8c34aaa4b43ffaf2d96244d1c1cf8697c418638c45780b72e2e0026d423864004281418b0dc885f642c6e9132e5dd0da06b044f7627be5b1e0c2d886a08061c1d6b179d16d1aec09ba60e3e04dab636c18cce0c2b5c5a0a145d00636015d0c360e6cc0196c728ce0bc8400d97c4cd3ddc87106518e2fbf2b611081e537a59452aab58c4d0325b2fe6d003c5c6f7bb1a9dff66aa235d85e3050a16d033158c6d911001b57446eb0bd9460f95402404883cf7b19196010e48a312ee862b400c68b968b1640f8c787037de204e34f1713f4d24bb7f7f6895c0e0cd4e2714c17b171eb855e337028a0878f1f25a0a0c474c4494a96818969a638b520a7489902c4e5903d48f925a6916470e4ec79a8b26982934904476413c6524bcd144a013774b8310485275828280df9b1c2ca8d2c2218993132e10714ab1f2598e073c7c968ca02667c9941428f28543d7c90c039218fcc68d2e355e625020e2b510d0e0588b02d81f5924c3e88a2888c5e2c18bd6618694ed421ba22013c0382ef02f84260664515684501a11d92843a2c85bef6daedeecfc9e5b850dc2c492867707e806470cc3c25cf59967d77831870a6c53ce73797437baad11282c6d8cfbca4202d0c0a83b296655d967d3868cb3b2128872584f4afbd37ee64fa25056517d6c5674227553ff392baae6bb5ec6b2774393421d3bfd5bebadca2b6304a6a89d93ef3925ab715b67296354da35f987ddd4e68e530c9b91c5b15a25f522b036d404428e01c7eda6ad9873828a88574b68fe3a4f1f33d6bf41c92206cb203ed7ff0cf5b2f44216fcde5f8f9943ae38c5f931bfe6467a52d8ccf675ed266b5ee664fc36dcb99176e597bea855bf6d1dee7af04e1ca0eb4205bae5fa894b7c731339fa75c0e9fd7b2b2b2acc8db97b465ae33e5be213903a3e0f8d43ff39468a6f289d22d7bda050575b8d5c25ba665f6ab10ed338f7af45b82106507f4370f07fd82d0dcf5a93724dbbf958764626262722135a0e1c280e32300488f122f983017841c8ca991024c948b2530ce510a0c940b1ac06c712103982b645c5803a68909ee295accc1148f194ca500428235396bb0abc0c51078f3a155678796121b5a1481b91ca5be8cc0096b394a7d4982efe09f2a6cc0581451700c2154802f9bd614d87f6871046e3438087cc7b4a0c058971660b0dde2e20a5f2c4074bc5469517991d2f201df2b5a64e992670ab675701250745e14c13707a039d8e628d5e5861c6217497429410e1f57813b47a92e5780cae09aa354172b524f7ac8d090c37fdda777c58fd9a543e6f8fde498437e6440d49ce019fa351cf52398efc7d068aa071df294e61045a107bc4d30dece625cbd97cf008620fd05ccefba4887260e29259df3f33141da6382340337b8f354c0a4c124673b1169f487734e1957e0ce73769043196da49c722e4d29ab341b3fb168ca3658e6f027cf973fd146cc22782e19b128462e030c825c312ee82c80f1a2e5a20510fef1e1362dc3aeadb1281671f471cc1af48b71487e8c20904b734e39af2111fa8cbb467ca13d784a29a517e79578344d6a4ef07c6dce386577c7a739242225a5d487062dc61af4e5df5863befcee4b201e08a69480162308734ad94108ddddfdc590524a638c31c69e4c9a4610fe8557dae897d1bf7eed9e12f5eaf14ba173a7a029a57fe3bd4dbddb7bd23975ceb9013d408e10b3d65ae3a431ebdf5823c3708e1cd24fae1f2386710b58aa9f17d656961fd552bd76b56d6bd8dedd400f8823e7e73712b8fbb7ee966d25d9e0f0f31335d003e234a1fde109d2f03e9df369831d94a81fbdfa309ac0f3c36995ed19ec02e10b3aef8740027c90e5df1962190816fa635efb17ca1a7f831df0a4534e8ef1b153d2bc82e2d4d556acd65ab3af2fa30d98adb5d65a6bb5ef66b4ba550ffbcccedaf58478464c1554f735fbbe24c5b8fa0f3078ab35c3aea5b8d57584e3f6de97bb7badf5bbae5a5badadd5daead6bd565ba99dcea4dded7aadb5d65add37dc755dd7755d8f104c4ddd0d1bb999aa5a88a0a014a972f74ffb34d73d63125911a84e80fbfb6211680d07397caf1aaa463695b16a42a5052106864b25a35ec05db451adb576e6fa75b1c82f58ba383a0a436e755badf54b6de8d7a665443c8011f4dfb4cc8522d8ee6d83163fc0d14cae1f1020fb90770150c4b2fb191b603c066af1da17be8bd6635ff87af18d617c2f8c8c5aaf970b106cf10fd475f8fbf9cec4be372e05a1c5a8f4eb1a4b82cea119100000005000e3160000180c0804c321b170246771b60714000d6ba24262422e1ecc62418ca4308e8294410819420021c010023364354e004537f1204098669d1d42ac3462d2c53de8a8328b40eba4ad3dbf83fbc6b37bc8bd36424f55ebc4f293d5ac06eec74ab957233e8243f47de080008d98030f1a84a231ec52f64724bac43872d6f2bca42a59468ca3249eb08c2546741f8902e7e14d890d6a44bb7291f1d40f4a03b09bcad3dc422353fafd8586691e58cbc31f877826a4bdf66b2089efbe03c00b3969af6f91f2f0c7eda3299bc75c8d5b483d97c957e62b6f62983c4bf265d65a21db8ac137f5db83dbdea3a98df1181b572f7891f20ef5b6a62481036f44b20ae80861f430557b8d223435de8b26ae00735432a3b4fb3610bca06415d863f59ec65c21a2bb728fddefbfc8c17d2326f79ea421fad4f1bdff0bb7431dba4c6e4580faf0cd228b00babbfba569b1735935a814a3e4c3177bab6dc53632b644c0798ca5422def76f9f86401459dfca7cc7eee1a4d6c1fc2d27dd76502522e208ec03de3092af7916cde6e5cf2794e97380101bf91351e7ae7d931a2baebb8c7e9e8bc6f71c6675496b04b6721c04e13d6f8730d0ece03f4d2e6b28444358baebb7262e6ef43f5fc4af6dd7d5cf51d39082ad3451b80bea0582330b10d78c4423c4a04b57c04fcaf5a5600ad745d438966f65a3e04403a623e0997f1c170aa6d5da17521552b685912bb68ff58d1a57e8a6d5714cf55efea284be6a8576f2b96613086924aefeacc1732c60cf1c1b534e152f57a5f81d83c61d7119e7d9fce517c904e8bcd512f65460242bf368c1479d7077615e42d189974aea3e75d7f96ee3cc8ed2e83f6fabf5e24572e17f50ac8cf40add54297235240840ffe1f1cb6be09b4f9081c521f00d48399ea84a156e322b2bbb71b0307d927ccf9fef2de5c29777deb9914dbf270f77ec89f9b904ee1f5d342cc7e59a1567dcd172fa1308ed87e1490a364abc3ce7cbc4795ee460f84872d4bef5f3d1362e133e73f305cce160d78a1952462d6ba5228b6d4b0871e235dc1e5ba1c7ffd3d00d0c34309645e9bf7551f3b4b32df397c8fb89de1e2f42a0d3129941696d27401516e1e157a7e309ee9716c634d880bdeace90053991f97f68119bdb28f830d0d8b9f8a0c3356ee029534d055e526660c481747bebc5b6a4ed4df4afd6ca84f8952209acc88dea0d77f84477bfcf236c1e6660770210318c90998e54b2981cd966aa7a1c2b2f74236e48a801896ce192b1582b804beb8d2619014b0fbbe8e3c99c24bf0921e6c5d61d673c5410140a31c1901088ac937ab7100e72dc71901d49d5b2654aa17126f8d801fb35f165b356b6246ad3a6000514ef61920e05255e3ff12e94aa888c5a370e82f8ad67a9824fb8042fc0bdc4e5881254e6541f33d84a995d468bcc2cebce44acd81b1e1d05d1d523a05ee5d40ac4a2a6a72e2d8fcd2f06be15e25d04d1e9bbe34254754fd5878d0d376431022608c3a7c14dce8e6054af0fcc7fc099bc54dc1e4bf63fd9276550521458a28409feb32df3178c245224cb496780e32f00230198c00520027814a2b589f85039de2e675e0cd06c6035a1f67579babc59b64c546b75aea5a65c78af224dd5a99d32770c35662ca6c3f1af8ff4634349423565aa85100d44c96cec5094d9ad7ee46704119c1c83abf6c7bc7a26d4da74c537f062703c566f6d368bde2171af315dae19a996e2ba2c145c425087b2ad063ad355d2badc20ada21387b28d4e15d235d8b63ec805559320f971f324de5e92e83594fafc7cac296605f8151e0f875c2153256a5545addf36154a33b33cc7a25252baa0dcc5eb2c4afd052995ddbb1f3f7c830baaf519365a8e6b9d634ab99287f84e0198c1de0b1ba87719fe8dfc083f40e5ba9ccc9980f28e33c72a09ace9c712370fc9f991f5e827a7f1869139e24b73060ae9f02c3fc01da4dc0bc1230c1113a9ad5f8ac43bdc69396c86fecc47f9f322866a6d9844496526a640b837e734160c7be6f07141f16b78dd1c9dc997e2046f603dd48ebd4ad8b62a743dcc7dea6ae64a0162e6c08a335b0971ab85594492111cbe275164253c67bce0f5ef0ffbbf387210d902dad7f38ee6ec5faf9c0b6621720a26a523c52f81e9bcb73ad43f6046413086aa524aab68216e7f2d34c8bcf867fa4d6f14f7ef0d9440bd662cfcef3412ce54c27288f88439a5a029c4d46e8113069594839b1b0eac3b028968f9d484acbf2332552e93fae60498188802174a924f7968ba66ff46188f0945497c527e4aac3877b1010b0a07a45508baba9e3a1d62c16a3d939a648524b903fed28eb1297dfedd7b2413b4ab8f9ea6db912689db7a8178bbbf956bc4a5af7ee2a7434bfcb69689bf57727a9d65033f7a4cc1bf17f7aef30e7e4bc3f7b1952dae0573a9d0005dfd77e1485807da9a5f9d46d1628f1173e9cfa485ae30284b29b57c06b24aafbc802218740fd8a441254aa6be674a125183ab0d3af6c3bcef9bff92e64f0398b6360d07c48beb0293a3b73790b42f161d41b184aa1b6c898dfc38a5bab1320a28af339b1b53aec3bee52e55aa7274af34372e089d457c210504383fa555f744dfac828f220511080747df5be53f6349c3750b5b8787b9b74670454702b11decf259e26d53a71b3ce1117c3fe226a6a70640af6479b162f416e1ba400524c51abc74d83f8298ba22abe59d0e86f8cfc8bdf88135e6fb87a37faa72817d43e98e3144d8e27e4973e76d44caaa56fe39451627a238a639766f2e0590d70b09c24e00155491613a810229e08a59099a3869cf6a41d36c593c4ee68813dc1d43696289d42b7934abdc4a2b7e8bdaab35569d5346d432d1c1648751873e6167134b1772738b94bd491bf717d8b2f7fd29a798261051f6d7b16ef9f8615d6faecb86851ff5422f61bb6e031fa03604806a7dc4ba0612ba74ac576a4d1096ba759ea6c2ddccba16dd48d338b9027a22be34c650d21c1dc07e2cf4acfa2701e192a99fef600457dea54efe25036e69bf7d89b9e6b00c22bf26cd60f59af269081add21368d94e084b93c9c4b757d8d6a1ed1683a14a66a7e69a40295a23039a2e3699bf877b77bd52863c3159f8ba0560220cbdd7f26873dcf2bbca4ff5c4d3fac439223e6db06a00764bb5f543e4996b361f282fad3434077c0cd0f7c9c7ba0bb7524ba424f1627dd86fe27c764a99b2aff669cae9931becdb6c9c18cc17327a6d59c88929bcc686d49731493191a6a560225a0045bff1dd9434b8fddb1f2d25c101352fde95062f3495222de6cb9b5edfd002e76b4788fbc3e273406cdf8218134f46dcfdb5c3649a528e34e0246a0cafa72d6a95615831a61f67dc216ebecfa65f56d0e7f2d9614dade0dda6687c98376c2fcba247d149bebd742502be6257b80368b732df38faf7904bd13d6b58c786f9936a4723e3757b642abe31698f0da91e7729581e4ff930fcc2f6f61f628dd0974be4be0aff5430046c9b7fca4a818299a52aa6fde9fdcdebaaf9cb84cf19824a329543b0a01969f95b7a77b1b6aedfad4a6085fcfe7d4956056ffbcd4dc74fbe3c26746d911f563e48f21247f61055b8b8d540931309bb66b09a5108dac72a078442a5385dc05f52264463a75372965967ea74baf305de0ac02221d59b989cec9045be911523277d299fe03e59cf340467d23df6d54b1653bd0a7d253eaf22338b83c9454faab25d7690eb8a9bc53d96c957c8eaac44c55dd8d4d659690cc971b56c132b1745ce8843570adf143d86879aaa46a4dcc8d11731df4946f26bba6230566de3e520fc8c2cad7f4f8a123b5fa90df3465bc2907ad09216bdef6451958736ce122d99735b2d9761c48cd568abaad92e885c5c3bfcb80acf38d76db1802d3d63bbf01f651061fe8be941ab412b002e183d4e5150fe28a5a437bfa3ef338f1651dd12e80e148631d48d974905cc096e46774e32418abc0c8c4844ee1ec5fd558bb71188a3a8bff0c495da4be7f1192025ce9dc1951032548c2b4650df823c44a6632065b9626e349649e55abcbc1c2886bc9b2617187ff57ed60cf0948319fa05cd4dc81b3c4d5aba7e463318e4b0757f2cc52def6353a6f4e785c53820def6e45245bae401c1b13c7a67fc9abaed72f4ee868f1d732f99e4e388f65d2d50966d84b9e598061673025b9be74f7eacd839af3f2b492d8afd4a1692e36fe182953469b3e1cd7610ba6ae796c5007f64d516bfec467b1ce8a303d0dff2e97691187a97b8891a6b81796ba7718753d74fe96995a195d2b056356e946a22dfa9304c2f1f02372b8ad924653f48c90f212824bf7756f5ae398d50b9cb439dac010b77a765ca0573fe2d2f88194b46c9b130fda564ece68af56a087fccde015265e49aa67c0d00f3154fb50322de75011e69d1c9c4f844874a2c7187b572a978f1de4e873c1b3c523116d563558133f21f09762332a3b02fcd3d6336c5494cdd875b1e4ee61e293fe2999ac86eb765b99e101e2c9d1c6aa4fa0086d6a24c9348f4c06bcfa46342132105472bdf6dc723d56546a30eb5a4d75518d2c62ba82d1f68c1b164314fd6066ed81b48c94541c847b8364577c58cce2d02c422a9d818d6b0f63401baa4ed3138cb6d4705ebd6b0091a46442aec265d0744ea5cb0e7e41d2953cd1475952253386abff5e6c042efd5a029b8471cb5c8600e2bac5862c12258924a79a0222b053171da45c01d77580a8c6bf2ce49f4f902b9f15bf923ff7c02701fd62f2b480396ea778028be42e9f9780017678797f222275442a0f894f014467909a8548fc2078a291dfd6e8483082d8fbd805b147b200741202855eadded19e17594c22f9f016c16a9b441a7b4d221c1be8b82927fc26e5b4620b01f32fa864d6f5ca82a6c4b7759294c3237a332435881f21dad3ced5e23209083e778122bd86c28ac58a906cc07cc5f2fa40e40d135247da7bcc4352b0cf703e00f692750e7a73e0dfa0f909878de1fb7dff43554aeb49d413126956d1b8b0c15f1f12e366eebe394520daf071e0eb964db8a6930707206e5990e61fb8257a70d58e3d789b30e7d977fa5231612e84a4cadc167aac0660055952bf66fd7ac7480704539d2ee374d3060567934ab1914e3e520d10cc6a38af8af560e528d23b30b7abee9086cc4e91fb8ab97f69321c34a5d20bc904c32a24fafcf3d19de525da9078de43a0317177a48a0d4bb6eb03688c99068696687362adee054e8e8abee46701bfbf0d9d95031c5851ed6d793bf37069ef4017b3e9a49b720acbbedaf8cc1f5fc505e1bd7db115634566c2534700d143b01f58ad027fbb4ba10b3847ae1ddaca1ebee871f4f43fffda3ae5011dfa3c6fedfda3bdb9eeb2f9093f49a78ca04fd1cb52c7694c93f0d090cb59de09173b26e846d4e591622d6257e1775d7e2712fbffd0a41408585b0830d566d9cc76d4ffb9c5c0d9c0832e2eafa7eb3b7781aaabf758865aeb4b75a62d71c21710c0ab2306056a4059d25a5a072999ea50b7c02e4e7918180bd065407e4b214920986947c002b2b7bb884ba42540fa911087c172a810b50122c6b76084b793f14cffa724d114c4049c47726d9a6940da2a8da4a9211495ea5022bfcdf93af2d11ebae4871913995fe1303b3ff5ee5cab0f6aaade1fee8534d15ac4171492a885ea3bc8be9a9bbcc03b933143a1a8ed70db473e872b5550ef0056952e88e27e7e10a1081524cbf45d1664a751693fb52a8eb251a2461d98061576d0f00502da2a686d0554982204ab9b9c324789e854d08edf8d4b1bbb7ba5477a8e2da4cd87a6879e904b130ffb5001ab68c5e9aaa59a92d1318e6dbb31886a95868f296424879df0785c9550545ccc857da849b1829542e738cf6aec6c4521ab3f20dfab9a27471a394c23f1ce31c0a2ec96b4ec18e11e669e1888de64d34f2464c46074b7b3cc947198360780ef38f1feb59d91c053eeb88489351685f9860f15db08ceeb9f96c7b445f25f6d98c435ecfaf457921d03e05071bd2f1a812f64d82db4dc6cd1249a565502c6d045bc1a1387fed9c24ccc5c4fe98b086862f5e981f8ed17870a6bb14ebae117de40a75112e3b6ba1cb420d856b82c0feaac3357773d38df8494657efaf1736cee2f1df30eb00810f959511f1839203c50f42e34f0c67baad4833da8e0370ce221539a829043a5138ded465b2a06a717ab00f7d94710046e31b13033dbe92921e2fb913d8736f10c8ea495c4809b72b04784a3da4ce9b13fc6162b867a61ddfa37dc581422a45d3dca020c95e1ed974bdccee0f55725176a1924b561e15cf960e503c97ff591caab065fcf8f21c89fe54202fd799a43c96ca3ffe247b2394b7cf3bae2992a6ed1741cd4a0c5701e7c7681562a4267ec92db4022f46fb1858db88bedf7ebb089e12369f553c449955d81664dc26cd0314ae6c217d91e080e466b07950d3ac5d983759b176ddce22dc37762297038d348b54b8018f955bf4a647469959c86925389f2637a046fa4eeb5d927973abe49a4031c76f0fcd120c08490dfbae127ba6d50bf3a754fde660bea7d8ed87bbcbc7a1885b59970f07770505f00b17958c1395a74cba31ac04906ef02bb52066cab6017e2611ad2d5a11c1bb3d687412636e218dabb21964a50976c4e14b894e8b0e1e43dcd6aaa27ea4ad252399250a020823601dc15d12b76dba5196a9b8e523ba1dd4e6555a4fca62ce9fa71803c63a6a5faa19aeb1816d5b3ae534b44a9b58b9027435d020e535ac481722c914120473b91259b350206bcecc617136c1d1ccaa4412fcc80000aebfc6c3ed00f486c55e339208acd4d75a8bf6534631a30ecd42a08739ebc788d81b6b740126f79988bcce5b1730988cf85cb1ec3d49db6fe212acb91ce5ed91b6467bd34802b08cfe57222e3e670515de8162cd1bcb12c815d9f360dee239d8fbc4c64e671473f1ec32356a251b3a34527a0a44440c222bca7b25cec4fd523ff692b5ff80e309a1f4d9254aefaa46eb2d45661070d4c1ba70c7cc51a3f7cf7bbecd2287f31380438cfaf74f4deb62b3f308f3fa7b2f95779311ff386ffd71843cdb5a45c628ec79562238a322cc6afb8120ae3b434caa3c352b0c6a8b04cc68a9af7b03c46688df855ad90220654cd364dd93e4699da98c4bb4fda37307aa3a1bd2e5d504ce6a960b26bed5fc845ec748db0760fb5ea574929fc6ddccf94b6804b0a76fdb1ad8f6ff97fd9bc94ed34c0ca6b3f883cdfc52e9185fd9e574fa73f036563852d1924a635628638ce8a509c33d0ae5192ae581f2f0205589e545c2982c3c24e2a2ce2270fc95b62b65a4105fa621818771d3468b5c58687daa56d2089382782488f85969cbdd7a17c999c0fb7e01c91c5c1c3106a32b7bef3131b945eeb16b5c14d6ab60e295e5805cc52637560c1a872508f5e0f3c08a067dea7118619aa0896ce1ceb979a710b9e2bbc17a83cef70f9fb8e17578c61557cb8017367136b397961a82a8fa7d37421fe070f68bcb6e151688aa7749f4803012eaf0f2d50dd899d32f5d4419d7c7fbe767ed7a44f3ea022afd4181362f808062573ff9b172f84a5aa426e66f18e691727ba9310b269f2a43da6ab6347e27ad1db2e3f15c7972e107abccc464f6dfd3d08e0e119b7c434507275ba9d4fb300935b5941b022d85592fb208563e20e2dd24810c2ba8841b04eea969af085941c0689a8245c79bfeea7640f53a1cefbe0253145975fffe89ec4abe4338713f310657bcc84a021e35f8eb9e51dc4e14336272663a08af5150bc8b71e601829ed7a255684793e9b06a77681a7ad9a1a001977cf9c3748bdfcc2c77a812b028b2db9d46ce341e6ef6d96deaeb5ebdc7fc6700ebfdbdc1cd25bbb00fea7eb0e910e61ff736ab56844abbcde86428f45f1c6ad583f9257d2e8964db9e90b1fa72778fc02898ec9e02ced6d22c2427c5f497e1bd8cfb5b469a940606ea1bb1aad8e73429b26f07c2b697bfae9bf8b64ba85de219e67924271d20e4f2d0fb464cb7582d9ef65d4f5e89202959fcd77832fe3081e74eb3e4b232c6c377733432fb5567d9b38fafb3dd88c674bfb222b73eacc66670cea3678d963d907b15740b9b45ff6628da799d86b857239f0a2c334f3ea8f6dadde0a9cf4b018c3155fdadab661b0301372fd26ec1892a3db86352cd72bc56ffe4825d768c5a79dac8aae4f5c9d2a7d5d72e2fda5d915188d2a2579d30049b2e9a42e6ce2af008dc4f139f8ba293c8690ea455bb64f514692ec0b7098be4210816ef8bbbeff759193c3097eb4283641c2753660741899f97d91c3d1581475a1cefd46d24c314b7cdc2dd80333dd62901cd3981c6c8d71b507c74745bb6d6474b2736e30c4c863e8a42cb10d850dd280856d2c57cebfb50d190414557e9836e95090cd599a3164aa281317b55cce0604634824f8155c615aee7a414187491b00caf1f63c3ba4e50e18411485aa2906acd31303b9403f31ae7d983952cc0dc5436343aed0f41ed09511fa04c06375d53d2cfeb49b16f522bb8a5f7af755f182bd02a3816ad32f9bbaceccbbb099f35a662a8d8ac43d279a4ef7bc5f7a61bca549fd5433b6dc620108f65e2d1b21905b732e64e1bb00cb51155dc932b3a6d5e8c702eed6d6e1c8a35030b8f45cfa2a5ed895dc8d1666de1f6df55db55a3201bc8a832e46d7f0e8592d76def18ef9285dfdae14505b5789f3cfb4e4e5fa4051f0dd93a03b2d3f731e77bd1ad93166e32968b8e4b802faa890bb5cc33631f0af86634e8ed396ab8490b979c005426b6fc5d78a8cee25f0e6c546f2baed71a03473b5567e39aeabc7e6f493bd45992b7c50c0fb96d46ddf824c6961ef33b1edef0eb446e8b1965479838e6d01e3ec1517ff38db0b9add8e17cd789608f38b06e9a6a5a7acc0dc7bac96bd0842fcc9eef6aa04dad4bc11240a5ad5e924e651328bd7cf360951f870237d7890b113f9373f3fca2197247e62f5a1857db5d1bec4b856a50656a1c18e8241736773b2e3723295cf8644a1319686ec988d67d0e054b636abc976c3f6bbfc59ecea1f46328f16e41c4d2067c9742f0e87ba510ad9496d04e000e82aba721dd347557226e9378ca275680683be3ee3178db50e30a63fc69bba7dba2fa85aa28ba797245441ba367a2795ce0e6b3450d74144991312f871655771b7ddc310e8b1f680485b6f4892c59a1177c05f63056a891bc3055dc7720a83f585eed1d75c03d0a807338dd558b8112716e5edf797c8bc00a65ca319c83a80aaca8f0bd794da50f01654d79dbf4980d951bc3db460a1fadfbcc9c2c4db6708f4e4e2e95af4bf7dda5c3515be3bb44082163d5b340745442ace13eb73cfe35834d177359022fd04b3233af577b53710e6fec36f79c4f30b69a0369d58f0519fc26cf8e3c674ab5bd55809416fde0a2b1aa9355e57c4a2ea81abb6359a9d25313374ca2542caa2e814dc5a0158011c8be688a9ad291cda7a90175a0c038f336cec9aded85b797da10f75ccf733effbdbeb9e619af3da2a57c10ff20b160bfa4a0863ee36ba07e8720da4bd26babf7bd97a003001f6ce86e1b406241dcf171e8699dbc2eda696dac135371c3140e800da5e390f7f1bf04472934157e827705ac9a316b667e4d40673079aa582a43307537755177d58769adae52f1bd1f34897071eba7908dfab4d66abc6b4d14836a7b0c05159cc80b39fec4401d51fa3b820e6f7b5a6150a0aefe3dd946d9e609a5d23aca8207c0c2c31e91c5e0c24e61aaf142c42520c8fa920eaf8813060f39781ce300302c71acf4141cf897c782821ae3fc01bf939eeca6700540eb6ffada1f6abeb9a7b57ab584618a0ea5d113a901d0748f2555785eee32bad03018736019a1612b1924da14b830918d25941033f9171c9c7aef27a3e53bcaed403a500b24b5ff8828554706221bfbdaaf5d0d05826a067f211c949143e76f06bbe3205774411c693a714770d28e50060e630633c4f5eefc72a031592cc869a4fb4d1b94931fb1c82af83bb2c1ab369680b0469dfc1c4b67637e1b0921748bbad5750f1b4436a3253c3bc0eb785f2cf97f23f1c981e681b6d85f980725363d480757a58bfdb984cb592ac709eab610bc34189bfd871bc5c2035e45320b3696070aca618cc348dd779829adedeb048a24b629a23c4dc0de1d8dd4bea80108e3999dc1649f8e7703e61ad37a8ce402a1dfccd60613eb2e266ff37dd1f3c7fe642efaf399e774fb5fa903aefea4e9273536ced5a289b8a805249df7759e33b80edc1fe3399f49f5e639f50af83b0705b12b3084e24c56579828b495e56cb3d818751f41a0dc793b9c662f109570a3cb2bfbadb48b7fc0e64c62da0cacd261e950fe20f70096c1790ed05de4578fd3ec83646f2da8d053b3434fe740e164c9772925cbea9b32b2883ccdbae6bcdb4e569fcf2b59d9badf9c95e2b12b00e2eaba0e6d1c9427473f62beb92830d0bce0228e7c6f86c37768a08c93cd64a43ebe032df7cadd57aff524705c2434a9c52794b25b58b272417847742798a3fce053400e84b9a02b94cb3adc7352e50fb8eb6d0c831c7f18dc05381ff6f307e81ebaf11c9972da53515b3c1c83c554e452a3c0e23b2c04dd1ecd360f0d659e72e15d7bc75f65f7c9c30401505df05283f84965ca16cf983c9e2fc6d2b194a006b92b148b956d7920d4769256b806f20c64859a524b2af6d47f3471b733f06083bfaf0aa328acd8767760161107a2fbf44d1b4da93e19632d8c092a03b452b8e355b47366f0ce44d07ce7dadd4c7e6596b79cd7f9af8d910dcb71c8b22b69cff629b1ef559679e72a622e971cd9fba274904f6be889a615d41ea230444db24391d4b15572c9acf2a8409aeebf15c72a4b0d9b39c5d52e82da799fa579d4fbe2bd20b71b701c29ef2b4f08b5fa414a0014b40377fa92153518fe091a130322775f73448fa39eedecbe80c6cd332cf74ee8432a12bd025a10d3410919625ecb95f8310ba557d3f14ed8e7731e6d9cacc8a7ffd7b265ece2077decba0a95246102add7a8ec4fef73a4762f5de8320803d385c96dfef62306999ac48565d15a32a919e02c0b13bfb58654d9edce1085cc8eca4bf11de91c89014c983648a290a6911e693c30d163b677e04ad3257b28687fba51ab45264bf925d994b3ec10ea50ac2055c2cbf7c3e31f541306457605f99d40437fee0cdf54264f9a6d6f34fa7e4cb6f00b3eb3efefccf91a0e16d8995f9c731d30a13e4a0c1fc1602d0d0832075470872aa3ce4a6d0a7c071fe99c0213d29878c23d05a4a0aac2cfc339aa010ec8c632511915331eac10c42cdd7812df8afb895160dc51b30f51d49cb9ae88904a4ca89af4606c86325f286e15804452938574a541743640597b4dc1444ca2d9401cc455ac2b1740c936e2039a9ea35741524a88d22a4459030f5e62c4916445865fd050594950cfc2d81f6195a6e054811561f4389049c54b9a63186890f79c1e4a43d4815883f5065617d32317186392c5c0ef64fff81848d45298fce425e202f012afacc46c59b8c85131f812cf46c2b1f872b613a5e5a0c02f10ff742e02cd29cba30f9f7b85c9a296c28a3b7084129be6a00dcb05c724b17d959cd56afaa04aff4f362946d822eb6c6bb59ef4c400cf04d981125991271554c4a124e30c4890be55a45d16e9673e3f30f5d51162103bccfe7b2778c76c63801939116da462bdfad6093ea196001e698202260739559ffd79ec2f767f679dc2b68daaff32b6e9fc142c8a8da1cb0097202e6655082bd71e4a470a734fdba57e1195158b12a099f59afab45790f795e6bf2c3900ee7165de261f6c1ecebb14f2f01d79ed01ad848a90151fdcd92558db54b653defff1b7b3430a42451ecbfabc43d8ad1fde3bc444056a055c2b3abae14a0c991a2e0b8f1a6168ff0fdf3efcced9b9209f11dccea5382434b7878c0a395ffdacdea0fed51628232ecc8bb134be464c6d02c8f91d6cb0ab3dda4e7cc29b07b5887ed9a2cd39f689081b9f61b3b34260b8239113db45912ce6d5d18c964cd3729902ffbafc3bc3f1846463b221af38b71981c0c575016bf6707b8fd32d91624fe4daadc136d11af2dbcd4e7dd6718c13f9a08dee831cf0f41f22f2cc3bfab49c1c5eee358d714356dfa7efe49c8d833f69e3adac4cf46c8393606f71165192cfe67809d45637bd223abbed8ca63fbd2923673f45363890322f624bc0897d5c56b33210a59f8a369ada30b8976ce00ba3d4837ab00cd65c568f5c0ee6d2907090136eeb70a5f1755e6816eee74d51d39e9fdc244b96ed45550127ff8d9d9e7deff2386733ace8062daeb3e028455854fc992e9198091c44693949356310c04f7df476c16fbdadfe03fb978dbdf11aab2d6e2079490e980c1487d1e751e7d8d892da11eb0b3e3bbdf5bf403dbc4c12784de1941530e86990c4538728a0668dfda43000a79fafc9093bb9446f39142805bdf19a3c2f41fb3326e853751a1684fff93b534437a76988e699a214191551422bf7959ac4e49b0acba0d9904c82c2a5a677c3283bd0c627cbf491180bf369ce894dd2477c26a071c1ebdc71922dd200020fa5d1aaf3c68f8d57b4ca9be90ff23b4e26730817c47a0a2493302f91a5a405bc23968dbd93b5e18269854d459889cb108eeac40b398c2d7cca85d705d6bd878033cca24151965185ae97268779a3be2b166a87c1c92d3e87904d93f5198b64558ec64fdbb4d81ca5b15161d618f4164129f5448ec8bc4a017db75beed4a807b8fb1e45a52429c4509f52a7a8beb524dd7e1c0efcd2ce1e7dbd5d25fffa91694c6b6267e36ed15aaf5137704f7ba26370b0580857c8b6cbd0bd3754e47c5770910cdc2511aa5066178bc75c7c579939f0018a82619aec8671b85517e04288128a5451a009bf9b00b480960e7fd0ce322d386753069574e1159417a677a74eb9f6d46719d598cbfb78da040c89b33062d441905f6ad711b580124637d8311525910a2f15da35b85cf16ee4b63ff35bd31952798fcb645c2c02059695892caf323282b70ba79405631480c886579e0c3e91cdc463b2d43081da8a3670b7c62320818a07b68df01b5d56038e162d3728bd3840534e8e8d60b360742d4e638bfc90a08f7ebbc02d0735db8ad259059e1b6035eb40885e4d4e67b9adacaa2da338fd1cfb0c4f0972ac70808c02802b8a58919d08aa0aab95b3730fc67e4a03b87ed1a8606f934a58e095d8a1f762dd226071792ae5e0052b27388736e9ee3a098d5eabed81325eb98b395573896b8661f5fbde9171981f04a1041da243ee960fbdee1e634dd3b5d6a8d907474301c3821149e2a9db4f2e878c22eed1f4d6d003c9623b591c71e196c4eb1efaa7d38b2fc2d256785ffa86047e3943e34a7e5109244eefc30a8de1c7d5503800d2c0db3c2585b73b61c8088e39cde54fc7165715cbdcbc4b004335ec5b319c93302fe809f1887cc319dc8e205898e45f39cd2902b3bcb41e2df9d518e6493c1a6a9e4667a331fab4ca605fd8c699d1782c085895572b1ed891f18c12c09d8f13962a8449f1718f14b529b79a309868f40bd4592d4326772c6e852793f0e28a92cda7918189d0ce7fd116943964778484e911e2067aa90108f14c2153031505a92a9efef80332fc4484f029068184894efd54c7f15c0c9331fdadda472e1ad8376a406c397d10e865196fdd308296706b428fc70aa6cd8cb2a090b8ffca6e3d7a75f153bad7cd1bc3e891d84086ec02731689e5de6ac2c2c3565f3bb16ed1230598e367a815fb282840ef4fb48fc3e1ce24fadd7392ad1b33edeb89ba5edc9438cdfb11d56c620225937221855fc6b94d18bb459fb2fb218a0667dbe4f8540e1809ca1989ac9113504c887e4bd36cf10988a83f2203ed25884c12405a1d0fd2ad7721e4163d26d254a247b602e0cb7525631376140a8af84133fbe5f57ae183bdd7e6a3192027375bf201e45e0a38c58ebfc83cec52af2386519a8a1c22873de1a9916636295bbc8fda5aae8016885a516359b019659f16c884fa26fb81182080d00360690c669863b66e61f936858050ef28d1c4853173ae1cd992bbd253912ce03ad8d3ed02c219f854c54e2f24a5ff4fc9c2a5d3fb2e9c4b90a2ffd6f5efc82a245297d89a2e19755a773847b9b65ace7b245de542aef594db563114e0079a4e89b8d7b844dcd11a1a5008e37280e222ff44bce9f2f3552c9d3ca76ec5546b8390e1556143b11419957765af818eb048e067884b94af53b66a5478551d3b1ea513fbf51f78dd10e308dc1662eb47012f3f6e4ca1085a7dc0491f19179a20ef4d4dbe7e9f9d0953c98659461410b76ce636631503ba55cb95d2d4a28ad04913863acae09bd42e0d0ab297d16503c5bdb4ef9de466eb52220c457c18356aa58f360406d059d7f1237e69124bc876f17cc37e4b1e668fc8814abcb5f204d3886ec519561c41c7ce89a94e04bc9c4fe16bb3828e5dd928ca2573fdf07aea351f2630e5747c8cc0493b0f4543f329ee64d4ce57fc24196d8c2f255e6d9c1212063861e1217d728dda92da6932dd49721387ad07b9ad03ff55c291bc1172ce7320eee7d39ae6d774c0ea1330e7fa38ef79d5aeb8d36c82c8af296afc9590fc6c2645e2c558763a5be1a750779666b0994d08a35170fa250ba54f18d2ebc0f4311b998fd1d15a348da10145cfc68e7f6db433900e1a3b5f1d996e4653665fb8ba79a18e44bcc3d83fdb52784b88cca80994743c856044708a47847c832040764c6795d64803c858361154336d8de2454bbc24a9c916dcdc89e2c0e0313cc0a640837b8326f8a376c403c2ad11abc4e22f7457cf7ebd8a421228e8f60d175069c6b4c84abf1cab17f212e44cba8e3c63802f7762ff5e954352363d1f54944b8fc123d3dde20da549c8e2d56d3784fea93c8eddc0791f553c993dc3b20ad088e20f3e469190d6cd01acf4f6fd5a8937b761c40f27f426c28f414a719d31e11ea8e6d8b1edc9d03a3e66287502a7e901a36f311492ec3313d6efe1e580b792dc5695a8f122ed142d4cf05849ae57107c28d4650bc480250b92a547ef497602763e14cdad20772bc7209f787439f53d9780ef48e6436f7e1017753552eb222e115f157f202f08eea1ff8d4e132b021d600e579ecec2551f44685a645500b3fb7d96f645a06be9344314f44738cb94dff929cacf7d51ef3fdb5b6cd8b3563d05faeb9a1f41e7a873fe622bcb9056041c98c47e61c52149f96dc9034ac99dadf404ee02dc0a9c1ea93d81862400f03d0fd0c61394748472f613a2faddb723a896c48a6039945a42a811bef4ccbd2795acf8d1aa64ae815b2b352025621c32963d9b4d3356a0e658559afb1c5caf84b646a826331e0e6ba699aa6c0ab5b1a10e622be199ad14282daad931c6b02db7b80edddcb11a616c3880b62a579987711d36f6aef6fc6493485d9e3b703591f6e8bc2e82b11e2ed002f4e8a51474491a494e0ba87e1e6e913fd12d89fcd82e65655eb39443550723928082261a3de1d8e067d6f8699663aa153676a1591854704bd94edd31278ee4dc524a73a28c12039342645579af50ab5691ba5f34f5ba8697118a4d9c5636926efe535701bff43331cfc25f6cba8728a348e10b714bef3104f93c429729b52daf6386101676c14a0013630998d617085104295222e3e26b6f5ecfbe6bc29d776accce90695b1919236c88d8e59c3c62c5da1049f54ad773bc69ca184542726cfbda6d790421dfacf75b4e3f85228306ab21b05ffb25ba1de79d6d915dd23c510ac4337c78fda5bd2faaa8926b89d612ff84545d8fbabfa08f2758616b6d796c4c3246a2fc2ba93b1732fe290c79b2fc4a64020ff3c49afeb3e75c763a4f69ae68a6c0282f967d62125e4463f9618a426512936cddfe1a14948c4b5b23c9c16354431cde981044975ee83ecaabb2a61de18933518e56ed7f54380c61d9594bb0884224b8229bb5b03858688998cfdc6c56b6e36fbfd86cb485f5ca60bb6c888de80f6247105849bcdd65ed21bbf195c6b2b3a4158e6d7499ce5141f0fb8e9908374d7f321cc6f38e142638a9852c2f5cc22f7f1202342cbb2921cb8721d10a44df8b22025b4525f914f9d7ce208e5e9bd993b41315c555f84200aafa3f10189dacae82fce368450ae3e103c1a98fbefeb0296ed27db2b429cee84ef6a9bb14ee895f0b916f8283824d08fb6823850958e6b73d25b05437ea94b23fd01223c9f1fbc82764f3523e52558c8b7ef95ee531f39a15c2a6f8999d0cf30e0c7abdceee2b603dac090d96963f03943f6d85becce3b470704af2feefde22162cbb892caa0882de665aeb3b958bcbfab09cca6f145840ad6dfc588d2d643b606c7b9a33c1ec52e4d195a390879c5abbfd3d4297e85f11c0532840d8c2398a0c7bab081bf6887c415aa28ee1fb24c743a9395eb8748886c0630c191166bc60c64113272244dacb917a9b04a1a76638776443dd27feab2e7b3c7b2066a7c4d1a83786f2696003253e463e653b2cc21e36b3311742f22977285127f9650d2af273198a3a09f5c4ef4587c8e8500bc22015940b566b4f859a07cc49aab416db2b25e9f8bc5df4db51d15a0b628f03bf82904783e492052efd0479cc4a504934c363b085faddd9502469333844ed51dd91e50636f25f35accae352e9bff265a4245e1d4a7132e9349bb00975571682707b1042082eb121eea2da0d2ac4032d4aa2db4603aa54157209a5a8e0b0283433aa5afdae38ef5486cb79ca34d5f62463a28e04c5e6586fe2a7240d27018eb4295581f8e9b4eccdd3921d14d41d67262e3c1aa0b08141e1279d96272f1a8dffc7847614bc194deab718744a984e1a5efd38b7b8242e152a9d0f3519e1c777955126a7a00be80f90ca550273d08603cc4bad30a36c59e545d22f3f5d00dd7fd964c2011a76168ca4e681c4ee0c1e0562498c13bd32ca92496268896cc3c8dea4f387934593a9bfce91948e78ce0c0058346f2c1b77b1c4f38283113e57495fa0825741a78b05726675468585b9c7010b42d1700b85f0d131f0f7e7b2ae8a4509c09b09df2ba531de55987d70bd5d31623bf8d77f9c532808bcc611f78b3ddda2e4a042f906061beb8b217aac230209a1f2e060c145e249cdcb3b2eb2d7fe8fafa12cbe630e5b2dcc60eb265164bc85aff27b1f875c3a5b7142b06742e349af1024085905d67b7550d6e775da89756a497894451d1c73af8edc4333b60e10c51b8968c30a5a0107718e1421b89df7dc270f73653a448be76e8725475bbee7b8bb58ced24329bf7afe78f681cfcb5faada2396981826de59327255546f9f47a92988ce988dfa227dbd0198fe96f0f7b776f8f532e3d534702346f73b780789c2b8868342099390e5f65b1981e5cc33ea255bd96deb1d079ec6a15296a5a20da47980f42e49bcc2fd48674f0f6c473f2e2eea749d042938cac486826af0ba4879f8a4a6cc92fb30b929e97f711c2710f3683f3d9c525fe0ed9a61a186294894087410296a94b61e0e85429edd007f3d88b24632cb6e3f7e8df8c5e27c72ff055b20601472f5727d6f0094937f5103008e932969c910d94a5ece242b735af0eae7e9baccb3a71adf072d25d2d88d54990142be4ecf91a8ce684b949dba0634f887ca7b71935ab169a10021b0524fe799850fdee4b75651bc6e8968d33971a0229e39fca932105ce49ec0198481652e5a16a123334c8f575460646593f2d4b71f50e1f445f1130a3700786f64c815ac01cf822f92083913c2a81e075eb7c0f0420b275b149a65912bc2d4b10afefa21aee309d702cb2b51715d54fce35ffacbcaa4d10cd43647dc7c506cf861aaa06cdd63522013460667b6acabf5f4d68ea8ddb668787dd01b55ed6545abe849c22c1abf3be2eda0dca8bdf260023ecda47e6f5fd508d6614a0bc07ce446b435415425f2b8c0277e553a7fadef744bf9e81a56c78a3191d8152c1e18a17b8e10b8a71db20413ff69c4664161c2691b171a694bacdeccac8d688992a793cc3000154a281682975f303a2f6d0c1eec89285853072f0c3f93423f0df6bea003c102b07f2ed0bd08c1945e193fc0b10057d74cb0062b0380a95940d4cffd97a1cd7fc694afecdb7d2fef3fa248b3000e740bd031bd98c99864eefc7da5f2b289fe4d555bf49f136fcb6a01127a85e5499ece29676c7978a777b5ec6fd13a0d6c2840f976820805547bd876efd072363d865c5580623cdcc969e95da7a287f727b7c3beaaf681730788c91d13eb3fbf565a6a7d42c15dd85273a8fbd3f819a036d4ec5e37cbd9def065d00be7d34ea70b00504093cdf01168c56f52c786ac9ea23a57952fca8aba27aa6f4e7baef016129edad125c9c967ee4f8ef3287cec25a0911a47386a2ea6c89058a780f71cc7de86ba982219c478c68ae5ddeff428dfb7a6a4320c36ab637bcbb01263dd0133e6d8acb7c153d569090b0355ae09666483c2ed0c4e4d7d63e29f70f40bbb8b5cb7f06df40124a32b8d335a8577880ed1e837dde0d010b00f4bcbfe20bebc2ccbeb4bbe7ea0f7326a4adf7a2648f3187ae24365e91c191f88eabdb1d868fd0076fb827551493ed4306c54c5e2f89f0d1ad6f5022fcbb0040b03d5ece3a3ead037ea8d2433bc928ea1d77b0b9b39c2cb1c355244b188e7d4cf4de5831c4a72fafc9e9f89ffd2a94f7e68c4d6a42eec19d02ffc65752568659751de5a81b592c57f8b47007f50fdd01c354d57f69955154b1ed00d39c6d464c7bbc13b2acb86198500d1f58edb82da5721e0fbf6781ea99ceb9c2206ac37f8a628aabd0ffd36c14d087c54a9b16fb8783214bf9f1101588bfbe99104ff0747dd7d4f3687e3e65ae5a7c6b3f0ba117ce38872d378a6af300e1335bed149b9e43c07d0d6cbe08c8e182212bdf88da265088f529cbd678bf6fc3d2e270cd0126124a140e5051c969ea4147070e220eeb912781de9615bbae9fa1dd043049c91afa75cfb3b20aa43130ebf9b1fa7d8c5436ec1320c5cdef60ca6fcc1a4a6baffc1c7c868211add81e63e0b6925c28cf143513796eef271885c2f5ce83a59740df059576529fffe3dc575d2a342469474aa7c3f32c732514ed7101f3f496556ef0765c7f068d14f2d90d74918707b3531ffc023ef9f3fb098f0d59fe745865b01e5d59c429fc58353ed2775341cd97f501ab80f9345e69165e4758a6b147c84e66698a645e976202f9728e164e324269c3a117e3e6c543f1fa66c71025ca7e78303aa832ab0ccfbf375b396288911ac2de21d0ab05a79536fd3f3567940d607463e18a793f87c550be0a335bc44b1c886c22acbd298afac8d737a5b1a30ab37e4b063d4d21daada28152c7afbc6bf9f8ab323c102e0b244ef189f4e934a30ace1ae5d4d5acda32ee9fe84c1fc9661b8d3333608fd3c4fdcdd05e697f90d27131c498c9e190374038bbcf504cc4f681470d4aaa3e58c9efe3b102a17c1df6bf5296f1d19d7f0bafba75b13c9cacda73ef854e3447e0281125f64673d43790751483be4a3e490708506905dc4c2fed074ff96b9cc311996149ebf7df31c2cef81f9840efc46e72b97b1358f3d4224439602a2d333d06df47af5bdfa80054649eb43e8d1c2f1512f51fb6d862bc69f6297a2c7859cb2ef366a47ba4fe2dba220d04271bc41e835a13d7e100152be16e3034014367bafed000066668fc023a701f68d0c2ee343de46c8142e1361c7ef2b8b20598f73838fb3a8f02614c029f919c8d35aea5175d2ed6bfb2dd256aa2b71739ca5cc5dbe59df0a9a07123cc908a00fea922d4c0e1e78d125cb3a6b65986015a428ae8483120ae019b106d5ee5b1c950014474d0e69a6368888383cd841282ca6d0ac1d96093e6c13f8825de136b333c432a2b491b3a2f427f8f3bad64c195b7535c156bfb3d5075a2aa5a2fce8ee39504901e9d39696b1195567e79b7a1a91af090301543664ec60b6b76b1d002c32eef821dce153a3d0ab4aea03eeefdd4206a781376a395c1597d3e6a1264ee58b719936fe339c4c57aa0a35125b072205c247bf61e1da9b3786427854c893309d2706c7b210b71d89d67687da7e0907540b7fde869853962ebaccca98fe0d2851e43b54bf2f42a404782fdaca31c29398bee519b08ef34861a25810e5994541a0468a924595e5f845bde8bfdaab81cc67e230b9d75a8d1bed62669f5027258b3681c5b93c5998909673725e478d2c7f83cd3505a33a02e9e1003348d2081a707ef197585814bba8c920f4351bd24327d04747dc67118415edc23e5da530f042491db8d6a0ecb82b87e9659ea5240348d1f5d85365b18b88815306b5b96430a18581c294fd6569193104a7503276bd52509dda5a7db7d676583e7659d38aa78a58052ac61644a48d2c6e075720369aeb3d6a4ae35def2e12e74a65d0ddc2eb3c030eafe03a6d736c0adc4684b9344267b8b61df9b200b14706ddc4f1ef057a6eeb0d9d0ac18b4ae558366572c12029824dba6bca497a2af6473f9a82f0ff813ef07c23f052ac2d76b7133befc9b659f2d5e04c1be47f4d8acb3ce5c84eb81c4a4a1528958468862dea7e8c555a6e24825021a6b4982bcc2c322edd1c9ee7be21eb7bf83fc5923b14d62ea17617b107355670f769803197e0823382b1924e24c4509ef4cb4f01139c1194ff50bd0017c4a451c2f0a54806477cd8e708f41cf3d0da5c7fa7c1e01e260485d1f8fdc47ebba92e9a487df58ce6e6ab5c816ade8f2405f2d2518b325f031d17df1a353a89801daab9e3bf4cd4d7a8960c49b20a4574577fc61e162f94b3ae16705532c1ceaaf615862951524ed189bf0a99f29c46a59e0fa36698f803987638c496abf9cebe69038fa53e85126e98e3fa9037e45e08d45fa473fe91b0a9accc7cb0c7951647903510ad037f82fe328766c122332c46748c208ed7434a882be380afb489cfa02aa8a48c954362b81db9c517666d634a4049ec472a4a53f151c6a67df2e8043a59f2530d9aaad1e3fde621d921769db4fc88872a8a452aa510b332d10df25d1567d7d18c814245367615b57faa1db87e425bb5680fed7d247fa512016ab54c03ec41dd31eb48cee89b7ccd3ac0d7d815e50b61d1f7c5f7e5649a94f5d2fb86fdc2918807b2f3d9177b3632656316b642521f1004c0d4ce6b208117d51477f25745539eb56df676f9ace2493dfe9f3cde7543f15f64927b1c7ac5c0c0a1d1632766afa17c13c5c65ce670499a3bcdfdc4fa5b1b1b3c8dddd359d9a51c902bd36f44b5bbfd518570439bb1a6b897636502830c2cc7a30993e9dd9c4674c934916e6ffdae50377ca71138b6659067d7646ad0e4d97ba66cbf8ef39ac292f2a3262f0050c8bef2e92f95aab23955dbaa558f1c401d73d7b2a003511377bba5a933698935481391f5b38770d4513d9878bcb81d0d26d69977fbda81f206816eec2bd86bd7490fe9240105b9f3143acd7f9cc029dc9b94502feee672dce6d1231a088f085113f4d15300ac10814eea3c658c3cf2e6a4c2edb185350fd05b574c6780baeedcf02368d9702724251a4a7603b06a2a992a59c4649d20b567d81fd5f667b928d31d76fa5aa6722aeede4afa8e487f885fea162e547698c3f29a9cab8fb641b6e3222a55e95f124929b63526cacdca2f55eac60c54469bb82429fca549c470d38a825dc77561a8ba61f0b26949382c4478e7a899f45bbe48d71ca5dc7111f393cdf355383f3ac8c864425f7accefe5c7c1cd59c77df9890103d43b7215da0d9277bfc34eddf72983c5fac7651e0661a87cafd3a2df19408dfe05d2679382d02eea73624fb22ade7a4580d45549f20bb957c4fd32731257a5c76aeafc81941c656169cfb43b6f9d28e353fed2ebc4fdf8bb163eb2662525cba89b45464e9c62d92b3a06a796007594dded2352d5043a9925fbd45643eeebc2db0fb06aa553709c131fc269020483972f7ebb75f2a787f3c4600a734eb35514ddd61f5eb01d9b52e67c4c3a20d982a86e014b74af0e91b078836bd0044ef511f1e11de462e0eece781f040be4aea39231d9a909a7b051b5c294363837c6946bc3c07f0eb9a266046788f323f7221b326501e62e1044e7985865b6b32aefb514f4e83bfb2f786390e561015f308cbed514576850f1b3893944787bd79c1bb74abc824fc6a7df04e70853c8269f62291abfa2f7f7c6b2c00bf98a49d4e6a3702009274feb8d6dae69ad4867f1b375b2e728e2196cf3d051d7ee92deac28f44125bbd8400fe390524cc453a0dc41167c34a61c16da1fc58c1a3d7011cf70bbc28a2594426a4b318d6b551adbbc45cb8abd901686d8eef7c5e5dd8e9a574372207a8b268dd22db4b49ae88d7843610b5998d1613b8b38376e16c8274ff1fe87a61514318d6ad7eaa1a2ba33ae578fd6295ee6005948b66e406479ebfe9b0fbf36c3b0b9db4a1d2adf232b76f7851eab71abf0caaca84366b10ff21aff76585ca272d24119c50c1107cdf35ed6e9c36f60bde593c16437e1620eedc70e67421906a2702ec138eeaed890bd2054d9c5f296611b443570bfb99b506734326186457077a5c4e3eda2cdbd7f3f5738685f8e37f7811459ddb6770e488744549b742d20c974200c8e522c96a9b2236ca9b9f71735ccb790abd46627828684263a1ac10b37eb2869d7996f39939a281f10ac724781425494041b0cd1272a9379967fe85fefc0746ea854f806687089badc1b1c850c805f20d523d0966a2bfe0c4dccfdfdc88ce00464b89a42d943718426f1448f2aad939d78d11e7dd284b4f9cce72337e3dc2c6c622128c6868f8b946884fca3cc3d1015383a8619e0c22c23988413bda433f7b7cadf132c3dc2eaaa8196ccbdac2b6eef8e0e5bb77121c4a32b08d76d05e0f0b756d561a5e66a56c28b212e269a7e378bbbc0a514690da57ecc93dcd26ebe6ce9e4d1ca00d89e9681853f8c0973008be73704e9990bb472059a0f570797a5990c8a4401590b739e575425424780600d6c2c1aaa42cf7beb7910517b5adc8046126e66e60556fa81d82c5faf6078e8301f55308ac7098e9f9d787304a021026c4fe26af0b8d2aa61ebd15ba8c27a8ad847920cfcf42836b38e8fb8dcccc6d9fc5df77e0726db388a7327a2b4bf4d2f81b26a4fafc7383970d4ef7b2ace91866618cc51e51966eee5723e219c2c8da621616d94a1b829f96695d3bcf1c5e701a0e7989ca284f4f8a0089a9cfe2bc98cc6d46d732a417da1f9f46721151dfaf96bee119ebcc355b9b899a15a58d8beab4c3cbdac6da3e124123a68284f4442d9e18381a92fb8d032d9dfbba7aac84ab327b057fa9e33e653d391c1b909629d410bead3e8db46bf7a10ff999b6adbcf9c8b2b411aba15633e7564c4c9843ea3c9d9b26277b49c65c701f14cc102fcda00a4c9fa3595bac1055490f8b1cfde75e1c44eb15fdddfee6ed20ca9cc66fed32165c22b864921c69e4430679f1acf4c25732582a9a3e3bc2ccc524670968a041e784356348b4272bd756b41003026155050a9adf39c81aaac633c2ae453706b7c214d98b34ae87d4c391d049d357e62bda59599db6a816c84393884961ec9db3470ab2bd99a2db2c81c350e6b19d5f8f44d1a507b7555acf83fe2b21f30f5168348b07fae23d50b77b3a3aa3120e9acf650aaf77c526974260c607c77f7801636ce2c2b3decd2c4f2a868c31494a3ae1a9b3eb0b80a56e231e0120d57f3803ee5e050ab86059d9e5a094f9126de892608ed3083b121d1c3cf4a2c113eb7da787d279d9c5087bee5e381cb7911d7547e75bd2a71d3229f7fae4f546023b420950acdd6dd900b9a5104b2673c2b7a69529eeece9a10a114e741fc4d5db5cacea392881f097b934399585e34b1cef20d05e8d992ede2848220a9c770e3f706576e09bfbd937d115d5435bdd6770a7be580df35477e48243c7d67da58c4b3f113e30eec4f995a36b58688b0c5397f76c3e11de8ffc22591ee4372568b076c77b8cb16918bf835489abab23a12653671bc2f30baaf4e75b74323ba72bb83020c7450b0e526f5ff2d46a563843471c5e0a20deb84e94aae718979cc7f80e2dcc6e0fe275066ac398f8bf9234ef31cfe0f7a7cbfc8a990231726a270eadfdde087b2c5ebb5e4767248849ab0e051ca1240c826a6536c1edc907aff2077c2d613da4d9c89a0c2e95ca2c58a238d94d485be6406c3fcfddb5978108aa3e392192ccdb4a17d3256340f7e9207cbbe293c200e9593d6c55aead3f54da81351ddead8bf6332c9f4944259fcdf2f313e897292d78cb405a1a66e3d8ac8ade847f35d3e5c0fb5d897ea07c25c391d9e42f6d69e591a836f380fa4f34b73e58610c42359e3f38246ead4ebd69de4275fa023b181ac60b6d976637653de4a81610d94bfa03e9ba25f9f99de6ea8fc5600c2e5f14a36f0b0f61d445dff6ef86ce9ca1b599c353373466fc47dc19be1fdc0beddea45a2797033e63251d4fa33868e630d992f78c339ef11fd92bca66b32479dbf92697b7e8669bde9ef2b39743da2a6a68f906cf27edc44026e5f8835ce71f46ad750f1e845eb90ff74c27c7e4283bbbe37682abb550ca8e0aeb2b70864b296720c3a29d05ca01c1b474942def66859419dccc37be3cb5fa2a5da0e238abdac9176ca3fb342259ecc10c8e294ac87ea12d0d614d15e603a438848e8386b39324e15b68e46fd1288a7ce261e4434b6f41aaf64de6914d1b444f59da7d5c8ca0febdf190e5616a57cffc42af2cecc654cca81efa251b5fd97090744069d4adcd36b67b346446c9c41883b9a1e4d7c90a4d705b343c31a4834001489cf9169419420d891ab0b4b1f78fcdc29952e85e074746698ba3fad9538d8d3febfe38a213a9538a323db9f43f9c0e7e2b9ac606d16977906ecded9b2579fe059cdc7c8bd706b19895db5dc1b1a9a9d76e1475e695b68ade8f2a0a4470253643d99ff64e0242a042f7134a0cede28b3773ace0d1886f055c128b1127bb72ff58472b3fbb357e6816ff0f8a637bea0685c239377c16432fd918980c8932760c08488b85635bd4ed8d07f3f7f2fc8345e326d811b2e7c43d7c06e692a4d16c5e5f62d979373b6f831f941b48ddb5e5a6f2c6a2ca9dda3ceeae70a9eadf7888d816ed8313c975fa948213781be7a9350af17607e902f7e6f944822322bb885c1e21a370a15128e903376711ae4d0f3a0a82c6d40765307fc2bc1fc2bde0ff4829952f22d618c9aa447e3552bc353aaec1272e5471d0de4484e135e416953a4a0b73483dce11472c3f3065a0d1479924f6f0939838c35afc6e2ac6e442a6c0b6c8fbdea829de695d26876856e50a87ee3c500818bed4505578d318e5e8a7eabb0fa37429995ba630738dd248c68ae96e15c9b91e9f7bd8409f28da82ca4fcec5371a56fd11c7346c2e158d3cc2a201fb3c9c1ff445994ec8307f0e04fd081b3800fd6804a57e97195bf5af69de73ed74da7d6d79f6b60fd5a7172c0a05da505c7d1a5d42ad0169f09e8f06d761559a5ad1aa0292144183f168b16edb23178fc37cd69549dd1f295eb3ac1717c4d7264c566d7b1f4cfcaeceac67e0a23af1f1d015d130e324933d0bfc555719726d6558d949082cf4c8a1a4d675a7d5de95bea64d058903d757baaf95cd003ee495b2cf8ccfae950b37cf92482695b338b34d673287ebe10954d0fd57e4dc2443f099b2ed66815dd979c72b1b1ebb8c0e906a5c6c1ecd4eb3f0551eaa83efd680e3a0d8e973fab72a37b3ba0036f0dc6226f49220badceac1df6accc5165754a15f864a5016f705ea3db451bf1039d623aa54c1676e7b5209ca53ad575827328daa51bcb316f55f1acd6f50e7e80888224a9b00cd5f1554f9c1d817475900402e429b4232e34d190d28e10c89ae2d8eca07253f37021bb8c4b180da854ae2bdade8c2d8c4a61295db236b15001a8905401acdb008c8faea2b7801b1d63909cc4d075f61ef1fec1eb4d071f0b2b360afa2cdef866377617d6d7c574622026a90d2bd6ff9bb14b54319cb14e24c0d788ab82aba96b563a953dbb465d8e3425b280c277711fc691bf61abd83b7896b543dbece055618fc9c95e1662c55821aa3990f33c2f2d77640e3f316d8f9ce258d14cebfd934a1f7abe51e3de00757c4c739c7a721b97388983b5af4c71bbf5e7d7a620c2397b5a27921ab57cc78c9bc543e9349aac14dc1f077fc50ffafcf2237fdc105a659c8bee5b3fae351dddb067bc6bee552eb36989f6d8e4dcb2730068e4dd8271f9ecd3f46f18731c2f47a9f6a059587d37766e84f2bb2c8c32375fb89f012e0127e7d2637c0618f77bd464da4742dbfd4bde65eda98385e41f7d9330da7af57250efb3424d790b1be60ff4c18c2422feb0c630d530b94bd6bb81f9239528569c6b542bb41aa86cd680aba1aa59618a11d460b32f81207b5a70d5ac5595bf19e9594d2bca4af85e4989fcbb175108a9ebf61075fa0d16cbe420f4b9f350bb141ada1499b4480c1cbbddea0708914932621186b4517970637aa40808914fad5727b6eb949a60f8ac3aebc18c66c3e623cca8ca0f51823633b488d4268ed01069ae70a2acf213c5a915f4b5170d01fecda19b8f1a0bfba2264dae0b97c394c473bf39db5be11c50c4aa77b0e7b2c2ee65cf96ac2f0502e455032a41d7b35c20cc59451dd40e978334902c07e15d18bce9d600a41b8dc742351a088416d3bed988622a8a590fb1c9899778bae1bb9523f340fad9c50946bf5df00e5fb86ed2fb1d79ceee810704d8b197c779dcd7f3e897a46bc9de7b6f29939401820a2a0b680ba39dcd72264751c4168f78c4e3cd36e3d9ac2c45d5be5a5e91887befbdf742f9a47ae2d5dd1d2f0fafb23cc053655003cf1663dbade96f2e97cbf5701110d46a9bcb1a6329fea8ba72ce244966f2e648922473cea5bacdb73fd37cac62759b6aa9beff3cc0389ff1d0ab1bf67a38cff9cf82e8d4ac09bdcd501dfff10715ca80d1a2ea4267ca553ec4a60e22c0e8ed250a3d38b9d2db7b62265f2d44354cb614a1b7e7c6578b9c6f30c14918bdaf3057dd709d71cc41af167924df7f5413a38a8c37400cd601c4abaf623e2ccfebf0e8e8bccedb2f314e84200eb8678af8207b9e1761e7653cea081e67c96e67e77754cfe9a4370b1f5dd447352e5bdbe34f5ffb46a6b4fd45c1d13b0bb5fd1519eabcb32beed09eb80cf0d5797788eebc1ba530ce4ee901cca12ffaa4306ec50b3e40fd7e18e762bd2f4e89408c83f100840fbbdc7a5f0c629cfde58259887126826033842fb7decf438c73d164ce7ac511fb1dea8baa5b14eafb1e605cbe8aa930f5de9cbea608a5ef93eabe3c7da7f4cebb5e9612de17873b8cbb543ea408d7fb9997aff0a81b7ba354ee8957764dfac284a7d357dce99b77220fab5bcce9fb56dd396b44afd8e547eb35708f0188ce9f3dcdef7abd699aafd49e0e82a8f32866cf3f74bfb6d4ec4777b52ffc03fa59db2a451081e136ab77b47542a404edd774cc73d9dace1758813892e4bfcda6a6c06633528a81739712c1608d4d166be328021147123416f6c40f8e913ca1a8b68ba5b25370e66f16ba25ed3867fedb6cb75b93b6b5214baa1498224769be8917a6331569fb576db98864675c27cd3dcbbec3719ccdb33c6b7cf11f1f21b9395f42b499dbd2a0f7f70c160bb2dac0d4367bc24cccb3806ee8fd4344a03af7ba76787c6836786c8f4e656ec33521b29e4b43a6bb3addc266448f0f4fcd6d41467c7c69ffce5292deafc9b2246db6db4d934a4f3f332a1d20f096660684028f8c00da1fabe9fd7a2ccfd9ecdfb3ec89590f4142b01a893742b4978cf6ab4d2501979080a00dc139b3b2f4908f2122d794bf4a413f888a5a0bf0593d0b1fd96614bd7995906ef8f4b4ce540e6b48505295a491f0a25a671ab515417e144b48d4ba88c1a0945a89b32a6bbb73903d41440923db294b7a7b02077adfa882f3f5c3e2a3396805a775a6daa5a1b2d9ec95b31d9d34991ff9d71ce759b8fdda657af4ac2b473deb2603bdef3a3aad33d9b47d97eda802a31235126afa365b10d0ecc7e6d313b3d9643c3b3a28ec75ba5a369bcd669636d236da3000c37987fde8cbffc5d9ab4691e12a9dc951ff20493351cbdb024a9224a96e5114c51ff7ed893727b4bfb22c33f918cf66a31dd5adb14d63704549410179b221c0f9c076606f4b355b8b30ae1fa82b76fdba4c964becd78ea5de2e70de2c2c6a9aa669fa2d4bd9ac242f49925634c0bdaa1d495d5ebfd7afdf8bfd71c62216b18865497889e636bfe52d97b71a7aeff5f975f5030bb46569b65caef3b4544bd65a186c8cc562622c63bf317bc2b608bab4274c1375c97c0984112daa01dd378dfa7dd97cd7f15d6f17ae1a3ecfd70b06b3c56228aaa36381b7a5246b6532ecd77cbd5ceb64989001cab205cc9e983535611ee7b9a27df486ca744e1ebfa58ecece0e0f8f4c166b71f371a1e55902e5ab647f6787874766976c4f8fcda7366bf1f3e2c0b5cc9eb03f4b87e855373ab4aa8232f5ebf1b84f2174e960a9e976cf9bc9a057454b7214b31016121212f26b61b0ed420b39605cc76fe96d02fd63544b7cc4d3f373ef7541a1fb14af92cc85f6c8d0598fcfab458b9fa0202122d0abde9692c4a1a1a13c34847d68e80e0d0d0d599fd90ce8d52228484888461b1a72594b44545484b6f8d29e989d404157c862d81e416757d0290b9ac972168ebe6919d4d57dea1bf432c0d54231daab057aefbdf7e27cba9a6343e918f4aafb242afaf4d542bde4bdd69e6cce7befbd379f188dd9576a04eaeace3ed0921c4b98b0ed420ff90cfd0ccd808686868686827a8686868686fc56005bb7b8accb854e15d4f6452a2d667124f348966669b65c2dd7f9b2f6b42f1bbb9d3c60b0286d3fe6d336456dba1d4a5b1eda7acf6df6b3b01c86e7b0b2b0aea6a05001a14c41f95d1ebd7be3f26edc9d6369fbf8aacabe58e5c1922fdd5cd71de39cc5eb18e72c8a6316c79124cbd2244bd36cb55caeb3e53acfd70b068bbdce0b3b6f2c765e546767e7b436dda5ede939ad8fcf697f2e0fcf7953d98b03db73efbdaa03dd6651f85cd6da6bd5105cd65dbe5de87c95ee936174de206bebb61707a22eed891ff8736e8e1e5f11d059b15ac6ae7695a7a721b89a0421b03ee8f2950052df1fba20f747612e97cbade62b0146faaa24b87f733084baf54a808fbe6a08363075b627ee6f806a2b667f952f9284ed1268d8eb057b89b0d7eb057bbd60b0d7e97ac15aafd7cb086645ec37db1809732b8c314e5d34a0f74590f52eb1fb6b03f1af3d614370e7e0bc4710da00d5f6adde595f545b9cd56c4f94e0d343617075676dad0838c0babc4af73930656e2b5ddc7b7ff474fea88702e24f30ae2e16021cf4832b1d0faa763a08960470b170dbcfde96786a2d7a6e4e4d1293382555d4257e0f3e5d964f6caf1d4ff15af1baccf3049241016af57c2bc6337bc166b0d7eb63feb222a27bb6fff76d3769d0b4a3ee9b8eba6deafe98ba6730f564e1ad1e9e54b6442aca7a91fd1511115d19a2b118b27e940361f401817ab108e2d6b4745bd2fbe658f0e83dc1889a8468d4581c9150280c23868cc50d093d37041c5a55d16e4ee8b76dc945f323fbb3bfeb130adf34f16eec6c6c098bed96644bd2dbfe9668c040876e43341a15a1a0df9f2414a45a3f1adee084b0935a9d24aab76587a95fd3dbfe92b0ae3e70db7d9f97cff758cd63754bbb2cedb541ebb17ac3b5619958b0389afa3bcfd38c99ae173fe67a20e82de18565c2d636c3e28454d2de5cef3a8dc08586e9e084ccd7514fb7394f67e7e07695f2a3ea2ef505ba6df782a925aadc763eff2d08c8fe6a5709bf6afde80ac7df49b7158a20e7627adfdc8541e717dd56e50156774d63d3344dd3344dd3344dd3344dd3344dd3344dd3344dd3344dd3344dd33481ccccf4af12c69836c318d3f4beb95a97600742412f1412120a83828442e10fdd57b86f6e687555f543fb0192a1970782089c4200da0ce88c347fa3321a8b5ad08ff9b39909125a788018637c060131ee5d24024e17a903dbb94e17f836dbce0f2c1d810b0dbb4012eba8db49984a7495f205c6d471867f40b7652ab7f9ce773fdfe1b840dff90e07f4590664fda8bcc059d3d2ed0748f6f3030444b423229aa157c99ca9d98fcccf663e3f9025d96ae80e1897fde8dca59d02427eda0c8a751cd0cce512cffbe2a0f5a6ba4f6dfeec344182037b947f93424cb0c7cf637140afc1799e27ece634205dfaf5d63a9556e9fc57ecf12b01e7dfa91f8d73b19dbf4e3a8f3e7ed24983abf481eab63c1505c550d43b85ad0dff2c25735883eb64c91fec5a271d810bed7a7f2100a373e66897fa57e987711aa8bb545de30ccfd40bb465fe0cc4b82bc447f9b31028547140f74d97cbbf999fffc4697995ccf26ca5fb837314bf27bd5a965ecd935e6daf2eafd649af46d3ab6357bfae765d8d0b72ad6adc4f596ad79ba9bd4aaed3f53332f6db8436496853032126f4774c9bbf79f48669f34feb74855729bf697e36c922262849aa33b7e5306ef6f9ff3ae13973749106fa98c5cd1ea3f9b1650a3624ebe8623d1a62f604e3844855841629c208e61c2a66a6d6727cf34d5504f34755a8837104db43efc85aba5080163b68b958cd54a803b374b1126d41ad5d5ba2280a75307eeb5baa08ad1fdfc54aa6421d90ef7a972a82ebc977b1ba5ce9dbbaa02d222ea875db5d6feab68d7ee0587635adabc0698d0623a7ef3b61c2335aa273ce6648ba81ee92e74c6165a28a1012a3127ece231cec8592038c95d5756764f458c9d466b959ef93322f60ad2e7c8ee99d21c3ca6a25533bc5c3eab2e29d2224861c2b99da2a2bab0be8144a7214c404907649c08ae1855584116640842bf923dc281beb98d904ad56114620b192aa751532c67554adabb043b4293ff0f0a3e5142958f9ad2ea2f3beb30933945a7194363ca6ae9ab748596ff08852182530298c4031ce4ee14dedd61f1b1ce3d0eb2482fb94a8a921b78ed0d2c25b830819c056e9fbb64adfed42498b93dceaaa9d29caa0d9615836644988f81c19c69040e91cf9015665e42956448d8b063409d68f949053490a5a46a2e848c5408fb08149f9503b92431092140025e9018604078f92283b455029393f48aee82819c28e0811fb622ac92166240518122433253fd1c80d4148c2c8928c8949f1701d910195521233920586232675065582024f122d4758801951b35384cca9444b7924064c490c3c4570bca2aa5c44a488437c98446028a3905c79153187cc907f6511dd6bc8d3586408932ce24389dc400e715312a10a1a624546adc94374d8611353521229614705344a86d71021446e9043c2b488d0500e5143f27002f18a6a131bb2a58cc2c15564ca8cfa220e319f642267cc21c056911bc42146ca23a2d06022534a677269fb9667fc8d39d1850e7c0b145ec1fc0a7d8d3bd30a49e0ce7de295ff6e6e04febcf713e21f292c7322b0dc61e028bcbd7aa2effa7a39bc23af7aa38ffc9dd8777b18e739581a35d7dd1d632b7ded8a6b87c0d06edd88cb45037afdfa891ddb13f9955f1b5c7b42c422c61ca05af5142ec7802ae37b7189dfbf5ca2dded7db57035ae98b8cbd93d97270f8d71b62bec5ffb6aa18a3bb75d372fcc7092830d14e4a870040d2366085170e2f3f2a2088aa9ad131f15a9ad4a16c04542dd20d0245cea5b67cad61fe7b2442db557e9df3a531ecb53e69fa2b20a8c52ebb9f5d6998ede3f8f65eb9cbd7f11ed8f6cd67339f4d6994cbc7f1ecbd609436b49fe44b624134847b60a8c4ad4544f4fbc7f1ecbd609437764feb7267fda52d389241348d67379a2416f9d49e9fdf358b64e18ba23f3997d40d3fe2c06e02fa4c10094509c4832817464abc0a8444df534c5fbe7b16c9d307447e6330ba2114001fe400750408a9b128a1349d67379a2b2d9cf5b67bae0fdf358b64e18ba23f39905d188bea6fd3b9880ffec0113b8c0821437251427ace7f24465331af9fe792c5b270cdd91f9cc826844afd63cd80012d4df07031b50b1748105296e4a28ace7f2446533da7b2ac3a13891c4644205d2d2d105360b2a4861742ba154fb0004ff1e1080806352b1748105296e4ad67379a2b219ed6b9e8e00504271a2290967820949c5d192ed820a2c304a51e2567bf70f21e7cff3f6c58108390034e198542c5d60418a9bf55c9ea86c46fb9acd539db7cee4f4fe792c5b270cdd91f9cc826844afd68c6c4849286e4b3068f8efe86838ad0040138e49c5d20516a4b829a138916402e9c8568151899aea2906ef9fc7b275c2d01d99cf2c8846f46acdc8869484e266c152d38e7704f5476ff03060e1b402004d3826154b1758603d97272a9bd1be664bbabd4e4f6101b020c52d03250c50b038e1f4feb3201ad1ab35231b52128a9b054b4c4d1fb078e2e11febf108400618b0705a0180261c938aa50bace7f2446533dad76c49b7254fb3bec082141adc02a094010a0c4eb0487232b1020900474d365c054c462a4a2cd546289fff0b884f6b10800c3060e1b402004d3826154bd67379a2b219ed6bb6a4db5253ecf4d4f5d69904f0fef93a2dbdff789d2eb0e0fd5bd7694df1feb0eba4df1fbd4eb7f7dfb94e1abcbfec3a29bdbfcf750ac0fbcfae138af70fba4e19bc3fed3a9d787fa2eb84c1fbff754a7a7ff53ab178ffda7532f1fe46d7c909e9fd91aed38af74fba4e47ef8fe23a01e0fd6fd7c9f6fe165ca7a6f75fba4e15bc3fd375c2bd7fd375327aff15d789e9fd595ca712ef9fc17552f1fe1a5ca7da8d4aef12ff534abd4b0490aea3f6d72dedaf410076b47f063eda1f8320edcf8248fb3ba9da7f8591f6070092f66f42a1fd7116687f2626edaf6285f65f4aed55f2bf20f5abe46f419aaf927f8ab4bc4afeb7542945af923f8a547695fc4fa4b3abe49f94d2ae92bf89f4af923f525abb4afe47a9ed2af9dbd2a4abe45f417abb4afe46e9d255f22f91365d25ff5acae22ab92a3b5d35a0a84b1cc9d26cb9ce172c86eee8c048a46a9aa69f16a57b3694d2d27ddb2cb685324bb7a5d2f87ff0fba43de9b6561abf2cdd164be3e749b7cdd21886c6af83a64f1a3f0c6adf287d97e09fdd08e04108bbbca22d75bfcd547793fd95eab6401b24d551dd37a713d57d79bd7d7dfa264db96c4d6d01d33ee9e64c58085ffc5e8e64cee5488ee398cb91ccb91cc9711c733992399723398e63bee907a6fdb1d0f8bd1cc99ccb911cc731833042f958942399733992e33866164f3c9abc1cc99ccb911cc7319b4be548e65c8ee4388e7989c6cdcb91ccb91cc9711c733992399723398e632e4732e77224c771ccb9242f4732e77224c771cc495658362f4732e77224c771cc36e1b0e6e548e65c8ee4388ef9831a5597c7efe548e65c8ee4388e190471d3b4f8ad3197188fb9cc39e3990c3d73ce2a56dd84b2815908208040769b357d200b41e30741e37224cb911cc7717c274b932ccdf2cb59a98670ce40c80fc2b945d025ced915444404add1de769d32b84afe654a4bbf56c403dd1968ff3183eb24aaf62a617c3aceaefe77c70cbbe5c80b296d56909a636415835a7c418b28646449a2a6078f5f142d2ce435566690a10d0d64cc647943837421898ec8550e3288b182e54d8c34bb29554b56903ac2062a3964065458628236f0680343106c4852acc22128670d9a16c0007145da1e80c8b5c0438c1150bd2b6598aaccf074668c0fbb1dc0aa23533c64612204101c575686c099a0468e0d4286e0f0429520e922852e6f72ac70028c2c6ca80021ba817155839c1ad6b440c39429a2288aa228ea40040a54d0e880e605333ff0f0b57ff1dc411591c6461b2162d6ec83ce3f9d73cea2288aa298a489912b2798b11292c47b70930437ae3dda3a91e1041e222a09ea0cabd4cc8bac2b1f328490d19a9261c6913145864f8609478669848c2732701647b2345baef3a5d3e3e136f7b9ed46b9cddff358b64e186a6f94e3eeefb704ca71d7e7f33d39eef67a3c768ebbbbdd85e1b89ba3a1ed87f081e33cf0c0710ac047fe58f6f6c78ce64ce58fafd91f7f5b6a72a61fcfc2995eb01f4f00678aa16f5f01416fbf0367d279fb0f70a69db73f0167faa1fb55e8eda7bdfbb350eebde5abc53d82061f0823a986d76331a66f72ec9a44dd8da5ace6885cddb09c1ec2244fe1a0e913ba2ff076f9a1fb95f63cef7fa93c03dafb5fa033f1d0785a62d07327fd21f4463a219d212a4b7f04bd912ec8e58a996a501ad01be9805e56d0da8fd91be966af970b0834c9e636fff1f3635241976e6ef31f3e3e96c4425934b9cd7ff47c8f88033a801fb237d25d2a3fbac015d003b8cd9f006ef3d791c1c49334594fd2d478216218f2bc6972c51d6d74c4da0059a2053646caeef024cac60ebc2657a6b27298593314b9002f851d478ce88218712955799883a5aa81f4818d2faa284bef4a8e109ff9ea4adb78a52e9468809ff7461092bd4c7ace7a2e3792427755038d7ff7272c93b8acb6e697ba9de941e9364bbb1dce824d5ac4b3198afa85d2fed3e2af258e20cce5ac508b2e7af6878022fc177b36e3b2d862fb98fc22f79114c7573dc0aba0f6efd9619cb6e12a3706c75005366b0ebd7b7827d8a1c995188c88a143c6ba7b7a9aa7770f54d2950a5474594a7858adbbc7e782de3d52da7ff7fcb4ffee813246e9a93146e9b1d193ebd169efa1318535052728130687c12bed2f83c2381731c24cb901cacb0f4684ac2de82df315210306166c58a2a6c6ba65521ae3d05bf62b929542929e1aa08e75cb6ee82d036aff2d0b6adfb22ea30e598f5187ec878c27eb01e92de331f66434c69eec862fcb27cb6d994efbf3fc30cec593951d67726489e184322b0b7af34ccd30860a18149e788962c3ba79807a05bd7982082032e7c8092f549841c8ba7954d09b67a8fd37cf150fd61a1e266b789af0f878a4b4f388593c6216cfa8e319753ca38e67278871299c12b85061d31b23c43bbd77d4e40893664dcec81632ebde196a1b7aef5c4d400629147ce4baec08b2ee9d2c9d82de3c39edbf7974da7ff3d0c0b28305cb8e969da91da0f61daae0922094e18e6fef48697f9d2b8c83400f5a9854ada9f16bb2e21a7aeb6c29f20688304f65daf858f74e8e86de3b3a0708717246386307189ed6bdb34341ef1d5e8f87f6df3b503b40c4203a66c4203a6874843a43ed3a56a24f87cad7e5a7f3fbe94cfd80da5f478771568e9424249ce0c3148d124ed05b6767c20c99253f689022e58675ebf0b4097aebf4703cc9b98a01c4c90d32ebd681d23abd757cda7feb4869df3a4b70e8c4c0a133c30a0b8e761d18220f0d8a3c5428f286da1fed61dc0a83eac96e871c1d7a2b9ea1378ac5081ea66ea4a0838da875a33e2d436f54aa05c41597286a8850b265dde82f86dee894f6df2850a354620c54871803dd81c24069a037b4a34fa22e962585712e7ce86cc8b041851f479cb0c2d03bf6fb5923430a1141cc21d364ddb1a9177ac7807b8ac70b7290a430049a75c782ba04bd63c2a195f6dfb1ab18969758112fb12331a8984f7b2c4a187b12c6785cc57457b1dd558ca7fd61408c73b16308d50e47600189a8c124e80d0bc2b0a3d2a1431703140feb8609714e6fd8b00a09951ea8c84c91938275c3e6e80ddbb1188c22302a456056603fd89476d814295894146cc914ac37058382f9b4ff6b88712e9a0451c20e2345a2b4e890a3f7ebca043a60b901072f0b971fd6fdcad271f48689b942058798a7184480ac1ba683a3376c7783de301eec29d7788dc9355e645ec19750fbab2aef5e535e54b9f7927afdf26b4afbbf7218775550e2e4ca9ba7ac39eb1bbd5f3aedbc145e2c01e2451160d6fddab9d1fbc5539f9cb440c3191e5e08b1ee571bbd5f50da7fbf7cafa83171c6ccb9cad27ec2199e55c3d32aeb04669dc12ca1f63f7918e7e2871b334222acbc59e188354befb3e7019829210e2a1c5a0062dd27141bbd4f1f1a1678109a70264c0deb3ea552a0f7f9d3fefb9c3aa75c39715cd1f7f7e98276c13973a7eedca17a9f37a8aca8b0b4bfcb87712e4890423363481318ac80ac6bf476b93012e3c91a2d504d8cd0b06e1796de2e2d63866a5648014c8c34eb76a9f1df2e218e5cb7ab0a337105c14c5c425c375c4f2e1ea7de2e20f8e782817f3480c2ab56166e67dd2d3818472b0ac6d192d2926afdb4b7a0605e0b082f4a3b143ec2ad1e6e0931ce850bc2334d8218e105051fd623f4368746dc049126cb1a1cb0acdbbc4aa3b79955a5cc0c573c20094324c8ba5b577ab774da7fb776ad1b6ec6f4e266cc2f26d00c6a37bb5c9950fc6a8af6377da6d4367fdabfccc2383bf461f93043e3c98696158dde66ce4e09218ca1460c9b9c6fdda6ee8cdee64e7b0d35783e72e0d2244a93759b66f4367bda7f9b5026102c6bb0b029bb945556da4bac6039152c81c1a0f62f7718575af1f144881833b08258adf42e79598260434557e5f3a1cabacb9e117a9766e01c41454610389cf1adbb2ca37729a5fd77f92ba1ec50d6d8a1b48105a78421a47749038a1fdddf6495762856be1f090ca33719d4be6eb28bf7207b780ff2074983bc413e69ff4df280ca8222739bd44d614084114eb0449d6075c2ba47a02e42ef3138d4fe7bbc1ab1e28c4ce28c4d46df28a57d5ce2b991470e88f61f77236f8f3ded20303324492e8735715058877a8b46ac80c30b3324e400c4c8bac52119bdc5abd91b2961649409734388758b5944f81e75238d312296315aca758b4bb60845bb3845bb48a57d888f7cc39ca5137940df220f266698a0c9c26190ded9ea97a97eb90b30ff80790a0804e30608e0902668b6ac3b8fd13bf77696dad13b2fc19163e0d0f7779e919573da330ced3c6cc5c382c241ed58e842071d32f42893c6cc0b1e6b95de182abd2108205d789070e4ccbab14f8cde58ca49ae4c0e090d412552d68d7f61f4c6534636a6ba31b08e1b43dfdf7887c630b4bfdd58877798a73dcba530ce5a098961aa87353bf0c06405a3b7ff4a807983c310bb304760acdba7844ec5820b319c19daa05ab707f517bd5d38b4f22b526fc7a28017b15e8e385494f627ed3c78dab7efb4ebac930aab44458595a628ed4d4b44de0a59b2f818a690c33a84de3738ccf2b11dc63a45dffb76bfc868ff0c58a79755baef7a5f9e178d0229283e2938d82027c6baafae8bdef60a1643460e34360c093659fd27e038bb1ea00967ed32bee48045b5054cf0caea5cb48b1e5747f410e64a162c6e56dfe22287325582f0e2e480e2b2ba104ca3f657cda04a24a0dbb5d3f751c8a961a1bb35d4f76b6ad0ddcad2f753d480a68042770ba8efdbd8a0bb95d3f76d2940776ba7efa76801dd6650df4f8103dde650df375102bacd9fbe7f4402ba4d287d7f48886e73a7ef0f59a1bbccd2f7915280ee52a8ef979881ee724adf47ba4277e9d377ca8f9d26c97ff26d78467326176bf9b1578b12f66a41061581ee92a7efa78882ee32a7ef0f55a19b1ceafb29a2d04d02f57da439e826a5f47d5a0d74933d7df7e9e587274368fc65128d1ffd010a0f1affe0638ac6ff7ab5c0363d3e3a468fe3f8b671fcd94def53e3a72e1a0ba171151d4534fef3d502a3e497d6f56a41b65e2dc65a10748f57fabead0cbac7297ddf5604bac7a0be1f1485eeb1a7ef071141955a40b798a5ef2bdd40f7a8d3f76f57e81683c320314080e95242e3378fc8a05bf4e9fb4744a05bfce9fb331e505115a360d7cf99ac0f5576686a1a7f8904447716eafb4856d0211ee8ce52fafe100de8ce53fa7e900fb407ba734fdfb7c540371eeafb3618e8c6595cac74c9014963268bc5063d8a484ed08d79fa3ed20fdd184adf27c105dd7ea5ef93d8826e9cd3f7512041b707f5fda127e876287dffc604dd9ed3f76f4bd0ed3b7dbf66028aee40d118d05b18f40606dd174ab7d1f965355bfe19cd715e459bd0a2c50dd5e871a3975bf197166757ecaf16f9ea30ced5c347f7afab175b836e574fdfdf2e297ddf25e54c4bee153940b4aa3176413953ef1607fd1b1cf49c839e72d05b0e559a53414ebb801f887ad1f04ddd6c7fb35d12bc84e05530887759474fca27ba807f418df1efd734a6000492324aa4172ef389e7836a1da1711db9190683c39385f34a9779af1a3cdf0d980a31a41be80b2fddd8d35d9dce0ea89fd4ce0952c026cc830210283bfa017b6640fd6cef836403eae7fa39aa22059cd2f85d3318b2c65d561610520b407138250886d0110f3cdf8c7644e51ce686743d2917518c17701c669ec6ef2ae205af82c1cf21ccbaaa0a12509b2299707df7ef94bef7fefd571715bfc8c3b8b32783a2f944298d4f9f2328fea339b953f70aba48e47a525f3b7d8ea04838501b44189fb67ff29c69bf867a1c6afcafa133ed5750e3c7ffea624bfcbac272b97ea0d773ceee6f67492da0a3deafed1654e405357ea0be39bbc5476e94138b9871a497039a4204911c5388209263bd249c0c7d58302aa35404295ea1a0a4c93ac2cf708591cacd3ac2d0d43acef4c5b81437640cd792cc3e1f737aeb98fa151dab08b74910619aac23e42b7156f2c754040b0497758413a8be0a78c010400d08e005bda54df8c85fa90b7a2353d79530c4d43aa6970417566fc1841aa0d631bd466eac2eb2cc5ea60c99e03aa6178d95d5d53aed7d71c297e03a065946310621da6431020607197284b09a01e6e39980cc1a3725d000d38209eb189462441c538a958ca7f2c90c2d4f66fc744cdb273372c0d222813241a498e111216c83b3a0985a41890e5e7468c30563f91c3e6e70128304333a48cd9a4c692b84b64e74b0d2285902d15dea7bee406966d03dbb679660e90cb475b2244a0b39404784116e1042aab7eabc75287f2dc0ae3a3a2aea630b2eff5d9b31c619bf288ee2288ea288313e4770b5891ca8433910ddbbcd86d51a0d74a3da51d47fe39af147f2fdfe78bd063eaba1b85aabd63f805d61daa3fb68e9692c15f277fd5d61a990fff9792553215737c01abfeb99945bd9d2b60a9b307aa33717ba65a6dba64d9254b76961697eb568fd7d6d805be29b1fd8f4f84ebe7d3939fa4b5df2a352bf44f556ea2012d40cb537dd520dec89fb4126a0af77e9d2b64a1a2edad5b2e5a3ce95b3bbbb6aff031f82c06daeba75b76e1de79cf39679ead302ea3f8b01bb19c9b224c72360a46024c9f1861646b2345b2db32447373c18e2a3214772446a73de7b84d5187224c714aecfaac84896a65992a38e23ac46921c7f7073a507940c7224c7204fba287132a6857194338e24902339c6c87ee443e6c3921eb21c6a0e21d345a920eb61c90e16a8e43568016a1af4be5f6bcb6ff3e3fb982471b624eaf9b105f47d2c922ef4bd591473a431ce30ac8abba8968b3abb02bffd8b44dfb7d7ae660e9e7b6d30a4af6afaec097f211d39b727448c028d5da38f26c8246f07a4332d4ecce1231773624ecca91520dfe5d75e0ccbea25f36adf3566ecb04cbe38b062f4f8fedae0c82c4bcfb0b195fbe112eae1abd376e31745f1e6b2548f30bea9601d70577fd77b04fdc3d511ae63ac273213d0d7151ca83975062d7f3a5c64317ec860a0798d4e148a8fd8510115bfc84e40c2e31123532303fa06e5624386c30bb21725047770f4d8a0838605ab305b60d8c981eee830859646748aa0a490aa730679a3e7c6a07ed436385c799566e0ace2766bd5da647189d45b0c92297010472d1700816e83339236b500a780812c53e017a54906bd7131421c4a63b6b42801812d24362d170a9bcba0322eff62b1de62d0bba43072be92c4da90170c8d8961cc76061643fab7d96e370f3a12438f94624075e6d8e0ec689cc3d2d92153411517ed2f1e0d7778d818fd8c0a4f09a1f6ac149388231383b591851185f185f866a74667c59036dbedd6d4342251f5a438d221f165b431ce10e7b0415392c9123d2bfb10945da16afb60c4a03d1164c3cfec4b1a0cb66c26cc807e44120254f4454c172345091222831291f8820ae5d36d2874a00d896866b42128434442312a444552f4f62cfb3ec7e244213e22fec642c6dff807f91bef10832e5cd9d02e0e6790627076d37b36f2b4bf18c447a80c62b0e8bf0005833ac33fc539b64f6740a88e18b447fed6bec8c5e23c4b7c3353831a518a6232e6a5ea69456f3168c52fda4b40ed0a9cb6ef4ad5f48b88d2215a2a940601a5e92c4d7f529f9e589a653c3b3a689aa6a959a6e428a6d8d3f4620026065f62306857d89e5c8a125caa1bf4be7525dd0e3537bf5248004a5b1c0c523cb1c8473054e4a1232ec848591694414628a5389254234254daae86ca202920a51db3f2b6e6271f15e133abd223877691b0c8f2500d3c4137ec5cdb093a483e3423f1e0ca24842d51c49edd1f7b391485ccb72a233a435011b9cc81de77ecb717e8fdf2838066294cc8003edafff5e680765546e5321163a603ba73920d7497474a486441773e8f4077a96d76858bd65aebd65a6bef0e97084028b5ff7d970c7497362b025283506aa10db0f7b02730072f6d55e725a580a23c06f85ae2233f4a82ee8c2e41793ce73953fe5d6a9d97d3582d5106e5c0753e9d9c2327a7b17aa6417799452cfab50f739d8b0861525b274b725e4a1b9aaf462bc8691790e90336dd81fc52b40b138430def8c008d1c604cc83ce34bb226b4bb59b11040e14354ad8ace33fe07afed1f5cbaa4bcab5440574bba4fc97b46dd2d655c4288eab88d11ca32a232b232ced4670a48c8052464129a1f637e2619cad92821738526478f283308ade463d3447a4a4c42899a2c2ba8da03414bd8d7cb92d4dd00c0922c30c69d66df4446fa39ff6df465346535c358c70b86a18b960a433da6937bae1dac171ed8c60b87ac32b57af4496f62fe1c338dbe5ca8b255a803f6a20013bd1bb846fcb0b6aac54ad80e5c5ba4beca07789a906ecc0c342048b1a2266d65de2a77789a0f6df258425aac6940832a6849012bd1250da4b001996b8312cf19455229755425762a7fd6b53403cb97253f5a3a189de3520961a2635048181cc9a75d7825a07bd6b3f64958172041161e204d75d63a277ed4afbef5a560d4e8b492d4a8b494d4a4d4afb4f7b0dcaaf06e4578b02d678c05aaf06a5fd490831ce0e212c22e4c7952531aad6257a9310ea40f5420c45b2f0725837892bad446f12595b786fa8a81932c5c2ba6b49f4aee9b4ffaeed6a375a3590f0d2aa81c4171250484c2141a59d440b088916107d7f9358a2fdd52c8cfbc091210c2266b06145b722d19b448ec44efb1b2b53c670598363ceba49f0b494de2436091240e2ac89c366788581de2a564eed92ab6aedd4a9d64e05b6766a50fbab3b8ccb808d174a644cd912c411bd552f47544172c6061abe2feb567bda88deaa7d93a5431539327a40a265dd6a11bd5529edbfd59f0ac5546b98aa0d35a7eab4ab3482f8c80cc219a6c2e17078a5fd53288c5b81c29cac5f112cdcc0244aefd40160825015c11a62820aeb4e89e89dfe56189cd4f025d7c40a9b75a743f44e81da7fa7c1b40b93b40793f4474a23bd913e694f79fcb27e692ed569ffff61dc8f1416568842254b9b22d61cf4fea967c1871edcbce0bde9d1c3badfa7f70fb5fffe2b2cb3c733317b7c93e7f1403e4afb2f31a1de847a13ea8b8218b7424e1a1b457038a1861d84e85de4e2c906126cb0d1029c15d0acbb0807bd8b461e0a37b8bc0903a565dd4541f4fe9cf6dfaf7b1a668d222c668d222d454b8aa0144d59d2bb88cadc154199bb22df2e9222cad215f1b4ff2eea15f15863660d1a222aa22e4455da89acb2887e5944535940ed4fa4c3380f50cda0d21d412487901588de443b385e6008b243814c967513f16ed09ba877e288950ebc34bd1a4dd64d046583de44be357a1349112df14214c30bd10c2b2c38da8960088782c221a170a8fd877a1867bd649541814b121276bfb506bd87a04a6832868d9a2f5f86f4d63d04a5f7909406e4d028218a90c90b22eb1efaa1f7d094f6df43c021aa28433aa20ced1882314463e8460abd879ea6685753b4aca9a19cf6a749619c15c286609037737a6d7ef0a137ed07d42ed4fe9b36d4fe9b665506a1152983d08ed09e683c6840b4d3a24a9aaea4ed4a1a4ffb0b0131ce6a6943c29c345cda38a9d1436fa1a0d05028ab4a6f5a8e06a3c42144a5c46145bb50949094d04f684abbd0149e508f2704b5857cda3f68887176c8c20c537c6082a5872f2b0fbd83ae2eb0f1338302950d6194ac3b284bd3a0b7508ec6973342b0f060268799750be9f40c7a0bedb4ff16e2093dc5091a1327884cd09420aaa02e28f40eaa2a83a4caa05f1934a5e3054169ff1de40b8a1a1367cc1ca02a2b2ced40708640c021507028d4fe403c8cb3728ee8a2be544163e58d0c7a03f5807cda898021851d622013e7c6ba81a47a7a03fd78d01b684a3bd0142a4038a800b900070806108d24bd816e0087c02b6096f69ff9300e045382d83862640b112ff00ebd6743ca1cd126852e3964e1b1ee198e41ef599a2a3162868849018fc6ba6740ad43ef5950fbef997056556416a4c84cc8ecc6ec69c6c384de332052b39cd44c27b5d3fe3f531897fef0222589148f2b403ce9fd0314fe5c69fffd93f50387d4f11385d4f123e507c84fd4cf12ed3f50c81f1ef9d3237fa0b4bf8f10e32c1a1c6cc0700299aba71d5618f4f6198e09e24b95135e8f20d4ba7f5ed0fb47a7fdf7cf4efbef9f1ba4093e5e48137cbef84099b27d80c0ed4375a4b74f1792864f144943dfdf3e4b723b9f9ef6df3e50dad7ed03c4cc1a336c744f971ee1f04a7b0fd655cf549e6ba854891bb06a1e430a8dd00c30000800d317002020180c0a265194c33018a8f80114800f57d4466062349587032946410619639001041000000084101828a2a1028567d1b226b4bd4aa59c74df112fe9e745961ab089a06531c51865e1868367715b8b3ee44983ebbca863a5773581d46121ca4663cba6cd4e0343c4d04673c1d5e7ac7d4ca5d49a8a52d86c639fed8a8b53ad8d484356b2f15da32175023c5fc1a1a3406aae947f48ed9b5b460b9a4d5bd7db88c6be3fe1c1ca7c60d8bfd9407f0c2509760f9f687fa5869b0978248ac902e205ce24157cc869dcdcc279362e2dc234c9d64ad65cf3bacc921ded1c2908afd2b8a90c0ced1d3b51b5ac94de5dd89131936765151e21bab3a28e5847cd3639f6d321ab887fbac5a7489530ee2052d3f73d3ec92aa346609b5bd42c5c733a10bdc4d189ee47eb5f105567229052bccd95dade9f3c9a3cb901f0de6ddfba5f1b3c0ba5a64046cd269ceee38df62d9d5759420046eb4a22e92f545c77935e85aa3873e962a4d61790ac017f8d8041b62b4c1d6d07a096110d7641b481ab4530c9240d7a106ca74b45111886559b373f21036996b03a3eface203126c1929a0290dc1cdd73498cbc29e224310f46d68cd41753b372cd27ea7672120712ca92066b3f3e342fe9cb3fe3979619c98399fa90d8cac953500bddfff3782a7673ef857c703a275484638e631d55bc5c8683ea81e3b2397812174e72ef64762d26bbac995189a33a50b9f1aba67050ffd359bbfca6bde5373d90dc96d312d0342c4dfa498d22973c4a2ad04b56fb8dc30c150b00998a885acc67c2b0bd4ace29bdec4c75e3484c2a5e642a47e31245a87e440544a057268c2353fb3372892516cd5782e6e25da7d3a25c45556c9e0f4ff21582cbb96192aeae59c05cb5e01c51f615802a46339bc463b0b84f264f2ecf6a93c4f09f00f63ffcb14f0420059e02a411e59cf116b0bc95778e49f28201595f96aac13a961b84ecbdc97228473f85a22a9d7c598e510a0c265ebc3b94131afb280b09b54470987f43b6f1e71c6bee53941322254474f2d010c398198da6561582dc7411df68d6599d2fadfa528ffdff6f9c95f985ee388ba1c500249e55d6b2cacafc1fddff87e4a4f43ba903975ad434713bb177a999b640a92aafd452a6bf4cb9d0538af63b4aad4f25d336a5789b5002998203eae2f7e737c5aef2444cafc3a383dc68eff571b06f34ebacb232ae8fcea11db5540ffd7bc70dbb47def4a5bc118c853a91177d6f9e43f3e80f8d47ffd102f536df9cc1631fbaf9a24ee4c5ef7d7e1855ea7bbca4b2f3d2cf131f950ab22af5dd04988070c6a17f9442154d9a892de5f2fc94b9b94e602c4b8ecc5db611f17526c389f751ba5c2f10d1a8077f8c1a48c942839843dd5f41271aeff52fd08538eb9759211e697a1999e804a12fb0e8c584dd2f985a6169b3e59578a6a020f341d1397d4e52e85a1ba67098f43b87735909425ac10f2175b1cfb46f35a17b6e7c7d2cd0101b81bde7894cdc708291c5237415eceec07282d278fe506cad1ccaade18bd176a20ea2030fa84a30c2158dae71c199330fdb114f199d9ae04a85062ba6e8c111e27863b4fc4c37eb1304b2e753bccc3122117b0d3147bb809f72d7847fedeb897fde4ea9aff7dff4e223f7ebcedb48d945a29e904eb2a694797707b43287e155a2a6e08777ec35167c8946eaa18dcb15bea2d20d87aadba98cc825b71ef8d46f3d64e7e5fa5c72591e78a45df446a5a579f472c9072d54a791f92676684950142d2aa602a91cc422bd3c44e8ff2025cb6a2ea81bdef5113c9f9f8fda7440866650176dfc4da3e4159f51b2c02345cd75e72bf41670993b0228bf78604d8d34c99bbf6e43ee982d81661ecd77386183097842cac467d0ee659e022248fbd1950e168a9b2a8f5587c04116ec5021d03074a5f6685c81f5a493c2294f99e43a7e43f17dbfc44a0ebf17455de1f77cae669e79e37450880ba930c10f9dea0d92c375067bf00f0e7e30c206fd543c432a57acb63b0bc3efa813c644fc73dd07b058fc8359eb3911bf7228c07ccd59c0f92c72ce021668fee6e8382a7f19d150f2f746529f09a0fda529f91bf3c084298937db7ceb9f032a3f1fc46598242a17d63190c43dcdab7d9331ac70f3808993201dd575d044aee4b6dbdbfc981a58d0111ff588a1d359b5fedf77d290ffd13442b56c90090e73c8fa2700f9ecede018d5cee57999d28dbd5b48b2dad44f39d44027c01feb69d853d488c0451d267dfb78c78ba0734921d160fc45cd86fb7d86b85552ad4662692d6df52b93006f7b1a373b7a6f3d5b77c80eb9e51a0f6d6eaadbecafe6aeb883808693df48b55a07e13d3f0706046950687b68f7ad37beca9641ff79a0c2e0b8402de30e4cef6a3b39897c2783f508e9497bbad79b0260778a527b07e189f7b2f918d0a13c8e5404d15d2f8ed982c379eaab88783372e8d074a9ddd99db710b6c1872f57e79cc91cb8737f0422c9d0ea5d62bab1a109148c24bb35e9cef7d2ef3f0155967cc64e075e0dec6f46d72d24c5be29b919f89dd651e4f522be9414b7455572a4c2aae1f57181b1d674e20b76e78bb6958d4d821c2016cf0a6a6a0a8d6e75be026dc4b689afdd3cd2cf2838ce73b38c1cc2733567d618944c7874d939629c762e6b24f211a07a54925c7000dc8d48db39a73b10191a48914541df8ad9918ea0c46241c2ffa4f8b8a24102e1b1912ff428964ebed4882f6b10a84aace2f59914e782ec559ae703b6b0dcf2b4c06800ada5fe480cf9705d8d1b339a5226c9cfa3a96457721029c1b6f5876809b0f7159d6d12484634c2110a0320b9499edae7d03057f54bc87d4a3ff5141e822e329513bf2a077be5c6ca62ab8a9a8a140fc9e54f0d717496273551e062372cd6ad0d4b85a207b9b009fd5cf7f61cb32780d619b7f9a0f9e8ce646ce5cfba5e12a0a639d3202e42dca0eaf9ba2f8323fd1a6dc34c58875bb4044b03893cc11fc7b77d6a20830f4e5b5a24437c4b87d7f5d96b2258860fe01f86796d1bd11536b125fdc8be02aa2efcfdbaa2e97dbc1b77978354f675e7dbde6fcd95d1b22d7501aac64268414c5bc939eb3020b137ca5722ab5c16909b976fec16e9390007d4b678ccc3e356ad977c784b40496f1bfeb27fc7649a0f1a28966fb1f834eba1753297e8604b5058a37de7c076deb2ea8e450b4b56b4e47f0e89f3021d5680f3f87e9ae0b97083d8d8aad8a9ce5f64d3f3fdfb057d7a0bfaf50b7bf50bde61473b77f6ec17ecdb2f74c7ce5edd027dfb85ec3c1d1470dcd98753508b830a1dc273677f4e413375accb2ba8c1a1426b71ee7011ec74f2f63b930d3d8ffd6a65965b9f5b5c017e0353b9df2c8fd9fbe21530d34e9e3b7abe4ce285a2466f9707a6b33deb6eaf450d2487afeff2ba21164091469c783bccef45e927b9fea057078f1233d69f0924248a1d6c2712ee45e89e39261d79392b51e82e2a670cbd7c8db998fce4305da877b85e3eb7574e3905c4286d6458a333cbde24a5623e325f5f96910ce1f53cb97816650b8eb3430e6a9892c282a237ed55ad9e3278835a83b5247e478877fb1a2a3eb90bf41361b9024dc17199ee9f1148f47824be669f6e43bfce83dce230c598b0db7b95f5a72baa661b1188ead1696108ec74f856efa6564ec5e523d757072e4955f913c109735d514c4009458b569a72eb621835b4813dc750b60562feee80d4f13955e79685e3bca22f843fdc393487af7c0daf7b34dce7d00adae649bbe62fbf98867f338aeddcfb9c697a5492a6aef43982f5f3e7bb02f0cda3cf2bf322b77922963d6a7a83207ac661b7c61e79450304608150da40dda562d261472254ecb95e7f88d2ec5de289fff0ad3f75c0fe0941996247d82a0a3d1b62411caeab5f5f50e22a0e3eb4158c5b95499550348ca6f91bff91dbc0db282cb24ea729667a0c71eeb6b631f2ae6b1d3bff222a210e534969d5766bca7212538918b2ef5477af396468fdaab81ec8d4876c9f4d1b941a03dfd012d79e234070d053757e72142ab2e49c9cad9a7aa907432dab14832def283cdaf021bc2aae20416f3fc979a87c7e0394666f3f2b98bf15bba04b262e3847251aa568a2044bf629f0cc3ff820a72baeda9c8e5841bc756f2c4cfd2e409aa563ed87425da2ba5a77ee11a129249a06df0f41444cafc91f0f45f3908f2acb21ba7f5587b3c5aded7a81a6586d3ee091c4984aa0edab4d921e3bd171d5e4aac059d120df811fb17cbb409e5770073682a8979aedb3997414dc2d13646e15eeefcace6113fe29e0f08a57361c1f87a3146be1171454897bbc2e96f66b97beb8135e545dae14fe51f1338c15b0f6d8b1e881cffa66df5aaf269736b56e488d29fe7ce176ceed92c7219b3053b15896195225be4a2d9a62808e7fa42d8fc273799b92a633ac81a5c6cd8ba6908b5e6b753b12093c56a4da3ec816672240c2a181f6127ee30250d426c0b99841b643840e848249cfff7d83443aa6de74654c12e0f48d39d6a2afc799949680c7106b74570a424f66936fa7b062973ea4fbddd33c27aaa21ca283efc9358d64317bb1c55e041ceea0e95d2b0caf7d7a65751410b5736848ed24f82b56eff78a68605126c998837017f1d99e7d646ff8a4cca18a7dd5a7a5e185383403d7a1c9c3782f604cf072c5747101f6c6e7f5a2921a086f60d5d7abe6acb1f4f2ac9c91c6e8f8e986014cada507ea12e471f4c21d016d2fbe187724a5699bae881d11f76f74b09aeb9a67ced28417b5331950e2fac3ca6691f83f4952c695e82906025c39f6abc9ac2ec43258a01b61340c22d7e5892dadd5cf5690697a600ceee77a0a4beb7afb5d35c314417af0ba2c5eb94b235589400af20760600b05d2681128575f6bf668b177ecd90fbbbf24dfc606f8902f4282f6888ce0eb82f6d858483e7c22da033e445f28373659cc196d6cb79ac686b11d0e432a823d35d8a303e5889bf581e6ec9ad382ce74cbef14f5c233c6ff6364bb69d4f660373eccb5aebc5befd1f13cbc787640e3bb8c104e2c63a8d43eb4ef8fd084b11e198d65cd9ce8d86b9d118bc8fcb308718c4a152cf0023f000a2f5b43782b4f985b25742f06388e3556ff9cd7f7954cd2f6d2ba79b82b25b7a98a20a5d4fcce6f14dabdce4016084a664ec485fcf738d55b832a3e75d080ddaf2e32e9566c3d66fc30dec78b722394df27d9e157ede7b68ece25f0a40f0264737e0e2f1389de87099e0a3b31c8daeb7333964662aca9bbbe3c7cf5f87c7a4e8afc65b9e702224d4fb1100f47d986d2a8ca60250de7f478bce06ef0ae81cfcde8e1b1d64d190224878754af858f2aa01282e74111eefc677c6018d8b48eba0e536944ad8e45c9205cae6f76a2d1b1410f67b8317e0259a84ea119b77c501a5234577202f09bed685de86aee0493b5e7bf743fef78d6395e5a2b5e800bb07a2a48be2126524ecae1f4b93a06e2ec6c08abd08fdfff615dd7bbc6e5921ebe320d757fb77ec0c4ffdb7c6f0d17596f7e514af4efeefede5a03e9bf6bd3b8ec19b9f1a96f0501f56eec721997db30d26154c2012a33c492b0e9c33a97d7bc63e168b10f6cbaa3537c321c78d6d6656fd040ec0d5129223d26cd445fe7094baf17f61b90212442c69599cbead1f48059a37fd37bfd4ab1affc0d7b8ec77622316b26d6d05ce27093ea064e3ba655870fc4936a21af4aa5c46a982c9d9d0d4a79afb583fa9345727c99ca696186cf96dff78eaa5db7fa415723431364c324eef73e0ae89f96e5f60b9876964558e239a85ee3655ae8c9355ea490997197a1610ddc19192342e89f3009165cb3a16e4e9be718bd02baa9b07c0d06449a47e9555c3564891d76b3b40cacb5ae403399194c1ece526b9392fd19c7b772bc50a8a4112450071e9db6471f26b2df469a28fd1a4763851322cb89d80f3caf01596740b5aacb4936c1fe70ae6f8e83faaf638a472abe4072b6ba0a07550d7b85ea0a5293fcad69b215605e162c3684da7e2844a9068ad9fe7af78d25b81ea379eb3fbf7a7d0ab175462d06e818e7a1d3a1ed6ffce0cb879661892ce96408c1197dd3eb7ebb45b47618447247a81e958468957239e9b68f20ca183c34688254011dc1f04f13f336e723fdafd02f7489c51c22c0c65caa1b3785b00f51720094e8ee82abb94c375dae55e40b25610a6b3314ac9b7a1d4852d4f1421616bce457d168bd575bd30834f7ac090f5fdaef2019e5e972330e2291e9abfce61973a275d580239dc0588a0bf20a0a0cbcb7ea1452396e555076f698c230d0409a394d059c2939b6b6068799c6561f881940d4783c7757f249c0e43150ebdb46a7bfa35ee8116270fdcc543c8c384c4ee3b6708268888c711c96badb4bd94f3ea1446cd28954d0dde9f6f0aa32d958cbc888c9007ec16fbf9781d2affc84bfc4fd61b22591fa2f3394a26eeec3621a9691b98ba1191f6a26e202485d91f707e6338a442da1af8c0736f28542e60c7709bd07d65a60ecdc35ec16758404a8485091a777335c80e28c74f8d274a72e0cdfe023daa9bcfcea45ed1eb15e6b717ea7fd344d9d949ed93be01e4a61c3fc3e9dd269151dd5a16d55432033c79990f314d8b13f11d34112a1913e31b0cfc2087951b224bba2656bf57548143e0e66c20d7c807763135f7f1ede1edde019f061a58f1b8d5b20d0a3f320bee3ee0014084a2ff19bd6e55aea5130ea93410c87393729020cde15958ce506af4bfd1fccd2a569747df2db5e50a551de30b02de3170dac3d52217c0313adbd213cb0b7a3d8f59dfd57de8939fd884815ed9d67909cf4db43bcf71c33a7122b9655459c1000ab2bb3556c404f9d6349babae05d1c69c1e532bdf64cc952f0df2de1e2d4418711d670653b40b2442d89efe14e07ff641adc805998984b362f5da445067e2154e56b158dc9c51fd3373d76646497ad798ee7aea6c6f625819ff2e7a76c623c46806cca3bfb9861ac348d2523f8ad089cac7f642f883e9577e9630a8c6c4d402538b42b4da3bdd853984d76602be7ca06bb7fe9291e426c25456e3692bf8608c0634ada05c8786818290a87e1c8c1f22d19552f452239ee72e1cfb9b340252d1cece58c3645353ab8ef9702a93236a55a10c50f7de444211a462c2c00a585328d0ec47c5cd323a43f0cb798160bd190c813076ed7080659baa38fccbda191699185cd64e8e69b4983b739d2cc9c3edbc266364a87b235896d7e6735a8775e89538e42f099a39e889dc6c51b4edc2967959a2980fa00e5671ec08b48fde8f9022f1753013a5faebf182b007d4249c70ffb9985c7b6a8849f8699cccbfb6641934059925bbb51f0385499a48b2762871a96cfb3476c322c7f186314439a66f0aa4225e491c5675b151278bffddc6f2825d15cb1c0d2342fb20302cad79a54a4ff38cbbf1dd74d087abb4beb57f1e353bf7c690bb72efb47556916082c4315ceb2f3ea8f0ad12678f328acccd750230f213eeec8a74560c08a5ec12a5530a1117a1ebdc93c92afb32fe87c5efd30d84c140b2eee3b879d30c9f7283977c74dbdfcc9d7a89f942dbf71a1678adaffceaca09bfbf34655f60c34e72b05ef848293669023e68a3f95bd5ae8e474023041e81bea132c9277514a208e64b64a89376f76f06ada142887412aab912eb542a3492009193e3e1c3eeeab1e13ede5705a2afb94b24720df5f0ee2d7589ef12c0d1c8a1371de62c953b2116f8084f3464952051eab56876095d7f419a3ab8931e2482a783f7c5f034891c612b7122297e03c343c76b4a080d35db53265c1165a2c73564150bbd6560815f3f3e13aeca3ae20d0590db6eb3cb64de7df43b112a27cce72c47f9bc4bdc540a2c603e3c570093438b3d5eec5657d2cd67ccb5a402d11ed8bfa569b6659ac2053f645f2a4103f87a7cf1db1fa721fa24fa09c48e7a6abbad7e924cacc2f01a7f498f066546c42dc185d8574791f83f2c5d9913ef0d805ff55ce4efb32c769ae26667a12bfbeca27acc8ec22c753120ece2b72102d4beac253ec18f014cfeaca7839c458a8600a81397adc23acf24d0e97260695ea5926dd6b7f8b05f579b0a670b790474b4f367d4c219ab5c42d3b656b3918d448f311d5e9160701b31b92a2e445fff06f08afc1eb09d0c11d14b06e00615e14465fcb5998396a5782d09dea6fa6480d1daf972cafa083ce4dfefa8d0e7be1d88ec7d34934ec42a9ff4817ef12cfdcb2ec3544899425c5224f659d665fa09ca9d2803c49740c6276de2af4d3c6021f6aa5a936a44e651fdc6e8a663ec3dba3f635c3c8bb97f37ecff498da8d89e22a7d89133589abce94c7affc6e7c02d6b0406afdb20ad1ac4fc1e838a520afb287e81e57026ef8285272826f77309a60c1dfee4b2e271c390ab98c0bd7660a2afa669f42faf5be6a1efc66fbb407f9ea12113a63e40265bbaa98590354b604012760d34de84cb3e768dd62bee137d02af13d1a6869de9257f94da6795e0d64be436942ba7b67a206184071c26551ab221c017d1d20824cc2d5eb61c288a77d89dd7fff174b055e0c3c40b924657775e94fcbb7f9010a95ac3adad9604a0cd9c0812298407315fd7933223d3f0b172cb41e30f4451109380604d97dd5d383faee1b76f5566576f7c3631fa962b86a2aa9aaaed904e539536f886b94f392c93c5ebde8295b815813ebd77e566b212aa0ca10aff53752693b6087d54508e3ea0f5e40d1a757335280704c69b78d2b72b4e9eb3379f02510971b667f212da94b6a492d82f743d2e0e6da7dabd71e9da373701dd9e37b688fcca17d7c8ed375b29696f36a5ef4777b780eeea17d648ecea13db08ecf91393c8753a327d7c406cde46bc7f6db2373681c9bc37b740fce017360d2f9d7f357b373b4201dbce305bbc7424a224bec29755a4fae893df55f31fc93f2e07dcceb4cb4f74cdf395c9d7bbd6d77fd7ac86139b8723d743fec87ed61bd0bb5cfda31ba6b41bd4b2500b80be8aa271f91ab7fee8f74aea50f885700f68cfe398185ddb844ec45e3abe69d45d756db93e408b27252e2d93fc9bc3e5a1d2ee46fe6534a7dbe8f94e2f32f44daa28544259bbce0ef01ae791f30858791be5d850909df32437d8feab36c7e87ed6a6d2702b4728523e57e11140a3ee7287007a4747c8c1727d5a48408c062546f750bd27d19e2955dd0602327892931b62814cad2f14afbae02a61dafb7ce0bec9f9549bd3ed068132fb2383bc3e8f19d5ba6608a40167122d318b50fed5b9d04e085363c3a26408a80268088986b33151256edcf6fecd7e96ce75b3b4485815baf6a29f2f5d0a3e284cf5a6a18f2050863de89a1b3333ef86d34aabb8088932e23a470d9252e94dc1fa04df083f5494e208bfc1bd017e406f30a3f1a78da234d755ee809602aeedf6394ccc1fb0136e27f526ad102c5bdf6da0680e0f1ca5171be99c6f1d01a09a6283102b21e50d1e23b296dd55ac3014027fe946f0b3562667cb5b0cfa4b188932ca030b1e0c9803111763f50d327cb2489bed2f93f3382e84c21ea4d2ba78cf6b2ec6dd1c144486d9ad55e504324ce8ebe92295b4e251a38020307fdfbdc10626604289e4e4f5f8ea3439a144f1b2d073035eac09e0cae7406482313f8810c17ba02a61faf544f8a6968f4db2ccfc4a76a69f7dbedb9491a57c90faddf9f1a0141d6bcb8c5b00014d0c89b6ec324346ec7fe9756827811c1d622296b65ae0e2b6d0527769b704a4113309c4ba8415e54aeb93c46b57cab965000c12d94f3718f1936e00312aee53f120c4ce03ec4c6be50a636a7c45ee98644d4b7a1365133414596eacd5ead2ab245c4cf78b23a33040d309c6331a4ee4010fa1934a8da4ced4c8047055502f0127350707608354098dc29234bef41e2fd5ed10314592bf079370c38945b0578643907cd3cf8fae0569e07afc681178e0a8142b0962ddacee3ef8c452b162f8e9ff3273744fc073714fcf71b04fd158d0db6fddfbbdea60002fc899980cebbec04f3c287e5bf4cb30e873133dfc35c783d44b0547844b1303c905888785cb22c7a78ecc1465a5df4f6f0e04254c2138f19eaead357d2ec9f4a5a22cff4c5316270262f6c96f4789704f45497a2f6f096d0f49097ae7a565df2f48851e9aa6d89b9ba53b370f78cec400514d89dea00f55f2beeebe98eaf0e873157e276bff068c6d9368e2f396ce3e89283da38bfe4b08dc34bce6ae320b5b336cebe376be3107ae261603419506ce4a2cc84948de18b28c814d70c470b975f8633d9311e11dee1669c78f7a2dfab984433c1a07ea6be2a473e987cc41f405b3812d1dd15d9b5511d2a2a450ea965389be9cc0773b38205e30815c41df02196a445293b13d1d1a26fd6c6c4cc909bd0a8a088654885f3a3deba262be87326ed18b68ebd62960bbdf1cb3cbde89609bd57cbeb7b3dcba27a2b96a37b99cbc87a89972f7b17963dbd027144206ccbd684b1f27a43d70a97bbe5617bc374beb3a0c3cbbce99df3bede0d771104234f774ae4c9b3de6e85a4eb91542f5eef403d9a9b591d819b50fd967b81ba835b4cbd1677bc3a831b499d9efba6fa2ab7a74b41b96f5b7120532721d556167660ff85bd17ebb5d2314be469e989f2eb5af32a9cbd7d06092abbe057824317ce2538ebc219090e5d3896e09c0b27253875e1840447550d2438e614f59fb98f36d4a76118a86ee0ccc4cc98d5fb98093e74948a8e963257cce7a2f9dae06d947596ea122fc8235a70f752bd9de988c119261a0eb30d406605c6f4abf244a0c2756d696ca76337dce3a9328286c0a05986d02958fc3d82d718e8dc08a750f72b6be9f1ccd25cc649ac96892aa10f2ab57a629ba77549eda1d95b9ffd16cc6c2a7094fbea48badaa42fad157de354faba147d0ae7a7e434cdbba79f31ce265aa40a80b61c8a2b7c5e317913fa7d144f9ebbf823d6a677061847ab17f38ec44d8d68db16c080ab804144e8c2806a257926ba3c9e566095db1a86ec6df7e799f65a96101ca89fd16ed28f0ad83c60d7fba8f9fdab29659c2206f1406ddc512a664cff8833b102dce8d19d0cedb615bf1293d08f5bf1b88a6cb407f8804ec13f432eadc2e2030062230332ec9a5dff825a0690d67684b3f61cd9ccc064a802982a647fb2cf1313d4b317e5e09b2ec6ed650e5415220d82cd48264109ddf685a09767e70c63d2a0d81542523b55c353f1fd6f538f39574ac2e77080b4e66fafc71ca746c24ff383f8ed6eab8b39a782de550097187478acd5216c1bd91b0ec8466a5600e01ee950f87eb7adc79c15be743ae0fb6c1202c832b9cca327fedacc55eb62ee0a7c4d057d9f4d826e2c635293c240f1eddc5e8fb9bf022f9a5b949110b9a5e463fb620d31b5c29cbd73f640d113fd6ac7aaea317785444b5dd6f84020a378092c492f2152384cbf9ab94a5dc8bd95723fc60de2dbbbad16736e35301fa261f9dadd5e8b72be02f85d9f4bf59962658660894f1ed79c504e25770613e3a174fd6f578f392b30242df839f6099e82b8404d488be577775b3de45a01fe55f0f3d9255c29d18338513896aefd6d75216785a75d057d3e7b0308a52980e861107d75c72ab52877056ffb4d329b723781bc0bd70a09174de891aa1345fcc3fceb1dabd4c59c6da1484fb147717a27229326d0532b72e1f55e6c7831c4bfcad98e151d76ba0563a81928211dceb0e54ece84ed2cea3bd25c11824642d7e4bcf37664512eb435c7ef0f851900c735ccf4c382c6cdf374e7fedae8e9e52f2ab89193f30fe155838ae33869157f59b684c153df87f4316e70b8e36db83783ce8fe12e80cc62f54d48d3dafe9c8a29a78702d257110ee6e7641404edfac842e9a07309f02b6229e2ebb99fbd8551ea96c639d183058e7e13bcfc791612270f8a8680e62df22e6c75a68b95c1a017514fc6bd7b018c64c47c9bacde136e2d1d30b8bc4e3b1b33e948a9ec0e833d48faeb2242dc2d1668f7a25734ccd281241e90fa3d940ddb679f6fffd1713b467be7fae0ee21d150b7a3bb8744a3f05f3084e47044cee5d1264bbf13f38c5a65f3b1e1b63f6ec0a23ef8430e18902889184ff82aad61359131700aa3292841770ef8114ab9ecf609cf7a98a841e22eebf3045b1702f547c46551db1544826ac12c0fe9ba185c45dc7fb7fc6953d9a2559c97715008e885cf92eb320f5f0f816ea24f4067a01be806ba897e99f4efc0e4fdebb04cf9dffbd7d1bffcc1fbd74999e67ffdee80a61073107c6d38f1116dbbd3d4058e717901f254fdee3972666b801b1678fbcf35657620146b81630c1798a4ed0586ff0103ffe50623b017dee2cc87ba4d5aa8908a4f93e6f355e1bed98a0f6020f4f0e31c98aabe5b3597834c00ad0a10e052f025d48738a0fd9f1e8174e84d8e0ea976121a2d46d8ee822e00f654e65c1b67ab2cb8489b869ee2b80f98dc1d47d2caa08802ad049fbd7007220f225a546aa05abd1cfdd4b47106771a385fcd07aaf50e51085f0fcc91808c54b9f04062016e3ea3713eaaca3228b493b436befd8647726a53bb484dcf1b1b03cf75aaa5725b89a96861c5c6f03d972c39e1692faa4f2a70f00cdf29bd0f8afc7dc07cef7f46d7393004343761a6cbb5d4c0c000920234b6adaa43689be9b7be08631b6819b3ff95e2239629f569d5a8a0d63a204111954c1a6b1b5ff5cf3e11f6443e9c09f5d8799827a4f046e73ba3a389c7aacb8a9a0022dd547dd65dd3915e50432640d4914f8062e24aae004167cf927f675e80ab0afc5a0bf96f7a03d1a3e2a6ac932688d6a81bbe0e60b28105264c6c614dc2043a865203ea4bdf27b190ed85dbb5ebd16ef39ff9d84d5d388f1e08cf0e2ba868fca8aeab8afb3f6d561b702d39d2aea62f0ca5b7d26518908dc83300e29af1a554b98800c40e7773f9773e09138b1a37bf8df3df447219f7284c62b14bb15bfbf32d4dfe709cc3f8d1b4aa665d872b978006f2b9959c502a0a19d5c9095bb24609c55eced6af4b0ef753725cb06ba2796c4cc5fdefde4f0a1d8863c612148f811cd46e9716c910943223e0c2f860e8d11313e4fea18129e0553140672f149cd8e1b0449cda0706cd7d53aa46b894ba29507d2ea91d59c12d2384451890950d8814b93f4750e0d00b3828ed99e4348230bb7ca7d7dc7610bdd519b89939b207d6ef9d0a3b90c0210a1e768e2518a4171d25ec4bae23ed4cf423201dedb4a79945cb2f16a9b56d8b16bf32bbdf79d42d3e9d8f9ef7e742a91d406890e34267c532d34951567ffec2fdabd79c7627e52d04122441d3c48954d6cd6d460e22059e98aed1b84038d72c73300b48564844f3ec7515ed13b93a50c4a2ac1b9245ce754d25db94ebd36b6202b722223c045dc24dae00d1ca6d2cd30a98d3952d27945519d31552127332d5b8e187a9975c9d9e513f5c0d08889719bbf200e616f3fb8c15efc3b1c8853123c6a2a4e15fe0ee646eafcc7d312483e1077029374b3c1f404865e1c7e455c48bfdc1a8002b268ab16fd3297be05c19a12608feb9b2d613d54799543e0e697e1415facf1c18eb2dc0e60e2dc3a3312fdc3ed6dae375bd532793f3986ee8904d1dc5b8b12c1a498636ffdf8854efd07791298deecd21d71f13720f58a13620cb31da41592e40224070337301f7a035915c4c0f5353b03f086c41c202932cdc18d0bbe73bce7ad6c6281f0b885b1d202a36cc433d9fbcb8e8cba2de3174ea6adbedc6b2ff77b4eee8f913aafe2c609315bd5b6d7d00f91b053e577415bca05ce93efa9679a009e418a86d6611f10b4fba5cbbd820eea42b464148d79fc873fb3244ed335166262d8cfd8f5cedc873543b4300529c35e9c3950f1b32c281e87852989839b21e7580c57cfc6ccf3490761c32b831f97850d7226012a14f0a6931a6ffabdff8e2e9de5b05bcd84d013e037dc88d1740e8f7a52f9f464ce248eb3da9ec7b200e0442df92aa20cd071ebd5d825e91fc2132d168a2834a5abcfee500c8e185594b7f24b6de524dedf0ff4f5e35fda40cacc3849f68a198b1599097e19878a22d3e19d12d129d5aced427c8eaa9db502a367f74c3fdff949c330a7ae27e9e2970c508634da8a1316791a12a931c08d8313a6840f268c2e75b4c6d81ca6692f3ec3dd784ddea53330659eaa075fed6fd8b6168580938cb5f162b8502c5c68b00d2b024069641265cb02e2e176b40b071756c76efedd69b1f152ad3f99d4eadc5c8b1384418ef2c5acf5c17148089861725b145e4b1c707cfbb6733dcc93ef8dcc277d6e730dd1b3bf6912fa31cc9b876cf39f67c2853c03ce2af404a9730011804c724d60f4a29921565c0c7a50c5d98280f42a1f3a9cf501a2960b183602f3a657224c03f1831c6501396ee74630f9b327fcb782ce2cacb0fa4f74ea54c93440e31ea501a836e381ae2877f03c9b6cbab911cbc3e5a6a409d03ea62520f6f53cb4615cdb89977c40fa0c41fac30f87f57536b991b148ab5978f7c80705910d1c2d4abdb3b7ee14cce8f6043f3dc86cf60979f0fd2f48269405f1bc2586a022ae9d5abf6dec3f678c4bb1cce343a267032098a6ba6f9053948cb9799b0d2bf052574b1c401995954c13810d1ba899afcc0928e13b2e7256880dba3d21906ab312469bb44e4f4ce9f30622375c687377951b36681b3d706359ebf52b0ebeaae35614eedcbf19cbaa9b339ad43788dd31def415e456cba24bb9137afd07f907beb8fb760d321a4c9b8156e55ff8abd81992cc8350e803d9102b27c2d2ee31c443bad80ed05e409cd1783b9c813bb14568eae9f6d696c7ca30c8363fd8a639a8e2d36b83abcefc378777621d1d49267e441146573e065cf870efe1dba001698fe135a17dcd58d06a312ed03ab477d85afd134e9590adc96965c3abda91fab64d13b043d14e1491eed12f5edaf405d43cfb52bbb8c242919ee4b76177c55dbd2c29ae5dec778bd99f723a175fc28c6a5f95f6a66f9d651890176a4745c7146101afac940fbcc6999f111461153ab359caf4e083d4e1b8ac932d4fe6c807f950681a87eeb65394e03f86bd5ac195a0c5cc98ae71b416e4371b78cef8f5b25f1673b39eb1cd13bdd7b04847a95908173c54f480dd4de8ea70fd983d60764a9718c6650268528153e6d43e4e33330570414979911e5c86574660483a4f09de2eb87646354f704943d56dd1c598e00a74fed12a9949eebadb7a915b46bf40be5e6a1af00b8a6693a965ff3fc22d4a778311f42de5fe7739d2d18eab0a256a894fd3704d7e56c9a48edd8004c6d03946973c10965f14fa39d29d8f94ba61356c486f3b184aa1b06652b97128847d1ac732899b7f8a4de6f49088f40894d200eb8c66499b956600fb9bdd0aeafe6680eb99537d76be89e01ffff5546018f0eb85d4d10566e6b10da940e877442d2d6fecabf3c790896cfc821e2910235f3029d0f951cced15ed4f034e13db06bac69085438e4fb9ecc2a619946bd462a66a84bfc950fbbcdc6bb7ec03bbcd100387dba042b3f0d5e0be12cc21ffd972253d3a337a9ad019787a63971a66d47a6963dd361e1ca1aa1e9179105e9262a08cbdb9fdf3812696cb0be08afc13bde57688a27781c8d06dfddcd218dd8060d809c77d5b8edc2ddf29c81dfcbc9304eb0d22df8796540303232f900a48c3ac68783f2d2fc97541918b1a72629f3eb17eb035c5f94a5c86eb1e468b23c94e344f067ee83e4cf98fe570ac8b4a73b8c7665282db0ee9dc191bd75dd82913269faade8d646d1802698991bfd21367ac1a3e34e3b40e01d0fddbfd74e6c26b50741330cfffac215a4e180a75a1f71df3ae137d20f8b508512db6e31429cfc46ed9390126c1e5d0c9f45b194221284e0306691a03124a625546c10759f01c7fe2c21a8a901d8b8d63192fc440c714b3dfcf809bbd9ea10ac64f3a4bb82d655b6d87942c63f47ab1838e607100c5e97b19aead2d705a9e12299a84238518a0c58585e94233f4ec00fe7be027a899f67edc633b8e9d2d08c335212aed5e6ce8c636af6f7404ce3d7876173b5fc88228b0ac1977639624b9005694284551eaa7bddbe3c7f28e6ae7b95ccd4cca36da2be974c3bb430accfbbad0fd6136de14b390494152e523a689861098bcc326c4873def0e0b0910c5cf0641910cccf4421318f1b60a2de169a622f5abe1df6533fe0cdd118da80c68bd412c624e5fd0104b3b9cd8d69a6a096a64dedff94554f5b078b5e3f5286907f0918618b1ddedcd874deab8125d012b0be9efbd8b5c95f78a5ba1bd86dc733aa708685ed6c49bbb65f02366f4cfb0d31c2bc8cf9107163586c376be1635e86a691cbc46f78450dfb898dd418fbf10d8425a3cecfd22cb6c3d9a1e466f2036d88cfb17943cd205ed514a8b0be831403d2a8a2bdf4d439f0ec6a365259cc8d557d099aa83b7577e33f7b1cb68af3b3765b50ef1b1dc7860970be9b1e98f118b749d8d71690a88ed77c88f5aa70e1e76d44433d71e4cd46e9bf67f3d37d65fa8326109b1b130bb12451af9b7afa4cc3e5df7da16d05644e4a2bb4753b037d0c5e2ac00ab937a4d31b36312645d6753b3f6a5eb58f4a0765fbec04368b033517491997a3da3da37faa168a45bb995336b7774dc8aed81a69a646a2e08a3cf67de7494eb5387bb89074b833eca7b902caa2bc1015567b9301b52175703ac71632c511ccffce2c8070a08c33b66f2c9e949e13240813f735d88077e8676b078d6c75c07fc204977023d0b657fc00b3e31b01a53d7eb711dbc28700abff02b88dc00d81d60d3c11c545aa188466301f881b0937792c8b768805cd4208cd73736ada525fc8c782af35c4cfd224143aa62e46be898f411f6c668426e8c33a68b073125e44a4419f3245fc1504d2da3a4cbbff70fc74c46c94ba9a1a76c4c3879fb69e84d3813ee7e9fc81e9df6286f85720608a894afcc912fa763373353cf2617eb40186e92987a22a5fd3710b0769018a3f106b1318518c1c0ddedadc63cfd9b074dbfb534173cdc53fdbc60010f5c9aa1aac82f40f983d97e53f9ebe7032037643f0b9db9eccc250cedc93507bc95018cb6e77465434b599e57963197091f8195205fec4f167ce1b03a0e883dd002152d0941a6e28d271db486718655d7d2b1308f1a765cd8dec57b7deb13c0437ec6fde2b57ae38f14fffddb11bd0419c2583baeaad3aac00afba6033c67cb3922699ff036986bfad45238c6b05fd2acde7a86ff49648187395b7ebf6dcee53b1f71b81f768eb2b2840236644da9eeb7ef210eaaad08481d8ae5d3897e6505e291a684726c6936909a03f48163984303ec0d5f8db6b93ad20cb95f7f071df6020cbd08edc38049c787d7311362cd5a4108a5bd5a62416b581d1193bd51a2ffd472c5101d89a3367360a8dc32d80975c07e77875c4136c204f2434e1419d9a4b894193a01c2d5a1dfa239d69272f0b925cc606a082cabee18f40278ebde45b6cf1970e6c380ea9e19c089c066b23b1a6e5a5fa330823e0aad7720c793cb468512b334c53a39bd62320ed505fbcba97c6b760c80480000820cd66de59fd510bfb1960d3c7ea2b1b54bdac4227303457957245d25509512c4e93739ec2afb53dbaa73853ccb117a672bdc149a85adbee9fc168b9d40d35074708bc5c46b66047833e6345bed87786c79c160e0c1c4574aecccd09f55e7c284b4fea038a2cb54d339e6663ff69cec6c81932fab2ae4f090997bd482c56a97c47a50063651a58b7a91123657298aa8c94fa840b7d3b3a9b0869c7f94200aaef806395c21b2c3f566fd8108db8f6049934d13120a0313512a8fe54cda45e3b1b0155ab939dc0ca90110770e3076d75b6958f0d8c89c9a9bfec020dac11e46cbca59a10a16d2fdef55ad60f83d59656e64c9a27bcf09c664d59fd4d0afcd00cc7e648c7431b9f17c81752a0573f8cedc2a75d0e18fa11582ebd949dd7c740c4d829f12095f307c082918654d7d6c14030f44c323a0a4f9a6cdf86ed908a475f7afc0634dc44c44e284c9548d903ac2dacf762a363f27c5eee6a6091897325c6070ecb54bcd61d1b003ebafe19d6f0822aad3363c4e9e8c89b6fc8e103b07b9b96b49dae3b071e932c3a2d50d30905c6391dc70bf1d8c8c72b3a9dcceaff228f113ab9d9b32cb263ede28c45ea9946f1bf390a0c43b29841fd46c7de1a18453be4a26865652faa42761deff2a2146589506928b56da4cde2cab07a203b8557606431438eca2de8a82fbd0dc7d4431c7ea94d28c05a0f1b704acca08bfd454d80127a03c46b60f8f3e82c28abec20e5b6a8aafda09fb02bd29335d8d5f4c10595dbe0b39199c75cf07fcc0053461ccb96670768af79a356560e9d9e6faf8bde9f3dc0059ac5317337f01c1d348490882408e8457b45665e5843f0013755c3faf689318d49872c77fff511fc1b449b565f9b20bc871ca0d475cce1fd7fd80a86ece074c5cc815556c5766ac8922cf81ea414ec7bbbb2ef1244a9b966ce8bd7518111bdcfeb49a780351efefe6be758fa8d2c458802f1b31b471a0bebfa8157c2d0c0eef8cf38e0fad744ca3af32122f95b6c4c12e9aff3ac388b65bbadf75dc15c8a6c9baf16d58bc3a0f78e4a983a78999181f06fdaeeeec2c3c780ff77e4b7622143109f59e3b5ad08d4ea1335c11ced461aa86985f183400b4d1e63ca800d058f0f6134286a72ceca0f6f2728710fe6619afbb23a58b6c4b0ae238de05d828ad92e212cb7ff13b48891035a72b67f201c572299ea77e31a213ae305639b6cd3eb43c8cf9bd0a7545283c7be8601f1d89f52436ae3a5c41a312e3bfcc1d80d864cd6edf9219ce7945d2921226d5f6249a60ab33438ebc282276424593cdc632bd0e44858c5469a80a7345103ebf7442aa7465f6d0eb0de58c73da48f634ecbcb35e53bbaf7210e7880089b7cb43ebe51f11c3953dc991dad8e51cde111509452d5a6ce1107560489c00122a9cbf58b6005e385b06966acfdf94a0880d4cb20dc513bf0466d38be6ee6f04b27ac26be728608d2da3fe657497c27a4754bc75b537e3720c22ac16448f888c60cc82b108890dcc39e7f64fa101f26a1c526d9e99ac8f59f44fe111fe0b619b693bec9de4255175374ac438448c33677ac55c58c1e3f2a20d2f1e61aaaba98e2e33522443aa6b963b52a66e4f8d5039106ec371b75ba22ff55030ac907b90455930891e6b86bbefc4ccb678a85684014e615508125c236371ac462c4d302bd1a0d2d05a23f4892a38ab63cdcd569f0ddaa031198c6039908414265bec58105bb1586c04b2642bfb0c8b5daec6d707ed782bd3e28f6abada80f73c6a698f1dfa155af140ebffce40bb67b4e5c4d494ab2414c0c792b71757274020cd5fee1fea5a888e92d184f85f039672661a878e3c2a727f72f57a3248fe718c5ff0c01cb65a659e21f060d96a1bafc4865e5cf81af6f2cf3f08982999f0cb9de55b4b3e6f5049b8b85a9da501517ef92ba97cc67e0608514ec5b88946a17454b7298cdce4c3716e616ed3004ba06e539f8819881ef8c9bdcb6666b3549d8e561b3bd10a154a85cbd99d234c107acfe7971ae72ab052bab4bd5cabf5255e662bc0c501740ad431a6eaa6e62984dd978535d766eea8a9cfbf80695992e1d05b38084583104c1280085c0bc4ba286fec61b60ed2b2c5ad276b5731438b2546a7ce3552ca3e5e935c054764cdc46d1824e529407179c71b8d164e78e00660b20ff56e8be331c9370ab9b933f32bcba588314a8006086d9587db7a6bcd3f412e30968723b51d28b816ac0db93d9a23641d715120193274a9a9ba7672d948941a3d9619c44716b426ad3df25d37332c9dc6d2f83902e0390d87463f821a4e69f3158d8741149f204333e18afd55765fcddee8dd07cca0d080ab786c6827c195dc7ef22ea6e97a71c8be03a9280f86dbad1569fe9a68862bc004612058cf04c77fd00a25bf7a4addcdfb30a536c30f30fa44e1c5bf6bb1b2f66ccd5f8aa98888237d3b42c0d29ce3d005f0d914ad084333d3baf9754df851f59c79d58ac0f757be6fc9c18bbc290737e887e7a5271fd4ffebed2f0be7f1876b43fb6bae3aca3a395d5900a334234c209bb2e0abe53fb08b50fa792c9fe12ab2f405312be4f81737bf8216b1c9c25e82c3312775fcc1690055a0a63d77dc514eb9cee5685863d020b7a1097475d6be7254ae258be7dbb5ccc8b4ee8a6a94e0259c32dd765bb6a6b40b98d6481b8faaeb96992fd948150c7fdcb4537447f5658677053f564b416bd6fe5670e81d8f154aa682bfc744105eff8c19d02550015d3bb6e042fbb03dbab781684401e214f74dbd90b54a01fb581ff6c49e1c0791a077e13b50157ba655d2d6f07b80ce3fa172dae9f9a7f7afa7f8c098fce93b693a12bd6fa3da45b8f617d0bfb7bff8b1f8ff8f3b300dbfd6fccbdfcb928f5c8fe512242d7b96a8421aae4003727b500998d8e454b3787138fa864c5b6b46f1a209de9c3f02af4f45521f8359111f157ba59be8fa25b83b47839f84c37d112caddf0f5fdfaab531ca97a325aab1eb7e22bae5fb79aeba7eddd507fcc7ff83c7f8da1e7038b95a29e26b64dec95f07d45b61a0baae9f04d6f02b15647ef3692f2d2f48c98bd376ecbdaa06eb64a3c00af251c28dbee8c26226b087ecf06824a0cd9f73c8cb91efa9eaa0b6144167644ded947b0a7446907be0c909add06b0fe049bb2a4424b35eadc591683636f0394e1e51d1e9e8653c15e10bc83dff12bb48409fb92a8a1af5985a938b224009d8f8416b3e9af838e98e3de49146a588007f94d3b94a7eabe88cb5beca323c056429fbde4b0c34bcd6e6f82d0e9f1aab02faed1d6e3ec23b356018162f66a09a75281af460c29728535e84b7f8e390afbc6ba9e5852b29c04e37efd03a516e6745908b930bdd5ef4daed39244c2704abe8556fc02c5904cb6178252b625039810f53c877ef87fdfd8f252afbcfd034d1c1c58622e4bc212d681b51d08db9a94a035da3889880f78555a73de07784380b2f1b243b1bf8f9337dbed3c8b715926a6dd38a053b5c69f6bfa309f515e6aedffe191a514997758be5b8c12cba1eff8b75f69087a42e1711ef078114c0a8ed9a8d5f2e4e2ec1edc457d724281f3fd58d98b3e901f8ff88d9e79e2a4ad3587ffce60d48685f7a171360a72dc45d79a2beb41496b15ea85ed41c68f0783fd515be44bd3103ace6e54f7eb0738cab3031df84c8c7a80e29bed08079ce086aa9709b62f8b211e7d7554c480abb19b5fd3531652a665102a474837e8a4d2c0fb9819b412086166db4c2ca5754a1a78e1a03053dc95714f79d4d226f407390dd53252c2fede0c6abf44d9a0ed5526962652bf3f2451274c92674229c19932dfa71c7b369eb0548e9ccd54d9288dd20e12e59faf84bde276f99b49d866a200551038c60d94a2b13ed104654ede6062e8493914dec00e350ec9e0b42833a68faf2ba142ea2612dec00fc62e5f4369197461c9ed67e2105ed31f94a1783fbb77c85d498832ff42720a43d69938086fbe0f0839e6c7eaaf88b9db1a9512a0976fe216624a74a978201d6d2feb74ec8810abd413646267f7d4fb09b39dc0c1e1c449230adf4af95bbb06df8613b3c93f385f9e1050c69dac6317b7beffbfa27ec701ca498e6f771f0449022acbf40328283e1b82e04d8185e7ebabde41f2058a7fb2d51965489405a21b9c1be0e79ce7f8b7790c7a466ff8772272b0abf3cd3ffa26120bae56ec0bc3724a932b52f29f04c84f6d498c6cd398150dc34e9e35cdad0a76960039f009982cdb4573b1263e0ac213e006030a22ec0952aec6a39ae6919f952f83dc416c0440f3aac0e41008c79d6031ba0e93bd2ade7edb7ecaaa7e051625d41f3363e9b6cfedc11cbb1b4319e64e82b970c75a1008db60cb8bb45000f64130041e96407fe30bb96853bc5a936fd3961a45d1b6731074ac32cb4788d572f860cd7725c8972418286564ab180bea1a00668c71168f57c4ac1abd7659587b25977041a7bff8071464e21007a5c08c54bc9d30eb41830902cc2f19c9ba5dcbe2589adfd2377289a3494b9e63c0a48cce3891665daf16cd77c8d721bd3924bf59bf64dde264cac94d35bc2083eb60fcb15a0a28cf91a584bdc3043ddf3041c5457d3beccd3be99947baf1ca1d03c6106405cf4d08c68ec79223202b95468781c1732c3431590086ee69b844fc690edd4d26390942a527e34b6010ce1f4edca1949f1ca605a67180394d5dc98b7f006860fdd81078ef586b1011604a2ffa20525374b9391f5b9ceec44a4bc8188aa6f0771d7c47032a8ae2815920d2324e07ae1bb517385f946d1f81df40133407b665049b347fc5178c491eec9d8374f88277766276dfcdb9f8e9bb110a11e230102fa2f89b8e723b995efa8190e1daacc12108ee5bfe957286691482f66eeb4da49432a514fb0711080f08eda2fea35da8a77e8342bba453d7591c7e4a5da35ef541a54ebffa5a986c170e29b0d06094c1217cb55a55ab1e1a354d93a9a749049a403f745d9992e40cdc6432991ef04529b236c88ca166320d1541f1a607b0011459fb7325c64f55cddfa1308230ad214d4e27c5228c15cc28517bfc0e2191910592110c69b625cc0beaccefd00e92bcfc1ddac2c687de8a51b6762d0a2f6728690d46dfe2473364916c2b00d69d04a443a1d9d9b65b814a13357d343beb279fc0fae993546a9faa59fcda0e2faa77bf57ccb8c1872dce3553c720e95b13eed14e74f91da18152236b7d61abb891b5d217169d6b7ac1c8df4d83eb377b94032d6798dc24cf602829cfe9222121219a18076140c57ca4507cf10dcc2e5988a7481f16f2915fc8322939d949393b0e65f2f6e89426a7d4644a7ddc44b338e52d4c68aa2ae731608bfb56d13b5f18e7e1ca0704d29d972324b2643b3a33223cec498c4a82c2ca6fd1ef10143d4ad4690fbcfc9ef13bd4031b1c4bb552e547afe28b8180ec8b650c145f4522507cb10681f8fcc319e17143fc8054a75a65f1c541b023f1ec7189671917b11110cf7a68ab153d9d346d8b31517a4dc56ca89389524ab90a3b47a901925d90b8b3397793b37d606c71334a07c6c35180f8681b02cd904582991c8a6988c935d73e249a9bbe8531a047f6b1db31b9b6327db19b80e60b3375b78f6ec59bc88a1dc1c82e407d3a095f582183d1a8c00e856627950a13264c1898e943e233fc763bf4a3d160f5a3d9998b04ebd13bd4e77784064a8b7e0e4656f7f47422abe3e742bb918cb29fbef1d19c4a693c21a414a4b9c64a4a3f4ab128907ea5f81d8a62889705cd668065968493df7270ec755769d9d1d64a428d4633b1e0f49a22a6f0177692e3d05ad6deb29ec2445b79a176b514bb5186025b9c34b8ff75d1cf5e2143e9c34e52cdd49a22aa7fd8493d6656ab9fad4d991e85c70d0de42588af91712d0df6518325085175b0af06da9eb2383798ed829f1e723fc4be7a8a113fbd26490ddb68a36baba5180bb9c89a2e25a51c77ce28de987af48edb5037ba20d2b98300759366f22af5ed98f25183d353ac794af34d6a50fb2d06c5281da7fc0c9a517efac6296de4a3034b18a3166089d4ef1535c6f8c1b55a211ffdf4255e8a1b90369ab335adf694130a393c5419b4a7699fe13f6ca39e48ed8597226bbaa605958044dd7829be7a8b654dffe2b9fcf446e3a76f3a38ea315283d36953cadd4a318bc2e7270b0d45f183521ada810f947b5b71d4db9ae3a5f85a58ece7fc69a3b5991e838c627eaf7459e2c3fa2ca0ba20fd0939753aa8ef08c3b6a0069f10404b95daf427314a0c929f0b732a45d6f412c4a893c6e44ea3c3301a2245226bfa0cb09cdee123c307032bc517ea8b2d128c66088ac6088c631f0d946d61532ed1661744fbd8c7e7a76f1eff44d67494c74091357d87cf94df13c9410d4e0e3ae2a47619b5ab7d3a6b31791ca541290d4e5fc9aa7fd846517efe5e4143083f3d0a3538dd871ab2901635cda49962151f94e4f4a2c892453f4f260f694ea7fee9adc5c94fefa3e8e3b149bf72fc3452bbb60aa5253f7dca4f0fdbcbcfa59fb33366303ba12a948cf8302ef9d9493ffda4997a4abbc218f4d3a7c7a076ed1091949f1e9fb46bb9fcf47ea25d51cbcfef54a03a14f1f463194ff1f88906a77f2ca853a2cc03c3be955043dd82cc9805c5885ce0e44f7e8a3f284e9327d946dd009a211c121a2831ceb0230c4339e70bbba274c53717e3a7c578286fe5536bfaf30da9414e6ab041f61284a80d665f6cc5f6d2e2f0b3f71aeb6223c6b2af5e5ad2f2ec354918a95d26676ea3f8e22f96c5cee599d178f63594be8df848e9287e6127f938511a513126c62f4cc5a09ca97792d414e3c9679f7c9e5a4bb3d837e6da88070d6d147f7cad6fa3f8e23c7e212b01b9b979d6111259393b7c64acb4c3473a0db2b3d219332bd29059e1c597df2207051561a61829a5d3a3a79add04226cfb0010450ddb28063724197acddba85d27676f2dd1fb18e9f7826abed02c9ae8f2e357248ceaf4f74a196a7ce469d0490dd7680212c63b66d58780c387bbc401220df251fc39b5ca0654b01cfd16e1970a16629c2133e3e88ca0df347e878abce021bb05463364ce8f262e4c763bd3e9778406cacabb092c4c0149eac21609b6301a28d417b62ae37df40782ade9dd4456cb0c25be51d8fd72643a421a64dfe153fdc33d52eaf9fec2557221dc231ece4b65e758a4ec4afd04cb47ef28edea3e6ae9178fa0e82da55d3b44c54acb3ec69f2f3eeca28fb12476884a113e6c2c610b7de481d187fdc4476f1939de134aa03dbbda8805256b1bd0c3d7c186b23d3ec96559d2e5ee490846420c6a2897a4cf2519fe52de58da555f7a0bf5146f59eca2c5e197deb376edd0d00f5ece00939a94dc412d35d952b2c719c43b78ca8e70700ed6c1377886554c6347fa4583222fa538e9d948bb68bc93b216af5888326fce414664473416a5f308ebead3f1852be939beb04ac7318f545fa8cd7c618d2115fae8724916656a294aef38e93de40c921ea5a3e229464d3a8d2bce69c927ec0afc1408c34ba742b36c47c778d13ebfa5da0b0044fbac22273d8d93e24bca3a56145ff2a8270be160a0993935eef9769ef89ab336d22e1e3ee3b6955e5389c6bbf7cb47e7e119392a8ac5a018fd285c19551d5f8e2f5ce1f8c2aa8acd7ca106543f68f55e510d39a5679f3323a599ac8867e18ac6eba02865d157d5953ec68ec99e7d3e7a4d633cfa048686522a976a951e64d023ebaaaa6aa5438a9c2043898c1e72e0c345193ea83298fc506b55d55fa2151861e15bfca048660b178c10f12647487c2d6c66fa6068d08c4abf560744951e0a0db6695685163e55402105d58286ede7a36f13169e7e71345f2827cf69d9ddecd26432994c26d3f43297941a4401900e2bfcc24c7a197d5b3295e6520b138602c287274deb52e8ef30df2454a98cda3ca751dec6dac5d1376f4697b0ed5490a78f867d61aa6645d73e1a76edf9ab3b54fa69b1f3a1ce9f067bfba1ad45f9a1872f5874fc122531828732ba1cfda8b5728d3c5932c7ef86add47067617d1e17c0f0123d9a7e8968a841fdae4035d4fea88c280db0445d7ddd0eb50357981851c105131cbf44350c612b420f3a442b607aac7451e35bfa704dab9ab6ac76ed0b4d9e364211883cbb16351666885fe8cf1f68a5defc68ef0e7fec501b3b7cb86a52e50f926509fa25cddab4ef58139acab8420c13264c988fdf8e05db67a7825c21c6f8c5109f7ef3a3cf9fd1d9d31aec72a8ecdd458df5823ea2ebec74df68252ce176a9f5bb9b7baccc7284a0d0ae28c632370760c3c973bac6c552851a7bfaa9802c0efd20c07ffa6aec4ddf8fa55a63bf4f3d3ac73ea7270b61939a2c214bbdc9892fead223abe553a99b3c5c2dce0276ec0bc78a960f5843ddcdda485583d24df446264da93740728ac8d0af95100a6c7c5961313ada2e4555d5a3a6aaa6a4b249df56e392e8dd679e796e8f86bffaace3d95b3d773b37f77ce3993d9a49335df514f53c9ddb2e66676da457606da4b7d73d4fef07973a95e2ab15595bac2b671a2dce517c9d5c8673c9f58005a58753cb4bcdc399f452ba84f403d6b5c282f2a4e90859419eb4d64a0042bde1892ca9d3daa2f2f76b5e17a785b5912e5d665f13609674108c971e7a2fc3fdc14b0f77e8ac8d741298a8dccee60fef8c499f33c9ec9366cef9d277a4f3b40b072fe9178f1f179efc76991d6a2b95ecf1b2690cc5c396002a68dcb871d7b7188f81e24f0e2726d3899e28cd31445d9fce42dd3fdc84813ce4273f43d5a3b849b3a6871c35f93e7f3e757ae43c6eb20d6edfcf8fea43fee956b061f5546784f98db91e7e26e8b7c85129c3e7974a972b9e3d1afac510db883d7513def59be4d8ab45e858e237ca4b634c0cf14fbe92a2e8b7c971ecd9239111b2e74f86fe18524f02ed4fbe518ebd1b4cfc66e2d8e37af09bc6b177c38aa731f95251daf226678fc6e4aad77c4371eca13e7a4b458908bf6d5cf44cce9ef6834b1d81fce95428be60a041ac8b63148806d1295c037542a3fc848163ed621fcee1d5dfc63f413e7d03e7b0e4d9e78c12e3fdd06ffaac0d7f355abb4e58ccb67cc87d8de92e860a1a34bcc983d241a0fbfe9a78dd7317847f1a9cdebaa1ee771ee3ec1fe33a2dec6c673bf3d15a1ce6616da64f3fc0be9809b3a6cf70c787fcb37ac1cfd64f16e2a787a70f5bfc43021315e49ff91359d43bcf07508dad0df5295b1c1c3f9dfed0e9b336d473fa67fe746ed2ae1d62234abb70fc7496b5cb4abf7afc0c7d41fab933294271680c363ee4299a5212d0875ce567c841b31ad0c74ebb6292969f637cb8483fa9c71da9ac3a31c2cf734af61b73eba9a29415a3674f2dd59e7ee998a5b0b46b87b84c79fe30555453b376d5e7a467a1212e491fa6949e838290c387a92fcf736fda158ee0f3ec2bc6b383ad1ebca9277e53523a15e54bc95e8baf007869163ba79af8d4133fbf5491fc02a0d4a00967d4f53055f42c01b0c1f511aa241d4de9e18a0d003ee8ee6eea39624b2f3d47acb2b66bfa783f3a7d7e492f7b9b37ae119ba9354e3ba16eaccd9cf9090294875ad68d5e16ea670928a5bc3b3da7e5f46dc648294571ab7669ced3773bcaa98a81a547b934ae68a494720c51bef6c9152a88ea136314001895bde7943e130537efc6daaf93afaf64d6e646083e9e962523828f2c797a69828f53ce2394d229e56ef45dd39ad6b4bbfb710d4aa72af7699f2524283197b893b5dda76ba2d428a38c174d82da0acc775d03c6572b168bd500636f85373860195e2c447893ca08d7cbef9767ee619f2d53bcc94475087aea93fba97a39bf98e227183bfc0463f6d39dc91738ecbe0ca004d38a3e54fd091f5d3be3a8061f7d81d0c447efddd722c1ba7907dbde545003659545bcb367203c796915946be46489357c0802119ec0208c97b27b29a5943dac50f161cd950f7d843f8070f1850fb4c8404b144fddbbe8e1a9835d1c3df596173378ea375e2c3df500c8bc04910200ce142f5de302cacb1f6879e93dba20b25640280856a6502386c4114868704129a534a71f60b1051b2f9d4755ba42db51e5c31e3e3eac711941f1d4575be4f0d47d8b2a8c7e9efaaa0b42c11960b04312432c99014689d0162f3b4829a5d4c1a97a64f9b0468ba7aee9f0d46b16264f7d9505e9a9bb1638c8c2064f3d078880289c70620635c8e2450e60128c20c860464a297140b15750d7b010f1d42b16242cb678eaaa2e08ddad428c31c298d2e58a1b80895f0c01071d4aa2b8e20a0d0b262f7da60b222b481bf01428073a50234b1757102107183d22ead14186a7df58e9323fb158ec860674f1819352ca2a554f7dc31e4fe9873f3e04f274ab8c508518b32733242b6eb0a2ca154a9efa5611c5534f15a93146aa0a255e4a8f91415e745922e6a9dfa04029a574eb6ed70591b30aa0498217394043c98b95a39e0804329456fb94524aa76f6b7445e3a0582c868316bd7829fd06051d29a5d4eaa90b329faa6a9efaf8d1830f81845b85e8c3b5f2b4084f5db6cb004f5dab519efa8aba33f1d4c129bc3cf5160d9efa8d15223cf59c279eba9076ed1015609ebad605a13b53ead94c61bc749a84b5502a639b21f70d78e91a151fd4f0d265f59bf85a201031eba189227cd97283d8c51092361f0438aa2108b6925ebee410c8d3a2a7be1af54abd15d3f3c09ae00388dabf73a29cf3586473ce91a0bc73249b732e3ddca4c91dfbf4783c179a1539df1c49e72847c2f9d639ca8770bea1bcf32128ef3e6e73ae8b007b39d4e9d4c335e27161714e1e1d03eb62c082d119c8da44df50a6afd51f78030408c56c8c94925c9ceed74d5812b238351f9d059d1d02f8a09a7c6bce0045b57fb737e650ce5d10289c73f75b8842a15028946f1724a7230b424eaea1b62dba109d766d93066348a8a2b687cc71634cc7749ed331d3883a4a8804368e3a4ac8e9033d4632ea94b31a6e9190188b620a3544139a5fa21e29c07819e19748499526cc84c9951db4d44085ab616527050b4a426469a3870a304813cc92102bfb2d4da2736cf976d47882294292d8c5971aae08716444c31195ab5aa8fd5119b277bb77bbb77bbb9db9bbbb5bc951d2168443195fc0c880b38163c79ef5a045e968e7070f8fce8ca7d65a797476663a3b3c3a3b3c370db2b71a64d6e1d969970bfc5a1ba0f8d0ebc686dcc152116a1c39f970e5a4872c86da0863c41361a010220b16a2243e59200d81a1011320303fa06109498911db972caa0c855182490db498f925ea2182c915552c99bf443d3e4149315f7c51228a6d1182131e6aa3890f5731499dfd7005155d946c21424e01c66c8a2dc89892b74fbf443474f15b98a94311ac84012344962628986428b025727348693b752a842953ca4ebf707a4296db83ac6c8ecccc28b48b59fe6817110d3c7cb8622299d8b6464ae93cf1c5f513e95c4a29a59452cafef0e606885cd2a094dbe4780821633af1454369e847d302e7313ea4f3d487248524899d94778e24464a27125f33eeabbd599ceec66a1539c6935a2a3fe75dcf62e0df5c07b57d1ceb3c4eba942ee5b7234b9ddfb7415a532f663e8e8e889c1ae5454b91c8919050bbc25d8ab23848aceba641230b3b61a5c8889400846a24fa682d2eeac951ce421a0bc5578e905094b53172c2baa430129b630292b5c0a519d5210b27381c51654e188807557393170132dd008aaf1b5f2bb2a2d66ab9a7c33ad8d76e0b3676170cb438257c7476b238719545a5a207ab1a7ce424baf8bae1f186e3702433ae7224371c874f9fcd747416a73d7a0bfbe2202a032151f98c23c1e1377c7afb8eca677c0787dff0ed68fa6b41e5333e44f5ddf08dac2138be1b331f658244c3e637edda3c02f1d1c3129e5d00d16f78a8cdb8cac3150e67af591ce9ec383c06f236b266bc8dac8d0175f3de7c7b01dc505e8b4be5df3c6c6dfea3c1a8d3eecd2e05d3f708bfb9c75504da52cede07da53be3587a9be2970ce32be1d7b0a31ce5fcddac8f80c3442f6d953d8e7aff350becc17be007efdc27694cff039bd796f1ee3c5c0618ab48bfbd65f58b238d2638f97d68f7eb113066a31508b815adf8a2f09cb294245dfe1a56a1fd81d5125b8365d65a98ed1a2584a69de30e4e0528dc0c0ff4a79e7610f0f6b3ebcf9ed26e5edcdd27c7a95f56d6f6fdff617aa2438842a1b9ca8f837f1a55ad6f4e93b9ca8eb6c5a47799b3c44a1d651a8da2e546b5a45edeb665927af5c83bdee27cd7b3f233068dbe5bbb7cb0b444a7cf89b226abbd4b8d8a94d58a896968c70f746d626fa669364c926499d1eee929156177573cefb4798eaf373e2cb51d4b9d307c3da448fd1f4c97c55653f10c128729431c618390412026922253a3c57351d6aa8fd9113e69b9b1b1e42d49097382fe1e118d3b0ae4a24be5691151de5330b46bf591b9b2435be4e7b78b39ad4a47d1c9b8934ecd26a47962a635744954ab2e8218a016104162fb1258c90f9f9d8e22fbc94034e9a50529e1423ab466b7d6b6db0b0d05a1ccda31f605fcca5557dc0a0f287ad6f99b0549b07345669714c1fbd531e7a9fb438de474f79bbac5323f1b18bf8d8416c1e360d6b08a93e032cd25c20e5d577625cc6d965be2349f0b793f2fa1d497d7506182f8af70b65984f1df46d90c6ee0bb5ed0beb6a89cfda44cf69d6c223881a72d2c7460d46ef5c50f98785e28b85ee32432ad2220bc11fd63e4e5a587f42369347a9c72f9ce4949778c42a1351d9a3cccfd69b0f22b3f4a8d5ba5af92fa1a89063294aebaaba66a2a813cb2879501a6aa30b27ddb7b2a8d1898b61333c6d99d4e6c33fb2aebed03f828ffac2cfa7c125339e91c8ead8fc9cc86a6fa3c64feb86a8ec1dc5b892a5892b587c214a92044cecdb9b73f6e4e738aa9ba3bcd560cbdf6e3fd084a5aa81ed71574dc0b713591cfe9e59038defe5f2ed0c14bdea71deb6de0178a8db2d2faaea439204fbcc872487e3f06db0a369e186cbf8ce8d6fa3ccb793e3d3725ae52d6cc65b18c8a46e8326ce0b6f9edb422d04ffc60c0e6fbd5d56e733deee0dafdb640b0b5bcec106bbd5ae9039865b7fc3d95703bdd615753d04a9cb783cf1b5a7ae617d235fe7af15390984f55bdf6db52bc69b3fa6f3f5c2193e8cf1d44dfd42235f9dbd30ca9e390a1f013dcf9e7d9e7d7362f56230f2d543157c2a06d3d72fdc94e734c738fb16e3b53c308d9af2ee039daf7fa073ceb78b40e79c0745c63b19f7d12f196f07653c179ad57df3f05052b7cfbf55835d10d5e36a833596ea0d072c331f9aa6692d50080b3e1446cbc723c6d8b6664630084fbb46c09c83b589ce375813a24777a15ffc44b3a2f314238d27b26430410795faa4503618e26bfb6ee28d520addf3ac4d741f4018f12c4ef7a72e85eef9e3296b13bde5454592c40eca4f8e44f3cd91a0fce4d265ef80dfa132a23c77ec5bab8b8a24899d93a31cc9e69a233939ca650b9b6b3ee4e4289a1634df7c88e6db7724899d8d3504e5a7ef080058b045085162b0ed5b45d611454962b08525c14222abca0bbeb1977393029006a3f30a235451ca71993c3a907ef550a9c29b0f4fd4706542725a3744bbbbcc656b83be9232caa8ead1aee8926fa8eccb4d98a3047110476957d086fcf3f3ec424f64354051c34cc49bf5ec3a74a84588c4f080a52c7b1b757d03914ea73c2498c14ce70eec6550a972777757daa089adb97671fd238718f5a6156343abbb5b56832b1b3d6eceee6e8cdd2bb041ca78747472d6137283fed9767458588ecc8c024bced9a29431c6d821f79ab74b2f279e342981683f5a6094abcaa60f985c36689ab1638c4647d835df2e0254869603b80177c39430ba8a96bace521aeca674b9a8358db2cfb2da69518332ae227396fcb2d94a0d5936a5a85d56dad5cccccca1369b425baa98f0840d5b92383b18dbbd41b0bba6c1c8f9681758bd5514757d7182b0edeeeeeeeeeeeeeeeeee7677735134dab62646891af678f6ae02801fd42efa5ad6a0137559f6ecb314f8dc5d972ba59431b089ba6957fa7a5a8f1eed9a6ef2612e11c020c5f892b2582cf604b6becccc31f2501b43fdc3b6350dae0d8f8e0b66558e7a543a74505795631d293640f1ae09aa52e0c0be52397faa06bb6a26a75fb8dfaaca181afc87e6400e306a112248ec113759c9bd76c5856dab4495a886f307020178d9af57a2da523a33730fef05f039257bf49c3c670ccd0ae0b3f78716784ec52d32cfc9cc2c5f88421bbb147a3cb7bc61db9a982715f5a409d414a106fbe3a2862821d41494107ba820541421d445013d3b8a898982b6bb1be5eeeefe18604b77445ddf2844134aa670376ce9196a430996eb6adfad356ada4af0476cf1f0304ea9034c8d344ad4e81c75a9f96a9a474d7a74d30b5c8c73a3976d6bbcc15eb92683945a556d8f5d2937a60555484e1a753d5c764dab75b5720f35ed679465a7c096260dee3b7b65fe56aae9a66fb575a52d55ae450359bb7dbb18824487007f478f9350c0560c47bee312d8466d99610cdb2e8d39a787375f77a8d3432d32a1c49a56eb2e98b3365f54168a14217254a4d65a8b10e1c011110e1489443850a475841cadd177b8472fb42bec1de7aaa64485a493e3d7cd4dd6e8e9b238e9c18b13a2580d49b6343104c68c2446bea34b8d5fed90a8ec32a4e1a447071770ed6aae5dfddbaf9e932b7118cad77755ed5a5f2d57f9358fb5a10d055e3ac7983e706d22cb0235ab80e827cf5701d129f835efdf5f4bc9b151bba34fa98294809447ba3df637828eb304fdcd1d4d5466d9b6a67fdd6b166705fab33dcde35d1bcd9935fa8d8046e73521763853108105a3cbeea88546b4a8d576cda83820ab9a1924454a4b699774a17e492dcd9a8e34a58bcc4b91fc32eb315af27367939f2d64240021eaf4b085b40539862351e8a7d3c4c853da35d35dfa15f3f3049616ea26b4fcdc643238e3c346fac95306523ecae619b482c98349cd2106453e6186f881dba8126377778c313633470370a9a1169be2439735c62c26ff68d56788c15a1a6f268eb18fa6695a4f748ecd22447a7a89121de289301fae92fcb024090e92b010b4d05046121bc4d840c592990f4dd3344e72861041f01024a5062321e634d139a3d6b19db08154030bc688d1243114c60922a6f4d5aab72356416fa332b70abc2cc1648c2c3c309102e628c993a13690bef480d141932eb31811664409c02f910d67bcc08a1220a29e35ae18e32706414b4a73ce394da7d3696a4e4f27133dcd93364d7482e08d0b1a7bbb18417c41aac109d73fc34bd4b41abeb0218a2e8266663f6441225212e5c77892860f609a5019424e89524a29a594329a20ab21479404880f9d65f18a1664c41eb460d2460f0d3cd044df5f57799cacaefaabdd95ba9e0122767011461a51887cb4c861064d6491a38c31c6c83ca80cb54104eda3a861630716d55d7ad4b4d5aae67783f0a1734a78a8ccc387da3951d91f30afb0a28551684e53a36a426234455914a2699a2624a73b27e6a020240e85b13293411882933037240123238a7911625ab146951d597770a9d5e46df2d8a636f1f8536f8d7af48dceeee841a2e4995cc6b441fca244085e3c91e397c8061e3e5ccd7e884c3c3ad8eb608d8fef6fd520b89520a56ecc95a045e56ea150aff145bfdd41a5caafdba2aeb76f4d7fe1eee9dcdae9a469da69e3b4537f45909c36edd4ae793c2e346b3b3992cd519c9f7cc8e6a893733ee4e4dc7764e37a615bf7476fa845a8464d9a0975a293eb340afa68fad1aeb0d5bb141d09a70323471647d620093ec2071db88dbd93ff68d7a9571e578ec391e470952359390e9f289741fb986d974eb0ace84690a83c8723c1e12b67df51790edfc1e12bdf8e86bf16549ec387a83c870e5f7d34ed0bd391a3bf1bbe7908fe8ccff82639ee866f93f36ed43a232333939ab971e264bc9af246207f1b810cf5b92e5decbe50d3f2f214b03738bc9cd6a91524d31a125d5e1ceadb3b9a0ad2355ff500dd29f595bb69004ed470f5916a1f8d693bd30781eebf69970664714c1fb736d1a3ef783281f0b8e651d3d835d79c6a9ab62c1e4f4727f2d09c06a3b7c3e342ecee6e55d7ad89261c52b3b935a76a30ea9055eab3fae26cee51abe17cf704ce3713966a0be0e2d07cf4d6e29c565cea17bbc56a97ca02629c6bf57659298fb5a8fd4222bfdfa1bcf3f6d79361be167a1fbd864ff0d684e89184efdf3c5f9be828af7aaad50e222a9d433535db7a1671c4d506bb5f3e6171742c8ed4a458b662790b2a1f25af3850c4355b87cacf9dc9bb0b32024aa9ec99951144e54e4ed248f944bb76888c263ea63e7ae917af4089abb46b87ba1c3dcbc820fa908fb61f32aaf890bb701163f9e8726eb1b963a8f671a048830c3bf9eae4d521b47aa445ac3bbad060f42dc6efc6e47dd3d427ca656894f782f781b5891e9da7a854ad75953d6d152212000000000315002028100a8603c2b1703011255df70114800c819e4a72509689a35112e3484a21630021c00000000002402432800ec2a808552a4fb95e25c5402877b4a5322d7db62dad6c132e8cc756a4ab81e467b797e7045abb852912568df70f2650b5325d894d86cb1fc2cceaa7f4a53b2411aa121acdd32841150f884341ff71075239aa00e0526bd5454689f81feed7cbe5330f6f6f72b81670d41b36820203c55794f2836f7a796f07e164cb9b3b43d0016f70e564abf27eb4606e107015389585f2baba0c8c52640f1b1faefdd18d9055e4ae3f183452f2c3b66a93c019d85ab8db0c9a7b9b136bb06e96f31e62b54639297b856d4f5dffbb68f348d062a8428b6f611e3b2c76bff2327c84eddbb32181e5292ec5284bc410db3dd79ee29ba29864a41b021cf7446b00d8ac59741a00aa43ec6f1fd360fb1b4836eae4be14065841aac902320d092400b8a798f35921e1d51d4ff3f147acce69c0f460f8396c2a22c5b7ff9c50b7aa5bbf9544aa1c0ce3d95293916278f35f190aeb3a3b54bdfca840eb9f550d09049291ea61c5d00bfe950224b653fb8406dc248996a80eea5ab516c74c6d51be03e63642d4ac53c20bc5601d4086d6991607992d789bc818b9659ca6fbd2fc77c3996afac980ca8c8aa9557e11041852b0db177de6785769f8b49423c0adfcb36f48dab1ef4bf1b349e6df0bf8e8890bd33aedca4b1689ca362fd2474b522b57a577d2f56cd7bb92cbdc466675c74ccb2bdd8689419dd5fe3a6c44d030c386f35fbd9bd9a9ca498b5289ea49700a75f87e22562a6269701704e77a9a7e4480b610bcbd0fe25d3735f7a2a43e078501d2f9af3b59b8a5a66440f5e38471bd6cf69fa3f90932d28687fc13351cef734ae645ab5f29cedc44d8817098093dbbf291e9187bc99e8338aa37af87134a5fc516b17500146cad14cc38a7608757295d269bb159af1fa6109de23b5a714f1aeaeba285c7cd7eb65c39e79f267d9b29685752b2b88af24c37c1a6c3967c726a5fcb6bd1d5fa89f69092be5429f631a294585afff90e75fa52ef394fe4b2e0fe97ceb8aee2e283ce4b46e8dc1f18a9521ddc908423b1c4ecd4bd2acc3f24c6478b9953e8dfc7d5875812b7dc4ce8bbedb54901748eb158e356d49810288b124a72f5b6d435665baf2b8cb75096b319ff67452bd40cdce4294b6ce9fdf5af6be2bfda72adb6f6b9723e63a662172d86a72002c924541e502b94bae59c33286660c22b142ffd1f98446c5f126030176f82b2f4880fe2209090c54cce25b153f5d41278cbd6c77f65b646848d19accd5f525623fdeb31ee6b37a429ba7b2b9cab8ca746afd325db899c31211d89849a3f13d066b1c1c82208a0c293e4baab5a3172bb148c91d0751ee8d6794dca74a6ce15a8e4c9f2acf084aac54ea835ddb951b78e42c56a12604453ad76e0588316e79e630022e155121fb728956d9b76811ffb0ce937023c1d7fc14726302557195794b42d74395a4c6e109feefd1746c751979075f45c15627b13447ca5eb12dabdb048c69f6b57dfdab4cce0937faee28a92691933da5abc34254f0ed4a6abe8b3efa618bdb6c4ecb559b46767514d52ebfa1013ba60cd565893b26c8ed10cb1b2e78c29bac76f7331f8d8bdad6382f9e655b80c50231fa9370ae1970134101afa358bbb95ebd91e9890b60d34a69a7f54ba088177bb87f51f28cc05e7f33352ec8e2f390c6d4fd6460feb20890c3accdf720a74ce331fc07f3fdc30ceecfcaa3e1a1654611724779164b12356a17bb06e34fea13a73e82db1931752ae161b988091310fb0178b06b301acefd8d260fa04902d20075eae7b7bfaaff3abdfdda2f5f5e79151dbc8149ccb305cf86b0334e991fda2892ae2700d445a095ebdb940cf412bfc549df3fa26b572d04c744b619c405d11c802a9ca58bdc8de369d9b0239be6a0506777a896fcb6b24345cca61cf87bcd6ef55f86ed6970822b2870f1310f08bc9737ce0ee6d2206af904c10fe6797a711cd8a1e250f42deb59981232a19f3dee4632f17adb33d5610af42943df892e6ba7aa64d4e6bb62fc6a06d12843650c310ac4b2e029378cd6144948c0d6e014d0bb8cca0568e591bf220975977724d48a3de1fd542f5357f4b3de0dc2187d89d4fcfaf6a0f3358bbc9d93149c1b463199cf5d9f0b1d90f2432f74f1bfd3b81f2709154832542c5e468504ec664a982205732bdca8b6ee4fdd2dccc7bb118c72306f47efe4554c98e90d8b51abcf094501279343567862e011460778d1bf114dee3585745627a44745b196b9d2276a810a6b327ced8fdb186845f59eb566201a617d6bf523c5ab83af73c578487239e3d48c725826560b7f99c623237ec9b71508160067a13bf28138c78d9bd65062c827a3272ef80fbad6aca177f0b5cd6f173d0277df712be1a44c2b54cb1c9e29362219b72baa2e2b457f389fd05accdfe0a589c31e53d5c04d06bc65a6e8c10d9bb6bed9e7224583e40805f1304c251b8d4bb028fb74a38e71c22139cd09382a323dc625e2390dee832172017b7d039976528f78c71062247a796dd27046ae42a3ed64270406fd0ea1dfb5916522ac5ea2ae86af1f080aa94ed907988ebd543c0a545c56644e595bbf39d10528afc2255ffaa7787730956b206f3db977fb6aa8b342f56a79c94f041cd6836379e8805b65dee2e410ac10f95767d87225c31733938e818cc5c4b469ca7204c79d72d682d32036d18d14b2c8f656e92940437ac0bba5b916409eeb5782e2d7951b9f5b708784f608ba7a00b2a335e4c050d251c5608ffbc1be4948c24e4ccb21f065f1c04fe681901e59e6492125ef744e2e98383f2fa442c63039cb224a06a128dbb4843142a17637ebb289380cbf280e12fecb68f4339407b8da83d45632c22dceecc156c962840235d25c4bd3b4edbf400115dddfe5f39785d87ab2b8b32d832406289e8c4b55e31d6e9e3ae1f589afbce00d42e944f91e34dfa49712abcbe3ead43d6d9c0ff44d54a31d623275ab89e8f7eb21e9f1a384457c6087ce273b46ffeda4568a9ff255206e2d9eea6d89a9feab252cf05c848cb4e1666cc24a5167d96850a33ea51fb387b966c434179cafbf7117eb8452449874cd3bc8f116209570c5544c873e91c5540c39a0f944c615b971c1b8bdfc4f7863a1c2a4792bd5b34279b13594776348647bf43c49f5e3533fefa0143ab7a838913f1885118996d9e01b25aa73fec0c500df6ccaf657be6ddf4617b83efe0e1b948ff2f75e67133fbffd7bfd358796c50c58628cce8985da8600fe27de7e01c4645be40fbb4fa36965b28586d7230c457c4a0ac0d50f64a8f2b7fc4befa72fc3202dec915acb054fce64d020b15812f31ea363e602b88bc809982841404bdb4373832e52762e490229d0990965642a84585c504d8a7774acfc68efe24842f97392817f5e8cd01a407220dd2ee666866a95b0f56404542cf2cf260fb33bd1bd503a0345ca6f8795f256f24e7d70a9bc3dc6b74663f0aa66ba497734a4c42f947bc7d920f26cb9409dae8ae3a50c4aa175f286e158b49c779cb40ac970b41ce465d1400ca9fac15fddb8b003bb716775818859f1cde3d5825ff9d3b28bdf58e08300b9519a4746b3123a84c0222f70ea6756eb6f2efaf2c31bc12349e35971fa222c211174177709e20812fbad902887a647be6ee7c8e8cce768f3ceb3f31a653e725087fd234d37a3f889c94ce2bcb3eddb26cf71a7fc6a638fd3809c7dc2755a6f9a5805df71be07364dd414864ffc1cba49ef49b1c4891a395cc0430d12e2b63bcd4184a9171c102008010696f7efbbfb72fad429475639b5087b270501d64373b3bb86edb8bbe381f37454fb9b449cf75d3b0526c0865a9b92f1ca8f3a0a5fc1b605bab481afd2b68d415d0e33b083d0faddec5e43fadc4482c8d1a42c0ca264835adbf5a3a1e87d40df4f617ae81ec061088c0d387e6a1c99e7038cb1bab9185663156f827dcf500cb8b91ed11f1d7c212219bd1bc781bffb1f131c65f40b787e223cbde936c2df7c23a941c94cc768303d25289c99ed202f63b0736fb0698abe47cde91c70b3e9d33a5883a07bc9859171d6b7db2ff157f737dc83826bdb6c8ca1b71df1ca807b08ab2429d790c91b5bb7bf36d79876b393d4e2c5aa608866d26d48e9262f09206188151881afc08b93c26ad1b1445730540b221edfae68fe16fcd8b420c1e48fc0562f9db0a2eab33de100e333dfb4a0b1a55262bf09534e07795483f5c10a5a46912ae088e205c88ce5e1a38a1c846571b2032e008849c5c8324e042cd53770a519739f7c508b37753f91105e88008f72d6a8bf6c2027a76b3b0b0280965c8b8f817c85f9f889d5464bf47a6e66cfd8a09a4721ea586dc82697aa6a581ef0c7cd76302fc6e1575c551109d02c3460215a800fb27030695a0703cad545ced9cd18cfb14e71e80dfd89bdf75c8df576c80fe5d9e00c41718425afabe6de4d3e61f180e42491a7a909053c959f70659f813ae9c3af5d34289cbb39f52fe57f6b39de01f9b9e761f383ff6ed3f6bba26d383c6732edde05b97e65aba099eeed9331f2fa4b7dcd34515fcaad39e19c2ab9139cb4b274bb55fde5324d5f00457edfe73fe683844238b3c17e1c79acb2d7e5401c27a915cc57d1e5b25148d2c7b5081b1cca4175f4c5dc88094ca5125b66e10aa80e61d30e1c632818d34566ef28e46780ce21c841f0a599986fda3b4d4fd7cdea9a199d0174ad401fb74cb49379903254b7ee5b630b32e600cc44cedc0f53037a8dc0f7b3fb15c11f75e270e8af42f7196d1b222dac9ae3b7da27fa45ec9b6aff45ed42129087b66da5f9e659f65f45f94d2879dac8afa01129d0da1f511e919ea44475e612ece7abff5b3deb8c484ec93e9c5178357ae7128998e7bf83dced982d0c0287897bc817fa6d9bcc990e0433bc02f9b43b17a84539835d99a2896a6164fad2c2ac160fd99696bf54434f011ecb891ec7108f765db3d2c4541d4d8bfb13910f057284b6741dfa0be56c8c7d8e52ff0c20a9d01f5931e82cf23ea3b0aa764d32f152a99e4b5b05fa6104837397f138e99f3f82844cad4dc16f429e0e0b7ea3e1f2d9890390dae6d004e5a43a76e501e81f8b077774a0f6264f74d213490281f60c65ca604143b3eeaa9bb63ab4bac530f79d5a0ddeb7089abb754fe6755551efef6432aea5ee0f6ec2e19681e8e676059f5428383ec7f456040e624d1e840f31fb1811b04a7b30206b20afc9a62aaf65400585099160ddcacdebdb556c6d13dd34c003678b5618129ab3b0c355446eb4d94e00a69730bec25c3a20ed4624889fd2fd59ec8f5cc4a34146fbd179227e06b607d7e0fa0100a2ef832ab07358ddc28af6844bb0aa3a5639855a5634c08e806fddc225c981b66c378ae56ba5d70e160150618086a2368e2b262581e50fa1b29a5fcbc9bc5dae05ad30ae7c28d1d9b85d6ad4994e073ab74a50c13a6ed2e5d7b33af534f04655322e3cb63ae8234c1e6161a7616a5814a0981816279488a96e1551d5c5342bcad5bae2998176451ff42a8de544d0624f02830bd5d87cf84c6acb4dbde9a63e3db1fe66d9fb84e66e096d2d35b4595a8f0a86576f7b6e98605447f4fb643774df77d595032f9596c79449f36a5de9953959569ca84e8f5b2733f6bf08386a0cabb23d56c9ff6742c5e2d3ac0620e3677fbbf8f52f37b3956231fc64c19279b3fc650ec3db12fe4e2d8962179e80358f6e76d76d6c53660e8d279b67beb98a0812de38c03e8574c2610f14bb08d89e29987a072864a53c101ffbf61ab18ea55b3eaca7b3ade282a63efe5a3175bc2b3f09100ccf1c1d43584d432a4368b5d2373fe3971f2161d46dfa498b7b371c72fee807bf5c2eca052f8e6a40b0c9d76d47b11e858e6a885a8e82980cc7cb0a6830e8f16cb644b4dd5a9edee77db4c0b4ab352e3570599cec618d498e23e7152bb4da0921ed19e168746beee3abd6897a3f4883046e85fc3eb1b8088b8e9afa71f60b37123b53e6c34a08ec60be102f7ef59fbadf8b87881c5e7c803758fb9c23d577560c8e44244a716b17d27c7cc6a27a90acd1c868c5e15e2f051916d7162244af5758355ff02694affee61386b05c5e5efbbdf4238254ccb5b1e84a873d9e9d49d900df30fa2bba1e586b20a149d087dfd214e553920c61d339f8f26d483ea53e0f629bf88322bd0027c42816415e0eaeb3a33c063a795d2399287c30454b90b358059dd792f47b7e0de3e5cd7088f9c0b266d87170b3e5c06c1377ff203e31081fac0b59e180d3a2366abb56320344c66fd4d988b695a9a82b1abdf700d78a45542170e02a0f3a1a6b6b8ec70e5af774501b437c92e14dd35e057637ef69a7e464f25e530885fa7c4df7da5cd2d4ae0eecb16a8074506c05dac22de0a84169a7e8e78194f646684cc99f5ee8253edd0a6351f4deba98798357341a81b03f8e0a81237385f6458b44aca09e2507ddb7e17cc9d29cf0855f8ca46124cdfb0b6fbcef933dc0f2d9667450b8da186271b658760d7bb341eebc1eb0d7547435485ddf74762b7bda2715a14b25bde8fe160a485a69a79e717457ecdfc4a6d27e3c2fd8909440030329e69bf91e4b88c2a34eb9b20fb9756044fda72ef6765e8a2d481fb0ab41e7c501d8b765c20ed5aa5e8e99930aff86b0ffaf584b044432b93e6ecc92129147f97e9fa1cfe02755c74a70daaf2ec1bc4f02b784217197513c68368d578fb05b49861082700622618196047ffa57937199da5430b9abd64a742fe062f4a09cc62e08ed91197d9dd3f9fbb66769b53f1ef4155bef863362570c14e21709c1045259418b6a38ebb7f7d0cef00266c660d88677e33eba8e346afc9fd9b9987af6e227bbb525d08e5949bc667e3822c21cf696f955eb0bd4abf2cdd44ec5a19599b91e991d8692ee54927079e5d2da0b6aeaa05956f86ed35824e603a965a7e87879b1c468463ee02afe038a6217de7e470f9d06eb3fed212e6b85185a323363978ac299171752d3f71f8e5ed114c276797cea9c939db076823c7cd1e43ad138534a31fac2158f149418137e78c9a2a999b1b8a0758390d1c02c19f83f7606d9534999d731109ed88c46a2fbae928dff18a7005faa4fc7f906c8907d5447131213418150cd65169a9222e75d292a236a38738af53c6f9cd2334ae16e94ed185e8cdc65490f30fb7340c8808ed09e43bdb5902c7530b1cc75b01a89e07457bd87e3dd577965b7ecfe7334401e27ec1bcd2b2d718873d602324a22d6bb391cfd8422e1472d2d130013c2f622aa97ff1788ede82d061dff9bb1942bf327cc1793ea43f14cf4f2dcaf3a9e58cfdcdc225b11f3b7d4db6f345154354f4a5e9e164c5e3fd9d74b22f0d9bfd7cd61e608807b8c43e60f9ec275c484d49ee064476f0f63dddeb761ce8a23950683d6bd04938e97faa68a191934987087afe3eebb45c31f7284be837311e8a78badde14be6e32f8d05f9cbb25e702a684a327f0f26ecf6f8829357a18e1c6ec400c6d2ee2afbdaff08c5dbe7dfbcf0ae44cea24494fb2af785054ee380974440fa7a9de9e43fe0f430b2ab8ed2008c9f61d4803f855a596153ba1e4c08c508008be4f53b7d210e7048b618b0fa4a09e55213ec64f01968589293272116724be2b4fddf5e6eb8e4ad01f89ce03be24bfd6043c3ce5da58ec1360a5fc22191c02b4c11de47f8bda7ebaafab0a99257cd3ff9d74d8466d9b923e05f167072cb48df748638e5f770cc730aefba7550d4e36da07ce56cd81f02743af15085ca23ce7b446d317eb9d66c9c2a7e111314b743331feb623e4c0f8976f686ad85a08b8754e761bfb8d722286358db2e05f782d3fefb6a9f09bbea62c7484a032d6e0e7d7df3a0780ab127ae914ea3608fd0447230d2aa86fe9d74943a6633414cd45f34c2b2bd7667a65756128cb7ceef3ac488a98956395f0aa423dbb3d0f67ab66e0f100a44803a72674b708ac3e21491c93c861bf8613c5980cffcf6a0287433c42811556c2e5aea46f7feb515a9c3b59a001c662062d1244f3929349bea716973c447a8522df25016b1accabc96cd2660aed5a7ee4adfb07dddd5b14765ffff875c08829810f832ae60bec71d4cc0f6b725f2aca084aba5fbb723109593a822defccbd14b780059f10a7ffd11c0a223813aeb0128744b067b2b26dd2e8081a8f5029922be06180426a12ad24ea1edbb9d800eb2510cbeb874ec9603e287bc0740e8ee23553ebb21cbfbe378dd723308b1d5cb401dfa10361d79dcf24ea9ce298934a3acd06bc29f9b65dcc047e21d8233970a25daf91b1162aca18a89d576024aa59adc023fd1ceb1e0f80310940a810b73e3186e18af795b38a5e1883216a20ead6efa701c85d8d3b96ebf0424b8dab333833a3912904ea94fce5d8b1dee9e52bb436453e445f62e47d040fa7d6d7fa395fbf31d4440298c7bf0942ec73bd1f2650166aebc6b82030cb1a448a70d2c3c5d34e5e3cefece78a053ab24a430929ecd644ebd283819339269c130e1ed2eb9b14f946cceed5a6b8b81e165a147bbf047f92a02f5b90e67c8ffd1cc3eab31c136a325c67f89a335d44093181bff05dfa66e43ecb800f6ee3ae898063489165374f059d133ef60ccf8ebaadd88af388c06c0b20aa9848b853dcc8b3ee76524a59a70be573095c428c18631b4874811602933942b55b353911828a2ab041164f63c3cb1a8d4a0faee8ed2e835d08a350c6a6e0da73b45d7e5891c91bc7b336015e6ffb485b5c6c39d2c079895fc10663a50d4b54689f32392f17a512ea781002ab53563cd5edb6f17e8179f621ea1648ff2d3043538f48bd5acae50e8f40e8c59cb93ae58612b75e44ff7f982f53d08dbde05c638328726fbc57a271bb27e976be6c8c237c941fb8d6e003e772c44d06f10322adab5f7057f84de73ed289210543ac1e8d61bca04746fdd2025cf5e8e756bb065bdec5f444e2e665557d84b540982c6d8cbca1052ae827bf9857b172469fabd1ce5edb5315e39fa5ee674be04db51c19194b33527c1435c8e3e915b3102bc949c61a397e8367f7466bfd9c339fa2553b9c2712e93f0f80665ed62bdba01f147b607e92bcdee5e99ea2b25ddbd7526d76181e306fd2596b56bcf3f74361edd5dafa42b8edcaaeb3ea43072ad2ec4ccaa8d8b511bafcb663a97c457ded1482e79974851bec3fde53f24340b689c3bf9223a779dfa30b4075d5b4ee5c4dd4196ed311ceefc73ec541afc733546f16ad1cbd467520469b180acc12c337a63c318c7c45000394b10e2b5f2729911db28a70f7b23de1a6652aaaaaff025ff792a4c3677222ffe055dae072597875220059fde7192e859ccd9c8b0b0b7019334654e8c42aba7ea771806f13cd6969f8b3577074a9a24ba5f393ae93ba09776c1c3ede2d7859884a3ee117af959ff1b3f0bc27eb2cfac902ce9784fd2e00fc14ef04a1b547a464a765919f1a2688f5b1453f95b491f18c9d9b2f8878bfb88611f9efe9de432e95e1eca1351fcff06fbd76de318ed459113a488a46fea0cde8445fc48650e46caac39c01cfb07d94c7418a84bfa00eb5f095c45f08338d0a4e5576f23b71e846e05a422e252a44f1a55b3707fabe45bf16aba908f824fbb9124c5d29bc9cac674b39751911d49b1dd7b58f5563ce2d675910f4c7c78b065cd4bb1e036261960b4bb30b5a93f4b0cc613dec7593f523a8dab3c3e113027553e11d846411355f0b080e2dbfb9652f132b04a7e883b7362067fc7bb4653250f77e1ee98b4d75462af2be22873b2266ff3199c2e77434de3c7b71fccdb78cc82448f864a49923812b9d18b8dd65f33376280ff989108684b31bf8a99366021f39a7170b70537922d9ab953772d17c291858d2c62829367030dd94fffcf5a0bf1119ad55d52cb48437110dde5745b7bdd944e4fa9c0f83d5d37ca9c83decfc2e0508404e217f7cc459b73336a17d94cd8cd7e1059160231691a1398a5af42c7c95123f680fcd59a57ca08d935c80ad86fa9a61812aac52e804e613b49cef4b9a16eb8ba1ca77867235a4b38190aa2efaaeca564ebb7e55088b1c81050f841813646f47a47e051ea2bd19c0f586cf467b23ee6db094039afd1e609d25c808b4842c7fa310ee7d35e5261d27610ef6976888ce064c8005ab4a86c4eca818ceee60bd85d110dca0e463772fa48c390c11bcf52a91ed79da2620a55f2c5cc78d5a730676de0a2e2fbe65ca184958cb7cd191569266aa259b1619a0d0fed1b5de08bea0f84ff26874db511c274f1c7dc8ca10f981779ee57255b078ac71992ccc0d46243dfc391f083105d1b170070d8f07b8a605e206fd8e12e9f0e56442e61978f7723fa3d2ba2e274244475f12c5eda44208aae74a4247e5ef5ba51a22e9abf0137dc87b413fd9b6ab3896fd1827eb18d21114798c1ec351099763dcf370679e5923c2ba485420eb94b6b7a3977cb9768c2e80bbf6340bbbf39e2750a508691f7e8f7767adda94f952171af6df48907216bb84e9a7dc4da8039b63fa85ee1d400d0d16b147918a2291f40d69b4759bda84c54d44c94b51b0816335dd16af028d226a4d0a2acba998e67c15c944674bd22a2a9527391b535d925c30e595edc8c3198d0521eed115025f580736ece28abf8dc3b1a27623ac00722d3fb8322a6be927b4b82b37e4a919518898abab14c30cc9ef2af219a9b21bd1e040a15d82aa6ba6c18ebda9271e0750d42f27ad683018761e4196adfb40bdba6930c016187617a9c0ac518bf9ee3f21889f0fe402b77d5993761332bb6b0309fad100640c59305209be186ab8b0b1f0aa3c03c0c7d25273f058879bf4fc3965129040d247ff7b697328a843970c86b6182a4edbab4e4f1aef954934a90ff8e45ca6053cde370d30a2485f4f50b13e93bbb9462d6b277c274a743f97ab5a0effa9909c998e5d036d99a7e5eb3198fb15f4d286eec617f2e6f5edaf47b3571145d0ee726ab0dda81c2c7bcf3d1a66226c6056084c4b6513a5d08ffad865d39819c1e354308cd0978d78a7a6262dd941ebcd232e01043e48040519df68f85e98b18d7be3608e618c9851e42ce1e0d39a8bf388d3a95780ff8f8d5c6c558262b182f8b9fd14b8df9d09facd8b0a0cef1c3436b1fbd34379c25342d89e6446ac220b04e10f15ca07e52f6a99ec06aa1f80b5e7c47cac1f70e8a90cf60b8dfc2953d62b8a20eff90c1c8a2e65453954f21634c56d62d17e67135ae13ab5b05a1dd441309ef81ad6b9a5c599823fdbb1107641a6273053a2eacf1e5de558ba72079965accf28fa5b09bb8d48464f830c16aca8f4f3c08139c90a5013ea3956b9934f8ab72f987fff7b599203f6d4ed67143273a30e987281792ca7d508ac8da38a3fb96eb419638c7b3eea6a36363a463cc1a4195eb82c697630a87e46b17f11f2239f32b231ffa2137cdcc20dc40ee6e31ab0676c24902151864ebc0d09526ac3903869ce4bd0a264a065cde78dbab18fcc7c4133171b1e69bd4e57b4877701326b13d34da52ec1947941ddcb94061e6f68237b3e40c7a52ad300f915a65e0b64d81199c5602e82cf477f5c859546770b2e816c8fe5d03c58498477e804df1da3574e6c5e0c97226d8c92d330db14ccf8dffcbd27b51b0dd3bd450981b7ad5d9ce5cab02b9ffa6f24f5a348a3197fb6324eaf86166abf238488002f018844338c962b70d8750200672d098391d9e2c45aef54e09d91174bc02ce6d0956d9347d08357fed0619c8de225d0446e4d72788be38458cf6c15e7cebf2cd8811361f178e4d74729ccbef8a0de8ed6c99beb156ca465c1d2291e1d23c87559a567e0a461cf8d2e3f728129fbaef387c7436dd524d5fa166e9d356c86289d7460f0f9944aae5dc3474dc300e48c09f61f409e4f4cdbb29165c2818c9eadbc41e81f9923f702a317e4e06d3a38ada5162e8c899f9951f3de0a19771e68fbab6708b9aeddb16a4dbe4a968dcaec3c407389da6bd92cd0c65fa5161d64faa8344cc680e3252936e9188a6540ad8e26f5d113a5055e439df0a8cffd81d567085b93252772f61ebb019ee97660b77e78d52511a22ee5050409c215e1e67636caefed4f2c90e12fecb8d19f689686f6d169eec4cd0f43f7a2d3e7b84475bc7bfcba39a928234b9ff4aba8bd1debe83e8f927cfd568d8c11711f19610962ebdf78830c9bb44ef8cc2bc3342d5eae70033bb89254b179693a1b5247e6dd62a68e1a24236e8c1101b000bf7d14230a90736270b3ccca483aff9bcb93b41e53e8d6dcb82583ab745802f53a1943b08c9ddc40ff6958b5d8ce4eb0d542a9ddb596a227844fae18bf0ee835761576665c02d878677a180317492444202f4cafcba7e2f86ee301f96a61c3fc107acb7d36684e7ee4efd52fbb1dff185b664fa3a3398cca908b380cc877c520851c9ef3589765f1d3adcf88869a2fda3584afd61fe8a1818238fe59c21c19c4b054b5c0c46b812e45952622b66d809227968e7f50c2d19173a6d470dd249520c74bf1089a1f50306030f665cdb31904b7ca0f534c5c5ad92af887618a94716d3d5461f628a568aa0b5e36d5942d8954d19f42e813340090926ed8fd68d2bbd46688414be9ac4406e28d292e5d42f496197e5848812468d86632304e72571542f1af3413ba296a18182a82990c554d1e9758df5c3ba4ea48d4756c3a79345722b24ebb3a0e6982ceeeae46c921aa6b735a4b69b6936b1a0f2d3302a101155311f3c5e8976744508c2cd3d96101fdaff1b27b72e7d1f97a669b3dbc795292be76735878c6ab4131060af817a794e6b6b6823517d0de6ce2634f3e29137304817c9cfc6181c2c977494f21a749a9ae1838c1e0a5650b4e6d370c0a5fac91b5364883b26f988d4104bd41b0d2e95d344f1a691c970c90837b23275baa872366014223be4aefc2c230d229b7b29921ff665a6c4ded0bbcfc87f8ecd010d9046882c8bec836b0f6461defc16c8999d98dd0bc9e650884fa113560112be501342813ae5990de32f01850cdd57ab967cb5069e387c7c455d50aed64b71047504d495ee22f0bca340f5f7d812e2a1f53ec10183ebc67decce1239f204066676f735180cb3bb0d002eb78814f58ec80e96908ad56d690bbeaf25e4d985cbc18960cd212792e697226835b3964dec804b23c5a9855a57f6051c31f9aa16a1d043f12d13e768c326aa28fcf547d37e9cc2895c122b8df44b7817d33a228585640c8b474dd39b0e751045ac7e0894253dae41f98f222b9668408e0796c61f49cf01e1b0351cce659a875317415032dfea0cbe13a6e4ded76893b3fae48a03e08bab72e1a0e8efbd909e11178fed8351f5d545c1139750fc061ec6bc9f38aacd045c73c32c77aaedf16cb24314d64f58427ee5a3d695d7353fc4bf4e925f6bf1f2431d20927a036d12c5e62e9a5e9bb110d642a0153f0161036e40b357219b69ee27d546a9cbae3b99f24c0e8dabfd1207ff9d14743a274411ced4aaf5c25090970df5501243c418def9e4b2522d41812b45abc0b21280ba3b9da5dbedc76c336100059ac69b2f2d38ec5aa950bea0d898a57e6e5ddcc73e545f538b5889e47a6e0444e41f0ce3f870e5e6f31eb2e33acb21b807e2a195be733b2457c002beb281d879c02a48f37a691823ef83ca4181a346bf7f90567806734ec1c4aca334ab69f1d49bd154591068d341f52a2d650be0bbc3a7a84e2c9c50e0ca0b70cc9b39be6505565d9113587220e2665bdd20a4e32080516585ccdaa4214b01c9849820a3c8871f69f03503b4cc08273d3260c3452bb938775cc9b151ddeee3ee3cc841ed320ba1237d819904410aface7eb71b773abc45d251b755e799288b4d63f0ba373d84ec4b8ad973d824c2f6d57781949c12af5a7ae72ed8bf97745fcf44b588c4582fad6460b562e783dcea0ac4c52a6502981046c3997786703ca5a32b8c9dff99e9e8ee4595212c1e7cd930e4bd69e48134302d1613976fc2682cf184a0233df653c65939ca5a845b674b81fe387d4077f09e42f426e4df9fc3211797125dbd5ea41242c5730b7d21ce2e75b21e8151a7664f9c9810743e50908ab7161fe87c2166c8ef2391c4d5b1987f869f80b8caf3c64a45cdf6160cbf6b84db051bcf87f974209ae7eae64d190fa00c30863c010681e3af6646d57b7c2cf09fe7fae7d9166d9b85235b232733ba64b46f048020e3de84514775f25a4bafa16c5c488f8511b7daab4f03c3cad651f2ac654109de20632e9b452c52ab0566ea0c9835b42195023c8a97405fb38a5d34eb7dbda2704b6387595d01c88e57e58b70673db036bebce155252597bbfe10d59fb4291724721387bd87c3bb36dd596a41f05bf49d6e0f235905b84208d124358b994e1d438c63923a331a308e1794ef41de6dae1ba8522a7a35bf78b770475d70cb5979f97c194b333c8b0db7db656519f855f55c6e4b68fa5608e9f948cfa97b6ee00ffe50666357d4ebf115f6e2c9b4b5341946c564e3d9f31934a326bccd07078f6c3c807d940bf5af88c2bde5ea59d7868d4073c1aad997d2b6219b696af9ac8eefeb368202c8b10e824f4a59ea281905da0e43d4c3356e3cad4dbb8286b95271b63a6a9bc2b16fccec6ded99b89304c7517e7e619426de64c61b4bf08f266a70df417b2627da759bb83ea4f2a18c148236b1a50a6cae115009fa27b1a3e9fcef9598dcf2a585a49e24b1afbb842a7e3550280c2c3d801bdf0fb264dab30d091e4f9f74c01cb89e8d0c9b85241254b9ce330f98b90e72450305744e8b05d1b854edc4d2d4469e3c50d1ef870fc5721da6132c95cd7b0c44adc4db039fcdb2a1087f2d5058ab6d3b723a7a145b6b02deac6a7b069b0c6cc61e7be7fb9a7bdf13505bd11b02a57212e08c5333a2973bd62d4436b5640be6665c269580582b868fb1204008e6e292a539299563692a650a319c6b5a426f4523ebc69afc74b334999f5f71bdb9785d3d59fcd4b164e9720153a852d8a205a08f35894e936f460146ffa1e0715f56283e7216d0d8b3658bf3f0a5cc0a7abfb0a69d6de130d801bcc1306eae3c2219d192065ad138a08ac57fd51ed1ff34e2411be2020d63d83de2d2767bd0507763fa8c0b574988c0a67b5efce75aadac42995e65fbdc1aac7b47a98bd4787c15820309e39328744c08123f365c2a13abb04d8d763e01ac20ab4e5b2259bc6a738ed0146c0a87ffab50775ae16b56e3ff714957a33aec254a8c86a3424652edbafcd9b0ade51d0a74b4d3f6a56b41cf1bc4b7d6a584d4dc8a0ab831bd7121a39a79e974b172dfc8d12e8bdd0f136bdf40acf712244da352f5c61962a94e8b2d44bfa81bcdfeeee22e24fa8743f5ad0df096efdf172249247904021ae8efe15422900c00c3eff20a16050f83ebcb5d3d02c4300e44aa52f83a0702306dddbba84209b96feeb11e0498264cc50babf4545716102301459c9eec3cfeb4e55cdf1f3ea903f709f2f8f3aba7e3fda513c62951264f2028089a7fbb8b409bedbb3a73c22607cba5f646004b19eb537e9a0ca6893de572c00633487a8cef45870234e3406f4c6e2f85ea5d5559a277ed076bca495a8b682fbd7e40d346a8c53cb723158df84a05b5df3e69dd5d0c2ecd3f5d20b37389ca5c45f763bf77a37b5d274f0d43028ebd991186aac32b2e565c4081e64068340485e9a16ff12eaadf5684a87f31cc17a27fe7ea86fb6c055429bff042975cb51aa8ecf2af6ca113dcc59504886cad62318161d8542d0a08270136143ba2ce4570f81fe09a68337b1a73a50b372143de237e4848299d755f9e33b7e681c8ebc993ba1035a7c1407120bd63715e9c99a529cdb3b6980b756f19b0acb850ef62eec5af6b38482146c54847c25b81ac1989de646f191df313bb571628c2b08f2e7f8d7e710f8e14faf4c48874d40515d211fad5becd201d060e9518c3f271f1208456d86affb134294f9235fd3f6fadca72e835be09f727891ff126340a2217e746795df2ca29a340fc0237cc9d2a5fb83a682ec4896dc3b34b9abbbf8fcccde25a0a22dc78fbc806fc54389f2454c2cbabfde26e6ac67dce8f592a536425c321aa1f49a1e2a05cfa708b903b6fb1109b01970669e955668533c3295e107d65dbfc2c26c7a678b4b88c3d3bfa8bfec9dcf7cc888cf58902a128ffec68d9b006d89887836a7bf32fa84982d996b94cbde669db19df1b606ea50f23e06fd616c900c1a04e7800fa5dd62160043d6a1374d043630093a388f014d26f7f8428e6644d0b5d0825a3fd8155e2ee8fec9add6e992269d148528a551552c4bd45daabd0a7621f5c93e043d6de44f2e9a15e5ff19b7201424bbb4037c4e4a517f7da55b45a1e43cd5ada7b90115d7a5a0020ae94a5c08d5cffb2100d2fa24a8b889aee275a582966c51566144948454b70c732ce4fd5732aa6f19846660f3f8ebc4fcb471e5ac46d5e726341551e40b6423a158138cf32d7c4f8faa72fe42884a7a11ee538e21b06953eeb2b2d42a459368ce074101650f4b4fd9e29ac78aa0413b03182a0a119d9fad4a37fb7e1fd39a8eb9f15512e7217a21b215cec76011f2a70b84b6ae3f6763d3a9cd0b07397f1a91ba9bf73582483f4890b80ddbb3eaa099e42b7248f2742b92b89ee7e84e0991bb8ce15c58aa6b0d478cdff13bd04890bee0c3eb3d352a2959bc0b642c5cb2daef458844ddbdfc4eb0863e88c2d462634de9012dd88bdba5f89001b64bef1ab18bb90c06ad32bbc640e07874e96fdda94dd77b0c5b7c443eeb3656d3b843ecf0ed98b0980568ac67bc0ccdf39d8e503892ae348105070cc046f3631185873c22f81c0c0e87403d1a72e4c91af386211f8fdcb26e1320966ff3216ccc2e44c54d0c6041d9ed3be79f1fef9cb68165bcd58a03f410ca2ffed909a3b331377b40f7785b42d8989db773150ac0fe839e924e0f1239b7c9f9b06ee03c1f4fa2c22fd1d16eb731d86aca6c5aed6e8dd8d151bf44000a970464880b33678be32582b3f7e6f182aa95e97b50010cb34fd642ded0ca23d708fb364f7ab36582039b7a34cb006670fda09a2c1f5bf3f0da2978bb0c8395abf5cc7ed676b2ebf7227f52601e27144f54d6ad740c3e3c8fcf67bf7a86f6a62abafe7729952519a01e5a01b81e9a57f257f38f310f410e322b9d07b800a0aab4e3f5c5af2fe5d0dfcfeb2046b7b53485b8ac9d70c600683cd70da2a6e336a867c084d4c55101716fb1b0cf9a37aaacab24e7147517d9fad0e72a2dc8b63c488260a6600c5582da101d75626cc85c34613a4f2cccfc24863a628fc47a5c812d3876f501e94e09695e05165a298f56a8d1043a6c23c06fb0d3e0d7c81b9dfecd43a535e0adce36743b5f2c2a8e591fdec67849249acd45339b77037e5688859586e2b8527686648ecba0f1ebcd5812508a296aefccffe1011f59eac70ff2e6cca1b781e26651b9a8f0a853c51ede9a0060e189705abba66312a9fb5c743aa0908816491822aa30b298491b7afce5489a7ddb4663904376656cd8e8cc113bc5f095092d9852382428e9e6cdc6112eb124aa17a91196c97e53bc66d1f7c1a3cb96efffcf8b526317cf410530512a661782375212aa73405d4543600e5682510d1a222c60938144c0bfa7c36e0c60f2ca2359c0f413eb31059e45580a6ab975835f3f897364c787bfa60ac80b6dcd2a7a09fcbeca5d8c4a57d37c3779879a198a5be3dd8dc74f5039a11360b9952eff5f8bc0ceba9effda60027092c5ada7912d9e8ea9a881c9e2782549725445843f451bfd99c52d2acb64f7b6d07f9ce6ebdb474ed9dbda55825801dc21336196c48faafa2068d9b8e75674e94b54978206d24987a0197f12e22ca50044e41d17f7acf9f52c59a1a51efb1f082e99dab7be12a57115145b8cab507b976b28105340aec6c0dbf316178c64dfb384d84f89a525256933082cf79e9849878ea29b1c8b30e8ca6167556eefdb01dd31c70868dd791427b72220722f513321d3360584f13e53718d8e5d4addca8b92a3189207d8c331fff6bfedbae65a770ca88512cb6c82f12f6c9d9b6e109873ee17bc6f2475f0972a17b5eb10275817fd12677ffe50bbc82f6d20858ca622b9344f0db6693d15ebcc1090c0b655bb77c2fd5b2efb15a067076e79cb6b7dca71f244c9dc66488b716c7c3c607c14d02f4510cf9d37ed98fe978ec9182c3a220fc460bafdf2302f9419fd7c445002b0a51810b388747db44f3698db0c72c476345948bf99179130230b536a6fcdacf12b9301d5a59a8c56dbb9875d5132e75fa7070b9a7b342a599d74cfcd400dcedb9337850ec75cfea95c72f0419bf2920c760d39bb82dd76772a09daf08471ddd72896625b621a1e26b929a9667e77a654fdbd1a5ccf570b9b2b7a583a97de53f320ff4807576d82867f6398cbd514d3e26bb3fc37fa0fa8759fe8f87d09acff0cf4cdc752f0e873b67f9770c948e4b4247a5e7dd1640d0f549d116a5463d82c665f9b81c17124f10f1a27cb490b73654cb59bf7d93045deb21568392a890e6e40d9de8bb2e4b433d1a925f989a43830f0d751cc4e38e7b26ae25864cf89500b8687151159a7621276e45685757a309775559dafe2cd0cdf03fb16bf307176089c33e1d8f540cd8dfe7238477280d0dbce0bb6cacc01d09374bf04a40a927ac25c4e4adb7a6ca45e49a4157bc444640765c629999ffb4ab00748684b749b13c7caedf82fb8c2aa51cff1abc06e0310d06f6ca3206db4f7de2b2f1f0ed7c2631f984d4a7a7fdfb404070d9fa98cd760e4757867325558b01e00e5e4b859d6a9b49ec0d467a9d7fe4054c8762bd0b8f5f1fbcbf420b5671de585db87b69c19e52cfa62654c50b1362ed6d964f1edb5ec89c75027e32d9d5e9e48186dbe49f09e893aab69a54925b1917333009a9664cc60e5c570f5af34a81a0b0fc6b423fdbbfdadb3b84b4cb353d1755ed7a7b889294a839939f533e7b9316073984b512123b3f45930d6ebbdd9876c29c557debf32fc50c0bdf3651307c51daa06fce71f19a99026b1736db47d5c3eecb1b5b6a222c6d981e27928ed18285cf369ef2f61e86b5acb7fb1f36b11cd88402bc2861675281c82468edb800c7afa177e6dfbc134518c08c54f7cbe1d5e3f696161736300df90a79853e8e8674c591eccdc7e4847166d860d25f01fccf33368cf55ff14dc92303bba0ff7d72eaf58793823c109ce6defb5b2b35615a0e11d130b60b5a6c05a0829412619a325c400b8cd26c5d66b5c6d3462ab8e9f17c67095f67d6a89aff37e50d0b8f0b0b78cd4114863f0dcfa90f218a9753b2e13f1f5bb7e724831c24203ad16ecb96197327997e7e5e9e84ffee65f2abdcff45f18dcf8b868c41397e845207f19d92f8455569a6986911ece9c540d9032fbcbe41377b0fe555caeaf94d1acb771f2a8f5aa7a1e081e01dcc75b5e90153bcc273550c5c928c23ad17635e3b3796d4981b3a4bbbb6b8961f1fdb9debd12fa6e8885c77c594ffd50da3ce7f90643cb448949dca8e7e9b3ffd622c807920915da568c66bf68ee24127661a503148e83ca14e0e3945f2c4db94120ea62e7f19bf1a49732b3728193076343dd50c8ab38999b02eddc94a3f15465a84d4d4ca0c3b227214fe601f1708c0335559e3599b38e64754630ea889bbf388afcaf4a03f1cac116be6becfa46f3898adc332aa6b42f98412a6543c325162271b42f9474f676a0d3c6a08dd7600b2deeff48e312e15622941a8db9b001f679a920cc7f5ccc4a4b205b0c137ba699125d2f51b03ee246d27e1270e0a2948c0e263658a8996d3b290e3748ca6e420f017817a24386422731266c82d3cfeb7923fd5825d67800843ec312edd94a0c3e78c6e9de955b68a614e16e1cace32e3e90b3217db517fb8fda6cd49c13fc07891469fc9f949a1118504c9c6c339c66bb9dae28b5a23bb588c7c245257a140ab95ff6e909d59d4e69b57d2cf06406d3850d34d5e609574b26457585b1a0fa610bf8dad53cd7d173a41e89f6ac95d999f9264f7c20fb887ec0317b00c8247a13a120fff976f81269d57d3026a95c7855b8bae352e5d30838755012779645fe7580c8772fcb8787431694b780dbc58fb47b4a8d1cb2074588074b20b9a10ed024c1fc80141e3ba170f0778e045eb34b3fc00cefdf196997d587dd964f87efb26f62a0579af24276a934a4958bea1b54834ae07e42c9ba21b0b70e1d5c3a1918955773706d97d8371b13a2adb59d00f276fcc7ec80a87f4ac7e6aeb918d93dd02582e88449e30899c752bd47db30c6cb6c6049e9c44a4d698da3cbeeda3c314bd7912bd1780252b9944f512003a711c2b8b7cf8624aaed96171aaf50fc11d941c58acd79a31101b3122a740e326871b6694cefe771397f3c7ca7c0f24d8276dc1c3af0abfbfe1229bef80ba6753c327647fe9638a591209b694b3a0f49ddbeeb1fd17c4e9b4491c642d3afd131bb7f6cd67b475e4f63c0cbfaad87ab9f08bac8fc45812cd79f75cea5ae1001d957772e82b1c15f4be7cad543504d578a38b0a3a339775fe44d207cdaa1c7a6ed851cf09fd04ed9d0c3316e2f6d4feb43ed0b1932b625eac149693abcee031b2b9dfec38da3b7f0b5c526f07eedd0e4405bd3f1980959f80b35c105938eb9e826a8052cc022a78b927d53e1a4e7c2dbdae05d5cadcd29f7200834fd3f253e408d3565d9798e03f00eba3db52e302401d2b223c13dbdb271e317472dea84bce7628e1a4e71724ce5e3e2152d8d239e6b6690df69220e262c55b786657200c4fc8e257f9e3fcd127a0fa95cf646f0da9c345528307aeb297771a162e5079b7baff9d18fcdeade68f2e00c8f8f25aa6b663c9dd719bdb6a423c02f09566a858b496fa564a998d7581004629c23ef19724a4678acbe1088538877291d6c010e7f16efe732ec4597b07a06501c61971a001d6754ea7c431a028e053752596b9dd52f55781633b62d1ec2b634102d2cd821eb90c95bc13d8af2d3c2977560e73377b6fe2e1dc035d92e31d3e67de794ee811bd0fa83ec9b17c1a5724f82a918397d59958394e5b41a91583567db30015b68414205742aeb9fbd088327502729dad9315fa0ff52164a4f86d41bffa1b279a951438e4d7724746a617d55c8998fad29ce3604bbf299fdaeee26e1a5d6e7129af07732c819badd5cabb79b138c4d59cabba6f08087258c0bba8cea50abce7fdd217797e9a2a5bbc6a12f1100aa7f3ebf7ce049485ca8627a07d63b9d9f07a1bc6f35efc86d3f01114932915800e7f64d26ae80ae1268c159f89493c92e60918027e88b6dc67f301417d9e30c3e8da6e34f365988fcaed473b2ec90618a2cfff308bcbbec65d1cd0c1b3c4b3b3313af64b2897b6e8f8600347bb3752c9e6c21b7803366d09a08c98ebf0884c6c15e5840fcab092c7bb0da44bf0d4ffa2210e3800534b8c7fb2a9db40c0f7f343667a6fde031e9b3b39e8da1d8151d3725b3651c7068f602edfeed979b04dde5ac73a25f6983e1da1a389171578e182fcc5f2441bedd29a68199a0b5a7deb538689435db0fe9d3d462511253dd457bd1afc1c52f1b9b607c80a024204436c8bf4cd7c789c529b43bd02117bd0dd2d8fab1c39941552cb2c01cdb8972105f5164620c2fd534f13ffc0ac5b80773b9d077779a68e091c9f2c940dc0506fe2fa3be52921aef990bccea222a6c06546c39e89d9c2c0880068e10d01248a5b86ad801ee705b114960d2e5517b7f355d8078a70c0746c89fc5375052c730e1f0c2e933267d808fc168d4997113727cd30cb8dcf380f2e85e42f676c32bc4ae2e23a1d031c04d3a57970f59bf819bf35bc658d04b09eadff8ad7887306574e7fed804400ea432edc0b0a3c966865d5edf81297414d972d5d494f4fe4c4b641eaf028fb5971c74315bee15e5240d68a7e0236b2b508dd54850805814ef946f19fe642494d489913744718fb06cb1d82904f7d1dfa098b349e4188008737b0f87b07c2a5b0113cfcd7dc5d32971aded4ae19de2365a4be2b4b2938ed6987e118aae431a8bf3d87597b485821270fe3480d166c20cf1a0da38719ae7f873807e82dcd3172ee1b99c650930fe4c54a90f6fcca49e0573b64b7aacdd160e5943a339a4e862a22e7f7a352e3a275b39d46433e9923f881c198ced76faabcef021a69b90d5413ece452df6465bb83d56272c8dce599363016f546f577f113d6a39585125c01f6bccfe84064c8e1a85f625ec0ead56c2b5bb00c7e31d63ea284b0ec93919099b32318a029205c6f307318374d66aadbfa6c3cc79269ffbda3b72efaa828e0bbb9063846bf3307c54bedccb4dfd567545f315411230fec3b30c1314f7e072dbf4d8178c3b36e428df152cf5e27a97f70e69c159b07b5e8b4873244d276d27f7196c29ae2d6f3c39df5787e8c18567a33e63f948ca8300b2f0ae1c37595242da3e9ad1355666f32cd44bf5baf3742857d4ea6af83b981d85d5be1b2ccacf9590ac67745f433f6d432fe11a152e549d4d9bb581e4b0d44395d895d717c710abefb56e564c4267682ab408eec0f793fe9597c97dc506ddeddade255e8c02b81ad7bde44535e78d7d2ecdbe4e5ebff4b92b15deba365be43ba8ef4f559d9958538e402998193c13dc6d6073ff6dd9f1240c573ace1310b3c0de1511b704b1f47e1b840d2c4555a3fecd422819076f3d208fc6bef066cd26ff70082fae29cf3da1059c50cac77ccd52d5b856ca1c7b2fe1d8c4d5053923e3c78363559e3115f23a9965350bc4f4489e894fa3e561b75b81117f8c46b80ecbd71849419073e4c131bd5746e709638a4fee14fc921490a455899b4212df1aa782fd633214704a3c8a7ab9b1611bb1138e67180aca70d801eb07d6c8ccc667346d17e20a5089b00967bedc0f7b6872d433550f0422510014b59a9afe458ca36c3e71a3bcac368543ae33a15314773593d7026d0f6e8f8a1c8816e216f8b3db51bbdd004d0b5ca53ede082cf999978285edd26cda35a891b708eb2a0e2722f9038e8965c1fb08fef374dade4b0d974abd24d3f922b17c5c9841329c7ca6f75e58a58b324b4b36288b7a4279344a4dec1d7209c68707c63e76c6e6e349a36646351c4803b38ad8ce0a1d1a958cfa294ce65094f1a7a2572c4c020742f9eb3bc1fc44fd7e0d216bb39356c984187ce40e829d192de3d92fdd8466e1b548e8fc8d8ce2385b88df7b3b83e98b1112fe2d3e4ce94389de905aa3c0959789d25ef34fd5e9091cfdbb1833a84d6a59c341ac9f9eb5c511b573c40d1f3321790a01a8531e11013d6cb92f6320e6eccdd96bed1bee7aa4055f1ece91c61ddbf7e0d5c27fd489da9045cef1c280e5bfc096e246da436791539fcab8cbe2bd362bfed42afa363b82e1072e27f256f7dfd202d0fe568c4a5ad757101fda7a479fc5b9f2a83d9e615067d4a7775dc094f750ce33b0e55f4d04bcb2b794698f4b551613d2245856c6ed4546968227bbeb00eea07d2999835c1b94a3319efce077f4c056ce1636eabdfb40da7d50ce25d78c17083b809ffa1c1625e41090e45284b4c60b0ca231ea6a60fe4a8a35755d4c6d10eb600c45d5033b20ce42d00395e4cd5cab140ac091e67005e3dd200c9dc5c10d37414549a5a7f523165abdb762a25bc974b006eaa960e19a6472b369d9f5d62d9fd80f1218c2b1dbd4d64d94a863f2c4965b46631679a5dda65e81d35530d8ce8fac79f4b9ab2ad50292857237a6d395098eec9c9f6b87901c6cee8538ee7d34882fd077f19a193b11e9c83264bd29840a34d7d52f6517b6e03145374874b730fbc8e301b8427b0ceb8d2ace0d7b8829c2f8b7b48a2f3dc10564b09c3a2bb180589aa3391ca002049b91e4b192e373cc4d28d26132e500d1a515fa6fba9327ace55f42f5ffc667c19cee3a099021cd0133ddf8b884b1d62ee6115400f6ebc1f1048ed955153e4b79bc0b2eacad34d239d193613c52019b912cb4fc9b862258d5eaf33bc924e46a47b9f3e4c6acaa04e4944f080b8f2929e298cce19c1e671c24d493e85b79357739444ccc8df8bfb0471aeb0c27a4b12fbfeef4506c094aeeea63a49127206226744e4e95ba555609218dd4930018752fd4c4aa63b868d83ea7b8fd0e800f1a45661befd7bec24a35c35b0837edcb24c39dcb54e2a00a18cde5362271e6da2ec20a97988fef4569948d3d3b562037fb67e1a80d43d1df838c00a469da3ed5c392e0877eee70b1dba11aa2c1f8ec6ae5cd456425eb7a05734f24f0fa89fa5bc48cdcc3eff48ada1193c83e31834a88676d938f3e6898f8ed1e0ed54bb726c2befddec0ec35b771fe0778f8e53c8efab2d4b8726c8d1a4bcb0597a34a77ba2f038197472f0b04cd7e9244dea459361030b1c958c8b320d58925104545b23f38db098d338f9db5b0b92101c6f29962314f6f18d26716037640fbb883184defacc02a192a7501ba2f000401ccc561cb39415d6ad2a1ae24292b88434e89ce082725b52966e8223c213d2c6811c1bbbe069cbbbaef502ba7003f4532a1a73406fb700b2c50dee7b38068e006d229ed367a6dd94f8cbb485b2e4816517eb9af9fb35179989b29a0264843a48aef208aa0c2c56e3ebef47822ca61823f7d2a2ccfca60b487986cfa57ea5a067e182e579cfb3a0db9d05f5f2f1237222aadff5fffb14e724d15daf0b6943f98a3441ad29d4a85fe09e4efe6c3a6a459d8ca4dc83e12ef039e77d8ab3f384a786f71c9ec53d91b12f4296b0e0a3a02a84dca24930bbae36b6080b174658d2e2a148d0e6b9f65b0a18290696edb47d83945d7e64b1be93525c4a06e21494691a5ab1a30a18e89131e840d6d2fc3979e6ff09998bca72ab992e8bbc08c2b387363ea0f20e4adab26137533dfe49930af6b5c692c6c2b51f2f7e12fd8f55829f366595b0cd80f6045f4d1afdf11846fc1c6a4545e47726316b1f8f6bf12f25df23a63fade7c81589f41d3ffd71a51d5f2e97407907eb4efaa7bf2a371f1279b403fd24c1c4a495546cf693661453ae30b82214e1ad9ec51125d00c69252ea172121dc1aa5ae52468d403682484df7910d30e0f9c6695d7471df5de22bc35426ee49ef5a8a52118e053360a8b0330ca7e14d4a3e06622d2b1d2bf75851c3c5bc2daa7c3cffd6d2a1d2060ff22552fdc0a323617c9fb749c693e811f5b6ec29234d6a679416956143b0077a2ff3be93859ece54b8e558a4f4c8c6192b870d682167ad9f839258ab3a7bd92e244d594abe9ecf72d2472b96c80063c603d274be0cd4c4a08b189931c3c607172f298a1ab3430e760eccd784521c3ff7bfd75f5734c96996ad851331f13cadac914d362a9582e5c7b178b31dd5e9d8f60e6242a466758cfe024100965d049ad0d4afcc8f91bbaaa1904eab5484079259e6396333a686769ac44fba7a9c96bfc5945189d6d1fb1be68e4c289bc244ce998753b029695603cdb8d4075acac5c779ab3529a9508af7cc9e816f610d76271182b7e65f8ca33a1e4ea9569e9d125539928d4409734cd54a455328bb17f2482ee1a5a89b447a2093dc4f89dd4253797625f8df74eca2a65435944290f95039a7ea3816e55bcde5187d3612c067f16f367512877f262ae34a78d99bbaf8a79980dee5e5042fd416bdc1ac3bea313c7cbccea36ac56e9ad659561a2bc38ad703584367de559a764dc22638b5cb3fb76b44b228e50be00a9d02b4e377471450e24f13325f001f2481663155726ba3912b10a303aac52cff9524796c79cb33a53c1492c29769937732e285968eced219cbf395e5885302de7a929962273d6949568b0163252e4e6f4283b34a7706b1e9131d89b2d90a851380b79382ed5912bacfd00f5891ba5e63b641593172b9659a34d6ddb3054e7d8610dce671e79be4ecf33ce2be9079998eec51e300a426541601bc7066e66163b54c1771c708e35fef3aa314ce92ff59efc702abe825bd6e3c4d8b8ad26938e252cdb8a62635c25598606c1d6d13648b640e74a9ca11c3ac36439e9b2622152638bb8b1de3d16be08e066cc1040dfb2c86fd3e57f4e4c64c54817c989aad6f5f54e5627af7ab1fd53589a6ec7a9537fd6494c1e25c67fcba3061af34959de4710637c69b05228b0e0b3e80582402894969d45b7341545523c40c3b3d86f09ca8e1529529d4661dcdf88269480033a720e805b30ccc24a0799a9bf0c88b14bc38603246a0a987ab69e85a65cdfa5bd72cdd838e9037ad41c38c5a386d94db9f48a2df06000106faa88d3f3a167dcd5e41d40410bef82b89745b06b53dfbed761212ed4ca4636d964cbbda54c49a6a007b5073d083be84b7e22238fbc9741ad6f8548244c08b60a2794962d66330dbe262db528574dcb1632f9ab8efad7439c2bbab35a752f9ba47c799473e54b26e9d43efd6ac924f952495e91bfea28087f8b1cb9f31770c19d43524952611c4a300ed9c44e968cd23c124946995de54ac9a55b1ef9d1917b87b2c7dae431791284eaeeeeeeee7dc5abbca32fbb70b0418288b01798ad2533dc30a94c1a0fd99236ad746f31a198ce201268d9d205f999b1fc3e96a6d4081e00fd8c40c2088ca3dfda124620410891f6b18f64a643826b369bcd74fc4041d911922cfd2dfc27818ccbba46805a927253e3e1e423c8c9c9c9e9471945d663bb39a6ac0fde31e3f607f10ebf5d5140858b9ac04f5f46396a71262b280b129ddec9a9abddcd4d77a2a14697388e9bcc851ca74365abfdae8dc2f04ca6b05029cbe53e44a8b26577d7da4d5b65d053d9eda993070dfeb428e23c507b3ffe9a30afbb0891dbbd578bd85bc391c8edc221deaf4058bdf71f921a1658b1416ebe6bdd8022742d10488b3e70b0a34be4c0c56a9c29384038cf7a2af33b649c0ebd077290fb55bf7f95b5b9324edb076a0e1ac7823490b05b68d558f97587c5e31b234f91031d1eb4cf28b20ed15f765ad7b66d9cf8a3a345dfc2b2b88264664a397e4efc3576bef3b70adb73d5973f96c0ba6a2cf7cc00ee87f06f3f4b605d2e9cb27a0657ab4b63cd19cbdf3d53ca263563d95c0fd9410aecc84e22b48ff6995fc170d52bb2dc6208cce3cfc4a3c0dd7d7bfe919db65a6dadd60757f7cab25e59567a19ad4ba3ebfc342e6f51e2e49c0a52e79b7a43f4db62f991b0f3d0cb6ab21cba6cab488e6116583c454484201f4ef2458471f46b20c56e2e570b091123ed73f3fd4764ac7dec0d7773f3d6da9c9c9c9c9c1f2552ce0d0e0c18383716b461a4fb6e9056ff6365bd08c4abea85ab07f20eac5c3361f620c238a4d82d1c3db82d3615fd6584ea5d4fc4b6a268e2b23077dcd538b2f3fde933a51b83459889aa70dbaaeb6a573bb0088f4666f71cfd154854f4b02af237e6ba716864fe48000f91bffdea3d5ac1078d34f79b91f92301555555979b5356ff63e31dfe7939b43030e1a45600a6ea8a3bba947c9374a21940d8f9f2e74549e4dffb4b59fb30d843be0712098d2c56c68eb46eb0fddf03b5e38cadffde6b136cf10202ebbf9fdf07acef5e8238f37760bcefc0083fe80be30892daf5d6f59747441349862fae53e91751dd3203d2f59fd2c45b6490c1f0a610bfe23a1716c6b3ff94f0e3af23b5be171211d2831d194acabe3f09edc33821f390191af1fbbd0c8df8657d7df97dc0faef5be0900ffc1b199c807f7cf9dd4b17984118cb4a382dddee3baf82a3fc311fc8d9ebba50c6901cb9dd94442dfa4bd916b6a725058eeccacc2003083b8a326ad68479c7bfffb2dbeaedc87a0e2dd0c172e0fd8dc779fbfd50ae7d1c1c1c9df774741e04be3aa18eabf3423ae028dfc2f89c6750e84a23f4c208092073589d166f8cb5f5a3fdec78a3b6c26fd10b57f43bd084c9c9a79ef5d78d8fc2f2e7c8ad8a273bca952fa300f990524a29a59453148887bcf289b40f5f09c34c4b1654ae7cc962ae7cda3c1d50c5b4d665dff552268de41107f97256ca1e188d2f6c873544da6eb0331b488e344b1d41d23edc0fb12f85f8eb041d223064132ec22fd17998fca5e3c5ed27c23b6a8c1f41213140e61c7090e1f63327e991c2324c27dc09829193f31b0e0e33b37df9a3b438cc8ff3d632b39065599061800ccbb9224bbc3d8a2bb1138f2089110979174bd9d9143e3b90024442e4c76d9c5eb36aafea2adcf9b17aee415885957ba1bb715ba8e335f9d6e16b7203e522f8c851023b2b37a4fb8d9ed0faef7bd6f7cf05e21ddded97b21fde0132133db3622c5f86b5c29d20d6ef00f99edfaec2ad566bbd2146e8283b505e79bd5f7d07cabb715d81a3d0ddb66f7b064d98f272208326ccfbd65aeb4384ed037f9ca76bc43e1b6ea056bdf52047c98576d2a8597ec952324bc972b214414ab9b93f3333f3d87339195358e73e4858023f5de692b0948653ea7308a45ff5db5f1546b9f06b18d99fcc9bb59cab371dbbb95bf701d46250102f2e2f5e9ef5f588e7eec93ee3e45bbf5b8256c795054341c19793fe7cb9c30feb381950557eb785fe358a2c9d2f43b75115a3f4729d6b51da67c8ce75bd0fe14be3fd6512ef1001bf5c4ec45f40327ec633c778e69c67066512e3f0a7014a24504669d145c03c33c2517279c5084729c4f5d70947574e0823c409ad75adb89a18c67268b3b0fc74869c5c3cbe61431ad1b8884c02f3ba7a661f01b0e8ef7ddffa1eceb76f81412c0178a1d8c3535cebe9790e5ddcef5bfc3b90c2ce7f4fbaf8b9c93d3ff73605565e8e8774ab70e747f7f541e8c25538f26f40de4ea803b9e70a7e9cf71b38c57b9d7e790fde689e6bfd356b306939b1dc399464a625b924979268605aec7fa239cd69fe92b0db6896e4d2683d944a5e2305e950be0473bfa4d23edcf707dd7cdde8ec64ec260ce2daf5327cb0fb207fd5ee168ac19ea060abb7fe9247368869fc5d00f9ddd6348566195143b8bb872c841725f524c60bad89cb1526252a5190a6a8c8d4a0a29c9ab80dd40866b03cc80050c508f541c70d19ec4f8f16eeee38cceab2e17a19f2bbc0e7d0868c31b67bcae666d7e6da5c2e39bfe50c02f4f3c3850b171e445177145d722ba2bab2c49a28312c33d07400f1f00114b055b8e4c60d503021ab62d4c40958957effeaaf1c231518014543115670817912d117221078a15a010de6310ce1440b2a0002052a70df0d8b09d250819aad914aea64ed231d8bf6612d6b9eeeab3ba32b9f86b33476e075d1b88d73bb5f86dcd7a3c31d0f8bc82af07ec1620716a7f4d7ead4a9a6d2fdaae97b619dea6234ec6254758a2ab5cf54176b1f6ea516975ab4b29e5bf8f598b615d5a26411b558a306db4aed23252b1ce9b7c00f64c96255ac7fa502a7b07e822ca266c5da87be7c16d8549a1573964c498ded62619d6ab135b3632baa15d53e3b2e1b756aec64379a36eb819a53e21dfe7c31c0c1e65c2ea28922ae09f28e9c522b79ac6200023bb6d2c829d13356032adbfa0dd0d8b14e6d10b335a8528d9115e3942655baf23f247f713225eee8ca1fbfa52b3fa62bbf46019258897465ada82bbf226257be9525d99193c9fc85f3b24ad5ca3c96204a3160673ec3bade19f6d4ec8004b6bf93daa743c9d4f2700aeb815a89a5c40aeb14a754a738261d9c524f0fce92d739495eb549d8fa2cd78fe5d2b2125a2e768bdd0244e4f18d9183baf3d348562f4903d0b06932c7fcf9ed03eff0b09b48c1d2f7fac28861e0c53a7db2341c19aa979cc7359be63cf3aba5cd2eec9af3cce79eeeec69c2f6d7d0b438394a39fadcc655ae7295e3e8acb5387f9b5339d0d8da9c3b73ce71d6ee9ccf417153ba780eb999b9796edcc66ddcb6f156a7ed208de50fc093e56dfe56abf4e4e4386ec61d49ee890bc23c521ec99984d2a2dc5ede2963475a9492e8693eb1ac31a8eaec5ce0bf516277786acc1d3f0d147f9d838de51b523675e5dbf679970d69c30f35530390b2fcde063a587f29bf9b78475f07798cdd794078c2dc79407702fdee19c65f7f863b40e6f6d38350ee3ddc01e248e87b178ace63c57a0e4e50047b7e8036864d9061eeaa01a5a90cd31d9ba6636d437545bafd9ef4ea847778a8e54a7ffed12f3ff7c4c441c13b64d841308efee6dbb2319f63e47c727f6e8a7178c13cfd2d1b733bc9ed97dc93f37c378717763ef98b6b729efef9c43579b4277f414903f8f75727dc0af8d55bb8cea4013ceca69019c7b85af5f40840001860b0b3939323313081f51fb9a6d175594f7664a9a6db2fa6c5213ba15a7c6a41b13bae266b2f3311050bb75f724fced3dbb715efd84c4ffe6a26e7e9af341cab65e29eaec3b6b09f9ca767aa7f9c4f524fb7a7092bbf2235e99e0d921749deeeededdedd6ab55aadbc6738428e34ce8594cbaa75031dece84744fcc5fa9ef542b09b23fe6abdf74230fb4230f97ec43bfa08112b76e49acb3a8ffc015c91038d1d19eafb2f44227edffa8f05b27680787fe342d1113198f74230d600aab0e3fff4388ffc0e4660c71e1f1874b12337b9b8acc67c61a3c57525b034dd916b4c7e6500454b0e24288af5257f71171e60f29da97de47b96f6e1ae0481af8e2acbdd1d7f2d3664b9fc81c05d576a5176e1e17a1447f2a416650f3f6a71c8fe955fcea4704719cba1c6cee90911b201cb06a0dfdd77230869634910b2024da85f0485d85d75947e3dbaeb9c73e88cb0ea6f498ca5d3dddddd3d69adb3ce3aeb9c3d9148223568d490995a9a52f21c8c0aa28a1873e5f2d7c3c33a25350515e58e3c05e5ca472265463743f6480662ac0946ecf52caee01c72c60f96b57481e68e2e15259f6f1733a75273e58e2ea586ca95ef64daa7a8985bcaeed1b63f10e6fba7a3cad27710fc52facc38e6d3903b2c767a38762369517e7da0f6a8f6e910aa7d6acd4165479eea701c10a5c46e9f838de50240c5dd31d05277887f87637dfa9e9f70d4521fb4ae4b25b13556efb05528c4799abe83f5479eea516b448c801b8e03a2aeb0052c6bf142eab2162fa0ae7c242ee5f506f67352e30947d7a5c0ee3fa0713b21ce337bb850582ba1a5c8f8ab03b27e471887f49a4bb528ff63b2a390d44dccf2c77260a85c477f88f48c20063d906bc92209cdae5cb19e931b47eb5bd0c53eb76df2e9cb8d6e9259bf052111827dad1f6b502be8db79008c6ffdcef6f237efbd0f3cc11fc9cdce03fc5bdf0a19c66a8127dc00b9790fabf3d4bbeae72f02386f6180d579b897b547d0172211827def6d1beb83aaf3480efa80f8b72a1018df0a91dc3c8cb03a0ffdd1fd0604e76d0c2702feadb7808b1dabfcc013beb7ef09c9150a387f332fdd444a2bf7b4886f9bdfeeb72e2c226b384a8e03b9b9cd184e58a5dffad7809b7007c817ee3ca0f5feadfa81adef0fc8f7adef817ca3a0119f5db392eaf7bccd23447484d81182d4a0e9e3d69ba7df0335f74cb97f2ebc790b84b063a5bfbde436ae72ac258dd1bd794e3e077e3da4f4af876c85a3d06d3d07eefcb0bffd0ef75cfd71b5f3a332d5bd796e0387ac565fc3d9e2b67ddf02fb7b9e94cc366f9e0baf63f127b57041a5050d997bc380efb7afa3ac5f0f19c2f7db03f51742ebb7705c7d6ba4dd618001fef52d30c28e555e7048ebbb9f5f09fdab6f3d078ecfb2a1c3da46bb6df4b722dc463790efe75555a37394db9a6b0cf477a17f1f6ce14e085cb8f3a3be7be3bc5b0db7979c91b69f64c55023862c1cf8f001d4e4a3d65a7d00f16802e2e10388870f79449d9569b38db37497d25dba4bf7210a054487b5d66e340848902020415c02524a295768824a29a594533cbe31c57be23d90356591652d5ad0eefcbf9eec29476a2a8ff9912b3915bfd2af19504e1475e5bbcca1c8a66a51c6a65a94376ab0634f5d39e5b1e68203565c01668d105b54b125863376881528a1a245a52b71a2c1249504d1f2999b13a651e982c9134e3c9121c184e18895344ed470683302a6f9829218585c4910058b12496ad6b030a12891ddb460835ccec305cd077ec26989232d94a4bcf0329f6c51a2649b459469439db00f6089154e30540598232749cad8eef76cb4cf9c377e86366cfbcce962efd60ced0ad679a094f2130738810d391ca93113049926f475fc504aa9d74aaabcc31b890829bacc5e08b3048a2c9ecc9914dd1313c0a02e687024c56989194cc0a60817b2dc74f885851c9ca84266892fb3851fb8762299e8e0d127494440fb659439e7ecaeebc2ae5b385a1a4a77274157213bc2882b64a290010a264d3cdac439ca09055ed6d87046cb10462b70667a92e0219b7a010c132d4bcc4c169224e9da49c7aa2cc142658531544eb0b9029a6f87a44fca704f29a594524aa907599a82e0824492279a8051152ea594524aab0e16db3fdab83abc8376173e6527f8dc0e218015ac70822ea868e9c10bd84cc189305fb091a228894bcc045e3b99928522563e73631ca04cf1214c973654cc5094854a913506112a185521c5cc89159660a1592c2389c2a585262e9234b182a887eee739adbb134a2c4102043740bd1055c5900d394fa058347ca9a18cd2136698288981fb49b375123d9c846a0461ca30c981851f66922db1211d9171c8a04201fb93282db448b1028c1398196bb3cd1f323f3d45efa8ea02e60b0d8a1665aaaad254273db236510111290062080b2a947c71054bc6161346448f7340822a555a9a98e1c2429b2a315f076460ee5de40e9a0e48cc906ce0a18aaaca8b950184955183d12a658ed89eb49525684c9420f64312166ceefce72e55586e9cadc021091a155c8cda04b1a6ca4a4dea090ca5400567c898a0062b5ed0a083802a1560d0266d9d78e9e243d3942ca2a6aa564e325083c2c8051e9048e20b3577fe7b1935d52bd0a5c6860627ba3c99d245988d86a02150c508881e6a59ac400b4ff385961833d9c3955663f9c20aa54ea068511475441651303ca1f9624a6d29424555c28ce182091f5450c2b9f882c8a33ddf35ff25d39ad5f6a4f90c12558c9081082956b50554ebc9b7b7d684a9624a0d4458a1428b25e68ca1ed2513a11ab644e1044d1924ae58521302c66d58f38e221a014f706813851a154ec0c2191fbae5acc692051a5a5400d305549b12b031b303d733488d1d6d50ed298877f0e859050b189668218d175bac81cd3277bab084e8872e5176488a0136c9207949d89e908336767471d0405d04dda087ed04963639c9ec8febc7c5b936ea4c3e27bbbbcb8072da7577779f6ce386d5a1cd24e41834dc0d9415519c58fa48bafe22681943268b3b32980da8b0233f6df4fd3798c2b6e8a328765e9d5b85413a77ce372af3a74f2e277f107f09c4b8fe5206a55f39342d6d66d77db274ff225e97d3d1a54da9a4316220d1aa65e9205551bbf3c1777d308ef9ab2bde0e88b0bdfaed837441dcfb20e23cf33790132f7f3b0dd8be3e90edebf709dc77bf024fa8d22110eebb70a74fd8be863b1d06c12898f91d1844e30b4b9fe84783a5b43b5fec442ff4616be80ac2b2124d50dd919db68f8323dbfd73caa9bbe5fb1021e8c80ba70684cd911835998aca16e718a93beae009d5bd454d769f733233b3e419e5a66b3758e9b700de289ded459779bbfc0df26c6219ac183a393070ec4dab67b064d4d6c7f2565df5195f0893c17e6a302db29c1df22b2d3135556f59cffa0889abc5b6af87cf39674d800c89d65aa766fd0b18caa7acb53afb5b77f95ff71a030833563a27db9835cc4072eeee93a3dcc671ed5bd39eedee19dc9983c7bca399673061d56ee798c95cfe464375f967e8445d7b69302da6a3b8eee911454f003f1934544dc4790123266ae3a85cfe2b553ac7715c68745b124d212d72731c94cb512e3ff5a4cb6e24a59424b4c82f638c95928316b9bbbb5d3b3fee88020c25a6ecee6e29657777cbeeee39635b73ddb8d093dcc68526cc4b85584929e55c8554ae6449da684b972ebf735a6d475bba74a72b4aa9fface66a257f25dd12396de9d2a56c2f9587afea6ab55a8523fdd97577d565e8f726856dce16a791bed2485ff9fd84598a0ce69c73eb2bbbdda5cb6a5df4cef60fe6edb07e61e44dde41011083f28b33e057ebb9fbb100970f70998db8ccfcc465293e90d2df40df9ec34b2970fd63c0dff985b04d9673b6b850320e4ae7106eda9a36f16afb7c358605802b83c72116e00097c3d15f76ff7c3a19874fbfbbbb14a61b27ece4e532952b5992b6366a281da58928ad950c1aaa28e6995eb9d65aa5918412e5f24b222783868a84e621a7d35aabbb3b4b674ef71b159c37e698c3917ea5949bdb9c7352caee54b20ef3f05329a5942c49a170e138670dc7ea944afadb7bb47f9c736e28c8ee2c6e3696ddac5cb970745df60d344299524a299d4f277b13b5beceec5651d85964e5773d228650c35f41617ba965b774972e25925b2347182b7ff5f23de01dfeaa60105b1086f5d3a383fb7ac84929a51dc7753bb37bca715cd7e35f0ffaae1bccbd7cf60224b1fe3bf4a7dc9c7a4b6fe92dbd654b29a5cc80c9fa338ffc4ec746dfb8cd38e437795578f31d1cbba022095bfd0a64310edade23118279ef75ff1023f316a93ff26fc0ef140f785f90170e09c560434850a822628a336ad8b4a98279bf4408e6854b58bf0ac322f5e96f44ee164ef19e05a220634d483319919224b021ae82c968c0bc5005ef97a0b04408b67acff3bee807e63d0a5eb85aad566f8114ebdf4b50c0414415396839c304089e846455101111c97e60de0ff91731984ce4a8623198cc05f37ee8662a168bc16415e63dd1d0cafb2111c90b51109a1246c99a1d68d021491330a2ba45e98b241811d1d005b50a5bfd5002a05cb0d5130d71555391085b7dd14fcccca262df7ef2749ad35d937264443a57375896f428bc5aeb98525db24caea67712693d4f975952363b1a25d28a99198ac7e7d1ba9078ce3959f393dc0b1eb30fddb6e9328a8e2757aeb34bda5a766ffcb4d2aedbf869edaebbbbfb19765de71d333b1934542474775dd8ddee3c24b3bbbb4f1f6f796146923539d5b1cb724932c926e9246931e791b323242429aef1948c597e621aff5cee69fe5e629a1c823f436faa178463b516840c80906d84e3ef841e8e3d34c2f1e77a758563b533c2d1ca084797d718e15875c29c100a46d804355a28a87a138eb5157e61c7905c8e149bb1199b3177f7a6ef69e2a62e1cc51a8e3d3f488ee4481ef398c7a0a0a0a02652add6363d3b235124d757eb426a71de71224da409ca1ca997d2dd5dfed859e4732fd5ea944ad29c735a1a268bf6d91e5975382dbbea5637f6266c7de19c82f31ac87056dd94ac5b75329c38385fe0bc831ac8706490dd342745dfc0d1e0932ca5e4592d6ff040cd93e52dd5491b3cf795f681f1fccdd43e599ada4783e76f2eed93c1f3b753fbdc78fef6d2621aaaa39ad62f1751bfaa918572f96554bf2a198be6caa47e552a52579ab991c454230be592d02f59c958344e542e312c25f340a643a6546533663237027dd6620d5d96c6ce16fb5dd643f4573b89566c0c7eac2128e47fac17fc682df8d105801fdfc68fe2ce8f3d97a5dca95a97729efe6aa792f3f4cb26558bb516c94c342d4e510d409a01486520356ab11f07a433257f4d27e7e9a74a4ea3dc9e52764a6120154a81b20fc77a41385a0bc2d10580707c1be128ee84638fcb9cbc1a4e349cee2a1c5dd7a55ce10c975a4d998c70ac752ae92829ddfee9b4b1604c279cd043d6db66fd4db3bed59d24a6f5dc44ad703c81ea655494eb2f5949ac7044410c110a541c1585b964b9c11a4857159e0d969db31a99b35a164be9eeee5e7dcc7477f6ba6d1cc7715cebf85e4ee736fe1b22072cb69e57ef6dfef560fdf7c3ef275be1d7faef6f5ae16cf1036f78a11653ba9516abbb0abb9fab1eab6e0b3924eca663a7d8775083220bde689ed7c1ca58ec7a68a3c5a25673683cbe3176352676a4506e277d2725b55447855deb31c3850e4ad7cdd4748d429968fa45e9a47482e917ad626cd484d2af6d369b516649332ab7e795db33cbe4727b9c5ed0f46ba3d1a86893886674fb1d4cbfb62ae64691b9cdd52c96cbcccb6d6ec979fa2b073a96a8dcfe46d32f8e1ef9ab83d23cfdaeb16bb5f66e86a4a33476b3a5ee0ad36d1dd4a99b1959f96337bbddd12aadcdfcd5cd66dd6cd6cd5eece9f16d46f357add568355a8d76fbb79abf2acd79fa6935da569bf9ab2e2dcd96664bb3dbcf2df9abce664b4b348ee62faee63c4d6994465d2e6b395a477375b48ed6cdfce7a7a74714ff5dae6e36e7acd56a351a8d465b5a5a5aaab3d9acce6e733577b998abdd7eef6a9d945134a72d2aa1e7ce8f59a90110195861e52b7d415564a568563427b3ff9ccc25341a4fc70f43f55cef3beebaae63fe6ee53dcf3c4695f54079d491b1accab560e567251479246753581bb318bdc2f349ff03796d48b9bd8df6e9b9b15aedd3a22c7623076ef7b5ec6eb5c2c9383cef0bc70fdcee59f43c24b7ebc2c96ddd5d1cddbc60e5bbd46556adb5d65a2fa8b6b9982e65ed9f55ca8a739946e73dc3ca381c0720544caef0a209095868327d30734435f3e568dbb66d6b800f4d358480c90c0886983d98b1e28b90a06688e08c78e205a31c365a78cc3e733ab7dcfe029f397d2680ad8c924e1627dbb66d5b992f1d519db587299e243134e50b125073e9882bdd154d92b0ccec4064868905445b983ac50af4a99911b090086228095dc31d110ba3e4ffff460a5277fc9e4f0492d987a5e4d9b327cf4df23b13ef8e76dbb629a5b685d7068ab53a34427047fb32565e6bc6d7126aabc5baadd657b088cf78a0a63e3e592f19cf6fe3bde7ffe9d70c18393a31deeab0885363d7282408bfde2d68754ab8372c4b4407ac2c2339e06411c7e2dcbc7c1820b388f3f25b805ffbdc3e1c8e48eecd4d38672b2cfa58a08f163b0402d3581e3e589c0d96d54306c8636371fb7c3404c2c24db5d65a6baddcd699b1365c57a7bb1e3cfeb1f3fa8c19e67b2fe5b394f2b92bdff90b8113e2ee04c77e0d085d69a42fdd7e30984bc39d1f3de4b2971496c47081b7c899ed8042cb10496ef092041a2b4558d46dc6e5029fe9a49958b30ec63c41d1224605158480872225fb33ac406489216614c590104c72a8521402da529a73ba12822a15c8b23051297aa1e584cf6451810f3f7051827261872226b59b7092182b9820620b1e8c7862ea20053700162c103b5145b5b80ca166dbb6add665674b114d86eaac12cb192cba335cc8eac42133a36ad344a6450a660e67d8e4d45a8081524aa9cf103de8f0c30d4b2091c554aa25e5d4b0381142c51341a490e5882f4773ca19256ece88694106029dcb5b642d744870146cd274f1830d35f0602465c9190b9e9879428211ec606609a9852982252b603df9a425b41c30466f789e5e45061a84a04226b543088e58b9649461b2f2a5cbd30f52a6e08106822553749e54b9a4898c0aa559786269167b82c50a163878e17ad2b206053039c126071cba78b10352540c26445d76a6133944d483cb09910d392ebb9b58c94ea6a81da18594238080e1062ee69cb3461296efbcfd339f36dcf92fa3c2520ccc50022d4c65b068920446670d972a3143c40ab21234a5604d94a1a78e82f4e9f6f5b6ab41d29284d2941254340c7d5975a165ce89e48513a927589431028a19868ea83b247512da70660829696a35c8305404dd21a9ed11b4dc1ea5d982821c5ca87a4a22651e0d4599c14cb60cb5d0a24d9454ddf02ba186a8942d18f538cad0d808000000019315000028100a0544229168344c6459f40114800e6e90467a5c2e15c8a320087214859031c000420020001042666686a62800a065093dbf6c504496aa7ddb404fff664ab1060d8e68a73476cca6ed2024646232ca16e72b5cae526abf39f664b3f6501b51220d29896a1c7917971a598a8bdf6149c9eae39135a8054f6aa4b2d2a44968f119f4b1558bf43f21741db85b2587145fb623740d14e974fdb37067c4e55d115e45277b1327be339cdda378dc1706440ead81da87e513ca87adc32e68692ffc3ecbaa548e0772080ac324bfe3d7dfb46fb673a36d93913303aa827844d94d8ca129ab2e517f57b21215f57df0997e516ccd05f36ba74760d61afad47d15d5fb70032dec4bc712658790e5a18fe233aaf7235271e429e2aba44a2dddf0652a33a467c7a1f7419caa98936881152a439e592f2843710091c18d892ce30cded80f7f2a2e6c89bc5b53aed1aa7ce5647870ac9418a027c8269e48c16f60a3221c24094c1fb3508009d610cd4423e638d04602bfa72999722eb78a9885e3426abcf2ac779153025321cedca34b2b6ea542c8f4d0a33559e6c9569d49b11caa14e2df02984b210e23c52ff20b402a76a30eb380facde81aad14c86be08827846f868a386952fc6a29b20c01634cc59c755a3ea81009c65ea4f9805090c917a1fe7d7138f609f1d2c0f9d0c4ad89aaf4231e636091165f105e4f39087c0a338d9e01fc4fe365ee729b9bd4fc98e50750657557dbcc4f68fcf18bc24b35be2a21e608d6410a0adb27f350de37a14aa5f5e8585bd83d8e56f5ecc5fd401b5772f161c45a7957ba4a0553b1b440d486adb1d095fcd27c350ae726de169984d78ffc561f4ceec2575904358d1846d2503950009427ea931c76f3c9df2a3811eff245df237df6b9f14cafc7b55baad2792be93b88c385c4b81edafa40b6e139be65a7f5b7d8cb04570951e166b5405cab1c6a7c4c3345ff2dfdf03370cfa9e1dd15e80bbdf81e459f24c2346334bdb2a0e797c4c794fd4e3ed737d4d4001d1f40467a15ef4e53e050a5ddab8d277269c8c189d544de76cc31f4d9e1744d1d670af7636f2415cfd398a0323fc7de3f3c27154a24aca7ccf33eb2767800eb54f7fca9e84e67b1420ce7ac0d2ca3f8561db1d79dc24e7985bc2010e4adfe55f12dd1759cde71e505c27aeed68cf3c492796f8bdbc2371c0ea5b670a936ae89334f31d2a1e5513fb79a2d989fe972fc9a186232ff36ea5e536e87436cbceda0a463bde6d3fd2256138d81677e6384178dd6ed12465d3d9f2ee6f698a62b31b33b88d70db67fceafb83f84894a8a42bf060c3ec78f69f889a2791a261e990e078747bb260cc2b0a64f264825f78f92f36da8990b99e6268f31967f358f2ab1ed9b03d8e3b05e2a75280db2acfc999d5033df7339c0c6f937ea29d04213f927097708eaeec3d22a17ab7d580a7984022855e49a4194acd4eff7ff372d4b5e3371b85224107bcfff78cd8ea228b945c79fb0718fd42e47a8c7fc1a5898e67b29cfb87ef0e29a0cce1d35c6ab3e47c181e2fbcb3072407fd055fabea29fd6838f94de4b9c56547b4f910802421db18b66c874d1d2c8aa40dbb7725ed6b5c7e0998b44bbc9f30b68ed0630e544a7c43e0ad4924fbb0649e6bbf213e77f052913598ef443b884944583668554f58805ce335154c2b0f9f4dcf29521f94eca229ec2fcaf2979698492c116ed33c3a71e1d66d81cb9bf55c5a7fc814d642f63816b858861c953394ca605869f9c8101e1b6276e68c3d137350a0a0c034e60b0c8a57425742534280203f22282e9770e04270dd721d6ac24f5b4767a3114b520c8599defa88fc0b082db03c76da5dc2114fef67d139f15fae1425f7e097685b50efd4af36b773f5227bbba5351a45551464e23bce1c4d8139b5a3c792e2f8fddc88e20c1eeac58160b52f147aa62f652ecb545db0bd82011a1c8209216ef27ef95ea67dfd96fbef1b1962415fd805c83627cc6ad24d147c155bd6840eb896664c873bec27fe360367b34d7804c7a0e3714c809048739b929dd9c1852d11c882cebb0e58e3b6b7160676816794e4181c966250318881fcdeba17302b7fa9415e2d24b1cd07f619d422b527d76b235860dac82a3670af26d9c5ccb993c2d3595ce6a42374bd11b04042b0f6cd4c0af45369d88fd473f725903af10baeaed4d78651767714791a924da4541c1901f242f7974686860910da5e5015c4f6a0505aed81446b6aa93c45d6c741e185689ef245c505255240c5416ff1b0031d0f6a36b94fd0556df128920a1f4d7a2fac4158e6bf4652bdedc8b002c8f70e44f2e83cd11e13cbd59a49775df661fd4e6b3482e511da432d16f5681c609da701c989e853a9d4c9d1f8b79fa8939a6fc2fda568da02cd381d47aa70eb12ea02e669898a3ee2be20c30e60306b536b6a8f215269ed4cd705b330426133ddc90d00991a6f9a8e981ff2a9c168aba817f725b6c8ddcbc03572d4f458e123963594a382b1326ef0fef325ff407aec894644d1770577a3660408797277a72b2e234e3b054da1aadac966e54414369dec98e29cded083dad1ee720ebcf7c7072dfffdd41e5a16d41f4f81861e636954f738f970f690159a9e5b38c16297e9867acbfc30572f43a571282d771a8b08e63acbbacb38e063e757904d8984bc9ee7a357e3ab3b94b9938699562670267be5c1fcb17b56401fc0403a8a0b94c322b283cd5595f135d8628bd8cb040c8e1db824b81f99a494305b01c1fae5686ee21ffa014d50519330383e06ec6f29e5e256ba62abe511269e31415143545515244c90eea3ea24851494679155dc440105224ea8a314a5e673465f0eb64521bf16f23edd24ef6b339e5cac99ba55f99f2b89118620ee9874e73483b30d41351e81cd277c959e897c924fdb94bd1964d480935d5cff66e56503ba23f359b52c1a710b3112b25feb320fc76f904823f23c52b08d91cfe3cb5150a77b55112e00d0ca9423c9d716de94220c403548e6b3eb9cec53f453143973d127348bee6b28cd1111040b91c6de30878f51fb64f50dfc5f55d58c5cd3f48dd2fb28009bc944ca7328bf9392b030f2a87f4c88279e5e5b9055906399c23cb27d9e8f624205a5ca3bba5b2b1c1ab7931a1c852bfde5334b6235f35376d67fddcd74ed798e8de81298b4d256cd64473b3e9cfe15dff217e3d1e5f0601c90abca48ec41758f0100b9544e09b54d893c3445358356b21ac8376c6f6776728a13bf1d13557e44c6ca519babcf7ecec45be63fc524ca6d48f05c21c40ff57a97aa4a90f81c7faa489402da6db3ff69b313ab804121170a8281028b78e18593af0542ee5794a2369459c144c20c803bbe493699a13f6180436de47054c2500d5ef801bf434b5d7eb44f9f128727565ac2e882213995a939498ea4d1629ab10837c18e020eda17ef90352dcdf942501b5c177b28d36e420e2a7a6412bb3d848e58e1349f847476353ae28a744a5b040288797abde9abec0a03333c780c5d13b81cde57e2e8b19d912ed535110c7aa72a614498ca969a5bd8531a02f031544f542e7f2d213323dee6913fdd64d7b5da11455a69052a4f36a0404a638458c41fc6d696ddca7795d99f412c7c2efceaf38223eaaf5f6007856d6f5d0c00bc93d046e0ed1a009485e9e06daffc55349451928d83656f8746e2a43b1782750aa293a86fd53bdc0918e1b75c6d5ffaedd7d6f55b8d3b1607ec9d50d492b869abb416438c91319651e699e5ed710ae6ce461e7b7c78597830fce2c4b31048e6b5358b0895d50397fc2acadc67a7a0e6f6ab947a8fc92b2b53ca35575eb5ab28bae1f4140152466779fc0d1e9dface6919fd554fa2b6538b857cda5c03fc62e30d6dc381b94200194a49f58c5bf71c817be00e09ba0900581615f7a427f67df259a53ec2df5f30d6dff18867e5136d1d502120190cd5345242fd4089938008e582bb78c9b107e34a1f3dd9e13c6fc98282332a183f13b152a86b5043d676059eaef05d42d024dd8a345e74651a9a8eb17b7af5de6357cbd199a0986cd7af543c0af938e406192e7f9c4d2d797f5bfac88f478b67382e452e7c893741754dfbc85bd37acb62d8af1397c7b7b62ffa65617dd62bc5628eda850beb702e46c149ceb670a58de59b83b6aa0a3f111668e193e5f2527fa48cc1be7ca19be08c82ec02ac816c096f0b67f30e3dcdb83923867b7c5e71c823ea8f87229c4bdbfdab1b781f7af3df637707fb467df4a0174b78d0a29b8deec4e819aaa958544a7ee28acf486e649a26026934330cc2f9c27c090632d271913e2209a474ed7e31fb1f88f9c3efa058d1bb1dcdaa8bc1b0a94d30f8ca5d4643a0cb7c9daba4131324ea917c8ebed05026f671c0ec55c22f3cc0536bf878f06ad2b0fcfdfa8b52f1aed249acddda836e2161d68f4fdd14efba11ac89f75537a191d74992e19eee045c705adda4dcbb09c4c086932458f4feae4e6ca1a8b53e01c9fa8c1211a0535fd9121164e6e3d097d2512f8869279a6be29bb220f59578e35a545cafd31657a2e90e2a3e6173fe1050796c9be751f87f0d7cbb92877bcdc7bb21a3e81a2b4449b916810795804b70c1ee835d29de063e3c11d76f7496856c69abf6b08d88cdf8dba5d75282e4253d21e5f6727533bbeb0674c4dad10ae43d643fe3c7a3756815d21d76763fbebeb57a676c7deb32a701ee91b649a738f1ea7e98502c0c0a7aea68a9ad25c9328ab968b7ab8766400234296ec21f2705327d94cf0dbecfa922e55434ced87e18eb673bf8875bc402835f59c40f87218dd88cfd6d4f1d6019502149dd6e312a1c12b52ad406a150c425e7d4ac0c4bdfeab516cb5db4a615952e90efff8c0a95fe5e2f567101dd081b3b9909cb9dcc913df642d291869d2b7f2d227a211a1399328722c64ad260c3a1cc309d3d859340e9e442c7a89c30d9867f46aafd4947ed8a830345d62945c02253e051aa2087c2c2addee5235368ca20809611e5474e1b77c635214bae3ee3099c3cf381c9f5a31acfdd66d1fe938aa931ef3f0e6c9638541973712352262945234aadba2cf7871d97d74e705b03bfb2d1e98c92a7477a1d82b9e469439e537d65c2471b20d389b977f95cfcbcffab0ca650b79dab1c4c036aa132e47db89809ca78c8db9f2bc5bdb93092820728e52db84cec26e434cd03ac3ac022c4457b4b7582139ff1692eaf0036fec2a20718f855c64d401f8f26cd85701dee53e5c47fd9e553135a3387ef64d523027a4b3ab21184287a38f184287b991f43a5d233a6e9a6aff4d54fb8ebad91a8297c273a013a30ba32a26ecffe9fe4819fd9d07faf0eafc5272684da6db9fbd3a7f760cff0b62a3754ac8f86f7753cdb5f0bf443caa432fc403d727f423965cb3f927c12905b8a324d47f5f62f6604b3e4bd5d46fff22a8a78f0c841a39531c770c82b019eb7541eae96cf570baaed7501fc7ac126a217aeccdc5aa3fe1213d4a0ebb8bce5c9b4c07621585fed13e07f51904b6f16331d97509d019436c090fd02320bf2f2e4f823437a88886a991775bae47c505c426b7990182c23928833aa10c28a965a6ce88d0e79aeb164d0a10fe1889868a0643afc945b4f5b0f1ed249ea0cebc915f69354130bbb0fe770e089a5479716e32da2c1a50388747b2511b28721a3c2f288a9a553d127726a3d11d6b1ca607c2ee3e6ceb01638cf6f885d998d4dced1c2763b722f58eaf14a444b6ba3766fdeb46b3929485e62df897559a4297461d00c1ab184eef05fc64463b64e7b04bf563186abf5b08deec51881483faf549b64ea429c99bbdc3c181a086f7e495d741779ece2da2e4cd9555276f42b79d0728e5cd3253645ce74d59a5a8cb06370c3e480aad45fff1efe43abc495823fd841de39936bdb327b199b93662d65e2c3ed426ad838fc45bb6026d4b7e7333da1c4d12938130d1444c8ed435c96beb6b926da81278d07a3d186694a2515b20e9b8de574f4194fe8e2b9f3d46f63fac16518f24d421659723c57b4658db8673734f0cc2507e0141864e0dd143304b87d146c26ff54d9f7462e2b8aaddaa0d82783a4dbae1579bfd193a874bf5ed63ef38dc034b5a4bee5fc2683597d00a8c119c7ddb2841da7da13e506485c82be5120c794fbb205cc2299bc32be0a730d6c7aea321893bf25d652064d4575b872a41fe77d641244b610ef5144244ea0c3898fd232e118f912f04f7541921c3cd0fcc9139f03947f8d36aba1632d52c3147730420d100921c1c80e6bbf016285cae4522d163c51137d7789e1846cdd453ced01581cab226804f2156d3ff671ef1a38fc02620fa26ba1be00ba05be203a5ed3e71b271beaa3b9615730c97737cffc14bdfd255ec13911bf4625f05f60b3ade365d8c31397aaefa9c1b8666e7a3d77531a98f1f0681627d98de82ad95b24262a9532b1a17f85856542fbc90b8e21cf2b5eaf77fc2c1c676b81d9e8a5d8272c87c0cc032868432c3b2f02acc5f04db773942cd77aa971f60499f7bde6451271150689957b52141f7cced7163e919219533970c0c40a7f0b13330bbd35b72c41cbac2f9aa8e7c2b28498309c3939c28bbb140d9f7bfae812240e04052e1c8d40d49bbb866d5601161e789c515a53bf160dce1f86fa02116dbd3963ae3552e5fa811b506564517f93243b7476373c9b40e2549b333899c8b66508d5e4e192b22f30877f5e91179e4fb61e198ea98fdf1225e43d865f5f9cac666ab4903dafeb8a48135100f462a878090cff895daa9e89da47dc603849e357bafc21a7bd2881dec5820366b97b823b9b9e54c8f45cac662d8f3aefcc9323a4f9cc9c4845c0d76da07e431b6cac3bde5146f2ade50b2c72c53f59bd2b323ab47ae4b21f5956e9aa4db07a0e824036a6d5ba9382e448d99ede78883b5c47d6ee89a540e9a518dbc73ac84fbbe89f1c7b41ae58acec27930c913054b2c0ebab033355fcbef42e26f85b64f41208051176a0743994fdda0738f5b6a8b698591f12e4dff18d8f78f623406d7b0b74e5dd62bae8885a02533a49847e23fb8803247731fb41c4da084d88596257193a48b023edd10ba81ab58905ca9ce95a80a57432874d3bb920834a957e83c578e0090d50e70a291677d42441f9334035d6327c163471e03c667d4140567f342b6d1e40fd937d1632532b885ac7cc4bd90e2f8ac14d8f7889a3c027730ba369015207537b863a40de12b4196dc615e02f4c3a274022626f694c9b8a2c0228acdb5c65778bb9baba8fb172d2883a9861b6f3f6104441813f06e17852b8cabc19ae28fbdd9aaae263148b4800c99e9fb83c583191a7f545a67bb3c17c2626a5723a3611829e08e60035c45baaf8c86163668ad0b435f62da346b37774b438fd3573d17834aef4fe8383fa60042c418b5a62082959115588fa7d16310c8457cced8b1156a556799c00646e4049b0c46720e44e1446328ac76d4df84acfbc0a729b1bfcf380c65cb1d37e4c005b6986a63c8a661e040dc1054cf8a0d4ba29262c8197d9d50f71f900d1a96d94bdb56f0c9e025f32f1fc4bf06bae929ada326dc5c313137780cf00a4a0b8721caa6470a9962291b308e8f99e3777af824e2de7cd20a7e2a716a58bb8eef172d51e570856d9f66a5bf15a80fd4b11a35b07799f8471558cbba42f18b4766a6ea73fdc241d2a98533622bfa284ca6baa2b2c1d9492052e1dcd6dad17236ac157c167871d76456fcf274805634393a34c5b2d6dfecf49c5f9c5b6533a538568cd4dfb5be14446841b7ac5ba51ea5cb9fa47293406002116307b20de5c9543d142be9113be570a1f28e976acaa1be9d5f9caea408fe7a7d3a9417fc5f62a02933d7382a9b6910cf7b55ea85479507daba4eea2d90e78b78db939c285cb280496444a713584ca79ff9b40ba0db389624bb382b9f0f9a968e3b72050c3b746a31ed70326fe9de0d6c4cda5caeb5803cb7bbf294e06bb907930e3e1832d932a2da98b29a7130cb61c2d0d0033e03a56392ec53ef8d509a9129fa9e2a9d2e4b4394c6ed26bc6d488754d59a6a8b119b8e35d27b586111b555690731f9bccce207ea69eb884ab73b1b8cdca07e09a6f00a3ba7576e63b6efd5f83795aa4735c34baae27e615a81a2578e80b9d2fd23f4427238b34eda88ba7bcf32c3c3757d5b0c919c0ef3936d2918b492c577bc0860a4ff718c54d9493ccbe24bb69b1c6a832943a43df9367e2e08cd6ecbe18b04f032380f0eaf37a99a017b603048ce63f070b1c9f3d57a10cdb712dd571df5d825551a2ace482a75031ed10246e55f5aded649fec41b984010e78b55780a27c763551668eee2cb87aa44edf8c13763f44315a16247d840a4611b7883863f0f01157952df07a92feec012fdbd56455d7806d8b431ec284fe18d4097ab625f583fcc7412d9cea5a18919024f626131e7e391e3ed8656976637afcb74cb4b6f1845c0cf59e63298b915d013f0af616dd3f485cb6794d065f0c849c734d5dc1c67daebd5553ea009d1d2586e8b2dc6162ff09c4fa369595427a27bd9098f2d33889700f14ecca7fbf489d4ce93df5c77344b5f95951082f854f8aa79a02751a63fd906c8f31e7e1b33f502aa10fa99825d6eab147861e41f3814bcfb063d5c31418c04d19263d2456872c13cf7cabda24feac75fd74e1ed5ae1f38444aca26ac4bb123e4e5ecacd5222a1cbdeb6578965fe5287a7642792a9b91dc8ae41851a80a2edf192a3b8d47a369c259a76803517d91800869898f1c2e51608c74df167c6d62d6e550df86f920042dc71ca3862063ef02225dad6b9c5d08d50a1fa5ea20738c567855a366407312e9802c4681d11ca3db883cdf9c2ababe724ec14585a6d00970b7f23b7782f117421558ceed2b47b9a8b73fdfee016e5df0b5b7144ebed4102c93cc5e4123999d853d2a0315501cf1ad062ccf5e8770c08ab53d231fc098458b2b6af149abe84e81fe23065b2c923d7caa2591a8865b17bd4bd1c9bceafc9268574a9b75d211bab0726157d758474e14f3e7e501170bf9546a723ee6bad48516019a99b1509db9b8cf24862019995073ff25ea52770790bb7ccc46b43b44adfd68be9ea1e316958d21dcb1820653468468d1e1182931391d736b46fc81912a70e96552ecc08be14a7bfc2212e16c297c8f145214d92a06a3e2a84e1db35376b16f00e14287a4fd6656f5f47162e383845f6082ad45d5c41c0ff779ad87e0bdd983e1602972502754558e05bd67188c516c97d7d2130dd743edcdf387369574decbc75276dad3277fa311d7a9e191b17a1072cbc420ab91eb2c2a1e156bc280cc81f65c12f7f2428e6829ed2c9edbbc36a4b935c82098edd1a4c0d2e9417113e59efa56ac35956bef0abee79ff0efdf9a3a4e287f673b921fd2affceb4b0e0d34af51300f5e40e14863938687f2f40c84a49a951513bd55fdb1a11b28cd64310c90b45c9bef227626e36fb220c74973c8514151571d19ad1129e1699b006946e2653460fb862d08e22cfd4adee90ec77ad529662e9eb072c1cc6963c8ae00c4932580aeea306ce20e1a7da908a7af520693ef98c95618f6f37148884711976d78afcc15439bdc60d3f585918f89daf552d149d3d81455966ae29977e1dfeaacc40ca55db3a47af48f11a4e76421970d40e088ba85ed7022967ef0390bec7315b8eedb5dc47a22cd16986e8f5ba5910b5121c06d57b2e073f82930d8015b2b65e6855c9aa02fb5f1e44a02d71f2ecc4c3551202e8fe323de716e1f83d63008afeb5788697ba52787c27e42c6d6a470988fcd4d083c671bc5200da87d46009133da257ca0c893d43cf622045e8aa187a93017a8e829c9ad979798de23309344634ed4398751109e885d75ff35a682d97472a48353bd55300b13d604ca6d580c518e30836f324073a12c5f69ec2a77ecc01696da5465d4ac085f48e400b9a236df8b96497485a67511919c14c41944ad4d260c00f03fbe051a74b18eee885aaf67d5d2fc624203c90e2796bbb0462fe8b987942fb1ba008a11606301ddecaa95372e041743853f8ad1733434b387823a4790259c4fc352e3c56e2e231a7fac7e6bbf82b02c74005250e760193f973fadc7565e3e6e41f43bd0c319b94879f8e935ac9fb1ba5fa81d697ac5421871ae39dd02cc94cf1dc52309e79a855288fca8c14fae5e8f345f6ee68b8cb8f0a80e1d25cd917a15b9b13aaaacbeb5946461b3388667e81000165cefa952a189db9b5dc043ac511290a1e757d801fcea739265ddeb2c0c63f6b7fb1fdadc7b9c934fe9ac2a1026c3cb94e127beb194d24922c379a8bac470b1085dec6f8eeb412a1fc2b39ef07c1c24cf8bcfbf38092d614130c661a262324b4eb40a623f68aad2af3bec53b3cc53e3414ca1268bf1450b7c85219a9a52b1fb15d3cb4650243714ce16a450932b30d0d1a189023265cdeed3ad34bac0c2195d63f16055a9c6e8cd3b5cb41a1402c97378e54dc26245195508ae45a60f23d919e14d53367856e98faa023e0f5a272eb5e33b707708684f485395f9a0446162414cb51f3eb82a1d1e2eda68cae9e8779382e4b351ee739532df23b4d5e649c7717816db2953762a6abfe490ee09210a5a10b43041f4547b34f5575a8b46085267e4b8a62cd287ca68b8c3992b8158e4045b715fb9a38501791e27b551bb4b14245256da1cc000ea844553e4c0a421f5bcf0ccf556d9dc3990a3abd5be94ed041cd5700c590854ed03ca0eb3602d3bf6c57bb7c5d7443c9625e6c11e520f6f87b49dc824df319efc5d4aa3976dc9ca24f46c638f3f37dc896ac989ffeee5612433bf554eb5fa50f3d624bfcf514f1a64f6a9eb8a6c745f0b53362c9e64dac1b378bcbc24b1c63438dff71e203ad2d1cf241041d2c5c1ea4cf5718782a455ab5a021b6a19a19b8b70fdbfd362946b6dfa58602f34dd310eeb1367e078b851fb53b5de0cf50c49b8b910dc47ab492345c05a721193b191d11d185e86fd52e1c9bc41008c9b5aa09f022e5e1463e66d5f2c6933c550dcb33bc8771cbe24758b9cde905e88f62ce969f4c61cbdb2a5384418a9905181743a24bd87dc88a877891e4673c38bff5b9dcef9ff4d4b680ec8513054f7a25c114aa34fe100556960903f479541b145726dd74008a9c407a762ed531e8af332914079432f50318797dd74ad980064048b853eec9b8d6b4435bff03dc74be662d98bbe56a2fe39698874a121999e57606593359c5a04ad28dd218d2d89984b48e6137425750d8d33166b28ac58ea6470a93b588a658450ed57177538e9b7f907eeb62867c39faed6ca0bcbfc97f41c726e2e53758bd4756a45609a7784f305b2694f91327df19605244f2c10430d361c25894d934762e1d70d962bbf3c4cb4fa8ba63ce250c1a73d6a3551952b4ab82306ff73c507d76dad79c7dc722f9a7a8e5814dafaf930f052bbc832b061000dc9f06c184306dc32b420d702c26a22c904aa05d6e9b1d4593f519f3a5180a6f104243c4521a464ce1b46885875106a5122dc6c75379f8d22cac0429272d101daf4c90abadb50d3716fda708a06064ccbcc8d384cf070dfe75b17fd5793276f465c114e23f4d391d19fc9ebffbe657f7f81b8c34c4a4147f4191d73860e4a08421106fa995c62c6e55e601d621b91e31af0445f0a9fcea90ba3308cd2361ac8c3849904e2ba50986b5a1c0e1d9b92ed008f8a082a6783c607ed6f16dd8cf47df04541484130473915e620ae96aad9d0ba28e20f0b22a699e7db17f5cffe2b750826fac492ff33dbb95623fd13aca35fed5ee3c361942524aace7fe0a907bd10125b032d4c56a669ea50a4252cbf1acb9738cc46545d8413edbc6b70311db9317a9dceb59c605819730d01a4e2ce8ae578415a7397cd6a9e348d70502cfd81d421b90b8d4b772c63de7c3454b4537628d9c9144c47be6d09fcd53eaf7020314551783d9c78289bd163550b273f6092966fd1f379a639104519c2905ab606b75c4e2e611ae24c2de20e48c7e549265a3d04770d7ce89385c4fc5894bc6b406da0cdc073f54cc137ffa292e6aeccc45420815f24a66ac33233fff885239ab024ee0cee950af4d536b5580422b1b9952f5c712aa07c3d38a456eb9faeb42080c7390c59a4a889d6986db142c8e7c6b34db8d8b65a31115e07d35ec4c279586bbcc205b929fbbeb911786d9dc2675157ac493ba1f188a4511101ab5c385ef49b0d9ad18d74e161628a145273d8ad292e6329a9c9005314c5a4b145d4142faaa3de889b4d4a9d415420adcbcc1266933eff510b387dd68fb900d7940c1a152a828fd0ef02fbcfb830bc4547fb03190cf5c9878bab97f1b034582658d2c8f51039bc04f1e3542736352be29137836616a60c4a308e93010d63125ad3121656bb22b984d92ab03214f7c131add49e978177bf5031722bbc26f1c304a9bdf42e1c450e0102b6d0940dca33d4d6b447d03d152d32a623d9652e732e0f2f5281d27b4de058f10a52f1fe989651441e3e2d7eb2d9a731c15c4a55781e2b82cf06ca0c440d777c087708782b335ca87b32dd8a14612c98bef99c24f862bd52d408c3101d3fba752305d56317b8b5fc484044844a901481a0498395e178a7fc6d2d96aff4ca881fa39324ba574751bdb1c6a5f5c0bd69cd644144d13e8ba58ecbd5e68e8f97b89f7d3a138c02b206108dba40246938664cdb5e4011fe6fc3d4083ce41faa469fd03d0c72da0cbc692c477e784b501e12f167b24606802d0c4d110e0f227efc108ca1ec50be5b9fd3ea83679104c2078888b290cd554280c58461a4cf437787f7d397b84996de8ade2006bba191a7aad241734d0f2ea4397fbea860af7b67b70d9180be2775a163afab5ede52d6be4a188ee6ce6377cf46cd9d058186f761f3109d5313d6005fae63c2b40f92504432cca10ea626b2282ff0ca9b6e43242e6d5782798ee9e38cb5e257fb3130d134489ab7318be6e625aaa52c27f53526fc90e62ee95abbd76195d452e8e9da028cf2cfa76525dad856cff524bc93113a147d694af9f979cacff9b6dfda464847f869943c29a8bf840657eb63106b520f55a5b6f0a2406483fd0086e4a2118f1d1b74253578bf11c75433a0073091bb78a109049cdfbb8148aebc597721c0f71fe2f243f60992f6fa584bef85139290a81192a201c330be31b40d1eab08c4c40224434705cc634da9d61c4ec6c21958cc30000e78b1a5abaa78481bd9d946423099d079b5955c1128fba89674541684fd07e44946c56af4a0ce7725176693483c7dc41d401e827b9ea57f1813592d5f306b43ab572f7facc85572010d279b707ea0b50ecb26aecaced20165a0ec4641f55588a322fc3ecb851151846f555a7f32a98b5d1d04b5648e220647258489dfceeb82a6d52d2eb40aeab252ebb2b7fd8e107b00365df95adaafc417fc2492db94f66d71fdc15eccb9e5c376fa7eeb8c674127eb8aaa1eb91a19336ed7678f0513b4fd6d34ed54ae57881a5a4d6643af3512cb58d32269235ac533b93174984118f39dc5bb88955aa88903847200f53b408d29daf821d601658337453729d2e6471ceb504566bb32b4126af9c005980c23301318a71309033ad542109e845753250480bafb50d8f9687f663a90bdfeee663ca0f921748cc0f6b5857b1dd5e0039e6d6dfcd3cef4992e95df30f3936ed66eb7d0797c6f9c53a34c1fbf278ae8b3331b6834a845f6f4dc2837b4a5bfc70ee3db07f00887ae0216513bffdda6169a4d1b661fad79556b6278b33291e3570fbbfa710faebbf3b05cca2b68a16feb6677b3b40292b0a21f0bd38cfd6d64f2e40424f2d4397d90c826b1b8a28df020f86c6e221629e6ea26e2917931817c220cf6338aad2ec501b0704dd8366479f995c32449c3e8932e32fff4c01088b7d103b6147722eed3712955a3308acf0bd4b6c48600d01c11dc95e3a7a4df7f9469fba1bdcb07b40039fd4d5e7e4c66f0fff8b145bd60833d6f34d6b95e4f76c15edae47a4c3ba274ce81f05210bd55389f8b248a2ae01ddadb23988d5042929fe26e47ebc0474ffd1f6ef0f90ab67e73a307bb11190cbd39bf8d409ccddb2fedbe7d54258fd4340732984cda953c1b047c0dc63cf9e60634c7439126f5eb9e045b2c729422de9b04e9ad44ae7fabb85bdc3fe5b73bda650082c4f7c654195f15c6c301d52c082eb28c98d5598ea2e6550dd9fa66e591980a1074c85b1a8b8896c1e816fa6b7e1803b9f83d821e9283f6c8a20162876a4c985ee4d94892f38ce1c2e984913f2a70e3380280258240efe79a74897d3d160c23fe8773a64843471c5c978b77d4e327c7229349524719834e03258be06b1391a4d8178c7c98a988850f9cd5695865ef15838d6f853b0a262ed31113e6bd62c091c625ad205b25a032c9d94dc001612d1c0776d58615313a5245118b4c0b91ae7c83a0a2c08e64d33daa2f19a10d86db15a9d2256b3cb469848674c993ebc060c62104ed690019820c82297a2fe08c47ddb2083c7ed2d233cd370081c8fd634450201c58b2fae6350ad36d48bf21ae9d484556c5beff0a3d4fcfedfda7d9c95fb145a5621410ded5318739ed405a7e714c1792a42f13139ce59718be22a0ec17eb83fae87ff2cf380f2cec44d080ce793e20a1c1dea1289307570fe60eb0abdeb90364862b1dbd2c1d824c329f1800aaafafbd1f9f98205692a723cc666538a44f5db2feafeb5462632a53f2c41d22625136064ff41cc0ff3c9b92b0c65d375ab6ce69aa8b1729bab958e9ff20e676f266ab3d07c11cc894c8f17c57264353c686b1c08f37b912a79d98f834490231952d49f408e61934d602b40a929b3be021bbf0a5b5c6c148022611aeafb58c620722115b54392935078fcd5a4ba8d9c99e0c0872fb9266708c52e41c4e625657bcc2952c040eda7f1aa49e4040f989de08ededc9b8ef35145921dcf159137893a9f6dd89d28c96e1b4905bf2b88200501e433042258c5ce6b0948819c2d108cabec3d57d7cff2bfb11d7212031328b1b0b58039a11fb6c0f2ba2aa5a31170a9d0ca0c0dcfa99fdf90f024abf8c40ff411cf364431cf0e047fb4c58f7a0ce3c6acccee0ac503d11c02c24156e4291cc0590aa0b91d0c2f6357df111a55a0402738a120f4c698b38f83aa32a540c1998a3205c1c75336726a897246909ddcb9948635ee5dc36b71cdc6f09e265c1d46a4905151cf685dea21a54614bc160a82c5acab1d23832dcbacd74a7900606220bca802fa82193b8f49a849cff8642813d0db9f3553dd606f2015d0ebcff6e602b5db02b00ac46419e3b1dc2067ba44dd1499a38bb2ee5d2e1fa950198f2016c6ce62794e2e8e23108bac3c105d00b3da024fb00818564feadbd7304766abc8fb83f8a191b2e3e5b3f012bdd2b0bbc82a7473e0f047e27ebfdb0db52fc3f52e8a05c39d21de8b0c332639ea281623871210d9b28dbac557b6a650d051b1c9bb04cd8478a98182473fe66f2dcbfb0412abf5a5fd7f8de8ad38c90685c50d9492b46d0f108175a67359b326e0e924dd2c679946b3f2b40d00d855833de01350eebc1b0b42122e319e009b78ebe66afa451c9a6a02ab35aea04f50e414728f2e5383ba9c6248bec5b6e210c5dd809a24941328d87881ac81860ead46c715dbeff9335625c56a03f28e2584a8d2591e6294b850825a664b7dd1e359344b28f7734a25ce785a9aa579ff73927367b91bc9f91444f1f1a0670c03ae655a0c99403826d5348cdefc1ccab0842748714c1c419c4821f9fec081a8087123b5bb1e79c179792870151edfdb5b84d878775cda460a95a1507236c9d673231ce6ce73028ea481e2c8172395a4ec836eda25b8838f9076757ce42622ab1639ff82aa2210322f215a4cf49a0c3c60cfc2b11001a914f8f858d751a03c395628cce3e4e9b5d88962504e8562d21c455026af654e510631361144b2ec48770a0ec1284df1653c5fb317f57ff4bebaef2cf1bdb356cdf7a16852dcbde399d15e752531c4bf513247f284ca138db46e1a9d0d6529e2459c1b61efb9f5a90c13bf56a11eeaadd6713dd2eaf601958a1c014ffd2f4615e72d79c2e94094e990202a0cc939bde89cec17793cbb08ee61462684faaab5685ccf94c71c661ac3643edace8b6b8044f3c642062841a6045fa221684d31e6d3e1f03a5e3ad502b41aaf55be90945aa8cbf13c5061d5d53ff4363d987334088766c40b107510087718ad2d93c86e17b7cd563e1e84d454cad49c7583075821365c37968cf795da6ca8c0c94a4737cb38cbc6c146e0a6fe6ae38f257823c98d7b66b656fa24f24222439e13f8a57c372574e770d15f8f70a47cdc62911985c6b5a77c2f3c351beaf5527cfd5c44640e3c97297d9b4109b05204ad13e771acd73ce406a9307aacac8233ea03ad7d6c63ede4ff98fbda0d9482405c032f525523bf2489bbbcee0b8d1099121cf62ed1b5fb2727ce88ccaa70f5d7fdca9aa0bf0e651811aa36608fcaecfbf2d718688c7f22128893f04bd0ff7d4c9b3743c7f892e33553b43dc09803f0c330b7aab0f016bd2719675bc521325331ffa34c5ae7d6788dabb45b1b13fc155d1ddfdc70d9f2be5b65e8a26eafb57b20ab4369a31fc0fa65d61f75e9aa3fe4aa360f5defcf11dcb33bbf16f8a0cb727ed34f11789782e95be7dc9b6016ae7a3952ad03817db3acd6c4668857812430c58692656af2d50154b4b3bdc8628328ffbeecf4b30c1226c9aa5d11105bf215595da1ce3d20130b1a72761b3470f355ce3a326f28a8dcb3ff48d6d34fd6925df59f26b9182f5726341907ab623d09f9d44364102ea4433fe0410050ac4d12b5a53a000f3e49d41370ae5c23df7058c3138e4d0ff85c4038ffd51953007fcd6be5eb65ad35e80c78f5e07223787a7a924ee0a8caaa7aea63106d8b30da083e4dd666e2d4aba2f2e66b3924c776473aa32ac459bd5740621f8bc758954b565cda150e3312e52fdd247e996f428cba1021a2fc366644a677dfbe2498a165dca0635b445b0fdc2682e49ae5965c9690ca72bb285cca9ad85e6c5cf70e1144b095d1ac160421ddbd0e8c436fabba076217bc0df2e69098a94dc61cfa27f77a44f931903ab8e91476d6c84432f44db63b687ac7c02678e1900688cd583f4a3365865fcaf4a66121bfb3d934a51a3553afc33bc1fd77f69a1e508e3c9b51f0341d6fe840f48a1ad883fcbb5190096e26a73ff80f01e80eab14f49fff97d7bc0b59324ddc3de48e8e8f7cc0400eb8f1f23d8c588a935607dcc83607a066b512448cee86bd614a8c9e5a43807a3d0bafbe64b240b475c348f98480519635c2e12cb1a0b2b06c696193bbab880baa2ebc861f34ebd88567cb5dfd94c1cca7225e4e9a5c659772e68cb8a2421497a41851dfff759df3c68701b0325a3a564668fd41696d627ce4928d52c59a924eb67f69058d87fbde1daf65ff3a850d1007d3df8f928a95c4ed1fe464a6f90850fe852571849137edb0308fd5ecaead386fc9a10bccb654fcee75c8f4d7be7743b54380898208ec7e00b825fbc3cfbe043f8f8e984a78211f5239b55a02085bcb0c826800e86baa5ce80d0971a0421caf6983a99c9293fcbde9b4adca7e2a2046c8b82c46520407b59c3b6ec71671584096aae4a5599378a4105aa98a1880fada3a425e4be9a8e04089f90ad259420241e53b6985aacd0114ea26c2de6525b032484ce1b8e4f84ad1411ab8e6b418c946974d9b80c10d6359719432885f6cca4800ae38d37aad0be12041924c7991b0e125210d48f3ec8afb195ae8df38729abc9d8f0420375873902a1cecc0465c036d00f4cf82090462a11bf0e86d3692b4407834ffa296d534547ce5fbe8ace3f05642644c0103e0a5740f415736a8ea459601e19e97651e8519ee21f5a1ba608c4a9c7d578fe2341f0d46c82cb680616c6bc64508b182c66def08959218e632d01514187ec8f7aaf120887f0b6f62af4c5e1573a5fbd69d9cb828824c9f6fb5196712b151c0b4bd399b603612122ead94dfb45d9d9166b1d586f2f48bfcf64cea22e44065b0ab69c9c8fd28f94f7b1b72e1a6a8f8f8b1d10062c5a4f6469478c824a3b803272ce6ead3bc0bc19e500802ca65a93e3cee933cd714a8acc95ad4273509f3ec9312a5c099ea2ce5c433716e4eed08ecbec1c61adfe9b8d1ce6a416267506b456ef4e0a3d613ea3f1d5e5a9a25e4a419a32cec9a55a51d3db8a5c6ecc5366b4a57d1e49138c8088bfae02a5b6b6b4af3d210f78634fa95c2f10fc0055c5824dc7ad3c1db3510cce39b0cd5a3ab1918d886b3f684b90fefef9ff90eb8b33d82c8fc289bdc52b6ee50a4936f9ee16d6c60be67449b5feff1e69caa548a2e9e8b946f40984f23696af49a382aa1601e72f6b7820dee37e986eb88faf2482159d787d66b0f047aeecabec55772c935692c55ea28ad62febaf88875051169598ab1934396227076f698c0a3f89110c0c4700f4673a62a90297e88f3da14b5a2d3aa58717c4f89866c4a9c00eebfdedff9d668e2c6f6d242eae85a9084f426c410fe1d44ae6741d094964aeeec02ff42c8875dcde95047b246f0fbabd94a1c49ab000922c1ad692b9f05e09108bcaaf3c43d9504f2d84cab2dac7268b1d2eeb24c9deca36192dd2c8336054ada7d8cf1dda0dbee65abe717dd1bbe4306ea0f08a8adffb9457aa6a70bb10e260dcf5e469604f521844d92640c8d659e1dde8dc99b2e8d52a7224b99cccfe2561f3e5b81a5f18f4025d72af11d838a599049cfb4bcaec88a1f7ae5f817a2b815d76b4b512051ba8760604a423d981f0612c035a0c4da439384ba284755890b5e5b896977585b41ed1ea82f93efd3fd3e1809174ba5783447558466889180296c4bea8b1845d7fcfddb878230fb9b57703a80890b1efad457b3a47fb51940b2566b4e9df38c0c2f5f819181f3698d1aed7aa5f8a1ce282acc4a301e76a00de92b1de5472300d563d0af93b11ba4c57f1abfe3227d477d0f4c00db2ab01aee036eddc2979af698db533f34b0a8444f3348617a469278a26100c71bc923851590c43e92a489a200aa06d132d7ef6d870f74245276d6b6fb3705c1e394d68a626599e3e68d65dd5e77e0c20efaa0c0526201b21c15265a6f8ba04b610752401f03731cd59176888b08c4fa601d1643c5860fb5b05ef664596429bc55c9a73f1b922247ee85fa00125f7483ab40601e150471a07a217979239e125030aa8c604899803569798945f16e8b4b25a3a3017221f275dfba39a4285cea54c41487ca6f41edf641a4e37d88dac49a37274398cab476c5a41995a86f8a7b363fadd101066d17fc3acd7f97bf48198541f98635db7976b460bd4316996698568ba71b05111ba2c7107cfa865716251774d00a611ec93bb1db5ee478c6368f023001592c63219e6c60571f6692b8f0b743959ca23139007983ac00c6fcfcafef4744eea2d84fe8b6a4537063b6b5f60d7a243af9f736ace6d0181a1c4ea5ca5e86b6a74e2a15150b368462359ae456c49dee344166aeeff851949001687c9c5c2c8d07ae87d02d2a43416f215ab08b41bc01ab5ab4ea8a50fadf5a26372cae6aea42b4974e5b7b79c00c954d0b20ea6a250246b8faaa54331b60f929dbb5ec0daf0bb825a70fb9ed9bce41ada71a6480226748b3a77c046124bb2806b1cadd3600cccc2c9d67fd60e488324723ede4110573fbcd51103fbb1d9d99d00729548cf0eaf9e32f42c8cfa884bf291ba89d39912010a1a757e1964c013bec0474e88839956bd6fc1625e418e4817321ee27a3c0ac9aa445e5062cd9a4dda1cd03618855890d505191527ec18bf0242d41d1c2c8e18b515412ce9c5c25cbdb0babc51012751c5ca96a8acd9430c24c63382320002dfa7658c7075621cf5506c4c42216ee4d37ef2e8575ff51b50536e11ee0cdadb6ba14d96619209838955f55d10d1dfd1c81c44147b3482a71c5482f1d22ab8fcb9b3842c846af5fbd50eb0a52c34a8153fefb2e2549af0dab472000468795110ddd0f9acac0e8e6865a4da3277bad91a6dce137f2e59b1c4bd58f9c643842dd7bb6f1eee70dc6f100f2f4bd795b9593de1b013ea8183343130b189607f1523371fb34f1ea0b682aba5b85e58af42941e15da5195cde5e21a9e9460adee2e007dc17c813389817131f8b73629e29984db0c538aabdfbcb03a7ca261ce0d71c2d2224acfc72c097b869549a793d74f51b9a852dac9658dc565017b1720f86ae3ad6a319ce19d5d5158b6063fc20b4a252aac4d8088d283228385c9dae38606c74802e7687b7c56b7315a194873e0bdeaca6ca2e155ab085bdb7a774681a384c77c563bf89e007786a007036a9cb72dc96686d1a50586892d438484864eb6c87c2af3d1a65fa25ed3cc6336a2361aef15951d7230a15234a366784881c601cb6a985dae6bb84e6b0658e4ceb99142712ebb12d92243233c6283ae260569e26a73411d6d52400f23dd2a596696ce47a1ec23755029f82484428bce73a9c1c914988bbfc3457fef481e26c779640350ed24df20572422bcfaa1cc2ee25df8dc58c2c6fcae8c41a3e3bec21326dace00b43ec224e7980c6d14cc2d53887d07567b6ca7b52e5340745244a565616680b1d8a273460031268c1bc37699829f047830509e1eff340729b319132acd0d51823e2505a5c9da66812111b2f073b7725676acd95c5bb7ecdd0036f16ce4613378846f6e48b04968a54dd66d60a5dddc4b1bd0b7bfc1de38ed37f8a660859d866f169e006ea5595c34236b59f26643c6210a461e24079a6c42104287c43ddc58ba90bfcf5e2fd41282c56d83281446a0be7a40e4325db217c95972e30396e40344b6566b3bf3ba5e9b3048bf62c7ab17277e456342a4d89d47eaab541bf93139b3e309edaf0382c00ee76d80897583d867a84d04b69edc69869c8ccd893b3b700f73eca19b9c9b948487b4262a54b7ea3ebec5d07d8b901b434f60c6cc06f9f8c4dcb991a998afcd5f2be90a797e7c820d9c9796e064a868593309b851e2fb1933e65ab1a61e013019afdc28740a2240877f43dc15223de8b5125c8700cc2290d7476984c6c75c7ead9b557076bbb61fb29ef92069aedd66d03bf1fb1202e979a45e6f624dc9ccac9297a24a6bac975f85e26488037947ee907648d2a3791f9ae7e7ab132aeb7390841463e863e520c475247adcabc09bae7de65d1e30bc9982b58c643c1a4b172b346759f1182d8b2248dd18a90064fc5b3b71941daaef704e1a74dbc018b0c6b34eb712b03aad7367ca9685aebccd2c392545a6d5e94a459fb770491ec0328beb138c4773f3e8bf761b2a70ca5dcdb2a8091ee6aae4a8874a1f1c3a3de48d9657a08875bf0ef2ff67d800771d5b986f78c775bfa835d22274626918c15b9a00aeb375bfe04569e1cbcf40f2780e9af3f13cc0b45cbcd318e68d8aaac87ce119eed8c55d5283b2dc557fa95c0d9555276ffe569c1b7866d1da6cc4ffe8531c939843e8aeb2381c8dcd9d094a3b3908eb49c8672dd0f513fdca68d9dfe3dfd91f4f2f9c748ca93c6b701cbc501f605542bc3c97d725b7b6390ab240d1091a76aae011d50174b2bd22898af32edb2617a88d0d8dc21da2224750f84528ff9cc4faa691ff0df48cb1f0644f72075ce7d484ebef7c8efdd6a36e5adeb9a0fc5b41eba288c1f91a3257890f542661614ced1df3187a53cc0217e20cf03e585b1ac191176e87fee82af1466dfc775f3a4673b0d1ededecce65a92d379f17391a704b53abe695032038c656a77991879aa7b72b8da7de34c07908ff89fff57afa5f2f2d55493bb663599c28bef515df8463ca1a318923a1a993c1fe7b4948054f1f429c702914628d6ed229484085b174dad8067795f7d26dcf96891f25bc5d869e8db5e98f43701a5f3fc5e1266c0c2b991815bf82b3608f31b84542209eb4e4c6042af3181a59148834feb037f6e59185148370a10bcfb48784e28539d126d720b2292c7bfb8f8edd1a61d5487df099652ee9a225c6d6f2f8fd7d0d17c6e9811cc6d00a8876c0c0a085e78556cbbe08687e293196b5b61b812f0d36c5b093e2438d51dd386219cb72563c36018398dafc44858b1f3889c53277655c73d4953af814f852454ec39fd94766487bb24a263ed18542a1071ab91dbfcea59f0109c5ba588c6232d4b07f3a6ef3767d6a553c757b66c719174cd0a01007319edc3ede26487ac2b98f68c9dd540fe300588e7b37fb4e206c3c0511d7e2027f01f6dbfb59694f5075c8fb611d50365cb757b1a2519a325be31290621a1ca4c428a3d1025b354bfd8b8057605fa3c91b50d6f4be8944d55c54659bd472db65c58d9fcb962b92b371ffd3a30303ae709f886ffdcd43ad66a8a712dc280553fb64fb092cff36fbf125960224344cbb28ae91f2577407ec036ba6f7b7905cb259512470112c3cda89cd5e42897c2947bc65585a0baad60aa64080824bd66596c85db037b5e6c321c58225720cde8671736577595bb37b3ab21f0e56f580f43dde22b4ed0e4ce1aa56a1f6004f833c3fff1c2908d1414daedf9230b9b25ac72a746da7abe0432b44d5a0fc58f11400faade84edf9bace4e00184226033a6ee810323b6e42bc8d1ae567476e9f350c857c66092d61c46d7da01a4d7358f65c306ac4a9dbb5dc35c84a470b178454d61f2899160b52883de05d1ba7f671a600a27f24710eb51cd5233eb061c22c0e1d677402e45615910a2fcf05ea652649a2801f9e073b2cfe2425e4360200b6411407a26e029160a8e10ff1f385e3da663eefe4000b9f2dc12bcd2ff1ac8436602ccff0fe6fec342cbc9d0352e22240b56dea0671c984cc6eb35d97a65ff15f48e005c72b553f873f7604e13d8ae26f8885aa08ca81446503a424d6ed8710054cb122acac5e8e04856a16e8029101a3d438947ad409f863cdbd083b7812cdcebcdd70dd8c4389640b930e8de5d725acdfb2aae937d595932c5acab8e221c531c007e89c3c8beabc422aa8d427c09dda74b54e2be9d81669bd2fdee0e3b1a0a5a514103921c593be91049441d701085a0624170cd677505c79aa68619a3e359a32338e4cd20be1a1ba8a38ada1f603662dcbb301205a25e1d2a83ec0bff82316334688c37386ddd9f470b971b23d3b21ab05260f86f49dab32634f465dc7cd4789b20672d251d8ca37f71f4d88454fa274644734d345c9b1986532d009a639c76b84f1d22563c2ad752b99a99b39b4956d9dfcf964f339bb9cdc66a08fc3026fd2c60ce3df7a83ad128ad1adb9602cfb189041a2bdd6979b1542269c14253e76e188bc2373c4eaa7bf015f49a75f6d1b09c653b951be3c156db45d4562234aefe52520f5bcef29c4ec1d2f9fd56a69cde9040b0c4e32067a320bdd662e82607b826bdeceaa0adc4d36a35c014377816a67e90e225f855490e788b00abadf893d2082d04ab7c58abe7697ed078380c6a180e2e2e95037838a183c72df89773fcc749c62516d9a7e97486e16516ca8ac4ed3fcce0dc6419b348d1eb3ca7bafa4e07241639ae5386346623ee99ff99d177b915b204d008fa0fc039451d722c1a674bf633652320ccaa03c8f54dd89673cedc2e7afb3ac16c75edc44f03a9731d02bcf8ef80d720420f3c1a22e005ad8800b3dfeca1682bf46efc0005239ba00a87003c286bd88d8d7670cf7108f809cc0ab835bc93db9c1480159cd1122f456e3d04c464fd3f8e7fa75640f22c00ecf6b43aa97e6c91816cef9cd1fe08fdb2c57dc77fbaab0752fe5179b9af0692891c72bdd041f338d9136ffd9b68ce0ac603c7763e0890303c5d553b9e2b5dfc0df3ce29b81d8521449f6996f4d1b2f1cb6833b65b631d5ca7c35e382a4f18ae80c68056452a269eb33ce2c1463f71696e38c4cce75bd0b94e97a438bb133b65606b12bd173319303a5d9c830a17ac6798e8076d3ae7ba96e92f48b6afc2b4cc0a5d921a83af00f45e2e46140df51af25fca2c06affae02dfd3f9e88baea2621bf4e6d9d73d6ea550cc80743c76d98df57193a61a6f52b4e389bd6c8296cea196e9a136a2bd2711f543f3f0fd2707262cd9f865d80ac98814de31ab2e5add261d448c4a6397304b8c697c487dae57a09714b907a77b88178c1a10561c2cd657a183ca31bc229234428501ecc3a3f438afe45a391a9a632796bde66f5de5932371f33f21f3d105c7551d45d3ef3801fa88dfd247d32e99995396e297fcd218470f14c81959e0aa0ebc6c828f009266305964f8b5997ef9393fecb42387daf570ce7346d2a4b765b998fce3b85b8dd22facd2101a10f01f41cf0dcb1aa76df5a2708de4ed664560a19730ddb08c473458a4ad8ec254d041023674c25d330e060e0d37c9f0d6d9002a923a726c6409eee403865b584a533918949c6fd4f5a842af43783a1ee80ede83a32a6d1821562100a47708fc5d3ef81733c0012f4f12506fc632ec1a2217de3daba7eb4367ab340749597230a9be0430f3d3bca0c3d8f58923d8b7e061410f7b50ed84ff3ea142720317eb653f9ee6203bd5e113471f6f6bc35fb0a2bd93004e14a2451713553130283fc8d1d8bcaf62c38a164853881ebe5ed7d3c0891e81c9c21e97b15c6a67474a7f025685baa6f8000616172cd3af61e94ff376f6667cab9ee5755d4ee674594012db8d85f4111082ec35ea86857d20ef57b749101f06524fcc9ac7ed96af2ffea419d149e7334b1c218f7df12938103573f70f47fca209b114b787f878b4c00ccc523d96069c2dbfd63090bd03c8615ff10621f2a10fe3a7c62e1c0ac997fd9c3d027dcc6dadc96e5311985d330a57bc4c437060ab825911ce9f92a2fb458e206d8dd0088f9708c60663c36870157ad5e1a0103bd4ff6cccf0b6de03ef722e647fa4d6ee0fd56bc4619cfc8d5c192185904ac87f6448e38c9ff4dda09402e86de0bbb24fc9c2c310557e126895b1e866268100211616e5c3818e9e94368d37d4f57b06002217c0be9f53ff58e20c6a10bd55420addb251c778cbec661dc046ba5b7fb5a10180fa80464dbb055e23ef9a7b1d22413fb97779bf811647c7ff04909dfb8a3059659ca5ccaaadc8f3576a72e039ed6d927c8b0c125ba4ca2811e597e67ae803dbd13e1f71fa5b3e75f0413b03f153d843aef9651ad23cece89762fb9d4260b7fd354e2433f29787fe7e967361e7b4af457bf3a1794901c89d5d905be55eed84f02e2b70802edf7823b3de9db2ed9c30b86085875b7f821a5695f8bba7bbf3977784b822cfcb0a551efa30a865ed193987bf0c3222e3464feb738d61b36bcaecfdea84219ad07d83e022ae172e7c1daa637c37daadb68d6743e3a0e19877eef584bf16641f87033ec606a3f183f8ae06a0b80fe100a8e3ca805f16575eeabec5917efa6f66c5ebbdc565ab94c3c2d65cb191bdee9f52005c58043a1cac32a9f3682634aafc4c88ad3c10538bb4ac8cab72368cd9e47d18576023c5861a8dcd4cd5a37bdc7544e13db55cebe54f8e9f54d5231e2fa670fbcd40b220b968cb7f643ea7416473d670ea67e62d362dc0435470093bd9449bc5a83ec6dcf3c74aaef4dd92319c092cb094320c9c6e4c3e3e15c8d0ffe8b361b850d5ec2776b6c9efacc511dfb6e65d2c6bfedc8d94ec87cd079606b4478422d7e695b7c5bb517206209fdcd682bfd65dcf8929b2d639ad98aa14374c22a17e8484862d13473489e520e159046f76bea9618563cb75608649457b05ede1b8458cd818969ee0cb1ca388dd3eb2838ea832d700a5fd2ad009730e04b2d28fa9a3b8f13ae2f9bd023cd4faa1ba8d3bd0a28db2076b8d0e9ed9ddd79a1f2517917c470a7678bdc63e5ac9ed11ae74955ee36ef002a423338b69d2d04cf5e9ba6a4490eb38210cf058901ffcd6275cccc815a6914c362ad90b797128c43ba854b21dd520dbc7cb0a5f2cf96ac8bb98e8ff5930ac39acbdd398f29cfe873f6bf396dabb0a638791aa13ab80079935341fd03757f42d395df7399c8c6442cd003ff139bc6a69dbd4c47e77d32cfdbaa772ef25881025718907dfbd874169ff110141868aab505ce4727db30e8da258030d5f27abe7d45d0d10abf151149e2f1121f44ec984db3dd7171de2017122f0eafebc95ef37ab58cfda03e215e038bf4d004fc5b8156bc2092966b237f1d61307833a7013d9f546260b80549e786f485d74188aac80836b09e038a30f4a09fbc2928da8dff24c44754a5ebe225cc930221d9516543b930f645f9846c7c02a103d5431fe031e18a669ac52d58feb56ac0440c4b51152b17248455f5e247f857de8ffa2d921733f030889d538f4536801f4f45b40eb63b1ba2f46f5498f9e1eb3861d9fe69fcd90145f41030a217195417ad9136ecf67a29d69fe45103322244cfb4e53386c566a482f750d50efdab3ac3160905c9a7021d1f3bb6d3b1529186a15dd75ae19c60a967c8fd9cd50a7b76ac0abf7777d754f2766b41b1a16e466e0065f94becc730e6f8c26f8628fb0d8a25f4715da1b29151dc7364c18d59cbf6f3b6a46b81131a0c006fa97cd8e118ab58ee04a16cd1ad7ebdbece865eae347b259f4c739af7bf1651752aea438c4f532eac5c7009c9b0e27b7b3f32d7caf445d156577446d4cdd90559a170f14119260af106681929f04f13fb882820a8c55a892b3af68aa6462ebaece5b71916e2a5ae18512783fb6a081cbc4a77477afbdcbe06c53ed53f25a151f40e06c68af8bd440493b56e555760d3170b5735786eb655c886a72aeb30595ab237760992e898078df48bb2fa253ff2de8c8588cb02c27c4038dac6b3028c437e45bd2adc2566e28c8260f8d5bfed16aa4e34b1cf63d62c632585a6c5817091794a7b0aab9ce2ab5158847b36cd71c89773421a9d67efe9101bf3cf4ad44bf4b5bd057998ade1f59bdcc81795f3eac169ae23b60c0649c37e1598577ee6af72e6d7ffd6b212c29cc25464f4aa73fec6aa1510747262843b52bcb6331aca9c35439d6997165ae6af13fb6b6a81c14cbccfc4db0077e3f9365ab5bf70d8e3c56022d8f3c81b25eefa81049017dfa933c0bcc39511b67e716b7f0fc6a812458ffa60224c0e54c258ea8f88457b62e14282b3153cd1cf388a7416a1496d0e1290abeb21bac42c7a9e6862d5b03e0f5dc5cacf8c2340e08641088a725da64b5e5c1f175cfac24694e8fb2cbb7029f7d6e69496e2fbdc20ce74a6aa78c3561c378f97fa3b405fccecb5da286281c5f45f5b5767507cb6e39d04535fc5916c9d0b58843bcfdbf43a0c5bdac3aee06e92b66a1cc9fa81fa6446c04930e68e5a839f08dd3d6c9b19080567fa2b18406c058c824ab5730784fd267dc0f77a205a6c499c138e1c4692e0ba9203bf3245e82092fceb81d1d256af900535879fa50c83160bed8900fccce560e4a4a93d117247403c405303ce5bc119ed3c533b15a3089f4c273ca6bfb87513d6a158d493115d7adf8866416bfc0618dee93ff283dfc16db386d381fb42127be0575422eb6fc8bcf2655fedad96d5a5e3ea8d7e58126d3a5ba3ff032c5e29fe7e1d76673e07780544b97ade0e18bbb023c88c5fea11e97cd33cedc475d89ba678c61ec7d32fd3e58ab1f231481027639688688848e4cb7b15240b05e0e3bdda93b8e752f5d18df440d09cef84f81e5d595ee3d48ce05ceb6b7b95d84da29125372624a6319e84cbe57d655cf8905c9774b60175162e9a68db790df257e71bc8c2fb4ad855ecce84110616e0f22889482e0bf8de7d2e4115cc7be4c4ca99bd7b8a638e5265ac02f7f3e47c01d2a0b2e7c7d1c87e302c136a09fe64795b527124d31b848cfa71f5548da638757a98973ffef2623b8f0758fb7ebd594ed9231e9b01c992ed98e52c3b5e485418ef82fbfcc880ba5eab4d3139d81fcde0eedce82b673e83a8221a6b752dd0a513fb0e6cd8066a67196be7d7f40c00a03fc71ec42e964d5ef77e4cdd1debe93ce46cfc7c4d251229a6fe4cbb53fe0664abd4446ab2bd4cd33251d4135020a66d522f9eeeada961fb63670761e5f0508318ff96ed0ba1e98ccf354de95f3cb10a9021e48637ac2cad1089274a7bd21fa2dbe790b21b558fdb17b61df4d1188e09e327164bfc9d8d5db2c9d4d2efa6cceb4d994582c8aba5488d66fd3523d09fe40e4ac1de4a12cd936b43bf46984b6b52b32604b670307982ef0addc94ac1b5930a7de2a9af13e6b09246d0023a49bcd37c52012dba814b04c945c837700498f7be48e227258e0007accb1919a90c486cfd3ac0dd0924f3cad159c763e59d282b61b09bf04e8305b8a82b952736aba0510f5eb61bbf1ab405a7df18d368705445910c0bc65342c8ccf3c101c3dc7cfee7345055e6cb176956ec114a92d4d71b0c84de90660e8109a06ad376a4c5b9502064ae97678929f0f5956d0e24ec5d6e6e00184585552e9e5c748bccf5ec0eeea19f8d7c32f279c4e7d19f478ba74260230575a710afe867aae3c90d28c4dc47781afd646499a28dddc5c50408f14ced91799a2a6088a657f56b9ac15a2333eb441575b9d36c0268e5601513bb0df1853404ae3abd3721f6f89d3e5e4c606e19aaebdec9baed58a2f3f801ad8c5b80480e47449e6d52e6aed02744c293a014cddd175399d0a5e75ae0f2d3411462eca12b797659630c04e95768f9e9d3417502d21ce74f25a73fb3c2ed76b5b84a694fdcac365ea11f89ce3e4d8417f7d1c5e8b8de25ac887ffee36083e6cba4bd356a5c32e92ee15dd86e300858420842c96926083d7701370cf4e7d73457de25b777b316f8295f3b02de61b7d714887f85cba3eae2752fa07c9575dc4e35dee11eefcaa6ecffb7ce782870b9c576f6c35be3e8a576285881adcef1c74af0e7b2c476135845e89802da40846075f773426e99c810a376ee6df1cb94f318202ad08765e5714590f412fcec02ba656a805d304cca9a0e75dbb9e7efd7dfbdfcb6fdfb38350435fa7aa56dcbd93c7a83eeead8ecf8fd86055072cb6ae45320bae5eb43bb8b1a8886efeee7d5817b13737641c193c51d2ea63ebd7af2cf7d8817514a7eb790b6f7de524a29b7945206a4076907d2072cdee35a3ce743e77ae81314b92a3d86ee38e8145d3f47a5733cf4096ac089dc0e7d822d38fd28d80ec304fd3aa3091abcee3a011867e89ea381f7bbc5efaf4c5e0baf8506238edc735ee765bc2ab95f674cc1f69cfd1b0063a9c7b1e4e258be7e2c3f83b12463178ce58bfd1884c1e2d6c39780ede11516fe0e5f45f80a9f8ee5bfe86118be1ecb172cc459c9f07344ae92e15b20ea4a86bf42fc2a19be8f7d89b48722eb5588a5de5f8178024781587efb5de27e1c1105dbed875cc91a6b5ddae0cfbe7a8f7e2b91760fecdef7519086fdee2992cad3ed0d963266a761ad0cfb29d8eef7de13f4be8f0c0e2e743d6f0cf8c8e090a46bfdca607ea07fffd4d9a309d646560f8e7853a79cdae3624ef7abdb8936f8afc61c38cfff8a365acf1a7384dffa1f8297934904126ab0c04f31f84dbc82df63dc75afbdbfef7963d9d9ee5583045014c1bbd7fb6e86f75d775de2eab9134ae0c2d0e5ea72ce99036978cfd92f145bef12f95f5183f0e9bdf7d65fe6fafeae2586cf45d77722b8bbaeeb401a22386e318331f277600c8e86f7b3929d0663e4ef4e28c146abf53a6425d877d996fe4f74a1dfd31bfc1efcf45f1a7b6f9dabce9a6a9a3d0f863792b68793e1d94524781dcb4ad5bdffa67ad745f5ae7ba8debd4fe540f67fb4cbef75627eaf25ae9e8bacdd755d476927e6ae1bcb1b95caa7eed1a72a0d3975fd9d1054a6faf71d1759df12575f03bf0839032a5c0609638d4a8225b87e35e6d868fdea572dd69863835fb1e458651dd3336b55a5e7b17b0fa4a1bf1b3d9caffb278297f7bd1a95c4f8b5d8e98739e12664a0fbe9d4f177630d126ff0fbfb205412ff279628d49ec7fde057307f5f411a5fce9fbf6ff4795149fcddfb541feb7ddf9e32175f155d88af4ae2e7e29481e2f72e5185f0b9b7a20a382229c62a8969ef9e25e6d02163bd377ebf1f14bfe7c00678a393fc17a491bf1bbd09ce70b21a57b0410312f0fb2c1432e0bd373a61d990f318fb44b2925e774118de7b9e27ee2d42f719648027727d1cc26427925079f48f5f492d7ae19c0d9cb73ff280f1d7a83c12e0be35e6d8089ff5e1eb904db004fb7cccb181c37a9c91bf0e191f7535407dcffbaefb95986363f5fcf99883ffea75c858628e0dd6b7be35e6683deb75c83c2ece2e54d6c85a620eaf0b0f468cc856a8008a8533b2590dd03dfdf1f1041336191727182132c82608a35be96eb3c609a5ab054c5898ae24d5a4151010c4089264f79de0bf39a88c525518ad239d72c3bf979c89177af95f105e6ace5d2f1f126a4f0637164340b11773e95d544a5549fa742a00697859912897c37174aaf250559ea944a4aaf254d0e957a20ba0de549ea934a4aa53207c6ba1971549a744844b2f6b519d555b2795120035bcac489d7ed2fde251ce9a94e3386b5a6b31a5f59df0e86b1d7b315f5bfc5b33fffaafffb7fff38399648ddcd0e9e30c99f0b04fa521b37f32045b15103f38d16f9f4a361fbdfc98116551fa3c4da7f4756e9c7a09e865e962de6c39bb00a097b3a977a0a65ed25a11bda443ddfe2ce992559f6fba7dcebef601a6dbe73e4ad0edff8fa36e9ffc71a6db8f092575fb2f7c6cddbe084cbafd1a50ba7d0954004cb7af815a996e9f033574fb1e00f2440302650b22058950b74a4296e8469d726eb8124d8ad6bf930342324001e4698b5013a427aef4cb5f5bb88ba44f6f962160a0b203922b4666326e8a10679cb47e01af4a6ee15e956716e1a5ee940bf2583d38473fa7e63187f7f87374bf5f87582327a77ef73a6439c61c07ecc76f633ffe5a471bf647ebd16ff325fc8eadb963f5a8ebfffc80cf9d07506eb4b65a25365dee54d72f7e823fff7b8b6491eadbaf36c9f6e4d8587dfd6a450febb2d2bafe0ec5133f3107bf0e99751273f05803c8325998c61e1401c4e5c2a70cfc1a16a6ff3f870df6f5c72c4c7fd78d397c2c4cff37d631871d73bc65fa9f32a82459f765aceb37000f2eebc69c3d56a092fa3560615aff8a197f2dc41927bde45dfffed23275ed8122bc9c55746661faada88114ad736cd8af6f47eb64758e8deeed7b50786999f468c29daa1547c15e8e312bf9deb4009f37a15ae7a04f2f462c75cfab9e950e725494aa72552337453b5dc2f1935079ea63ad395f5918c67fc1b3f86973be85a881a823ce09c3790bd208df5ab001fa5bcf7f82325cff3d97399d0227e2c89dfb3205db9ff53ef604da5d6279635ae9c43205dbed7b9b8523b6c4509c9504c5fa24ce1a25e2c8238edced58a52a499f8584d727b5a74ed55aedb1b55687ea122a45f5c61e3da93d768ab33d357559aa22e5095d42876a5254f3cd8ddac31d91507bb89b8bdac34d59187efc64e501ffa9a90b082c480e099fb23d15686aac53629db2d91e3ae66d19d828a645f42f386271443fe768a9d3b7602cedad63fa2bc6d23ad17f8da585a2af622c6d54c76fa76c0f05aab04ac138e51aa73a7ece667b2e70441f672c5fe15872fabad91e9d0a9b02a1d3eed6319d62801ecb3aa6533086fd146c97eaf4331e4db8360c2aaf920204e8e507490b5a901f90883a7a12448734605a6996602201279c304ac24c0d619a7039a8e1b1b3a8112ab23a62105334e6091a8854c920b3529a988a6080c71494162812a09122c3210d934f091f384d0d77c73c0a99f03443564c6855f2106e523a29a594ce3927a594d24929a5a212c783629ec71ac2e9dfa8b7aee64e3df79c576f130274a8872fdec27f7bbbc08485bf80090b3ffc0cc6f2153e0663c9c3bf609c0a9fc558eaf073c692876fc158be567047e1636ec2c2f05f47e1ab380abf82f02908df153eceec32e80b082f756fc0ace44e82d42be82b9658eae82c16088e390cf81e7c19df833afadea504fae63fc1b0e3e304a807abdf62e9a2af58a2eb5722ce7fa2b741182b0674ffbd8ceebf5147f7c4502c73f4f0b7f889a504baf75efd76f83e55470f45146a678d13a01e80ef89a58b0eba1e1471bea31a67fc6048e1e58d02f4fb1cfd9658d6a2cec5135c48755aa34f3a96aab53cdea854bdbe8e0de8ca0b6fb56a2c90c562ad2a0ec769f1b0b562b142d13e8ec8378bc56271fedc86ad15c8024116eb2b4883c562b13e90f581ae50051c16e71febc1ff4056abc5f9fb58ceaadfeaabab55ad2c91eb6088c3398b87227f1cb1c55babd56ab55a7d2b90c5fac015ceafa042d6e2cfc356c80a71582bd68ac55aad56ac0f64ad3e10e77178785b5c74bd8be384add6ea592bd66a05d25e592bd68ab7be157231fc9087630b27e45d0540101e6ee5f0e77b61f53acff3bcef30ee76015eecdb75b1de9195c4f85f58189e780482cbef4895675531a8c2f7afc0fd3b6fb1c3c97bef4d690bec3e77bbfbd5efc711edbbc4d65323a395488d565f52a2911ab55adfa2e0ad1d27f8e1c7bf12a1a811893fd7ae133fbf9769ac56abd103f8f78a252ea092f871564f81985382c5e1c49c1238155cde4af41121870e59ebbb71f5f559e2ea2bd8802efcfc7677616b77df8d4e5aef635b22f854125323dcd1e886b851e5f12c08c3f3b13d9ccccebacef75db2a8f1d241e49ae249df0a360dd5bb57e1288b65bea110eb9d8ff5c41b5ff7722f69d7e306629dc5dfa776e057100638a2503b7f3c925c5f895c7ffa2d71569857bbca71de57c3c3093f667b5a1f3e088260ede1c08d58cfcf5f3ff0fc2577bdc82e5cffc71f7b3e7af569110c2fb9be376bac7904c30f5dbf820a59abf52d9cd0c7e28853e63a81f3c40bc2e81e479cb2187e9738657b855f87f4becc0faec0f7c0d56a7fce1b7c13bad7dd7bbf4513baf7c69c3dc6a07dbef72f29f8633e61fb31ffc95a8d63d979fa393eeaf8698742d58fb1fefa7da1e87a17ce07c2f0f658ae10da68b5eedd39bcfb8e658d0e7ea13865dbfb4ebcddedba0efcd52d2714b81a3db0acd1bb07c50dc47a59a39be07d37de8875ed7d19d39af3d7eb7f6ae3df6417ce7afe78f5187c4cfebf9e8bf3668d61e8af25bade8503c35ba177bf5be29863a395630fd124062ab21cad77d57ea04271cca160cc318d58d224cb11fe0a2a64ae6f0515329c7156039420f48d63769f3f14a7ac4523d6f7e3a07d03b1bec7b246bf11eb9a627af32a00d0789be36e70daf33c4ef49e8a3826f83ef57a0fd28f9b1facde7e969b9ce33c8ef3eed5747a4fbdfbe07bf6f3e8eac1fd6d4e7bf62fa7bb7af7e7794b1b844aeef9c1eef3f5475be2fd509c5d53d7534a690e1c972f96d8e8d03793622fc8d893af59717e56ef3d7c6feb43fc5d71863862a9bb37967cbf4b7fdc58ce1bd62b2fa97b2fba17f3be7b7cbfe7e9f7bef7bc7d7fef172d0ac6f2e57a41862f5ef4f245dfaffb0467643c86ef7df7adc4dbf5473b7745d673d07371163a2efab6208cd54f105c79a0e77ddea8bb91ae686b4c93319d459f4a4d983aebfed557fce64efdf91be053e985aa5e9f4ed1672d53b01d07adeaf439fc93e299bf3e383f98f3b6be39959a4cd139d081f7a9d484aad3afcfa25713e67847149ab4a1f5fa3866b55e3c1cf4e7cfece1c84d9058bb7a2b70f53eb5d4fb27ebb553e8d1b0eaf03b11c7e49e3f276e60deba09e173efb5debbeb5e8ba5a7350883b6996ae1a86f5adf3fcb6aebfb675997f45dd5770f27f3a9df7b4a31dc98aafa56228aea3d55b64bfaa6efdaa56f9085052cdea77a6e662d34d997cefed96227bfb11d79e29cc6d0be634a336ee7df0665f00fff25ce1edb4f55493a1505df3ffb544222d56b2f6bd298de4365fbeb6d7692d32fba26bbfeaeb97efd5ce5c9e1b1e0f57f9567c5eb91e3388eac3c15fc579244e27aefd72359c91d95032f51b87dc5e32f69148b1c0b7ebfe8ae587791ddf5ddf5ea2ede5d3fab6bbfeb2798c1dc69b1f3e2ec95a9f2e871ebf7de637d2c100685e2200cfa54c9913ad513dc8ed82a130744bf4f509ee77dce5f6fc7c58e8be7c0c5cececef53ccff3bc5283d7b9b7a53fa703aa3c5bffed53c94852df397d7fa5559bed01ba47468ee8fb2b92cab3a4f600eda4a15adf5f8b6acf8c22692339eafb6995edb1b2fd1da7932a4f9941dfbf4b3aa6efdf2575eafbf753a7ca93392b5d6a53e5a1336efe155f4f98b1e263ff5af11e99f334868a264ebf7b154d54fc2aa92bb9ffa58453b0475ec9fd52c27d2c8da2529527e7f7f7bdfa8df3fbe99bdaf3b2fd15a8f64cd9fe0ee07c4ebf1e8c3036f818544518ce2575ee87bec1dfcf79eff5fdbeeffb3e17bff3b01d173b1cb878dd39e860031777e7eedc1d17238edc37780ec6146cdf60e4602229d6a44aeecfe004149451972a2900f1870302e0441c4e682f13c0cfb9e344009c38e74e37bb00c429a3417069ea9c88834eb07b9f1a55c9fd9d48a72ab9ff8a94aa92fb0520d22a0b93aae47edde25ebf06dfe2035b4a85cd2a4b5d9a9e661819a3e5c74dc74135ecbdf71e01aaaac2e64cd3b8ed85e48948a3d808628e5ecc262b629a8430e2490d125b9a96e82d63dcb440857e70b4468c0f5b76bc2942864b3044638c318611c6696f8c31aee11ac585b683140e9025ccb4a182540226333c09cda5a61d435a781282169e1cb1c46da162fa51017e8db748716649128efef08c80f01777711e46484bce3967194d20b1b0c919e1c3105e02b262cf2c9989ba41353614904c52445104c3945a18126c6123c54792eca4c5461bf2b37715ff7122f223c622a1a4a6889604638c4560a2cac2a6662ac31454b1bcd853e5ab6f4eefedf5bdf7de54153683de68205e7b0dfd920a9b307a1455d8c42f3a80c18c97216b82fcf0862b02cd952a6c7ad08047fcd58e31c6980d5285cd24cd54619bcdadc2662f5f7d6f9b44c76ea03a56724366a846618bdd4a54be78f66b4a6d7d2a4131ea59072232241d01e5079b28b24cc4d0678548a8eb7e7ace3f1dd59310206183b204872170121349bace084f49d7357156a028c95e2c1017db8f33453fb85084171e444e9ae0c20c09c55a53678e9c5ab60c35b1f0029a293780d122a4082b448fd7f9b7b01db02c0124042c1c197db15243579a320282f1c61b6f404a96a9265fcadc3044966f3d278de979cb1a37b60c88882da185c683054e001f784f016248c460d02425ebebfa1f8beea1603b9d41ff56100667394bbf0ea1449484525a6b138ee3b87b95889e281155e192a8a8717befada00c97e9585b8307dd6bede5eefd399f35c4c7decbcdce71b509d1adf2bc5ed46216887cf35455aad631757e6daaf42708b3e7365558fd273d864bbda8febee91503f58a6b16d85aaf5f826055743ade5b016ab38e8bcc0f864c04d0e9011db2f303fcf53d9a450b546175b4b31d1c2fb1e1218c048ffaf38886e008b0837337ee51d795a42020400fbc524e8f3abd477d82f6a8fb540a78ac1e7cc5acf7ef28458e8dfddd7775bf0e598e8d6e5c41856c8f36d0d721abd3963509927ebf7295b4138a0317a815b0b5d63c4bdd80fad4a2c999e3083afa7c159ecc9f321b9e66dda336e905a2c9d94ad541a83de14d498954a7559c2701f592ec7409512f0bc0535a5a7f5379b8a57a81115e914cda9a5fd62adb53a3aa206082a24aceae01db539fd67f52ebacdbb47853a703291cd75aabfe4cbf9e73075238ee400ac77d762085e77941155ed623fb765679802ccdda2acf50b5327bb8230bb3ffcd0f3657a60a2a8d93999b182612247de99643d2923fe73c9e4061641ecac3bd051bd03180fbea7197c7579f9f7bb1e05ef5a80329fc4af99278451aeb5125ed13f129abb17a50ac5516665f8b95aa1ecd1dfbdc91eda951266c469511034502a72f372d4c56bafdba03529dba48bafd8ba4f22c29aa3cfaedd47354758a7b43ab806aaddb2278598fbafdb23275bb7afb15c9f6806fbf2679d5c7eca113469b1fd0bd69a5adb0345e56daacb3fa19a8d7c7b4ca8d2b11fcee91a7c63b41a7b4b73eeb539c1dcf7aedb37b49955cba20899735c9ce2802f8d3b74995673e7dbb547be854f89607a5f727edc93f29a5ab67b99e7e65b23d384fed5285d10fc7d276e9948b76463da05f67b6c7f2a030fa94359616a9531dc0b1b423e8f4bfb1ac47de585a1aaad11e4bdba4d3d7635989f2585a17ee585a229d52981d4bfba36b6a932e708197352a69667beaacc2e6539ab6d11b5ec3c29832514f509d52a7da25aa97af790beb25e27d80d73e955c58c223c06dda30588ec2602962e3cdecc5638c294c528766c22aac4bb23d9d9385d5cad3455532cce4e99a98647da72427db73bdcc229c3acb3e53bf37caa3ae8eaa3c17a9925c8766f6dcae4daf7f75b063c5d7ea7091e845ba48310cfa8ee250b837bb6bf2d3bf368f52ca3da5f869e74d166bef09d0ecbe5c4da1dba7ba82a0dba7baeba4e4916eafcb52eec518634c01fc02c7e92bb4f941ee93766db6471bc0fedd31790630c98adf1ee0ed48bba48ecb24eb9797c8f67448d7c7e4e99884e023b6e722591853e5c1b48ecbe4c142934442b23d5d9909ab5fd1f4faa5c6b4ee664b161dbb9ba5d4720e743a8377fa611a0567e84e31adf2cc49d2af94cee03ac542668fc6471e74d22207bce2c60aa306d01e45dc2d3873e0944e8a7441c734fb7a611ae6fe3b6b478759b3b2bbd1b983adcd9875bb81526aef9c734eeefae8b3e7fa9830adb3b596e7fa98640b2ad65a7b6d13afeefb544a65def1d2acb5d33e9d77367766dd4f5a4cb15c8b29f339eef5e25c6b7aefbd39449cbee5b87b4147eaf35a6a9f5210dcee51e79ee7708ffa7c3b764194524ae9cc52269cda25b87b83eec5e106d191e2c08d73ee70d65a8b4410479f8917172970ef0bb72e5dcb71341a85a3511be58e74ee5c2d347ac1d4f5ce2817254aa7630e0c9cd6a3f90106f3ce2aebe85aeee7732f8f054ae957c9178dda39db19959d510c0cfaca69eed4e750387d8fab74764673a7febd53cf1e1693003ef0f2125da2ca53d419f1e02c70faf55a5a579d5167d419b18e381a28a51c476905b99fd3ceba7272dfa7d212a8ec8e3954905d9bad75062356f853c70b45db3dfa8522d7bd19869fc552855acf8f2352b085136e9f8584570fbed41d7cc9777ebaf85273f025df00003a1ab40880477a30cfdabcffc46ffcecf713d4956785052f16ebe8d539ef7cf2f05b5f6f61f81edcc2efe016fece48c74c5818be8b193e07e16f609bb070860fb3cdf049db0c3f00b6197e0b6b9be16b606d337c9db18c3df5f00130962f80262cfcf0c75b1308638ccd1c55d8444ada32cc30ededa6ffbcb14a5c68d52a2e3522ce54c081921f6bb0d4209444c8fdf191a4262134c49281a9c2e60d860c4efa628cf19526523ac6242822a281e887500c97479accc350125bc570098292aa008c143055c02c1911c3123ac2915350c617638cf197a28e31c6180b69ca39e759adc2e6909e315bc25f51685a1047a46e89aac2ddaa30422b261b9c202002274830c8e0449829ee0e162e103f8ef01777718e874ed0fd88f07383ee114494001d868c647c31c6185f0952d431c6b8468450abb6d912c51426a4ce39e79ca59a2a6c3e8d910a418a0a9339e7a01ba4c78c31c618e79c73ce38e79c73eef145a79905d19bc50489854d8df10f3a15b350b3d90c864aa7fac4d204149d7a9d2d5dd02ea245e04b1927d87821a2c8963b6b43c5aafa89e1c9d7f91f9273d85396f8c95df6de7bebb0a4c266d113295542d06109c7b009a02859b2959e9862e801c492b958e39d8d1c55d8444a62bac530e2b4a17e68adb5d64fe81f410cdd278c31c6337e60da1b07c566888049cfc0c3b6cdf0d4e15a0e8ac2352c738b0faa302b7e9e7c9dff225214fc0411a180cc11192432499f73ce5989a7abc498ab84d45582ea4df0a42594c72c097131e49c739e42626153df5ad7e59c8942109b402459d8d44f95f6e4ebfc733ce89031c618e31c442dc8de7bef1fa30a9b4748494c23fcdc706c4b112edaba8031c63dbce918631ca4c98311a6dc5004cb9a26211edc146d767c3b18d99b08a19a85e93219e514817283f0b76d606f80600b24d10509342efc90d122c51331dc1f74890452f72e19d145087f7117e776c82e71e151e5028d31ab7950521536a942807ac35f510c310424e70064db5898a45ea7882692448fa1a322316c8132858a8537536e8e3cf93aff78e8b66507d14107310523882a5cabfdd423b800c1d084eb4a11aa9f5be3b151b83001a3e66f8c236e9a8885e55625087f7117e7b8b6267673ce99095285cd11982469fe83338773ce39671c948386c421319d71de37e79cab3855d8849a51254ad32841232033858d922e5038e91ba08acc608364f16c5a6ea0629b5a6ca0b2b5127e6062046ea072908205ee0441fba7822b4c8c02e0048a3053c84045197263c38aca9029c594a316923c51e5049baa2a2db0aacc30e408204a724083b444941b17a33654805ca142f4e4ebfc43d14247e5e88706258810e2862778f821e4e14b0b686c8582a20718920cc088cb0e188ccc36341d3dec1003458a8793241c38364b2cf8e0b4b4b1c59069b53b173084b613c524c60c1a1794da50910189aca9716b3e3559781009f1cf39e79cf30fb5fbc3102f00192b4c84b450b325e1d42c01c25fdcc57976b2e50711007102c91fcd66b32217499f4c225842c25d3cd810fef01777f1c999ac24ad8640189262176b9c31c618cd5085cd25454fa4541901cd527ea1a3e6880f486badb5ae6bcef4b003153261764863045093a1275fe72f00a64d15102e360c8d996a0aca4d408498e1b2c80014d5731eca72039adb65c684885110246a1033dac7f295164954dd99a58814d31291294236454154a0e8f1f444173436cc04d5100ba48c3e9673ce67de54d804aadd109c18fa9c99e09c73ce79f6f8dda5cac2a6be351c369b8c2fc618e3304015366b3b8430432f3a9566684c063a7e3461c30e4c9cf010c4893646865e370c646f2821fe96895b626b736a01b4aad9027cd09684d6f4087a414b144e41578b1448423c27292a6084642172c2e10c950f239cce38ef9b73ce4574a9b0d92442114ff795998060a2c65fdcc5276f13829ca58d0d4143c812d8701027e37664538253e616ab5e9c5e27c893aff36f95a859c01cb12df1a58acbf2e58d92bdf7de49552a6c2e75697a1a232523890a27bd19aae24d6e865ac641684f208e88f1310b3215e408291fbea22db60e98e4e0a44689251ace345134c3292a02c3de144869869f5d04d5020b6fc10070ac3405f5c8926ba042f23d4006344194a18a326485891d43b05e4a52bbe5fd0548164dad0b2ae6521b2a8aaff090a646c4accefa54f2416ad67f9218e129a98153f2319cc354ec669ca166b359909c73ce39e779a5cdade73cd57daabe0ad822359b8208881d4666a2e813ec20822543f9044a0c19c118e399a587a68ef10d5bf9e9f85d4344bc0e213339dc9c6ae801081da29ef012249673ce4f482c6cea5cc3f20485c3f2c56631c6184fa9aab0399b8144bb5553b3d94c48ce39e789c546eb3957753a332245a7543405d01014b19c733e32abb049b3dd194790fc90edc9d7f9671a53c3772404b69a962344b896731055cccec045c994a9bff8de7befbdf7de4b63aab07973828a9aca31685539bf48624449d011962233475c19212e999f179e08f122468b11646e43b7f0ae2102a766fbd9b62f40f636c10c31f4e8888c7ef01777716e6b5451b3d90c08c618cf2b4a48758ca36e103913c38f1e40587e8c298374af94e981c91015f7e05e679cc3a82a514b515d7854d3eb8170427e899fb9232937d93b96a03df93aff4cba9665c76dd78480b1a9b0b52d53f860c2057194df8ba10489854dade586b11891fac15fdcc5798daa9b518608071182d6272e2cf830a3b110a41f1968b6d96c3664d3fa54425a428b715c882c6c6a261873790b91ceb2c588080d374db0e9a18d9317922c9145cb9955962e4e465982a0dd294c827c0c3f89dd344916367518aafbf3e4ebfc3f55234fbecedb40e2855bd5134f88e008a704161990e0b6a8c162a496f1c57a573253163675d0ed02db0217234554ae9e73ced98c940a9b556e0866965e59e8079029ea0844b3208e83c4405d3a279636433d67a8be4208277e4c69117204164e4c11c7c388097f7117e7380d96aaa2d6951e7c0c71c3a960c89b4b92854d7d827cc3a089dbd663ec5299237a78a1618476a6523c55d81c23452582146f44980228c79098603d05b9f75edd612eff30d1749773ce415ab014bdc966d6d4780461b5112f5d6078692287a061472541ebbaf9e64a1311da88ae8b8ab23f765fc880126a7c3842ca06c5170e4a088721199058a1b929b2238935666c2e8c6c94583f5b9e0881f76302af078dcd941a5a8fbd340589fbf331175e550aaac266d416416aea55839cb03380d0b6c632cb526f7bef9b86a8d48b5989635a2a64230000000802d3160020300c08060442b1348fc338ed3d14800b659050664a2e248742410e03298661180641108461106308410618a6a454492000145cc4f1f5c5acd8677a00f47d7aa2bfe9581490966cb80a536ef0f5dc93f51788c7ffb28c22f55f7c6d68b8a7b681db8c8f8ba539e401d52bd96b7ff35198cdeafc4f13eb65a523ae8d31048f5a5112e2a3c06e53b28bfd76246aa452445322aaae0cd70acfe5d5218305725b227e7754979bfdf45162e3d76c410dcb302b1bdcb1ffd120847f7ee7502c4cb50ec073d93cc81df03e123562372519533e4f45fa99159edacfa47df528bc3515096b1ae98f7f8b8a2abbb077549e626674860ac0d1f5c2ac7fe351f4b86adbaa969c4a8e730ee0b5603920a92b2d3ba2fbd5218e79c3fef8c010529700452ef4094674dd75fea9e459f1e3036a85f71247cbd135693c11bf44da6a56edccc465d64a5b998a1f3534e1578fdac71b75f9f1d77b0024e0fef17ed73e205dd6d308faab382ae44a379a806267f40f61d133ac3185c614ecdea3af9ebe38e8205dbb4aecea2032f7946c8cf92f32d402a7ede3cc80fa394b0fec1d608feb8e85c5121beebcfd60800bc01e514a15c33412fef3410a002d0960cc90e5410ae0f880918487b67d42321beb3e1af8b4232612e89d1e56e25b306c5882875f11a65c12604b6360c0190d907605adbd1b9113003a30714d9b134c710a2f2f6eef2eed055f4ed311794cb762f0b48495dc744a08b2d833a5ec2e576b9c1857cb0db30fab18056d3d8f665257ae14c971b96394b93c01b04dd102e2a4a533be836ca441ee06d584aadabfca6db57beb12b07bab0b472eb7265033b2adf6c75237067c695e916ec2a8cf25c3e525f67005479386510b1fae51e8e23c45597b0fefbb49ee33636538dc884ffa7e2a46db441be4e0e002d75db7f5f85c990c47a477a768909d8ed7c1d7aa15080538127b76cb9dc85aa317a0a1c18ec21fd0324106b82b6ec9d170121f654e5f3d5f5998e89318297238f09ad95a2fa176971389d21962c46a85eb6b583bfed7b168747ae9f84da607e5c6204569e2471b7dad3494f2863b9c969e6bcfd6f99132a3c26b6b98b27b428ec70cb5d89d0ec59db2939011cfd8442de96904b2da2855851811f8f2162c135619675646eabeddec779158b2b8cd10d10841a13d9f1c15a3f01a0fed93759b4cc79bf863850ff213ab10442909f91ce48db6e60c579ac04fa915cca4e8ceffda425386053a95cf06e6a8dd71ea15207fb8ec1e9ab06b65e173e45a2ea4054c396f0fe95d7f0ac3684bfd7fc0166061d151700dbb890f26cc31a49f04117ca6b0182bcb949b35a944086765b2f0623d77072f10b46de2ccb0796c93ed6773ba2cdf05655777822539f309488576d3d80983c9a1db0923cf0c041e954d61850f5749babf566bc2a42bca5a915ffbe4f17a505588c984311fbd30bde360bda14b94a0f6b5715ec602a2bf64c1ea77952f1c8c44cdd7020e3febc425fced38feff9445bd84d347958b2b0be63156d5bec0c5af6b6c15f12e043b2c10e055946ed5d574a38a4f5fffe421fe79bd3b312ffa5278ef36dbe830a377e90e999fba1ec9350d7c59682f0d4d4017d8fa394cd8cac28d0b40a0006c6ba49e7878c0de92ab4fb9f0e9b716eb7b4aaed8959f2979d421c9975f6bac8e2b7ed29622aa99c5365864ec4e1dce6a3f052dc4690713531b0b9d3ff8a001ae846c6c48d4970016bbb079aed682b6177d24ab913243ff207d3910733c12da76ba4a7fb3aa79052296688f5844a31e15d3c0383cc3506d35196c8d16ba4b09a69646656d800e8e22476274d0c0dd8b4e769110d5f0aed94eb0398f44749bd0906bd3caf1e44e246e11753b7e3ff721fc2e05d4c223aac11b68aa7158af220af9f6d05c43d2500b4b426f1cde69951f1e91acee0b21857932413773f1dc5c8dc32cad148a8336d23aea8b6421e07840c587e52ac3041baf6dc98dd9e5247b5e819dbfd2921621b69722559efb945744a3529ccc8609aa46e560d116ec7055f9c23549e5e006bce60eccb620328516048716e6f54cab190162a22bc33a1d6d72be471e93163b7d3f7eaf6a604f9665697bd2fcf13303ffba588c6c5980e03498c180e9f29a63cedc90a6b1702bfbbfb5af3da10dc2f3316673f1237f1fab898ea92f1b7d711f0b74652df2564379105afdccbccf267771358f42d22f818676808e65b05973a2a15d27fd091a9a76841bd0fb67adcb342be0f2322bd7296d651c368c30b315fd8177c2f11391d51a4b900368345a102a94a8dcd58e550f6cee1a4177b793d230ef04058ee5babc6223a6931a57e1abbf22ab84b8e427e4352a952ae3c3bb325b97fba0f088737084d5742eaa9e5a36efd9c545a125526959d34b18119ab91bd59b72058e265261a3e51b2badcea2177efd978d3cc23549dfbc03471a3fe7b0f5f1958ac933e5d652011b6392cd41249d2f63ea75d9fe31c8aed70a1755b4a46f343b758bbde235a03ac4ffe58e361b789676d29fd0d84c9c23a5f49a5c0df8077edb4beb88610c0a4c4e9cc90cabcd0e1a14d2221ef71df60e668f3874b8ba669ee21d124711bbbbeab192a7bac54fb889ccde28419f2b394963f25a97e309cd422798c9b33418ed2813067c5e119cdd339a0fabb59ae94641887e689c43a3f16de572b9c6c7f7031ba1b4bc109c90ce980ef5f864c734a715e54f9c9036c446d016d0e1a16f610a974a4947ccb456044fd79bcf3f7d83acdbb73caa50bf600fe299f8dbdb3ecf58139ed8682807cb271326b04788f64d8c286428dd3950a7241047fa1c048048d7981a764fcce56d4cb8cfaa08eab1bbace4ebd9776d4f42a08fb1ad484c4726cf0deb6f7427caee5d7c004f9940e5ad384573bbc8510e8694f052a159347482c72a282c9e3cbce1705cb91850c6fcbfca7c0711ec57bcfa5af6719759f8ed1e8df861fca0c6b05224c491dd86fd49d28772dc6f4fa7fbccc1a0a6ecd7d60518bd8d6732c43399298cb01395bacd951a8cd806456c4e8086c4359328d1e0990cf512a28a3029624b43ebd1115eaca4f21a7b9842a9a80289f097dca19e9514aeea4617c7315cf223630072b4cb56343930b2145770c595080a20870b9c3c1bdf48492fde25ba8608015f459a987e14731466bda3f11cf750a1166494861633be59cff4878b42ab1f88f141b34307225288a1979058608a2f55b5b953b6470cf9bcdb5d32c3c6b8400d9fb5413df5d8c6cb36addb8aeca37cb5d719a5c9adfdead54f9b14b340511416487dedc755af3469f6591be5021f253038e204b940908d9e73f457dd6c127d76dde6d83559c367526d84b3d183fed52e2a877e7ea81180848ebf691b921c89f36812454c3c90a06c61744fc99ce31a81e18872d23bd22e1490f207f40e575a467aeaf1fec90a47ae159a2923575567c895181ba7eff1628dac31dde6d3e005c04f20b9be56203a0b8bb59de1e4c1c06d36c55fededd6d5da21b966d1a0c345e2b0dc38a6150e0b92a2538ae44333dc45456d8b22e3dc56f4871c20ef5db0c79478d4a8833978a42b5e1eb7749b960dd23a2bbc646ef0f178a70bd2ee4e3de8bc041b071c3d96b45df32a0b4109feb5f13023f70023ce1b861c7316d75d0f225bd0eb15af59f68f27b19a5b69bfe4989dd1e4e386bf09edc480bc284e62faa548d2207231c9bc3210a87f73846a641e6889c663a0493daaac742d3a68285d410da1519f440a8788a7e77e9a3dd35cf2cefe0be4aa2cbecdc7781187c18d3efc24563dbef10190470ab87961c6cf401040441ddee3cef718ef1054b133b48b4a3a3b16512051dc6a4b7bb1bfe78ed65d3f6b469305b2839f6772c8d621be5979342c53c0bc9507daf7470d98ad3a10f1d613a56e6b8de6a22296661abe95359619e4bbbf7791ac5217c34bb1a188e93a33749a83f35cc82bacc9908f46dfc008643249a923241e5cb4b274583e9649c10c60cef2b7aa79d0a06cb2cc95230ee28e31f33d64cb45a13dcf778210dc98188d09d490f0a60b6fb658aa6fe9184fd651941c069799f4e2fb7af2158e71565e3cedf5e8dab0224b4fe8102e1e31a184a0cd1d94c583f485816b265e4b421964ec835e79b63f802214341b0f9960a49ec14161f11ba27465c2e46a1596161c69783cd98cd2ea83703694a55b634d818a0a8422e3922c2588f765afdf637a3c97f243db741660a5a3a9929bb812966a78bea0fe324bb7f60b4c345813f61e09815dc93ca5f88292a9013bd0cda87917b2e69eb402cf73518eff8a93ba3a663233b28749e09bc8e63727e1140559786c5008aa5be33654190510f4e6990c8a08d8dc187fd741ab15987fbfa24c792632f01133fb29dfea5b866286c53420b1d2ccbab23c5457f730220f1e9fdb604e1793615d9b6518ab544f3d31dd939a4cf05514b1f29e49f79b7bb2c655bb9825bd9d98b1f3809cc87999980c44d0cfffa6d8082f7bacc4f734fdb2509160f1d366643a5556deb713c8d39eb899f5a69f75ce56ccba28e0cc2d1ab7120da15185ed84a93a28eebe3e99fccf60f65f3dd42459e31a95c4f384a0603aff91d3be37df9fa910b78e3b2049b7e23bcdfdd7f73e59ca88503d06d6c471ceefa2b4a6ef3550e34431523bf562837fefe8be66dfacacf1ae75d419d532f88858db7a1a357d564a0661a8fae818ebb8fbcb2e9278ed1321065d602eb82f50635fe83f421d3a841bad38c7f1b9d599b1ef65afa43b808ad07c0aa1043b327946994e9c4d7bc98ab41dc3e2b473e36724f94fdbd024d20a09a025dbdc2c54249e130ca4486673c632481e4f6b032420aaa7cc0a847c695d3502322688e2a422cc5a33d2aa1bda03a3c53cd206481b64102df4caf2932f17383197897e5c836d941a91385dd833c637b15d75445018fb035f3d00df9e59db60781f5240c68651359e315c5e1e65c65742c9e23543d4a2e8f50606257588cb9398caf7569d70ac9fd1b6c5fb939550d02d0cd700f3b1e507bd6dd459185d7d9e817d6c346ecadddbc0118265b6351b3e565b6b59ccc47d3501965c2a866572ecef491cd285c3159f6f11caa632a5230476cda523c29c5712b159e50be8076d0e631371320fe360dfad8225d5c0820dc6c72d5a81f58311527ad378a742c65805c33f0bb57a364a04ab29d84de9d7b0ee2daf1f5152131c8177eabc40a33a4789976c7876d52aef93edc575a2600b6f90290567345fb07fb5897fef3c865808afafa0eba3f6b9480a85747d97115998681f2e5153f7709ece09f3b5c355ac0a1882f07fb964d2531eaaa82eb427a6bd52804c6bdc1e475ff3c1b08067a0e2d0847a7138f736ca511499f501bb2dc1b2d230231a227eb8ca76d39a987ecad158bd9d010d5a51b1fd916a45e435118dd796be418648281c6762ca02521f58f32105c1a0feb77314eedb1e83865652d981374874420a042deba90932514ad2b706a5ffec4fb3a6fbf84159db945601d7f84c83dfcd8ffa854eb42185ee10e23fc9ff698ddaafbc365f0104f87ab53316c75960ca83582186c10569594ec52ba39e7d3428218812e31c6fbcb1e595ab268dc95a7a7c0067256abca9902aabe5532b825d490d1b8c08f7c821581aaecee613036c349b1fa54247648e13e9b3f9c0caa151e22bc411a13a8f4c92882ef5089324ac21b0d6a1e5ea381953a9ef174d3fb7ce662028e5a8f341a4d9af33e737658ffd8e86b03fd89ab29c6512cceedecb1e1703f6e2c51a49fc260288bafc976e19c161e4442495291df0e083e7cfe81c543dcac7d3af62f70b4e2b75443573f62a77cb1e87010d6b3cfdf4ddd9231819dffa9afab8a5e2d92f64ee9a743028f3317f6d29b74ca0d53571285d5494eb0b350979f20d20e4b46499c496a69b1b99d4607e92db7904c2ab05df4a4798fdb78a6b03ce26ced6290d4b625757ffeb1c0d339906d9d896989952c59119b8cbcc7b1a4a72dfe06ceacb09b94cf2d3a4973e181b18c49f94212a9b950c7e7992d94fc94ed67453369ead79a9200529d2c32fa1af88b4aeef4049f358b22dd17a2778f134dd8ecc8cd0a644684ab8517a0a89d60bfd2344f79d5b2f7b526a1caaeb0484cb5cb874b31d5768896864c9f6fa805085ba540a70c025e66e85ba4281c9bd5a340e956f3581bb0ac9e9649d23a6a17280f3369628e14376151853ca301a6445cd74e14dee6c65682cec5a1cdf362f527b35c768f486e5b4c9c9df3e3568db0d2a6d3707837a2ae78cc1c8647fe8c1b5e304db5251d7b2979c96ece6ca6a0d747d301675a4fb71712b98e0eb418307e597f9e3a28edca92b8a89f77a1011cee88999da98f8432c68c58aa4f1049856ebaa675dcda8e419c2315887a6c73342693ec9133ac9c0036271f35eab122715eb5bad3d17468771a21d74f9daed68a30d08e52f1e8800b5b85e569670813c093fb156d8ab074c5bd1fa45c118cc4a589500ba025d8973835f75748a5e2e17034aa50a36d4f781255262f5d8ecbcb7c1da674a9f6872cb23a601421a13af29d1708b61227d9228d9798013184111409b5b62bb1f60932c3bb15e7700dc3eb9dbf27248a8f8ed0a4d8149ff43b1d3ed7bf86497088d980587efdf6ffb561a3e66f3e06659a435bc5b455ca6ebbe05ff067c1988861d860caceb407a6562ab0da54a417e5ad55c6e0ae3f44d91bbc9877f623f211fe501e6c8f5572a777464474078c4cdcce73656170bc58598c87887aa7a05123c96937e3ca28ab39769dae38e3af4a5e36bfa0b2fac019160c95482758645af214163343f124206a5607b0c5a579823291992324fd4cec2effe45cd5486740b3e3650914989ce4ed27e586ca28fc0b600f53f6a1358fee1d97f6251e89549811a26ebdaae93fa16132aac6f1aa110314dd11475eadfe6605170be4431206b0ddc53008e1c11f87dc2a90698c8450dac600626802c1191314300fe29ab01c46213d085269733985fd7d40eac2788521fb5d627dd79652fcd884bb63b42d52047d212e30b773001f3065a5fde3c65572e90fa18ea654a870736c116e4185a194bc177c4e48da42c8e029874d4e39238b2c70e9fe148c6c1430305f7fc557b8f552037382e33c68121da15268ba01e0f86dfcc5a08e15952e27724fb949eabbca72e0a8500f4e6f3def9e378806e07e954c91eb9a0c6f91481d92c18c156bb95b313258a40fd138bc881afbc906815ced8de33609e367d5b6aa377f3f97a8737c0157a27bfc15c727445940bc7fe44358410bff3f9cc0e5cc95d6ece8952888f41199ef09096d9aea4671da7db0ec61fcb6e5b5488ab5b2c50a668b953f7009a9e8def04b79d2ca6066bc846ba43f11d83216f267a08f8ae1683d5dd596b0f756b37bb7bc98ae76e932aee5efdae819adeee36334a67517e4aac530dfed6a31596287f287872676c57a89894691c60a41afc099c83887bc90c8d72dcbb3b2c723e358156bcad4225ca954c715de43eaa84c8d302f33e4a9eec86318a2fa6bae0da6a1c59b8aa7623277681a8cd78b8c4d6e4b125701a3c98d5cdc83f0a1b41d8f47638b1d112bbe8317b19ba5316ba6376835014483d009d7dae6e0cf13b984a9d140fbc4a276a0a6a5afdb6535dddaec8f4dbaf8806d07cfbe135b43a110f23618cd115d3d9b8a8638cd752335ef086697c548094da524d40bc3aca36c9694f325377ed0d5626075544d7e34ddfa8e4f5099425470f99df2eff57590d9c00f1d9487e04c15827dc16b38fbd09701f1be5f5f1c73d9621d2df5ba47f0d6eeb60989cb3724286560ab80c92c865bce530785412f695f6df6b3771f7c7fabcd7f6d4d472949b1f50c6a74e7862432a49e9ad325cf99ade2f8a7683139135905551cf773bc1a7c7928e381027f38dbca8a5fb8dc3a9e576b4979e0eddbbe3cf867f97a1d2153a6a507114dec1db730eef84dfe66f824373a96da85bd728f8e261c3058bba8c43568fe18cfb0398489357910737b48a8a0aa9b9c4c6b70f3ffb81a368ee891f4862d500fa2f2122c08b53b4587d9a5854dcea0f8594ed58c385c46a8942e7631e138e4b9bfeaf7c718081d53cbf81b855b56229e2fb29b4ef08fb2a2b9bdaa1bb4b6f1105a3da5dfaac1708920a3722e668ad9be499d5e40685240950e6187799c99d76810f6c3369ff6e63262d67e030e8dd6e7f209d315371d8d5d21ba366028a7cc720cb2c9883aaacfb1b29f5e123c8ff496a578a4db060f9880f09d4ca7b8842bc73d30a85be39a4e7d11ec6afa46c438bc9b1b4118bcf2d1ad27ddbbe3196369bc10bd49901c427a82eecb766e6f840b71f04d1b88f7ba57ef4c2858d8f97248be5b918c357daac02e1f79aff2f4a06e783880fc4f2d29c08c5b286d3a62da19ff5269f3f049dec4514dc46dcd187191540737df54479cf4485a2f5c0fb7891091e9d0eea9843b710f866f511ac2789e815dd9cc7862ae35f3e2b4b26a5ea16082c99bae6117611e972f010127ba9162c9c06cde264e71155e47e697f404824d0c71ddd18c21c359bec9b16ddd7805bc8b05ac43ff4537a3de5136fca10d1226f7a6aa6ad4a17755f22fb672e7967082c8bd9c0380cbdc590d1753e849ad8bb8ee4d03524457afdea5a352f1dabd5d11fa0df0a57d9218bf9fb24784bec82f273ad8e1b498cfe6f70d94ccab3a7ceebe6ed60a4a19992dc38bf5add5b80a5ca20ccbed33ce140d03d2423a9cab858209350aacc00e434d654f3772867be118759556526b6be89b3a63c7a7a545385c0f0ae5d258fb3a5b2fbe32b2b7d4f40951b79bcaf4905122ce5ba3146969ff5ace88d6d60d3a0808333f225582e800edbc405716e7090c7deb05dac382ac4e82c66e4724f7a876c2167099c31b1e7587a1922bef0af7fd9443cb92742a071be9b678502584cb931e0e44de019cb1cb6bf07fbdbe53b235e00b0316c2fe249540731f684e9074aa07e15071b75133e779f1062f856b21d1dc2662616748323349f38bb6d4965d199ca7e2d71eec6caff402d5e43cead273e3912cb8650c748ce24d8c0409c47883f3da46026c4995b828cc8e0255f474a0b0e2894f6ed20afd2aa8607349fe1f7fc0ebf0184ba3f8988840afa2b202fb55dfc5797d7fdb005ecdfda5191ecc5c0b2489fa249a41bfe623b01991188eac13d6f320a836887fa091871d8f88bbeccdfa1dbc26e45edb88aad83a464405a5e976a7f11a9f4cef88e93cfa236879826a0e8186dcfdc67cd33d05ca56b45252288523bf35fca50ada65a9f20e57eaa0e8a28b900b48dbdb323cc312eac053043682e235e6805a1d50de9b771065a8cf77e3d21a0522136467e0260c05fe4591882f08fcb46f11c87649843725ff536dcf788436792cb3b9a446fd9c02aa2b5773b4d7474ab7307e8836e157a17446c7692fded6dadec52a300cd95e214735a7675adac5aaee5dfec255dcd178dc9b890f8f0876264a9d840a1d7da44502ff19133a9e781a1ab6c17cd2800922ac4f7d251e21c9d43d9ad1e0261b7f0dee137cb5f57fc65a274bf8241c89e856e9f129434fc48f1c711d8449942d785a515c38df209bcf41fc665be73ab583c32d7f4b31ede1e19d41acf9f10d5e91fe9f624d733fc3a19cb3d2d59619290e94e1feaaa9b7c807027a973a0b8d6cdafdcb3625a5ab1b40825f92fe8a53a275e88110f2693df378875598bbd15a72045c50dc061cd41730263d1903cf2645f55f18a230e69618c0d90872ecd3489845ee286ce4f4829ead84f3261bffc6bf1be9efcd0fef090400d0c8a36caea68df0d88d4b37ba88ef7047fc8a47ec2b10a791d4dc9997d3e0bed6c780edf953e3099cf9274545e078cb5d6499f868d1cf0b665f69f276f87b5bd8b8ab93c3783278e0915612d39513e025582411a6f0030c8c6283dd69cb1df1ba539b309be5eddc9b90098c1567de59c4a4f0990d1a38774850213eff7f4e048010a2f56ba0ed3929cbb48cd57c2563fd50091cc3c13114e66acbce96d0d03f37e3076515b981d1c606ce395f832baf1ff43d57af47b0e0dbb60db3c598b5ff6b908f6d6ed61efa9995404825a342ad1da40c78947456f5c1cb1401db0828b2929a5d33a89b50ffeed9be412206edf52780baac3ab2c6d3d1a38f0c80978af9c44d53f1b0c27d3c330d26485737fc560035d34ee26af95ed91f4117611e1afbab95bbafdc5bbc4a5ab79220f8fd09f46fbbaa6e030d950fc9bf90e3b35a765c0b1919a64e7f701591902a0528980b718cc78b4241a5e25a761bd014ad8490429fde16e1a895d2b12c505e0051a13bf2b732dd4c6e9c01870266d1ea083792919ed879957533a1a7216b89d2b2e3600ccb688a24a16c7f392495148134f3d5078a7499934f15208a9c19facdc72f28aaa7a38c5297ab150f640be12b0a9fd1aa90a48f3926271f0dc5b46d028ac133b4fd88ce951843e405dc5150a77a339896a6eb12bbd318a43cf6f2efc2c275dd19d9de1747b8da94b9810fa41a7c62cb26c374b72357c013665f21b50576458f5a06dcd73a23cb7769195b0e38c24371fa8e69321c0beeeb6e5a3b7588591766a3562ada6f24f837b429032ee9176ec1ec5583c271c47634ff35b4cefacbaf940b7e31a7c7d3b4faeda2401b46c25fa2e3eaf3ae99a537ba3166ac4a68e7469a710e1130447a552122bc227eb78641b1cd1254be7956c264cf179cb1f2e7884fcd6c5a1e90a41ef2cca5862a5ead2efa0c43e4247163b6694cf1fbac73cc55c9debda94847b2ee3461c6170cf353fc64b5a6a77d22888dd16237a2ad23a9c5aaa1c7bef733b2efddc58b096d5659ef33b41cd47cffd79ac5daf0ad6ac1d70a8ad7b5c93e0ba2653e8082825af58806bcbe43f7138b2b93e618252c0cf3c85fe76a53f1522bb8ac153a3459c1844a130caad5856c4ff9ee09e2ef604b35fd3f6dc873948dd40f93ca41edd1626a3ac3420ce835a429b051cf6857f22b070ed90a05e3912f408b64f790f3c00dd3b9d86ddc0a8c82d0647e9efd10243afc288582c1e79aa7fe8c0d8a2f3c482a0f3fe0dd117113610ef0f81cb0f39a7a1f9e43a1f504a5c2dd1a261bff4a6025a426fd6e6b62db933b5b6473d558c53da96760b88e0adbd56fa0e98da85ebb07176fd7e2c7fc518b431ca5b25ff42c198b994c7a30f02f568f6b27412a3bff85d010cfafb42d81596f1821001c1b1ae5dc0452bfc9e14136cff29c57f94fc29dbf2c38178272f4b81be1d2c586671e63f761e43982e064d94cb79ce38cdbaeb87db35fa5b68224588c09d54b0f17a7eb6ef30b323810d7ecf30929907b42179c0a1826d1f678c54fe9a68641f989997412255f016317eb9123aad5e903095cb23884a7f9bf593fa57202abd01c59282a9a5759cf5f0712fa917fcd5168b97be8787a25f481e9f12d67e233de7bee99600bd0e316108d2e8ebba72c15a2d9b1994689eab0a21ac86930d340de12b439462215b92d29f05f59ae2cdda3509a866565e66ea5da096e2d76f918eaed266684eb7761bf782f80c2a32640260814b9a5c6d5ec5fde49f173d1252e54c767750d118d4a778152d76904167e95f781e6ae033042136632a40faab62f4d3ef31783914e6d9575e5432ba5404a1395e70c361bfeb3fa3f93d218bf51b59925b3d6e7b1019136c2a34f152f734e3ff896f93c443e34061db9ebea0b4eb3b1eadd916b63df842ec427c1297d9a29cfc3c54fcea36f79b88abf8f4556fbdb0e58dad5d23100ca50bcc8877be225baeb020d486409470f2c51becfc2a5042b640f79d0dc2bc04a3bd1578b27a471f1221c87c08de0883e1f60a9873929444d5c6cca3481732cc8946a83501783f9eb96b8c45d84022941808db8b135fe5ed5abb6eebdee1d609c2aa91aee045c4d59b9141c0d01d13d28c5500b76d6e67f90ed2593701c0f85a2852474ff0d13c8efa2658e4998421214378b3cf9dca22fa858821f189de812cf34ac5bc595662a16c725126634940bc494b994ca558318be04f63ba9d3886fe5aae44add493de1f11a0c05e01138d97590eda5257418591013176b735ceb6160991458425fdd1a28b747a3092aaca17844d445b71c5af25f6ce4487f02bbc71f96db7af99f63c1f0fa944d009115ad12c0192db328f1b222289634ce6535352c848455a9723d50de8dda51776b29630d0d96c6750c90f2de9548e27af9c3d3108bfc052d7961e98e609f784fa0708ca908cb72253383e0e015387e45af5865b1c4faeb69c49ccb5fd54b9ba307b01f67580be33164d157b9463c95a9935bae271ee01c6dde6d9928d406abd9f1b3759410ed3fc6cb8e89dc6fbbfc4795287311f8e2f2ee6bd1126ede90e41ad4a3f0da04c3e957c24c28b693749aff1ab218ab66d46cb168e442becde62a5000faac101c768a4eed28c9e57b01bbc97ceb6f1b2fba46469d01c6cb889956b374f2d0ae129db4ebb275edec2cb803ed113d088c3070fd5ee4503c895557ea554e37a0e46ae5526d5aa54a07f59bac03194e89c88bb4267cbffe442b169936768c43ad974269a7400b7cf6761650498e106d50b95001ecba8d5a89fae53c28a7744fa979a433950e10d243e282e2d98dd02ef6121ee010aa0e2faeffe085d9b813fcf39f144d80d7db5908848e951e8f1f14abab096ed187d76434b52674221714c43e3f6a855de037f5fef75d16b4a1c4943e1de9a5c37d43ce0d3f6a008ec4643729307e0a79822c9df08ec2e71959662a737b782098001388d7662b211fb80ddf4033c08c3bdae9e95d33c39a2ebee067a33c9fc6de9c7738336b9c4cd4f196f4ca8f5c496e730c6acfbce980be9a92e7790e53eccfd785ab7cb0a7b93f9aced503a66d3e3747ea432c33b88d923c8bdd884e5e6fee0f009ad4b8953ddca736e760b93d64d3971fdb9fadcb65b603d07af2a671107c9b957b6583a3410f04dbdc40d4a3fada90b6fa7ae4601e0f0a8ce008816371042619cf9f9df9131ea9ef8a07c8d1987994f81b869901356e77e6eb503edcb21acbffa425514606ddc471d198746f47a403ed25481afa12cc854ec5dbec02e0832eaf4d0ecd73d024eb7df1c62204f52a75ef557b36012b0fc191cc8a77cb319ee90f88e31e5e8bbb57644a3c2926a155f30c54f2a9d6f4a3d3796d605527f99ca408c79d2f0db6714fae9e2aa96474b61d5521e50197d6dba13b507973de61bc973f3d32adcb468f21832e930e266a71b3bc7da11c40a490459793c9f1ef56a4b87ccca436e9c6907cf2942f494420fb40756df2b25f6e854eb3fc971e8f16a9c2020f79f1ecf4987bd3bf05ed79e5ac06ae9a30701d14c54b09f65d68fd6b84a7e6285e1c77899f02169f9618af2b05266b38eff8692f7ce892bf70612fa2eecddafd94491d9c96a6aa3bb2263dc68e9f0a5569ba145e3d14b4024296bd5843c32fd46ea1b2be331fa5872ef525fd7050e1608488d8c1dd5ef35b3a30d91ee4692da05c1b76d8dc780dc844095e695dbbb78bbc78343006748154800879597eb52452e7da2846855e6a6ecbf7b05ef7bcc2bdc3d8048851a3207f6d8bde9f120428af4a1f13abca1fe427b2cbe1be8c344bd74bde366b95042e2f7d0c0e863b053c0c9f67c831b4ec711a616900a0017576a363c687d520bfd934be8a337f90f806cbff14ad9022049ce6d244e769a3b067ae285cb9f6dee0ca2949fe5f360c3386a735d27aefb4e31101c73151b1f3c639eb725fbb2565cb63bc8b4380db3dc9f601cda9a496499eaf55f88d23c735d70b727aed17fd3cfa83c2c368df544bd88580a2ae3931822a6c56a9082b2d94a5d1089af18c7cd4840ac189aa265590f0110c328bf970853abc92568a80404a8bb1207af3c5b819cd6f3760e38d2320a1b3e6948715b8e22eedf23334f80225a3bdd679c46c664e88217e3615ccd992764343870c08a4ec26e8e6a24fc2e14d1e9a4121e0e6c31ca15edd180ca52be8f6ecc8f5c881ba12ec5ed0260a52c2e7af3b15cb8072149d62241ac7026149a4398ef62bfb063a4a905b1cc7653e443a3333bccc81d4f73595e615e71f516c680116c3c2642d98b76f710f9be08d49e1d1a44e79fc51d88cca4e25e572385b571c91e9917b18921a263d82137a42a888c615ac097230be41613997c181167da7249abbb3072bf8fa49c277f90238b61dbc94e3932a9d1cfb35f51587fe6648ac847d94a194d71f0e4107cb096647e6d120cab11f07df345b410c4eaf93c484e602a0e551d111c84669354f49d7af2978e979b42981065e4bb49d0a1f3115191417021e141b1f66396310a26884884ab87dc124d2b40be5fee5fed0dcc248bdb49bebb9b7165336fe73f0c16994b24a6ade844df7184bf3c7931a8895e567bd4931c18882acba27ae5f29c8c54d317becf0d9e083aa3e2c3028f91306052e33e08aade03e71864451a57237b15aa6c63a42b32d181008af1eadf60d2a788eacf5e1f8f5e519e9c1da2bed8c88b37d3a033087e47059f060b41b4f1703a0839cd0b9c7c535ccb66ec014ade130438da545e9559b3a0fd205c2bc5cd3b4f37888670b8328d026b137e081b7992a9cde4228ee5b2c7c262e3a9a3c48c11000749e508cca15f3bfca860ab5d6272a93eccef9d27d34e81f77a999d17e04d8a340d8262c8e52a69c14b51684dab046c2d10f22f186b4a7be4e287c7866398c01d08568b808128b196b4cfdf5a50a519e89d665aeaa01f116132631710a69b18b52db21a6afc84d6b259a19a2adcb01313f51da8e52a64222a12c6d10fc5a67a4fc3741cd17eaf06697096743e29067966b788de258203209a8eb033f5922189acf0def82aa49c085982049ddc137adb121c47253a6067255c84c978933ceca7b8a9619e5f5bf951b9a20dfc4003ad09ea2585f1e1a7fffc2c40461c0b3bf22efaacca329214ed3fdd3c1140c843ec5c27f26ff15f291e6c6dceab5d2daa3aae8b4d77999c120d039a0f5a7db9db20570a4c76bad4cb4dbfc4c254f37d0890bab72f88997ac5b709270e582546be6fa5344674b668e3ccc20e31a5cd810cd13e4ccb053f6ca0fc0327e1e87440fd3198502359e635040674a0fe1fd456791b3ba0c2515a70caa0152587ee9e0e033a4b15a87cc313e2ddae6bee1b03fb6fd3b0f09a1a1613d912238ab9edcc30add23af84a5839e597087c28796073830c432f958f0ac92f3e79d0b75d9d3808b8ec25d94375b30250173aee472d1f433fb8097ca9190310fe15d5f95e3f8656b8c0fdc4e18b16350589ea34556a446e8f46cb127540ded72e39a8904e60b7b1901a48c03fcf060d9dcbb365460b6bb22a7de751bd1c57b1c1c8c4d3c6dd2eaba87e274df7319a8275f9ca8cabf57d4d42a0cc1b6c46eef8e86f66f125b35eadf8ef5f009818948b37b5e3786c8f8c4d3b61f088ecb623c91d4c1d063b548ec70ff93698ec40fb1b351edcc0abc2f9d9c231ea7859b4b703ffb1ba0d84767f9d59142643028de6589a61c81f9d9612a4b9fc0058a3a0ef4af08f4e1b267e4d09856a8080b5768b434cfd115f226799a599c3d292b9260c458692468beeca40da7742ab40de86390a70fd80d50ff690072e54c12dad1e7c01421f49bfdf93837db8502e015cead83c11513619db7ce8244b9c836d36e3725aa96acb14e0f2137ce2d9581437958c0722cecaa8be8e51a55f0ccb324c521e5a29aa73ded909f8ae2ba61b1a9114c2309ef1a86a3033685c5c855ff09cc2d8a3c8a18eb01b2ea275d4c339735dcee3d97293f50c47605f8b43270e42e56a3fa37894303cf4e500a8ca5bab2a0d1c3ed271a66cd663bd99c362bac00cbda72b0e87c11d2131528f91c281fdba0cfb100ef3a7f724a6eac92dcdd3b77975c315860af8a66396611291d8f0a4474679d75c454aa54f9eca62223c62618dab43b44d259395a73e02c8cd8807e26093558c10d6ad747fa082463f4ee98cc8b1130c6a759110f3f43f5635e64684e094b51a38b56ebd000efdf8535e3593cc04887d89b2ff9bb2df8f51f178f2fd520844c8023badb7703d622ba228cc2d6669746361c752bc23b80a1eabcb567d2b0c6abdfb748b8239b50b2fc946563f6c135d20d79a174c4cea6dd6d804a927900f1ffd3096b57b091200d00ca52934f3c0776587c9f83d8a905de1b90c02411f3f04a2c75a9d6562a800786afd1076934c412747ca8faa480a018da09c06be6343a242cdcb502663c48fb7559223be7c384b69dfea943208e5e6a241b51fe268248c7c8965e639dc2098bd28171da4b9424fba41e8e2d07bce483ec863e355a1c3eace14c58c10ee4dddc64a1ea1a9b430c831b324e16a7cae069d7b89106e2d37b4067a2b64a28ee15b4543755fac47e00a3c19a90639feaa0940a6a4206a1f868cba562757ed6b4ec01de866f88d07158b2b3e55bfad0b6f4494a678818e5e270b65f3796cb0c41ed9c8004eb13cd8c447b3333811c4bdc66eede360e6f662490ed04948f13554958a93002a8c27218d61f3b7766aea829244ef63a4e537220c5a02db087e9ed0b2735c402df274f43b7243f7b0b3767b317458b9721fb440856ec89437372902a36913e09975ac73cae56945fe5e68154ec0d0d41350c60bf25a5648bc8318fe5e651f0c90503cba7189860b361507b2d6dba6b8f957d8a62d31e38aa10eb95dde55a586db1c908cd316d9dc6da4cb02f101a00092f8cc4e2437ef332167d708451175afe9f414b599bd45c751ad33b432d0d30eb9a7caee0b4dd5905726c3741e323c7f48513f77ee4ea717554b4398010e17ab3b5a1c258df1157a1f64dcd5b81f877b62aa9137e59c591794f79ad02a6b6db127d138adbe3244d4a1603edc735e7d4848e578c883562dd6861881d3854af25b97b94c22bc03e4f56daf83cf50e2289db4b3a88bd4595ffc1349039cb6e74edb37bf3bfa19f89f1e7d7a12d59c3d137b59c1b0591823ca82f70c6c0ef7999547b4f25a253a2222b6b6dc8538067aad7b23864535888c3f07c23570ee4c166b51431ee47348e3bae6d7cddfbfde562d6aa524d2c09aa6dae8867cb706865d17f07ef29fcddc7e6b4fef0b52ab9b93cf8de14af0d80df470e5f1e9221f6b19e4fe70f7e163c6b982972759afaf64be7146f4b9dd1d3eaf152755607403913d0980a90291234b704c2c078c15f82388754ff6228f90a4bbb5ccebf9137068afa6efc90097b006f6f6c8a29f890d1048df750048b800491cdc8cd5370fd7ec369cead78045932a308e5eaba1c58371e6316a30fa1f313da0b95209252cd3368582f80e3b00041000f88048fde4beda159c613e6030f1b2f506d23d1dfef3a90c81a3af931d37db4dce2ec9af02ea4874d9513b81a086cc958b54e29010bd70bfa39240c0a15211838a94f31e103d380817e04d270de59c426bb82b1f303a53953480d22c0e53acb70df4388ecbaa626892af12cdcadb8f6d78bd5a7407db58b21511d0658745f6ad0e80840f3ad83a6f02b0dc8efff2bd3f1db664a758da63782c76ebcba36162c7607664a6cb16412acf7ca5c1a445e8cc6ff1395bb31f7463250633034dd2a2fe447f55c69811ace8b56763e6c88bce2f384a480e9c884cbd9bb0ba342bb5aef03f4cadb0576f7ad60fce87b7f5417d34829cb16ee7b914265f7bd694881f79bdd9fa84c307c52ad2804b87c728467f055e986386d8cac36d861e6ddcf8c1db922d5132254b0296e431d4140ba0d506c873a7dd838a448d8304711531bb32508cc1e938f5cedc037810b6fab436433d8e6c705cc298adcd1b2ba5e29998d6e6162ec11f290d9b0a7060d0f373dacbe367992bbd5426522a9aadc1f53dc884f36b44fc9a3cbeeb5e20a032549162d369fafdf8638593bf672383096d0ed0c61fa770c1408018f33d30e12f3ee1562360ab27e6ead08c5f8f0285144e03c086a02e0fdee50654ae935d97f5f7e1490b2bba10b22db7449cd51ffd794e8f813fa5cb6ae924ed8f5d6a41d72999a8477eac0e1fbf6ef382d5505db31284f4842864bc087fdccf693f24240038556366bfa0167e2829fa976dea3d59194a3e9b238694a400e5dad742414dcef6c83459725fb2e08e725bc29489f3a11e9d5cb7aab02e73d5ed0bdc9d5474e98325dbf29635d8f3b6a595998aa7421ce226220faa6793fc66b56164ad01e744f4ed000c28785e92821205c4f4db68bfcd2dbfe945a084e83a39ae513825b87380a365f5714ca9208e3fba00f40e1fee8414dbacdbad1c836463d87646438cdc1706b34bf5da10e25e9585a451f90265c381ad722cb3d09f430140fe9c34077500019bd2e1b1c3b7149019218b4607a551945e33ac509c150a7edf42dc0b80d9fb425f17812abd2d46df5955460f6acbc219a08921b3dd4ede1c5624da88294db4b7605f082ccfa8b298b10ae24be1c4e78f19edd466465bee2c6d5a4445ef790df1f29521d34f907b9d48c57f6ce82c91bcefc5c2a2d6e6a9e6f66b13a53063bb75a020f37fed04f0e81155991bbb6584a9c4a18eeda6d88c01cb5965e5a4434032ff7c9df0bc9b5dbdd16b57a1bf749381d17e8edbbea62380216cdfc6d5e6ba4ccf692cf9b420c25e104b414026cae1cf54ff13a2cbd4bf9b8b88731647c3bc9b9b688d72e208139594ac36f83ed2dee804c88df06bff5709b013af34edfa6483317d962741d7166f8f4161b370d4e92454b7b065bf9f73ed982f81e6b4843c6d62a2f5623eeaec53148f8e1abdd543d32cf5819701293eb29c3c2859c3467a30cf7dccddc24bafa1b6ae1b14221e20ed836f2377d0c2bbb9c3fc0d8cee131ebd901277202068b98e1e89888a01455066734243a76ecc15ffcb0eaba89129cc5c861be43ecc539b97d73e1ec898a4ee623f589be0b75e72ed67ddda6ddb7fe6c880e9eecab18d80a67335596dad9f0a68514f085042786992ef749de1ebef0071c6a857da30227cb446689cc4ffa23e15cefa8bb2f8a040ad3b1a2b1d675b611b4aab0deb2d24640506105edb8f6a3eb1d8f8319e68f638196c11d015099f5fff75774edb3900b152c8cd39629fab9f7a024d0849bbf8a8c53460a407cf714f73e8e5269f9b17bef455619efc620b6e0eec68496ee70217015822d16ee92c0ad1893746fe659d2f3ba7d0f73591f71594a966ffdf8bc922361b499e8d4677a85a51c8349eb7d3bba0290f5f581937299cc1be593a30b4387333bcb4003c9d31d14fe60d7b1fd59db58bdce62d690d7d9a1cc81d963b00212d3bdea37309cbb3bb6b7204c8a54a005fd5073859f591fe36a0bc398dc0ce16e3cecfc04be0e3ecdfcb07316dc484d62735a63c77f18298ca8c8cbae4064f768e0d77283213855ead0075e22dddc5100c2099d1f5b2e0817c4343e6e04a80ef18410f20c18f78296770c5e53bddf78f562e91f0a37e06b55c1c1c7027f2cea3a45781b4e5da097a40e83195b7b7dc8a1947d9bc430b66dae5618980ae11a7069eb26d8593c76ffdcc73d3c76e0f7cc56df32e05d6d4220c6ec5524587b3e70a726b69c2f402bc58c9b4646e5d08443a8966cdbe439d491e49277903ba4686bf5c65f50a369738492d6c351d085507cbffa3926015da181f1c68082988896a4ab0e691a82a8d7a4a4e4ad6b664a4d692e402b4d2c4538297c606013d1136edca4c645901a6ebc3eaa6aadba4a8aaaa8737392f3fa5e9f3561534bca77eec11378700f534c99f65c506b207e5cf85d2ece6f4922d73315a0bd092b50d2a38951befd976bd844a73be9dfd474fa7cad624d20c5925016de07b220de4e8d7d08a635b05810116f8519d17048e86cfc049bb5035d1a91a72f70408e89a8a58886480ef81e678b267ecd1d59e502b567e574157bdb8f0385fa284168f409625543ff536fed657dc955dbc60371e4bef0275ba6f55fc6db29db67e54c18d7bc4cff130300302b3c1644d58c831ed7eb9bcc89f45232d48ff877ab84176ecc5da6cc3606d7e033974306b47b22fde33f17e9c2147bc883c2eb39015c94a233c09b1ea814eda96e51c3707d43da7c757a222ffec34d7c01b5bf33d95037dc4033bc6b8b2d73de8790db9659f40ad74bbd0174aa727574b0a68adec9fc6564d1c478d6652a5889bb292f5e4dcf0c2e2ac499dc28b1f838e73b8f99ea0b3efd12046c795938f7cbc7ebe04d5ac4adf4bbf2498a3742ef7038289bc1a5515cc0d2ac54eea0e91aff090e31f646fceac42083b46cf2bfa7af3c1211146fd1987562c8ffdfb491e6a8193aa12e71a177b8f40b7c81399f8e80efe2f9b47a2d7cdeda16c0a1310bc2d3b2f45d9c6bb82db9a4d47c991b657f01d418a30e61f1efec955b5d4033419582d7e0a41f42c8c184fbb8746c06a3b0476af310f3ff0a872407d17a2f939a5946648900346303eee8eee293885f9c7f80b23bcaef5aa05faa87e0646b65c3590f62001d7990b118ce2ec6edf76034a3c3c3b3b293072383cd47f2cca0da7a28e978940306ddf833f3ca489e8de5d86adfd2b5f065048706394c3401fe77d69ab0f0ed1b6dc23f55c01bbe3e3f906672e33805c33b63c32d9f33454450cc546d98326333b626743c175ca30347c5a5293609dc498906b222211fa035a2222ca32b3679a1f847843546081df85cfebed718858759452a9c1ac518f4456c9db745277f8f63fc82e0503b5cde39e8266342ae9f5c68500acff458594779d7d2ca1480ba664b571bdaf62866d163aa2ca9d8b350908f148458e96f847072bc0eeb52ec0547cb3975f080467aab76caf7c983a3a1a3d6b2ffe73614b55437a01f705ca29e4c2eae81771c53e95ce6415b55f5c3d8f94435ededa5ab23e2ade3f1c9c6aac2ca01723de4c066c3fcb7bcd7da2b6c723acf5cfa38d0d7b1ffdaa9d2c8c3655f5cc8d4aa39768d4c3f34a0ddca9e98df163758790e3dbbcc627042034f95688201656651e11d9210797cdc28a88cec1b754658ca45cee421c3836f3d44e698673eea1111366a0ddb45301340aab3a591208045420e6cbbddd69d7ef2713213fc4f7e8694ecfc9c663845df46e15aa9dcdb2b9b91258e543ccf12c288e9f9291e2dccad994f56729e45643ba7bd2e76c33d17900a9d574906d66baa2d3823d288441a5a84f4e2dab902d6ecedad32ea9a7d204377783825181e5d38c90834f5c778b0b6495cc97862b96389480e556a3344d9280e40d1804844594393ee26d865ae2f36197a6e09b00be72b08c4f71bc2d372a96995d29e25f62caf78d3f754cda66da871d31c5f64eaefeb9735b34dc4654ebc0ed37d30746b91283040e9e47f5a68d72b2ba0964e890bce4118cc569b9e31c501568b4c211849e20801ef32c6411113f5da6e778207d73c0d5c01c2780d1e14393021da7c54925a0ca2ccd271d996566eb12b8073ed43b3a9ac761b4841bf7226a9c41ca0c7067626c28e139e801f81b0b6cd430772ac75c024272dc8d7635208166dfdfb262e8920507508adee94ed3c10a03530e39c2a71534f13c28685a9b2e0b83d8d5c074661e7d26af6c3939b771df5ead9ef9e650a039c8981af5815f50f27b17f1a1fe08d5dd45efb26bee6252911c4ffa8bef1382bf00864e3d3b174fa57fd0c5d72360fe74b56be05098dd9e7830dd76f70d309116f85dcb5ee172e871aab69b219b74360e6d0684bafbe52e3104333f4150884562185b983c3c046511235832cc1ca922f1d64f1d1c026cabd828031930a95219bb593dc7e3ce7e08d77bfb9019690f641fd2db115d0d832dd04ce7109deb548cdaaba424def333cf8a9a4d9dced2bafc5236cc82377b40c04bbfa7ca00cddeb8493851b7e27cb9de1ea2d26da382a7bc3d54e8a8a231374613c2c928e9fbde524677abdc7b6d82677893506227ad6dd7edabd627384e95c0e56ec5ec2c4278e01f445cdeafa2db586830cb666e44614be938c6b5ffeab87ff61b3a272062b844ae111ac2e6cd86cb8992c18540b59cae6f57588cbe34600d079b8a32c541c1128d0650822dc0071726696cdcf1bdc0c399b19bfcf2dd92906d5cc6d949045ac1e9ddb916000f1e228912021b28d7c129dffbdd02afb0b61797ba5aca603d83dc9d2fa074b021d8a5f3c05e22208261ae1b6311e7cf2e575c1eb17c4ff33c484800f2438a92f571b6207a29b962e8e3c58c3859beafccfdb4e1edb6ddbac22c184a161200f645ab435fde33ed86d22d5e27c6fc6907c5fe471eaf4d442d8cd7c099d79bba947b977288dce0effb2b845101aae2983156708e65753ab40f498a72929fc9824c545f13672fc6b5863fc09a5df3a7e862bf074b7c304d8ad6137fe399b736d0b39cad0df424f752d23650b21e0bf59ad7f235abfeb6d8eda7c1cd36584fb30bb0d0e44e916dfbcbfc020b03f502ad4d6badb5d6607345dc10d7dd30ec8ef8577eaa6f8646a55e2429351f717717d278733804becfe78ab821ae3b94bdbbbbbbc7ee88ff18b08af0a9d01ad1ac6b9822fa94d1a7903ea5f429a6fbffeb58a2a12affff9ffdec773a39040b9a6d694d2cef5f0677651050bdd5dd1daaa6be39b74da07a206e5455cc45d0c5c265455dbda8ad44071b094f37927854d32f171d7b5049aa6f5e369dfa44e529b2c401d782b4b4b63dced4ada496172cd90c4ea1bb3b2eaabe59ea552bc9e55880b48072f0dcdd999a3653d466aadafb88094b87e53c4e44114a9fa409ad9329453011d0047f20a3d575856401bea05b7a0098e668c94e2ce794055be3d9ecc819368bdfddddddab82ea9b8946e40e51a5dc3b031835a951d58ccd51b35435b55531f1002c0ac4a0f32b624dec18443912d91186688ca24440a39c3e789dfbf0bb45938c860fb0e27162815f526d79036cffffffc56f7d33ab6d2d484b6b5b1e8e569dfa2811b3b7cd6d3adbc6aa37c09e31b1a3140b6e527023800bacc024999f1466a209e0717beff6d6fa748f9d0e74038a817c472d9c329cde7befda507d73f1985c36496aceafaf2f9f1a3f391b28342e0682ae5f2e4a3a57546820e16cf6afe399eb5a85fa3434a8a1440d356a28526b764742a64254df6c44a2a828b70bff586bd01c1b039f667edc7bef7d0453df7c8b7d41a283e831baaf3e8711852185e115a3f6aedddd3d7adcdb6f23fb69a9413c35db55d76c78b17baa98d99cbd2901a894544c2aa598cde7ee3e2daa6f3e2651a64bde503af46a6532483eddae0569696d0b4b8b9270a4b4d61c42dfc85493b6e1939bcdba543c9e197dadc4ae881be2baf1278dac339b2be286b8ae0a4aa8e08a0a24d74dc33822581c2b399cfbffdf04eb7df8d7cda552c32a5c5c8b1fb20b2a6631f784da326ac1878aebb6e215b48d80366490524a8e8beb9bd94f71117a97a68bdaf5ff6f13d5206ca4ed7f4b05be7b1e6bb59aceb3110b5e4f77df48ac67d5edad42f3eeeedbb78b4c45e0183a62c63bfaf63782d099aa85049615a3539629cd1467ca33059a12f54b1396264e6d0e2390c9f9a03e8a767a689aa3e11fb140f3ffff248eea9b93cb260a09a71629f6863c57e5e3bd1f56da7b6f64527df3b2e97c424f9051dfc0dd7dc409adb54ec152df3c05bbc53e1295e06f205c7d30b8ffff38c5f5cd6cee1fce33c3f5cd7e3483ecc542c8d1837b86acba66e819977875aa2fd472ae23d1e9b06cf1082761d3cdfcffdf30d5372b3545e9939b2a91141c84ffff308f01563657d6d2767b43751b9c4127d1697422b5fbff2dbbddbb7615a6a7bef9370c01530471dd97b2fbd1810acabb2087844bd48197d813763640e3274091749f147999c0fc91bad2a5f4e07ce0c00fde744af20ab021b569ad7515527db392a9d454521565e42d484b6b5bff2f0a5b14b0ee926090b200e8b9e1ec304a45457d68fc8406007a89a20d4901d881707ae9e13aeefc64e9112b7b3724a3838c0d878b1e24b36548cb086d90c94af969a9b74c75d31d1e00b7a3466f018f898066448e4b6badf5ad4a7d33d614c40dec066225fe9196d682b4b4b695f9845cd775fe5aaa6f6e3a35085f4f374f9b3646349588a9e9c4f0d0e9a0ecc1dc38117376963b27ed23fae2e18188476c23bb71775f7153df1cfb82c49315c61ff9ff77f5fbd4231541387677af515bdfccc64bf1948a9a989cb0a508d751949c2d8df7ff0f77698021800d37c00cd1f5cdac6de981039c5658c1c51d61301a6697037228417272a06379636822e5c48b7e699292354de134e049aa8172b9eb7c1eb6a236ca5655eb6e9bd183092be7834503cad5ebe0e375cb1ebb652694ea9b99214c28c9340a0c4d77f85dbbbbbbd5d09395540537456a17980d2856b5cdbaeb46feffe19a0601e75add22739f50eff686eaf6de9b0798fae65bec0b124d7a18dddde19c550a4ad55619b38b9da2d35557dbbf0cc58d7d429fd1a7f4e9f5540b09732106e7b24a9f3228a355d824e8fa66d60dc1bdafb10b003086eb9bd95746edc7448f8a334bf374f85e078d106edd954b00f507ef88954d76d0cd3a8c0c9c9f38455720f14b711cf6806480f48e0ac6369a4eb1c06a89b318b7202dad6db93c66dddfdd7d17ab6ff60589c6103be48f046f0e128d274ba488bb3bac13f12b5c8f999f1ed197618bfacb350d254cff718ddc86ddde3d5c2dd6d99c98242e894da293f8dc22303b174a5e2f98bf79087449572216713b8367331f49433d3a32e828837084c21e3ede622436288edbcfa7ea1e29e0d552f2c452ea0791c5a17909bb85c561beb0601831cc18860c53c22d484b6b732b9461efbdf72642d53747a52644af5ace04a7e77bd7eeee8e4354df6c6491ed098e12c48d47d4a41be22daacc2506d569ad754d7d73aef71b168f499bbc145ddfccbac32331a68d92e78ab821ae1b1bb13e2dc2cd484f4a5a8b2bb813844206158a1b97979169415a5adb96c0a36b07d4910ca3095d82c587b73a32c68eb91d1f0d98b1b475f06393c2c666f22c854c3a29be70dc347974dae647a765e1fbc97cac4f8526792b8ae633c6828405cb028c66cbebbbbbbbf7620fb2f570eeee4e7223e9116f25ad58881896b2d80922132dfeb2181191ffffffcbfcffff7f8da109c4a924339e6bfcefc26ad75aebd75aeb9da2fae66372d9743edf6407ba59994f46830364d290a9e5b2efef45f5cdc7e4d2d571091933a7cadddd9d17c74a9a7a1eeb098839a08699a8aff1c933b9c23ddeb90b9fa6af98c76319a422047cd7eeeeeeeeeeee62c5f2833b0a492b668ab4286ca7872e706cb2173439eeee59b9f5cdec1a2fe522e853e8f4eeee70507d33d188543243c04a3e1c9665599665996ff37ff84a6bad35b2a7bef9372c1e4140267f64e8a9450b3f3c9b0533406f7d21a5994250c5b670d8eaa93d378497036ea47dd3746d04a32d2dc76feeee2b39f5cdbd9fc9ca70bb6ec4e2c4f9d820d962078ea0db12ae31ca41e5cc31942eeedba66c64303647dc90789cef011a5eb6d5d9d24cb4acf1d4030f18c2bdf7dec8a0fa66a211a9649620957c03f7382b6beb9bd91a3da11b5f39828e5f3aa8c2650775ffff9fa01ffeb5d05bd7b611b5e686c43bc519bb652f76c98bd629b7211ed1cebabbbbefe4d437f77ec3e2916427a979accbce40ce7ee2988dc6885a60a9f173b30e3bb70cbfbdf76619aa6f2e1e9301b0ce75bdbe68aa6f763e35082fa06ed6d78d0b272420289d6b415a5adbdae054145014d199a28699250777dddd8db76d8cb7d1b78d41d771203d113f61046cd56c4b31a9212f1bc160c650ae46315ab064ad3062394277748a77edeeee24b7dede502a782b8a0ed4639f6e28592afe6b9e8e313092d851a3868a28b40aa940d5bc09189d60d1f134e5ade81e1b0f5082e0081ed2dcd411f5ffcf7345dc10d76d6ac8fdffc31cf874093140e123f4d1555342540472812115d58e3112a99ebb4bcd0e1f47b71dac1756348fad0943c316a5a83ddd7d32f4664c8496614c3a6060c9b2b50043ef08b1b81e985ac996b6e63ac4f984aaf90ffbec7839255982dd4884621b1b242e3a1306c3920eb349c4b3daf0d2c61b51d90dac5d3eb95ac2ce18566e7d33fb2b86be762eecfbc76620ce609c0109022c8e50c1710304d228ae963c407daa2eee74541ed41e3ba22ce0ff7f727df3ff7f0cb3e400b3b41be9e086d4986e1946f6eeeedbb7cb7a559f330c91538cbd7041c1883003561334bdfab1a44f1ca132a463d58bf2e6256b0628e71793318c736e39b1ffffc312743ddd88efdadddd91788e1fdcdd7902e8648e62741a11f5d0c0c321da80e38c5fe8d5a2b5d6563ff5cdc3e2315962b514b6de234464816eed74ce55d4beb28a59aa20622ce378bab648baf9a145a3400c9085af3124e3c1ea42528960176ec4037022aa6f3622952038315de4ff63b4d65aeb7777ffffffb7c1c5764c96a313755099ae2ae754a5b8b4987c2e4b136d692debffff51ebff7f9c77c55218b2b162e9c6891c321f78f145f65c76e0b5202dad6d6302587e0b6b6bba0526bd49e3ffff7f9665599655c1b923da48c16a93048cc2b1a16ab10ce6b7be998db5d65af792ea9b974de7131a35e94985ef83c66a3a6e7bcb6493a35f7348c3126f6d693a65ef1b29450cc8f80130e57c188af0d6ffff3aa6fa66a5268da28bd27ab318e898b1d212450586a35988b142d5c0ffff5246f5cd48a506418ae9e26478ea095241369aa71d463cda440c345a645432847a46cc6b95d66a35e19a9bc05acc6bd9c6bff4c5923867d76dc6368dd2963e35904aa8f46c9e08e50014f31998040d0371280ce418e3061400070e8888c87468e0381e0804e290300c060442412018000480c16030181c0800e150326bf978007ecc1c439bcdb579f4ddb22aa2219d581ce6b8205eb04a0af55dc41b5218cb40237be5343fa500a58c387f0643eb08620753abcebfc05d14105b7ced075e454aa2b5dc89a2191f2e10f7e078f1ca4db72fe924efa56e8c4b1e092b5df28ffa87f6c6b2c0a4cecdcf3c02febf37a1796abe834ec1eca425dfe134a0383a878650b17a383a5bad599eedb2bfa2310aedea97028c2a77696d167f1b2e0516c79945925ac38e986e0507d3e098fbda2a9b72a3a1286c099e2189e7f2491e93e94fc76368f92df029385f9b2c5c763da187f0d3156f7d03d7e5edc61b3c2e187954b6ea4b5cde7e5347aafa44fbc63cc0db7a1641443bfa687bf1014411289e74dab42a10b5f75c8d83a4ce314ec5eb1937f38638584938755ffd375c2ef57d02500169f092e3b4e301e21840f03e395570a6cbdd7ba624f3eb06c09fcc12210bda7b7e85ee6a366368332557052d85364b61087e3e5cd048fc42138c4cf569b12c6f12fae10488a351e19f87228b9614c87fb86a8087d73328b9e6540d10794ab38e76a1aefe0f811eeb3d2e8f2a0a94dc69047328c44417e891071e2e796cae4b70a8def412b093056d10c4c1f501051e62d33a83ffe1016a01b20ccaa94e4afd9742a9e83fc862893a57d2c4a270ca3233d26bfe5640ab04020dce44ac54d4fc059760d89814abe8d0ae0bce31a80fe112ab1b632de2bf7877175a988dc3423f10173de33454cc54b7bde2a59d1021a3eadbbb30c8cd59f5e7a3b341533858f09f9e57d3bd3592ad6d019bbe910456508f6f47f9c1eeb64fc80ccb2919e422b8a928a38f2c94307d43b8956a26af0224d2c85fa8be93067e80d13a240449bae09a617a086f87ab236efd433815390066cd4dc8d1e7241617046d4ee3c00eb23d9322ae1b878124638c5b8ddba1c4fb1fa2503e2674468c9ade1335497e74db048952a2ce42ab7d4091973278b1b173abb9f9a307611d761f4f2102a9bd5469b6cbb1037ca407a092d47a79157910e7322f837ba7f25db036538e76d118ed5c0acf2f718510046fc9c575f757d68c25e3324c6c7a97ca6a687324b60fad72d4c629b4797385547a9d57c4537d584dc520b61608e5b739ba17061d3672f659365d3dde4822a4b10a2fba79a63e0d4fa6331a97ffc3c00fe81d25964a10f99531ebda1a366a1b98d852ca4b28d662c437e231a76472634facd946fbb8afd9fb52e7310129f98da2316e4fb30e61731f59299b4c1dc8c7cec7e7d9d3ff409113f7238f718a8903778a95704efbeaea53ab39fd0fc37b677ea9b5544d4a42feadb97bcf35ae40c4c3a32d5e7b2bd31b71f9976f13d723f56399a184b36be76d3fec3c498d60c4af634432ebba81dc99a882c1f0e77a52be34b510254bcbd9eb63252b6e0d7e4574a62d51fe34d340ff162d05a0a456a704c85879b4e61f3acf6a8bae6a59661921c8e6b1fbdedc06f1b1b455cdc4067e18a5f8c077875a2bf7be871ebe1ed3bbfe72f0fd46402b55bdf2396429105a8e0e1459705beb4742ef268527044babf053eb22e4ec511b5649ab8dcdfa8b669420da024d850817a5b24f4c0d346dde2f412107f5b191f91140dc09824cdd18483d7b0df8c3c70dec819a3cdc4603376187495639f65801300e8cd00ea8e5e136211d17f6006310bfe710002c91db4639fb88a704d4940b55a46af179206c614bf78ed6457ed3f8295575c9ece4d1aaeb0a18f8d3767302941e5aed1a778bd3b7efb1d27c973ce6c808c1b540aa3d6f3f4b17d1ddb2692dfb882697fcf6728b6f62b49eb2c22a5acbfc644b927f052cd8a5cc365a22c76c9a314ebc7d55e70aae315aa4530afe2cbe2668245dd5af01eac10ac709b51148dd6b6e6c97cfb37c21e9a016c102a1cd3707ed5a5ffcf61b6b7b43838cc520954966373ff65836447edd03855106b1f042bfd9bb2a5362bf26f2cbcd1f5b0a471f9647d7106d42bce9bb6493cf214d88d5f51711346dfa781ab183bcf12b6548e3d64882c8d8e568135699876e3fcf6e0a6a6bb28c93111c91d0f98e1239ca2f06d19f846eb271641d7c05883af3ad7d424381a8f8905e4e153456ea3cd2ffd8e6755da9a687b4e6f13a80f3fe559082aceaa16e4305a7b3d14fc35ee73705ab1c8b63763d2a6fa43321b131675123ca543eb9005714bce255c802200a7a41b5656190d24da920200d5699b9b6709c8aa62a1ea5b86dfc2a9abddf8f24d29e96d3e646c27240be3dadd8d6d1c762f28f59ceb1203a1e28d5062d6e7c1802d59a113d502d8507c38819188dd6e2f3f040dd190a820943b40d40effe8de5bacf4743e6cefcd35e03d935adbbc945a035878d4370bd8de552d6644d9edf4c4534175f6ef3104e6b30f9668a66d26fd434887612c3a2809d506cf2ca5c3aed3b72830053a9a0dc65c7a03fe324860ff36e006c8bf04dda145f2082ac03b570931ae03b098d4a7ef14816eb1a2a96ab5e3825641d38e305035ac1022ba663cfe59102ac384db8093dc1f7e727a6912d133c45e33a1131be947c075db85fa38b95ae4596b1e91dd3a6fcad250fb051e338941420c9fd1b8d7640340aa5bf6d9e03983d3b2bdd2789714f4df6001f0a43b3d579b03882407a03a7bfa4ad41d2f83ad15ad231e4907bb93bb7a18ea02e44f0f3982adf6444a2af453c032741fa8d7ae61b0d7c36f1ed2c2e65778e6fadcb17f80842be8b0c5e8ec942def2cb49d6e170c4b1fb1f4e74e8ed5ad047706391d9e9b461d1e60a62a3ee3854aacf9c29348a920350ccccf7cbb33e01038f98e6d0ba1e178c866f39cc27fcee7fc0655b98592bab48d7f811426a99b1c121aadb55858c3c401ebb278cb6e95ebac4c6bb2dc6643bcfe2aed104e9ac0cfe96ce95b56eacc185f27eff04274ba0297842d1d04fa04dfb1ca5304308b8c42b34dc40724ac41bd49fc3265adaba394a60aa8d71c163a257ee016e70cd714f2f9dc4d3be796192cf22cb290930f5689706655206789352d374a585378666634b04a946279265175b581a2e96ade44ced9550d4236cab62b28b1f626bed264e6768ed0cf07ef873133f6488c93edad42854788976c7705d299caf0f60fa6790f378828e43d5b071da24d3e6a6e44c891f7e8997fe04211dd8028fa4b09e2028b10568b8ea7bba237ceead691d31e5c97e52f0c3163d57ab58c541098be009797ca4e00a7ac1e9e2e08afe34657b0e345148099f1c28d936fb923dc77a43832c964aee807a267baad381651887a19feafec85c73f3638a8e3f182ff83bba669a4a73ad44656327b4d521422899e827191bc4b9ff2b6b269cbab5de885adaf83aa8e08459c634f682b6a84d46709281ba36b4f9a65ded307eb5b5dfd7043a56b2e16fe8655e973e3d40e3f08a89690fe45bc87711c1c9cb2a478550c18acf7f0568bb0865f501b54e8f1ea07a6c7eb374e285e5a4141377b8924979aaf9dbe1867a4ba06e694a7c79e1c644bfe16ec7bd008c959f614f9cf9bd4893979380ba4b01765b7334a4ab57d7bce189aa0b81aa9ad38b04ce3f93665ba25ed8f32d2631906525482e8eb037e5ae76412988ce4e299a4d3482206aeb654052f2263e4fc7a6d659f20d1cc72dc7065df33c1e116b9925326d6e4b54eefd748348b43d3d1ae1947a767802c845e4655e84b525485eacdd8381cd6913638454c5d623f006d99ec1629b4579621e51d71c4aa7ac4b7dd215302c12ad0a0273d7b0923e90127c28c93d35cbd7bfd65c2012b4668ad9cda92c498fd98040566b6e159dda5465f6d9adbd076909b7506782fb007fc3bf27b5b8799d8acb606f6fd47563fa7eb0534ace2fefa1001d484baa1321cc266d360557fd2159844d056aa2ad1a69f0e55658c92570abb6a7287cc3dbb88e714a194e8171256c6b37e5fa0a3a1a70811ad6bee4a1e8e68ab09f1064c428c5dcb98cd1b7a14a76f3005cb67042a99bc9340bd9dc01e300a2677eb27b1890ce48afd02e333ab49fe921c55486a750736ae158c2c95d0ac381cd0ef1ba245a2aac7506a5f7c6b2b770b1c0335ec3d29af73d3c509c9475715b3b9edbc9e234c3b31eded60e21e60316a7e4371e53fcf1dcc601851544e6272f6dfe484f23b4a4e045a117ac962c80a2d044747f682337c51fef2ae9766d1f50f9e7c42ab475bdaf9ee4ba75f21dc4bc27c2c3e99c09dd826530f240e0210690835115f75578681b8ef20ce684368a5584d808c434cf33a6e66608b710478bec4f3cb232d2859dcf85799725634f7dc2984c3631831aff6218fe034ea37a9b327ca1884208baef8b8b10655ede148d665c4dbfb8bd2d83167414662c409f317272a5127b774c9bcd4849ebdb571e5264711a1446bfbc3a49c3d0277612da1d22b29963030abc76c891655318db238fb89634c0990f5fe047ab2da16513d6c4c21d8dba5e63142b8e81345be26f1d5cd9b9ea1484e741f8700fba7352346e6060c2132f9a2664d56db2f98a391cdb5e32b72155612a235d7e71cc9d65ba049539b073aef48c313b2c213832d248534e6dca314d6f3ce90240aa1b2600249e850ed61c61b1e153e0b5eb7c7ba4b31e58c0ea7f64817f8b0e283a9b0ce5c599a1edf0918117d5ce5a1d7d58342a584dabc0819bd782ecfb7c2ed9d1228fb8691ed8782d56eddbde20cb6d97a46e09caa272d0e46f798918f5f241150b53b4a6986fd4353c608727eced89587e66fc11448a08a611120793f4452664fcb976696a9efd74f18b8206d0d3883f05d6a0302db1b4b1ee1ad1632336d27a721f6c1e18efdb5aa351b3e5e6539e56a0d4b883e8b948ed5f936d56fcd956fe71c08fdb1a25aef5c7eeae7d664e538251f6baf673d849fab8514e64e133e106ee7f8aaa03a6c47c4d1f17d6b0298e2b77cb29fba3927a87163d7bc2c0693700e962f364d5156a13f3ba3a868e26412ce9caeda4f09c437025abb792e996250922d53c5b2d0de4317ed96ae3725e98cc2b42223e4f067f3fea04291f3c66a8dc7e83fa1730593f0748dfd7457a743ccb9ad7edac2a2455a6e619404477bac5eb1385b6ccf5371d0e29718a8c4372549bd914c3f876c7513997b75da2599d158f388c1f77bdca954e49adb0cc753b2048cc7f714315cf214b81c8739497c49b750c7dd33bd6a282e69280c6f45e921ad2d3372aff5b1547808c23547e33ae6fbc0372c36fec6029c546f923cd737fc90065cc97100a3d007e39a23072d217728188f198f3afe4ada875674a52c842978041b5c2cb5ed77525629b4cc3159bd26f84e181ef29d533d3e24ecce8ed5ef399171fa357ec5de94220d3e0142243fb98d3f1f1542609a34b061cd1cb418ba71d166648a9563cafac0ac586171c643626eb3875c433571fe8137e61b74ad945f134e30673793a4d01418d3df26c89256a49f33401cdb660e652447fe103586331238f8ad7dc9f5963b6cc44fe9ec622e3f1f1740d9a9e4e076f136ea89b9b350fe275a695d7b37d8939f1e96285fc235e48e98c52c33a8096c5e03c447f1c81a6ccac42b6d9475963dccad0254c24cb916c97c2e0e15ab6a961d94a0b1c541f41c4fc171faa3893053c84a33b6b0ec25c532f703d8d1b400c377cfbf590540f0e4498fd800830be952f37f124d03a8d6c8b03910dac91d680cd40cf898d4a79c91d3a645fe9d60c64aa958bbde2f71c8e1971ca2210e1199f143882d4cb005d3c5cd1b1a65f5e8855e466b32dda216204e86391b2e6559137b17b388fdaf7feb3e1a001ca90aa1e1fa658856695aaac3020a686ac1ce494ec8d15a8f5bd3b272b8216394d0d9b5d5f8af8206b82306509f832e0c585ead66dec6df749b0c7eca105574472dec734983a1cc32993d05d0ae559d65a89c2b21629a90bbe508dda51f4dcdb2813470293f7409fdcd335b1cf8953c8de21f9887a483e69030e90c3d2d17c37dd0632fa1579b822d3ffde15f61f4868aafa7ce89b1944e9a212387bf47cea2103476522a66f5a7f5297129625c702705fc790b0b35ad5c3ba31e3e0972f82041e6791833b4e27b4d188642bad5ad127a46c96dc14785d27019785d97bd819063e8d75badf0a27ec30a016f1e514247d750c6bdd124fe3c0318889418425a6b15d2a989b38a270838f421120de66a6a78f5385a33beaf4ff13af909957f740fe31d071b403e7b27153a4b73438dd7b1a14d78459d6ac73b9560aef244deb0cea099c993490f2544a1e05337869fca053f16d395518dfc5349cbe7bd36d0abdfa0d4dc397c89f1637a2dde4ad83d15cb3c23dabde9ab69946f5a319779271892a52022d0a7454e78b61718b7b79d1646a5587fc36a99c562f6c817799b0e6bd42972a7a32b4a6cf73e50a63109f0270b75e699b3305f22e44aed0d7eeedc2310efbe542245912689fe43cfd95ca414336241339e0e5611556a56c0df0960412b74293b9ce780bf0f5511421c138a6c4f904c049fe2680dc6ba304beb9b4d1f89b93a3431072d0a92a1c739552de036b3ee30b69c9e031ea4c400f12c6d341dd01951a971c0a931e1cb33792468e8896f8acea7fb908337bdf8e3bec6b14e85cfdbedcc458bf6600031336a3e8683dbfe06edb37b80f19846e0adeab5403b83050aa4386be5039e94db5e62e9deb3cadecb9a64d03c9806d5c584b7d9a03731a527fe036f30483269aa051dde6c770cbd08067f1d81b480bb4497b63862e8898c236cb4510936cd2ccfcb4b3e50003603430020e36a4ab26aee0262e7158aefaf2c4b447cc16ce1a5d0d114943b2a4cdf60b8840834b6384b1a150b5a3c45cd9aaecaff8dfd0922005b844b7f610c5c1478c5373a6291e06673337bbff92396f638162e149f7adfa84dd8bd5fc2a51edd27924a9135a8e42baf1f31cb8ad293f9d5c61a849ef2be47907839c10fc435f55eee4e47156ddb9e6fdab77bd259623ceba59d14c98819c1501f30d5c5a6566db13424c0b41d48eacbb58a6d75b028f120d06e7196f21c9dd5f7e62e4d1a4725810cedc7b479344161c09e5fc9edd277a38d9c45f9d3faaf84ba75ea24b68848e35adf7fbb36efdc412109a042a1d744d9cf3afe09d1ee5f379ab36fdd0c3f4f3c00ffdc7e7632ba3115dc637a001b4c631e9c2313a62eb9f9db79e6da229668867f0467556d02ac4df488275faa2bf50e73920d46c5a301d6ab508d0c3844e1fa12ce26c8fc7398885983e1aec2ce91a9ff4d79c52746896c316016d5945a107307b80553411e52e15d22f9adfe2819006aa85d8fdd1d7348e8fa541cb8e2605f0d903e9467f2c9f9d8be2214d4a4a6a2ab647d1b1b6382741c71c9fb7c7be066dad42cb68998feb50c44fd9536f45cee8197192a59c87b130941b38e07ad5cafddaca1b0eb5017fc0e7195a88d679eea0e04f9745d31073058c73992aa956a2c8c4d06f055297d4ff090f98c47a981aa1973801e19fa164b378c05e5708147fc502462d1aa0f752e3b95d65114ce2ce220d4e0fb4637835b217a34d9a6be536bd1cd4839defb0da4cac08c98d49a117685cb9494b74caea90260e05e95eff663a3ed2f249dcc61449f545b397a8b6e88022417e0d279383ba937e707a7d139cd3c9e0402bb5536d18b43bcadf75ceb4449f785c1e9c76a72bcf3c7dabb856435352c470d45c5abff838d1c115e621128e799d5b015474b88b2b646c5507a9833ea3d585689de582edbe6d8e1ea16e94cd6f171ec961286cc8b202f2359eedb94c90b49840a666c25df5cea04c23ccc106646a66084ef8bbed85e0a4b65a3170bd80c23591eefef70537ba023d4e7bd475e95e0ae01f993d384d36b424e77b1ca683449ce55183d114cf0a5a2302080ad9a0c64680be41741905f103c92a5b87f2add2aa71ca8319ac93c094f54b86ded82a4059f55edb04b850ab036d328fe7e9826e440ee22872406790e50fd28a4d9d51a9af681b6fe69ae595c1c97e077425425d8a76d2b248e7a8f002868d2d8b1de7299733837508c7111c0a150044b2f1407c4a09ea8fade288d1930fefb7e3abdf3d80934a3b1883e2c19805e116ad506a6012284883408758034490b9cabc410ac812c1100dd87024be46a031bf83edb2398fcc58cf02a077b07be5feda2cc206700899926731abd95872980e7f36b52a370c2ad9142f4a64139859cb7031226ae288926450e678250115814698ce4ece839125e5e32889da946f24e06bcbb27d39b7589208da63bd47ace879b6f2245d2ca7ae186265b3f9bf91aab440014a015e0c09af3dc639866067b7b6c1169d6e0cc63918e8288c88dd19127c865c340e223c9bdb30ba03a4eea229a51a62be35bfd6ee5fa1b021dc369bfbd1e71eeb82691fe38477424920763d8be4e6a19237bcfa6f3228119d73c973d5e4fde2bb972e7de4a51ee27e620894a479ee350f05c6cb0dd931bd9b75c60660b65d903924aeec5d51c7397ff571e4f59cd14c2b35cd3d341b0bdf79f06b354cfd44e5e9f116a646c06d3f577a747cd945cabd16f491eecf6af0bd48b6643c485a34bf1764612b3f9a97fb8825e4fd921efd16391374a083c8b0ffb4ef64161ead6684901278817f467f18210da480aa7925c039f8a2cc7a81afcb75b5b5dc747d3ed681d4b55029a25376667f9d44f6f22a155342baa764595e21960aa04cee7f3df0c50d9fd53b5a58406d8981589dad7184002fb8fdf5a8e35a1e8d386d7bc841dca238abe4342613c0ce28db32e3c40d807447fa484106cf8b244531b00602d8b48923e0825d6db061e92493004ca88e5c56a39911d316c02b2e8fba525bc9a05053f42ac4ae481de82504c0281abb85a171ff362d6bf5d0d62b0190eabc8e39c117b9af5891c98ca1e9da879225f81a9ce96d6847cf4a4d1a3c8f0c3d11d5b0e539e45864dc58fd406c6903e578baec9a89a44765cd6e45b14b7c6cac13a72428a7c78b430bdfd8c0fc2b88c88f144b832b589d541808adf6a21cdab0be327546e1eb249681961a2cfdfed236557d6cdbdcce58da5614f41458f421371b47b6d268bd25ee58cb6d34e807641ac8339bb1689a62004d30decf6b7851a94cac0a3d124f30ba7003aeed29ddef00f82ddc2ee6588dd9e1b1f96f9f75ea06d6f6465a1e4f5321ec9acdea72c5266a9f59adb5ecfc031f4b1ffc58d3c9026e29d07260cb4049c760aa4dbc51c8f6d832366ec7f6b4b6ce95b37a2cdb30c8725e4fabb5beb9825aa68fc03684cd241a585a88cc4f8588fffec5faad031ae6693246b2f70dae97d7b9581d593a5b683ce0c262e1d224dc5058a737c1a24360f75770586dad22255c559aebf76e8156c1aa901539c2fc0054d511702fbc3572a445ab5edd116c11f3f9c96557f54644c0d66494d587f23e14c76ada816ce316f27e132891f51887b44b41ca5049978f60dc133a9ea7598cb633873c09ba51254ba313b53385711afcb0836de736e59579573c9b7aa2570c58a6676c6d7df767b0d0065f30b476b115e2bda6f4c95a8e99e79b80bc3a93cd8b3eb4989192418a7211c1cbcf2a0786190674016f8fb00b92cb2d17dc52421b6d2a52af7c8b3658ae8eae2a65752451f5eea46e3c6552a7e9c1275466de0e70b440fdccf8515613bcc80ef5af9edc6765fe70ba8cc9cc88ee06f61c0579b543cf3dd384553ec748ed02263156bd57b01842ba4cfc08b14864eec0ecdd441a192804a39a3f20c82cc2d51b269fa02b8af2974bb7349c4fc480ba794c1bdefd7f10063a5d3244279bb988f1285ec3094c3fbefa0c5bc9b024b5025063176670edc4e5dc0bb44662c2cbcd55ba0a51012447d3e86a6f498a56c21dfa429a12efb09865f06df8f038f8be9af855af6db89ae78ed3931bc6976bbec4c55d470216fad2f6a09e09e4e592792dd07cb4b970104953f24f956dc320f350cdc32196487c605508fe1a1097c6d76892ab79bb6d8523ee0577d334137b1eef821c977b3c78140e2d85013aa30e6efb79c2f3ae1113929f0d1b56dda226ef43b3bf9542ad4ccc10374d7c5e90e91065b3d0a453140273ca2c90a8aed1f6a963ea87ba0351a08fb2f44829225b287ca11d1d5c837e441e2ada06b6324fcd9c5dcea8bc004727a48e87aaa19c1399545a224fde195bc80214ebe2f54353778da3078e165d8bf726a8e4bcf18dd2e8ba8f8de964c5b422b4e3966dafbc30a6cfad6e0689c5348bcdc858a4865cd743114336d60a8265cc31d92d15a62ba4f91c4c5cb140354b699114cd279b7582a43cedbe60a89a43bf38264ced48737728f942108593828482af3ca1f26034fee6a6485177f13dc548a3f8f240a1374e6afc2b7a5e2b4bd94d91a00160c26907682dc8406d5652adb8bbb481fe85994c9ff21fabd5fd49e21c3d1d741fd1570fe420347cd0bf4c6d22f4c82fa30f701581d0fb60eb1bd1fa17c473df6e4ed19ada68e0c5efea39814d783fe70101ccb0b85f812e9f5596ed7b8253605c5792fd8ffc3bc326e44b0d047c401cb4bc017a28e7c7bc63d790ce2ef356243ba69ea3ed018f24d3075b0d641bca696bf4567503b0948d5c4d94ae114eb927e913deeed182160918873ae99c3f2d4c9b6230e60ba2fd6c4edca2bbec40b8ca00a0e085839c6b2037504eef9c77b2e79e62f19d12ebc179618f3ed45bde8981f4a626f6370317d0d35dc462d15bc35db41bca6cad7efa465bf9a81c14dbedf29847a1cd05a75684956f74eb01aa37b9425e508a49a30eab46d71126c6a7313efd19913061ba07847b235e3e4e88c67591e33855cc68f90431b196583174658a63708c240008220002200900200808c31825052145b3a475cd9326b392d6c2d22a2ff25b93d77b0449db5ba6249394325904f3032f045cdf838085cfaa4bddff7f0b3a2a71c1e71920e98251c5246928054782e5b708b9500d0d493d54dea3cb0c92ac32614887c4fbfe302d3ef5367b1b68f991b32bf7de0b24acb749f40e80ac662dfa07806e01a3a8f5ffef4192073d4c3f11c4b87a7aa91bc0aea0df74814968d2e0e1f8d9df63e9691e126f89e7c493fac9a18667f882a84b88c631046a7d2149922467f8ea6d028346494c276640596981f5fcfffbf418efdf0e3c58f536b7c2f8099e317e62858fedf2c808f19a87efbdd7cae77eefbd426070f589876f74904e8290dc922449923288ea6d16212d39494d7d0732aafebf098ca9c34876fdffbf8a55bdcd6216178a2a5d28ae7417b7a2e7eeee7e7b4d9544c554feef62e8ed7629d8005a21872e8a4a7d656991a1d7fe65e536cdd84d251e19e68767242b0745481cec9664d89f1c46afae2fdd17efcbf705fc0a7e195d1243603076b6c9bc31c667e687247d58e2fb7aa6109167022ea89eb2dae0426bebbeffffbf0ca47a9b4b24036438cdbe0bae3e11f66d8ef7de7befffff7f0d8ce3b248bc233f20d3f7a27b94498e251f76d8efdf7befbd42a0ae7caf5b71a0ddffff172179f0b3f4fffff3ffcf63abde66980d788cd9a0878f09c2a72be2ae9c9535e52cff6ff8dce89541374cd8a2a5eb4b92233f3283871d6176b667e4ffffaf98fa365f44170d879a680df5064af06aa14425a84372a81e2a522afcf0d2c4032da27cc08df4c4b041841befdfcac4d4b739fe154d6f491351e6bb498c7e1eab5f5ae4c00537a3d5abb7f923da40ab6886bba084502faabc6c78111aeeffff7fd0a4faa2c1d7236a0292e4e9aec85ac1b5a2cbed7c127c629483ecc1605d25c93381a732592e9d4ed78356a63c957cf161c27141f31812f5c81afae2bf1001b8d098c19544c50716cf8f2f27315b570c106e365dd5dbc46ada2a9bc2cc70f77c066e8a118a294925f9f3f28379f93911894311872c1cb870e8d23d076378ff3dcd010b5c2ed4c3367c83f110d9db41aa005289d9b154030b583b1662ccb4803d54375d8251bc25514292d778e491aab7c93335f25495b351868c48546fb308a903e2d20a42841479a005312b9276aeaf90af2de2f12967654d59fe604a7c04ffbeffffbf922346f9bf01512f04e9b9c012036c7ac14aa90165005e8a3f3540f428e1e81bbea066622528fcff47c2d1ad1a61ca59af876df806e39f4fe6bb3e4b867a3908611bbec1580879a56749500d1706b59234127dfeff3f86effd6081801f4f760c591756305c507a4203c8510e1ccee4e20677e11564bf64512e55495946b8d4e0f581a5a00f3a61bd4da295a3d015b1d10ab1234245bbffff203d32c88f0c4244062922832091e4470dc9bf24c6ec85d1d2d01130bd2a5b25d8f0012212833ad90245076fc78f18620c89817f2ae9d5dbfc8d4465d1cc032548d876048b058e2cc5ac2c5a42969e8008afc01003110b8fe9c7d2d6122536d408428166ac7861b6c028d06b743282b89682c244a5cafbdcfbe28771fe9359d1f56d8e3fe5acac29cb1d0e06030fdfdc1eb6e11b8ca5048e2bbd364742b6af03ac1fab38a7f3cbd16c757c5587061541b15b9a4f2be3800eb07580b5daae75ce59c3f645a6d87d330786e99871bc42705ebec54c312cc72ad35a4e04db248aa9099196f97c40603098e7c4ceeb1ce7f5cd4270a4ccdddcb837e94e828f257cb973b14b4bf468d74af0ba84101541f1fde112ae6c803c874aaa72acce313b2ee1ab36fa4eadb5b66b1d74f63d0f1c241260a276adb826c3b51b56b371c00ca6c120cc609ada0829c41a568bd54098996d1bd8c00636a062758c5aec18b65875227c694eaba1c04639e21c0e875347d83a426669301c8b591acda2e5545c12f2cd72dab0c8529ec3272805e4397cd2e5f283b16f141740ab9c7d13c1e6145dcb4d3a0315d1d8715b3a031561b18fb3a412bbe082d98dabb56bad55576badb5d6da15d75a97544aebdc35dcb480b6920455313a2db0b6bbdb005cdddddd6d69770b2d43abd495227c002b25ceb959eefaca395ba696ae28296e5a299bb41a8f724e4c43e048d4113613285631bdd302da36dd33101463106e28a594769d976219adc9f4d21baa149bd3e6df6badb516ac46b5d65a6d5e3188eac23a73a54cfec050098e58c5f38646332d684c6d28b585c8ca8d71121800d4900731b7d414260d7bd490739338346a41eb1cf628e6c6354b720e04b679da0ac5dddd7d45f53691969ca4a6aa2e037c57f702657aca59595396f706dd7b2f96a47a9b4c1b6081f2d948f5f3ffff6f141444516c49dab9d57f8f78a5ea6d4e5509ddbbeb26de7dffff7f5edfe6f8ffdfa3a23748fdb0f58008d0c72b9a7256d694a5ac67c348a5687b312f2b8235624b564bc5a5ac25aea52e213a21bcfeff8f81556f732b458c30ff3b2c5f6a34f160eaa1a16406d59c32b24fffff77de778743212b050a5c6517a17b65be1ec6790d3ac583ed7abc7f49bff7de2fa29ff8b22a7d630f045652a5422afebfa0204b308b51f90383179157911792d792979397d4c5ddeba167f774939490f4ff8fc3a586f4b258113d0e21efe91101dedf58b7804ed8baef1fe2cae7aa573e960f6da128d8231fe6c3367c8371932e22556f73aaea8a4c5104cbe67816af2e16293f30d8c5e3b55258883fb8cae5a310ecab46a17befdddd7bef1554dfe658b4fbf2ffdf34a6dea6cf89157a636460707112bed0bdf70e21f66d8e5734bd7256d69465cf865aa4576ff3474432a0481116fa72dfff532431a5e881fadfc9764bf5369da4a6aaae50ecb02e09628c86bbbb7b540e191a394c5696c6143109ca91846a848a42638771f7a7bff0701f496a21b222c38910304945973292e4090c8cbbf7de17c07a9b41a3142f921cffffff93bb7b7755a6074dc2db498ea03cf45ca6becdf17f2e17a175f20161fd4c7119baefffff2f14e2e57ffb630b0625e64554d1d69799609504941047002265edfab6fc1b665f6ebc08d365384647b4e355b24bb9e539f471946772e45c894b18f1cfffff67dda19407ec7c5e909c80c971c5850e1838feffdf0acc05157777cfa105d8547424c9c0000390e730cacb5101ae8a3676316a0067f81419b4ecfeff9f0b8d91de1066a4e7eeee50becaceddddbf2cc58cf4dc2a4aced9421c111f0255414c925c8199b1647824240319a9140d80430b26a09f989f211d4ea29031ecf8ffff7f20bf3ff60a8a4673218943c4e4dcb4ca3dafba8754e13050ace7ffff0a16d8951a64fc5cb8bbbb0f1956d0055094d1084f0cdf5490e7b008950c803c8745a4720ee3c6296182514f86a04811f291f245a3257482631bbec198449409c4312a146fa0c1a3eca001c9c969e0f23def79ef46092be7306ebc81afbbfbd80e5b4a355eb470b72d58ec38b553c96727653be4390c72f594242be8f3f7ff0ab73029d5b8b63c87467e467a4627c480be7c294d3980d1c211e30b4a8e20a0ef852cc3af21158e7441f2a5eab564f998ed3fe498e75029f847ff3df80689f971ecfff747e6e23a219215c3758e8a1f528cac90c5c89413a91d225388d450a79002c0af223a23567e98bcf0e501098ce11c33e44a0c4641475c7afc49949328ab1e4d56b910249aba480d9b92e4ee1e3609c911e4396c227216b32c2d3e8e6df806e3ac1764bc0d6cd15a821185051d4a4e6e978f5a9471866f6253720ee36498bdffbb4d91a20b202a28f9f2216a0d814105656815c99caba1c973a834c649591c1edde8e0353c223201f733821e9074075f47f930a53d4cc155100c35940f443aca0c51354cfd21478b281d921a4a0d8ef6e003821f1d6589afe78b945c0c875c6532cf219115462c6a5e74140f4f5dc6d041224128508dd4532d4a08158b1d43c1ddce74e106f7d3655996351e9e72aec4bec2038f9e072732a2fcf36c392b6bca92ca7fcadbb13c874a5e9ee0fee7ffff9180e9915950422e45862f3c212e3ddfcfa96678600d2d563feef41f48c9e80e83e090bbf10c3793e73008973c93e75009c8580582136cb020e3f9f27324f1d061545499e7300a09d528fc27e5cd7318a5d5c14609cb731855cc396c6b866cc9dd3f8c0a66993c87512f74d5ac181ec108aa756f78b4cb398c836201ddd30d4850e00174a3c9025a859150cc0e0a9520aa17a67c3801a01b41865e90806424c1d9f8bb7f0ae20d136028066940d97126d9dd7de6f0118b10c3239fb2666834264b755f9c77dcc400a771fa0374a962779a456d1077afb94fdaeff718e0309813dbfe39d2cdafac58d52d4f631fe7acc16a393be79ce338ce398e739cd7f1949976c44eb378a4e56891e6e98e1fcfec343be79c53459e7f5d28d6b55fe6448d4d99ee5895b57f379d3d33b1117edac54deb5f6ea96ca2c61c97fbbb296e99138d6bfffcfd65d531a0223af6b348bb69fdab7536adcd6aa359fa4bfd035152e02b6d8d69cd75feb2a5098049999b184899c3188926310910c0535aa44c36c17c74e6a929e7687e309f5561e62937b6b400704d0b90b2066cce7da0f9e5c64efb00556d8d33e852561013e49a6e5ad038032ee507a80988b9590b15f0a606147b2d675598b590bb1641307705bc0c6a9415e72620e66e3c017102ab9a4e02a8dc13e40a8a36496cde64f0e4eeab2129478a1bcb4a10d886c3e1c086d6ae959132e7beead547fad896b78633c59370a8ca9cfb72e746a73a22e7bdecab237e75c463ebf693ea081b8ba72f79af8e98998e53831a9b1b00b9a1b64cacc7caf5f7330499894ab1b02896a340b188bc9ad9826235291bc3203a9501e162af91604377bcc693ed5cccb33b3e8e77e30e9d1758fc20d89d5bdbf5d0ec38ae3bb6cade6b735e60b1894abb52a6d8ce9c7d04c81144bd99d13ab082c4303c02ac661381710412c36aa5cc07a48a146c66fefbb5842362907204791e11e3cb944e1d982ecfbee55a2398b72a1bcb39656674b6a4445133516d5486a9b435779c9b756085e3c7235cfc359b083c1ef158ebc0ad957da3f86b29d87c3071b6e5d22b369b44e330d70c5747cc8a558b2eac595ce8c2ec5db5e84217de1fb15c0893388ad0e5d4a0dea92bbcc44b458b5d2ff112f3a8bbc24bbc442a3109747e3a3e1da00e30eb08757c3ac022b1594738edbdbe7706284ecef53b549e5c6ddf6dc5d6da766badf5b78d41ff9b707737515598a836fff69a5388d99a3362d99c502470335b99a675aa2b787b7b75aa4eb96ee5ea549ddaaf2e36a94ea53a3911709e448bf1ffe36ac3187bf5fe9a3392305b6f3792666bc596fe77775b776f5bbd7bc4c6ce194958acabad74a94e5cdb02e9102aa2620bb4c06c85340968813b5f2f53cc79b586001e272d1e6d2d2782b54953030194e895e095f095f0e512502578257c5cb15947e44290602be744a8311a146d6bdb0073c5df1264555bedffef6f1cb3ff5fcba9c8d904bff1db0c5111b5bdbdaab009b6db04b30d51822c9913956613ac36dbd5d2d93356b1776c698e6d755aa5cd8a8d9ce618274facaf55a6e26edbde3e76e34aabfd458bebe3bcb5b63e4eb559ff3a9dbe8e9963244824c0c405726e8a1cd8aaf096a56d0b6125e7706aadddb359f794951db1538308f2c43745a829dbe528ab1941bbcaa495793a0ee7736377a74066aded9b22f18a446e9c81b8cab99bb173261e9bce16eec4d3628be9ac8d73ce2e7e502fa5f3d6a9719cc1c394e270b859a3bcb2fc119b37398f8b1fcc1c12736cdcccc1cd9cb26f22d41b0664282dd707c44924e270389cd36a69a7c515c051b17983279d934e3ae73f9d18a839a5a53aaa2ba98ef4a2f3e920f02a29d7139e2164c73d628bc92165d7644aeb07b52b0501945da8c4adfa7ce7cc0809009000b3170000280c04850382288a9234c7743e14800a378a446648344a1c0d85026150140844410c03311083300cc44018043028c8d2d0e4014eed174c4bbd5e4256559250678a784d071a2f4550eaab072e2e6854e9a3f81910214337feaff0952191194d4bf7df3623633e8fbffe23e2e1abf5cbb8de99f2ba5e9fbb47a08cdc7d96814d56ba2a2295e7737051601d17a23d3c97b8e212077e4be1389db2039b9b1841ee45f8438c54c8f1a0bb2357f6bf2ebb1aad5dd3ebc70c5a3f8d9abf52e17516cfb34db02154928c205c783a5ff59e5eda33140ae64c2ed10b8386535d645489c4a62baeb8bdfc8a858ad34e631fe2e28cf1b51500e076bdd24a7a313119a71988baa1775755e6a79cbdb7dc58a67edce494b24228b57d67c4a27ecbf6ba89063681a17b03051d09510d29e39d38ce8ac221772ef56459c1795da054118952853296edb506a5dadba6f56fa74d29d2f2943046799df33c8fbabda8596113844b4b85abd86626913dc2a9c681ab5593ffda7c74c520582b3593703b6e56a022353dd23480d6f0d9d93333ba39b5cb64cc0090bc6bdade45c3403b31f1413312780a430314a98ca31fb180244405e862101aa0bb829f091f88f0ce1a1c1b6736a688f97ab168b152d590037db7e85efc4de597c12327ed45c0865e3b886373dad8351077e3cd5bd91c16ad172b669081f1bba7780df11a5f3a09aed4d6cbe2f7c3fcbccfb5eadc80a808bc97bc1f8a04fae12254fd5f0b069395314fa204c5113e992839ad0cf9a68bd75ee1394a95678ab7cfe1ff68b0ea68a16836613fd93a9b35ae6918d13ebce980972b40e722d958c93471d194797047883a60d6542ccf6f1b907dedff81584fc481b56a231ec6bbac7fe98e3a2260aebeb9245cb3bc76904c5b766cd4eeef46f5ccc1338d4db58128a51a64a07fa156ff2e54d5e2a7005b5671f77d6d9cfee5df37fa2c4cb0d9840c19a77edff35cc92bb12178900b6d99ed99e0b8ceebacd6f2ac190ab6845a2ae51b4f4ee211677789c906eccbf28b08142a6ea8c7d4f4a83249478d99a4d2f96c851bbf36f681cdb51ac4b5ddcf326062bc36d0d6c673d187c76ab2a37ebaac2c99ec1a4f85bad5dba4a6175402d3c513085b374496696d6f52a395a4d2904aa338de892311081447fad1fa5572aa275b7f095a58b5598d02141da5d969daf9789457c0e3df9b51661b5cd94a3abe79864ae3d439b489b3acdb7216b187a75fa42c1037b1bb1efbee9cbe90d701f1711118a4f09da710753ea52f3217978244908b8cb2b4a2dd9b29fad1cdcf7238f930dae7a1631416b187a75fa42c1037c1fa9ceec7f81eb825d7e91d331cafb60eee6bc1d08c7d92b4102260afd7aa52087d0017d02421f2d18a7e5d93519090c8e7d517c561d9330ab089c222fbf1f48b6871ce2a3cddf4eb5dc653d00d78088e38c05791926804b86cce41b27d7459f933646ea8bd8a971bcbe504cdc08ae3de741a2cfbf95b7fcb01c7f56c556c24c1819bfadfd3e7a5a4b19ad3d0e54e9f7031646f7219cafe4b14cd5b2fee3b802f38d1581f5b7a56ddb7fe7b2154e4bde4b0698d31cf76e6aaf447957e398e52b67be46f042453a9dedc530e3e43e1ca2431f5f41c4ef10366c2b20ba3203cdaf1431bf32a24fcf7d109f526daf710da18574dbd7f4aa17b1e7b29e133fe63a9946cc101a0c31e6b54982c55653488b1f0b8d546f317e9f74cc9b80c66253909c8861be06c15de5fc53f33410ba953cadfe05dd5b3fda2a22c54d2cc9a4c6bf3276b07bb43e6b5f88b04f0556cb07a97b03c3bfdb5b1424ac188503e367356c3d860940fac6f0a93579c01077c60c72a2a962ed6624de0bd0fe88bf8c924c2ec98579b0de7d7648d2169152a026593a4749ae464fc826a7cdc63adc62a9267242fc31c8ccfe37f299f563da3e3276cbeb3a461da4c3cb3fc243bb952a341714c0f88ab7e81dfc3d8c6602a75e56e09d1552170c260554a966263032b85c90529f79851950e805f3a0df574ff7e5a0200b8721bc22b684951be1cbc453412f8c1dd72608323987f931013a5072bd0f3b7a754529d689f63336a36bf1b8f94f74e100b41e78e9c1feda952cead86179e40b21b750f3a3e47279b84d6f01a6463b52e98ded4bb7c0ad4f5646bac849bd17f783749cdaa48832b68c33a9a279e78a240634541779e0feca9937033b3121df74b6077252551e37afc6d12999dc7250ebbc92ac13f2bc6900089ff0cde51cf3fcdbc24ae0a82fe9c6933f3eea8be9cec72f066970d52fde815e5a7e578dfd04a3d6b19841877a851b4d3560944317d1505a4da58095d96b9a2c6f9c00e8c8f836448927d2e6791fdf7f48fe92436ea9ca8ad51d2af74194f5937e0213882af63cb506c500a0a72d4217fd58301087c07f4d414558f106b88184aaa91dbf2c77042c8081cc55ed40178ab43e2f8f8f02c791770688221faccf55f87c30d3de10686cfef779055557b7f672500d0cff9adac065b37d6e5c064087c95a44d80d7a542ea032bead38b4badea4fe0762418f637fbd852327ab894ef0a8d1b6fcafd2e8c844ec9c2a078114bda8a6e1680c19a048686ab5f3d1be2fcd7e8bb98c4b914653521408f528a94661e4a7648fc5f26628d561a63a24a06d893b0ed329e89ceb097dee6c27b28acc477fdc3754f6098fc4339c03662ca46f9836844177ef7005bc7a91656f3db16efd61825ffddc88ad5585f1141c402d8f4716445777ef0a3988cd618fce3fc8f1c52657d051f466f00f9a937a913dba55c496f5860c406ff86e405124b5fa0d363e90d402f4fff436f263a24b54cf82bc38a4aae795eba96556f4c5b08e52286b79660def27f24ac2a412c7d05f9172a0785a8fcee34a9552c462b57b391c4548faf0e7256f29957d16bf0d95448d96493ba0e0c60da685e4c57d7200069129169b67b9641c3ec47874ce2c8b3cb0eeeb8770a4957d94239a6ebc0865bba29d55b20744ebcfae8b8caf4b29663a946388065d7590a622a983514b282957649826c139244373e8c1566cd80e5000e027879cad4c7069801b530a70437d2d42bad24ee4c3b003c0d3b2632fa0a3f369e0450f7ee9cb328367853cea081ab115e2ae1c864bc88e7f4709d0e0ed3b09a3f687ff8f49fd28dbea9e5c042cc64f2b83afc9b3b642875cc53a5b4c7ab4a5c562b894c5169065462423e1a0c4a1a1b0a7578b6333099cd5026af52e80bc361eb94a6ba3446891ed19739ff8f749d1ccefbc2626935e1cf3207094a0b5a28c5329b280661ff0c26d5851344151886c9dcb235e168d2c5f6040dae5b85b13d27ba9279ab043f3214f6082e3c05b892d013782e150d03a4a725932e055fceae3f443f3adc1ce1e63a08c1c20c821f0ee347b43ed25f6aee46d7c73b0c9689440290a6a8116ca721bd03329a8da193c2bd60cb1b1b46e6e8e1db463c6b8d89eb119806850e9ae5ace16e361c6ed10d5a4d72fa38a7fd3b5488dcfa2e5bd3b0f10c15f9eed13d675b2e1f0796782b7c075a7994b9f8f419fbbc6f89c2b3d1ae83d84aa1b311893617650964a38d9424167a7fc815ea64b0abf3820558b2f2063f14322db01f0297c567ceb028ea6365ff8dffacf1b49c94a8b88545da540d200053c97c67f4ab1bb3c740e4ff307ee67cb84b092c4e474b1f78270f62cef62ad40cd07c25c214778e731c92b81a41d2e8cdae0777579820696b2024e9562f0cda3bd67f7a989eb6a4c3a5d96f76b4e2e5d2fe68334e5e97b68f58b862c0cf30e4aae53eb6df73da17e3bec6adb63b08e9483d5c86e0c84477755c9a874fe673434ce9ac731b3fdc817c6923899e5bc67f67d2b5f4bf5b335e7992b39ccc6704cd21c6a1c00b73f692808eac28e51d922bc8cad28a3ea762bd61c657140fd080c7a7d90f04ba29f7f1be7f04efa0fb09f0e7fea9598f1bae408a27cdd899d3c08a43e2046c4b58b688c0ea6aabbb9df44c9cfa714e67837a82ffa6093fb7cb94bd35e6a0456d60515c5afbc2361eea8e182716f8bd936d8cd6a2b09de73eda909ce4b3adc3985d2920224f613076e1ba17e081cf8942b235d26ab7bdedba443c0d0aff5b32b3678d5b6efa67abcc515044be2507e0b7a8fa8a7eeb344a101cf2b0c0f2104094b2992f0b66e18e9077d9e0601d07420ff93232e020f0f9560a8c965a0e4cd010d9ff1666004284961d9e9102591b2e7f31ec7a6dfee3225c879fdc432754f2c53e4abdc5d2abe5b09d484bc192b83ae3bbba022189711d228e33d95f5cd414c7630198f7acd5baa10b2db99e38814e4576717cfd0e25e6a551fc535a819dffdf23a5e2c079a988b2c2a2a228f5cfc238a9018978c2b6c6b979e8fb10e44908b02ad12148327e754d295df3c493a2693bccd6d72612d74616a8d61a6ca6faab184b82dd9b8c290cb1926ee22ae0985bab94742eb42a4da8d6412c4fc8579a3c23ec00c92a95e02472e1ecdd24de3a98898f2065b7b9384e7ff51f4a1f03d6a6c4c8aa7a5691983842bab2f97ff5c3477538672c27575a8cff1ebaf9c34336a65ae222dcfbbc20642d32a2e461f0a2dca0887ae42a94254de708f4f94f01034139bd8efb842b881081f12cf0e6c6266aed4c7341d494a6702cd4c16892d27b29095b7484010190a54962357be24d2aea7834c5930105b8a242edbcf76b1fb9a7b8b11a037abfba267b280a879b7d1e3206fe6427115559408efc772fb931051afe87db2660144741385f08834444bcbf5d9bde54317e954210bfbd8fb786c9603ddcff590ecb402a59eea7469f7a37d1d2782c791ac703460b6560ab909e73cbcae5299cffec0e01f3d50589e4c642f8468c6cb439d3b30f1fd09bc54b1f9062a0d7aa699a0d30ab7e188c117e65717cbdee4cac1badea10f1bc22b420498fbffb20098356c5cb93b0546bc3804d0452a49e0a92063d9cf1dd18a21bc5e1fb1fa5f17013cec7e72f2eb2ca4c7541c8a71f86cd2c640c5e500aa25fe97610cc87a71bded107a2c161f1f0dbc164e1f2a02bbe0b31394b4e8806a7319f90bdeed33823f6dd0f600353d8be7ff1fd2b0fd4d3947aff74bbeebaf7b7000a666e2c287ebb087abb2804b5503ce71700653c780551a29b64bde174499f67862ed4815233f175d024c6fe7d91a56b351b8ee14d76816c2f4a44f29c84a57bd514c1cfa627bc6aaccc3c5a7773071940fa6a20d7cb722ed17c665f3868c694631579cea9a99ba9a5ce146d7c8f1551a8e131bdffa9665b38bccb6bae1a65337cbbce5d2bb175d71e12e3d093c9724a63cdb6382dc84760a1ea878691e49fa9464dfe2b94785a3f3902a3e6117b120ad5c2ca7cb2358395be67dfd28d99907d0d91a4bad064d6a8a418c342a1bb7c314dfa8f3d2e7a70dffbdc0a09e34a3953f40c2bd821dd41efc2be496b5831e9694c65c03ccca1d71d82d89128d01c1c278c8d28d06caa003daa83e4685d6e47f6f1e5c6ff1c872d01a24ac9511718187da197e017fa66ad488318866177421cb168729563e15682a1e28621c553880e3bd5bc5df16a1015d4b8483ffa88c1cc3329e063400437e819e330f85f16d30c6237c19a7a5e1f771053125e56ec4ddc4e493f3a31b4607473d29bac4fe1d61e65f14c72ce576c03e1a52aa86fd00910da9eb552ecf2b0c963f835e164fd43de567a7c78c9d701bc658a3bcd38e9c23c31f0b80d01fa2a66d6c0b6db54637b7e7c817cf6a757fc59b6efdd0d1728537f6e9b3483bed5c2f29967af2a0146e19698eee12c51c31517b1bb692b0fb78296a4c6ecd70afdc62338a298b84bef98c46ece8d181f7841449fa8de22d2fd70c7cc350bc11572fc27311669a943f5c3d0cf188aca77bfaf4cb5ceb31cf98fa3559e82684e9c18ad2da52076336fb0c53320a0edb858a3c3b706228d9063740451d2f16ae82682d5b28f5a5fe21889b98fbd785dd55267c6fecc9bab0dc2e4c2eeec025860610aa039a5d448d9a0c27b5bd553daf571272c22ef86105b0919a2ec94a34130e03bf6b7d2bd229e5467885dc68de1a4b4f0c8683b028a499f87dfcbecb61ec00344a4a023fdc1be3e02ceb3e2aab3476b7f9da64f8ba68d7c4fac2420085f4ded87371c8db3032e900b30116ec4416788da10e4b425d52070273f6a0956746728301f0f3e564dcaf348fead42ae28ef007433c3349dbe8eb8aab8622b5387ea77b6ddafa21d02da7c9dbe9d314f806a7b906177e6c49800a6b646ddb36379c9d529ca1d6dd7c987c6fbc5cb79bf9dedb1bfead3abebff79d5f1b26bdbe5341df53a6f46cf7a027e077d87a4429b03143b724775ee341dedae833a008c75292757e72fed5258a877d0dd107adeeedf3b72bfb3dd3d546c0bb4f8cf7f27c4359965f5420e4dda7aabd4c5d654bd0266947028d22625f2409696b15b4a9736a7f38fbc3dc4ca155e91a63ea376aca8790941701dbdbeb39d7e6e619c65d5d4a9dc792539a666815976bece9fce31400f6b6c9027d27d8aeb8c6b94a74db56c46fd23475fd0063e44c5622b723a9576a8ab1d411e01861549d91d1a128dd9d1a5da2915f55bfb6ea61191d46db4cd9fdd421cd6e81d1cd7ad4f87c0b0e769f6cb4883f612ec38acf3aab3ca9b7af288e231e1e344ff0e2386385e2afc72afdb5c84c76e969b2cab591e67692bfc56d5c6bd8b6ea12ef7ae00c3dcb306770d6715d8472352172e22d906279f80010c6be21049b584168f5d1eed045ac076e28454d0c36f8da2e304da957ed71505a81ecce373779fae48eb78d9b4363ad30dd591a081a227534d78bb35ddcc1deb6a182fee2ee93f371471cf0fc8772f06a7aaf69ca81b447f5864a0dc5048b07951fee89262def05f979ab72f0273d8718e7c00a302b89cbe8e8b43649ad432699963ee2585527303db6884b8bea4e8e4ef06e8d315d6cbc4865b6480db59e438fb722372cbb499b8c21d4903cc3e99e8322d496d0b32717ba3048f8d3aaa81a49201ea4d089bea53dbf5ced8c2ac9e2a1f6c5e14969a06298deb1f6700c22f875d94c1bc4058064ae4d5f6f59a8445777e646344fa05a087e4431f1427c07b2a757c066a18df2a532aa996e15d700fe454774e2e110801ec15d1ec88a0bede61e9d48f2b363a6abfc5459e0e925de2bf74c684679e7617b085b27edd8be8c8702359eecf36e90c31e9b145bc259dfcc598523ac33f246dd45a610b1721b2168ed30afae4022746b4e07e73397bac093b63a79f2d8c598fa4effc3bd155e0f42049df0734f723ac78a78d7d47ecaa9771d68f0224f7a413bf97efeb7a2eb511fc39c16d85f22efccb4dfcf43dfe6e09f23bec395d7a5d837ea3fd718fae7ddbf2d4dd2b4f7d80e98c9a46203624ccc8394ec028967d50f3d4c5412e8334481059360a592006cec83cf51c78b09623089b391327c8e5324f5dcf93f5a4e78b051320cb41522854dac1c5c8a8d27f014b8ac06d40cae9cb77f92ac1b603bb413f936a8d70cd210942acc194da9256863abadde39128cbaf1bbf00a638be735435d1ff47a1a5e4c477639d129baaa42cf77ce646109297644b1678574e3b790b8708bfe4d3e42d3d5c6e8d0cf0d3f393ac3ae9efe5020943ebf62121e4d43698a3219ff7294c93b43b5df4da6067db29985bba96a9bd6b6dca544023e920b271a072afe162c825e76bf89af798fefe8ff34c41372e85105d1c14380451c2ee94474d8ad5f1d6566af0ad63b90d0245306e3725ccf7e4582eb2f60eb47eadfc2a9bf65d497ec979ddcf1845c5c7f78da0c88135daf2e81779018711078a6f7cad048be9f92f5b469f2348084dd1c6ad637f5c94994184a6a4d6210eddd2b32728d6888760cb3d16d6188fe1e3ef9aba8833a67dd1ac00ae03a91e85a128ca0f261176b8dce8114214cb41824ad00c210a069e7e18090bdf8f8f228fe60492955e4aec8b5c6300aa8d0bcc6bc2175d3344ef0124bbc5bb82184d9d9ca02c26b36abb3236c9f7991d8fb6922189c831cdd35eac5273be86ae2e701f4d0f4cab6c2f5d0b79a24fb3dc25a68101b495253025c3119f64a6f64ec508692d9625e37b8e4454fe63206cc27e6e25c19dd7faaa8307b5f4ae497bc53f0a06bf1cd9249d4c0d8d9aa3bdac0314d2ffaa76dcf0a62b75d869a2a46002bd42d8e3c2fffaf429b3c41d390e0cc752ecfa859a9a9bbef342f86ed23a256470bfc18bd3dad9b0cb423a6d6cda30f088da51a4de97237cdea0969e3d22308ec89a4a8f346def193f8583f63d97c1b620c069869dd1466f7c86b9aed63ac8deba4712b2514efc505cd8e58fb191ee421d0622d218b90485fd5aa8a4dfa79c5ceb935905dab4aa282c3886014b55178b87ae822283279ad67f74c2e19c95089ed147bc6ead21bfb8d00889ef5552c9d35747daed3c06502fed52154d23ed026535b3e1093065484539f03f78a9f6b7e0ccdf88661283d9ac68d6fec011d19a6a4f892e80d52e0d5b0d882a84f2d173374c586cea81c2c08d4827c7eef741825dcd3327a8c82b5e6c081373b64445cbffea60f7848494a3269380daea20d65e55462b809caef920a9ce2ce72f7dc4d2d9b0a08bcec92ab6472f7fe4771029bedf167115445e9e45d822dbf088cb3562df5612a8d4b56e74d76c05b47751d9e0f47d8dbbee564292c004b7a003c52426982a64ea16ea8c0f671aac521e36129ea736378c57701401c5e94561e2edc10f620499e97af8e971af78cc0aef62825acb8435b003967d4e76ca28849d8535ecf597ee3e1c772a87a695adede38a855845b97cfe981b225c7f24758cdad0d9c38a7b2050d07160081421e542990b6951228372cdbbe400fdb6025b07ee63b75fec8911a012b5e6ae7ffdf41741070480abad0bb864e7262dfaba3074674fa52cb7a614ea116850284dfae5163fa8ce273cb63dc25672858a9581e86d5c0a522d4143ae7d80bbae160be05301f23716165366a16c68dc8fc6401c45b1f098ae5270704547543a8bd0ec30fd116db9034cb75fcb7e338e4e1de5cc6d6177fc27f2a9c3e62d4b84f98bb93488f939aedd5f8ec92f411f7d8876b0029029307797694519a681acfc4bb5b4005c75b173d0cab15591e826ed0c688cc0316bc1a7e2818d6cddc41933623ca8713a3a8048d9c233383f3790ad432d25e1eeab9dcc07021e91d075611cef6328cbcea50b79bcdf1e292fb52589fafd374f0b5615e353cabf7b72ec2f067136d357638af6ec725fed91401241fdd8117627cfe58601ec3cc20c9a15427d2de78bbdf27cca425c8e02841656491a490c2539d225293627e0b31295ffd8ebdf89ec2e5bdbaed054dacc4a9470ec36a218613ea85575282435c8a00853cf473ab650334cd691b5b27747a13821470d60622bf0b1c8ecf0c85d5f621a98198da6d2aaa3655234dc89e36b15a680c88678a2f1557912b369e2ce9b0a7d858d6cb745f1b531426b488efd37f3741acf2577ae6f1f15595f8ae54a33fb7bb447dd681bda9c883fb3cbba63ab5295ea7601ce753daced2a92b3296dd218ccad7887244efc374528eb71da2e4f2eb43a44c493378726abbe92502dc02fe1e175b5c06448371e143a68b91d235d1d5fbfd937967648ca5bfe589cf79759f146ed6f6533d478e782858d758eca964c07227d94190cfac7064a5e753418b37e7ca37a255d76cc04bab062df30361648f521168a696d90a95443a991e8b8c87f92f902f7102761a1e94343c98854323f58bcd7daedf88a9196c700f2e07a542d553307cf554690da9031665bf4768334d6579802100ac568856ab8d4264cc4d999f338273e9b487f4361b16a7d44d45b2c68846cfb8c1c843c69e683c1f5e562b71566d388a96423020963f8881cf7339d6a5b7c9ae0c088422cb9bacb5a88be4ad6981c742c5a1e9d1c97138efd6dd0172ddf61ba0ea6586836511d5314ba99c258ac4fa9b493e5988c70221bf5426d4ee7764882af094e68e56ca288f504d243a98452b36fbb0bdf5e6d06435cb12b8e835c790405a331a2b6d8e9b5e543272c87288a8ab88889822438ba498d0c65c5d083556bc6c1ef018899d7ba4b20be937c8a616531314ba01743e956e7a1cbc862dc025f15001bec29ee0dd14fb022ef4102b4ce684e78c22f08f37f484b11d4d508fc13807a2a7c085eddff9c8a7b1863419ce5c65bf4b41f0f82de8ba4801903a0f7c0f6b61aae7dcfb6d93b33c5a94dd8bd612def927a76d9ee99dd201ab1aadc3217696e8841fc49a6be8e04c71d3db4647e5ea2d9c5ff23f84744fe2ec0c6e30260642e22d6d54d7d9c84917adacd1b86f4309ea19b4f32b067e46e4cff89f56a7c3f6085d7a147a1625153ea513f1b5139afff626e370023534caf5b677d26454f34e54cb7244826a89bf3cb50466054eca273ed2e6af2916e05134fcce004e50cad8ab567ab59be868e74076199b0a3d46650c0b8b88e1c5152d1699af18bd27e22ff0e8bd2c9f999ef8a8dee38b38dda500e67f78087166db7c96021db6c53eb470d22a32babca348d27a0ab5152da10881c965e22a3aac0b3d6230a8d26a049c13ad0073a70783ffa99d1aba82e346c62a06db055705114d0e19ec8e0d2439e770a173a4fc781b27523626d27c52dfd755f339b3acaefccc08a5a57e37fd1d34e27a7064e530d8a1c877bb6a44bd64d13bcd52e0caeb3664f32f220ec8db716c39028155987046da14885bdf560df441fb046440c24c4f8df456ff6c4a15852cee9e2bb1d2d740569a237d84b95435054295046d246c16b1ad5851f1b72a914485ab3ad295f7ba57535aaf6397aaca8d7d5777d0f786ac96f1b9f30e5dc92e0f28009b4889c5c4b27e63010cf61c33dce42276c2a599206a22209b432937e283160d501bb114427287eff7494c84bfda77fd7ec4166089f5db796832f346ffb58c269f91adecaca06b23c9c886c29068354cd0e2aaab5c31bb7d6edffc890add6e03c49e2c3a2dc1ea077e264d51b44ae2aa20cbdcce33eccf02da8ad606f8e63247bae24c29888131905294c497dd0f3b8afc4bb943ef5afdfa0c48e513e1b365fd0c507e53e79b3411a2e04fd564ca3d0264effbebd4007034bda66ddab911d1222ec9379f316b93afbba2f610ee5423062d717b10ba322279a48ec2cf753a32a129ca120d08192d038b22b02c2e12a57c60f37f46389bb822de419b33b7276adf0b1fa69861f07457c159cace0043c5b9b2b26baf48cbc0387588d4a4efb26cfd6aaf6c5d2e874ea4606f40a98f4b23d7b34984d3ab5e83bed512d26b67a7891decf494883976b7eb3a4db61348870184aa2c63682e61ac5561a824c273e45089ae4800b3531e157366348794899e63944ae41bc845d468ad519380eb78088ff96bdb9302876d9d4659712b34738cab0a646211365188da9a561d305473394f52064bf991904e0624c8221ff29be1c0a571ac9031cf268299a335dfeeb4e6ed08699c7f711e050d23f961c82f6f33e118a517651e9d7a6c5fe5962506480c6e2b652928e81ec37ce174bc6580cb6de821947aea2696ae7b98e5ab2e78d77d4f1cd3b5ef082c06268033d736616226bca938ebc822165710fd7806c2cccc68ee4c310e85b492bca15a5f7a1bd1a672ebf0b1b032c3ef090e0f412b7e650a8b54ed2a84240ab90db3743a638d3e2ec145413b32c03df97f0d5289bdf9f51c6f7336b628562a79014995a23481e5335acd6f2a64273ef6e0df0f97a2575f0a46ee5a0f95c98d885f96470cff4e32854d988ee6e261301ac083dae3fb73caa28fc666576b805ed0e0800654382ae946c42a15e9bf6bd31ecfebe8b6b78322e897206cea0096a1137413345650a347808f5885a651519b30b6825fb9b02a9d8ed20a7403f6604bdb2208b91a7fb59ce643f12ee483251acbf8bf847b83f45d7fe65412c9143debb7ac3c846d1354323066e0ef0924e932670e7e6d25cbf27dc405ff6fec38f67a5795053bfc04e91058b1fcf15ddecbb96cbaa87467c1ab06320fa35bd938da682c4d607d40d60c0b50e5367f06240d4fdca5b0490a3f97178b6c6eb56c9678c7dc8f5a44172668beb38da136b823e6be144ff29b6f3cfb5e48640c670f78dfd6cfe40e1296c581ee91debe739cae80e47ad80a2dd9d7d94bbc6078bbe470f67bb217642032b35f213e0797cd0c3286e7ad0b6fae370484a4dbf772d11164067c52a46f8bb96abea4c8f56628f27680d672b1b77f8e9bebffef7641ffa623c7c226d137b027036e1365dcc4c5515a1c8cbeb67d25644421f4cae292ad35315f60c6d2833a6e81b7131ce6b0a044b4913728e120363632b09a1abe437acbf232db2e9508c1bf547079ae2b378ac4c18b55a4b66b65b2257f18006172b4de292225915c96a34d617a665a3685cf5bdfc24a47a660817e55a489d39a63af44d49aadc71dc2762b5dce66ee25461eb82a154546116ee8de15a9e497c44fc092b223d98394076c537963fc7e0d5cdc55ed9d91872451c36374d237a236974d64cc2a59bc400cea9dd07ed2aab28256faa85580bea0bd48762ae977ffc1facb24d101a866305cf1c1c24e0fafd8f31ddd17417166879b0c5647a97323ae81a45b4abe1334f2f52b6dff6974ebd235e605b9f2bfd6e0d9bc273d26a913fb0b0f6a36e28a3669fd85177979b5a6c03ed48874e8a084ad468a07fa69784ac34430eef6c278459c914dd12741924357ade724149e91709fa95f202ca4e750ae1afe658938d73484def6c0ae52f360f824614f4be6d9bd39bdaaeafb15c9ad7d4980e11d6ea10df44066ba1a8afbc2e49c564207d125d3757fb1dbc6b95086abc4d2d7da11647e2b83d2b1e44c71f98a1284b82231901dbddca530803ee7436a23d70a269ac4adee089701d0098a300cab055ca464083202e37ae0739ade1b2bedf905e3232ae2adb40f8139195b04d2fcd64c17e14fb2381fe168efd52f915a875e5d5bacd83cd6eee0941248e27188a9516ef71a31fe2d30880cec39decfad5c844308600d1a7205e80e5a3354e7089aa7a8568d00341ca63aa574a36a150afdddb1e18afc6287a8fef2138f15a9904ef1e058b26c5742f9ba3f670893a3e21f83b12cdd623f6751dce7d6b1b453d827c78af32e7f0da7962ad448cfe5a4c6784de2980bda9e1ccd1bbcef8f9adae72b10974ee88b6a80fff67315592d1eaf89ed567ca5c3b8cff34a6c37be24c704d299ce83fc5feb8c8dfc4ff42d49f428d785d03a00380c3e7a47077ed90516cf2ed7a385514e784def81704ab98a99486df617cf6407c10ee6f82a1dc8b41ff8dfc722667de016f81e0894edac7b2992962dfeb49aaf868e511d8ee67fc7a8c5caec10c426f77ac1f3e4b44cef874c0134998d67aa1a3c9f1ca2ca47462feb3d8eb07a80ddef585490d9606c1bb1b07ee7340e74d50832ecaa81bcd9fb0335e79fcc6793787c952c42d11dba50d20ffe0496da0392d32edde32018c3fa363c3ef282cad20125a08c07a40850c21c291d19634f3c3413469c74e49e02e88d87e3597c84eaa8bd56942e124fdecec881004615b63cfd55ade023d207b417f2d3b84f05f04459e379d0e0c364eecbc1b682874ad2f0cd90d8def363f3cc39423c18d3b35e538c4351f32b95734bd065a23a7b30fab871c52500d8f417bdd2a7ce0fadb3e398afab18cf587b8b15272d2e5fcbff82a2b2cb568a342bebc420d6a4938588549945e02441f42c70d3a5db313d045c0b81c6af942b8ac3f367ad35b3bbdcbdb0a4e5bd454f9e86094f070222b1d2b795d82cd7c22834fa8aaa91886c874cbab380b6ea4dea4f3394c13f1f3b9423f99c19cf3d215fc889f594bfe5a84b67a2880484b6c7bce2d99d89c5ffd9e8cce5a96adbb9bd6a15102b64590ae40a7a834a64ed2543cb4f76b364177bcd20a53f271b454d5f8359f4e1c4d4b0468dc046c1cfa64da4ecdd2c41d5a6919e071ec6f7a8d36b83bad89891f4a862cb5ce64e1462f8cb75363291a5038896d36aad2cf5e72ddd7dac63c275aaa453990c6d4d55d36c6c70f32ca8c131ae78fa065831bb40f4c876df499c7e98c7f61fbc5fe476862c593021b93ec245f5dd760a7b2b284d1949fc27a594cc9215893dd49de270ac1d695f80d2ae5d658973a6f3dddb73079eda5ead98efd557e84984d1f9a147c4612bf9cb17b9966a5344c2899b86ab817c3545ca3d3c6d36b747332657be140fb7c1d0dc01a3ea2e4344479248c723d9b34f0500e0977a20ff42ce657ebba3bcce06deccde5f8f5624d9723a712343e67f0432567d68bc416c8657eed3e8c71013fcd22edede9257ce27960710dcfde875c4e51ee9cea40b8873880418142d0412a95319244554bf1a0ca90a671270735e9b7b344417a47f49b6af1727b34823c2152ff98f3f20e5fb34bbad7997354a82abbdd74d5da57541f4a630bc1774d4dd516328d869d4411909bcdb3e621db115874247c2dd789489e1568ef9ae6b3479208d00fa921ce8d4cee9a6bd93f4efb7531a40761a97184999aef965afb27dc712dd0802319df80faa47a220271148520615b03489737a13ee8067af853e5d42481073315e93ed487ed67eaa6630e4b687034be9f946fa60cf01341e2516a90c5832f68523efcccb4d65658ce182947a1e3ef3b988a365fa036d719cfaba3bd1dd4e812de3478dde16ff3d4f43f6cc2153fe432feba75595969fe69c582618759eaef5b0d715433085a52ce61e59f5e2681f4284e31244c8dea186e1b67cbfa8004c851222fda6791658fcbc8bb19bcd62e128f6829837df5e64aa44ce2dd6d857ca55ba77c547c97d76ca4da0ca5ba0a79b7edc3355be5a36f20fe7074571334eb2bbaa26034078e5e208bb514cdc8e834d52b9b33024b27baf67ff148d00e577afe05dc7193276450c86d23da1ef3fe1c8e18723b01b3167bada30e0761054266b2085562df5685beddd88ea27154472e43209080904f354ef8064cea3b9ae6ba9293583a97bfd49cfd4db4c515e84871ab7d8202dd3cfa023b9ed445ee585a29c9346837225ab107d7428107a605b2024809f08bcd49e77e31d8f8fa3bade8201853700377628b6b27868db75a82651277ac750586a267a8d6b84e95d743311db89459036e5c64fe904e1f2860e5cfcba6f684b70cdb023a20f05f71d34ff6794d53fbff5f42d4da69283ea778cc71f78481260498fdc80216a2b8aca8bf30ad8bf71947662987552b09d81e210448e8e4d907bb9f0fa82131aa06bb6290d39e235c64e822105f47c2e5912c8f9aa673c4a6f0ea872eae181e89f5ae0ff862240d50c9365ac3f66ac3dc091cf87e450309041235ba254313d4d119b0830f5325467475e3e25daceb4b20be2c13cb84867ccf5750e34febae47d51db582bfc5ca150dfd10351da5b758c595077404d34e4f277a736d94df0173de10763601258295911a59414aeaae97bdf50a01c1a924e83a89550547eb8dd5e493d670736557f43783dc3da162ee48836b1319216dba35556bd5993c7f31219ca5d90f8640a8aa1603539ef4a70528c591e3d98ba0beff6e1a373b84172e5916727370bcd6d064cd9e41d2fe381aa2382cc7a0894443cdbd35c25ad847b304083dfb5db2e964e1f21693030bad5350faf263408e88ef15aea1061a316db71ee63c389e8e001be13b646d9f0d16092423db0bdbebce8435992cc08d06486f4c2e2a563b377baa4b55a9ac9ec9332fb38a1f404eb41a8bc70325be2b2f4d7696d8da5b48cf36103bf94937ae8cce21ea836d40297e2337aaa7a24fbb7d59826d95b9fd148a39507e903182e3ed090f02ac4ef637ca3b9089e250ecb4435a53965a6b41cd0c44c52d102dd34d50b72d4be85885d17711ad1017c189b716383776ca1aeb6d50f66d9a1da4d8385873d4edbc325eb4888d0a8e032583ccc092b5778bddffd7c39e6cc2ef36daf3b4a78e9e378cb6faf4675fc6747ccb4d0fdfc0c8bdbdb6bd0036f4b934fdd3fcbe4590667b626e726670a3a76fda64c1d581f8c0f48ee6f1266944ae9be9f618b44c1a11f6441234f4bc601dbd770f794349e0f89d877c536f79c19e354151cd773352a0d26041b8c6f5ba787960a517d176181a42673e1016acc23c47cd3306dc3fa090eeeb38605510617c6e7fc47c0a5a9bc23600392239455a5886b4bcd5c27408646404fa58cba456305e55a503b96d31dc07edd030740ae233d813a4e45a32669b93e5c430b61421ac57a8c39cdbcdb3819d3984f60a00301cee0910e6eafb824e49e7346bbcc7dcf57fe4a2eab20dc2e1ed73e8efbdc9965b4a99524a01b00a4e0ab509f25d4a28656729df9a8dca739d98d59dd42bd46f73cef41f4cd131aca9a35e58548952332c4a3ecde2a36c10aa9276a1caa743eb83a462d83e53d01743c714d42994976f3a6212ea14e9e5a37cccca638874ca5f888318a87e3174be183a3e957bea174327864e8c1f31763ad5285505c8694c19fd66c6423f6f4505880e752f29dd4b517bf249dd8b92f6e46fdd0b114af792e46528c8695e781c25bfc76765020a9aa6208b7e322d2e70a6772f55ab27753b75355c689d4014a72f40967091d39e34d29e741703ecfabdc3315aa904a900714f6c2a409d822f5fc588dc41652a3d7c194ec35d0fad8755c26f7fa669f396469bba98e328f8a52eaa1c059f83524a19736abc542be50c17114e5655aa9c4605a851f2e5f6a900c9c7ac2fe50c559a825480e67fa53654f956d7f56c8c748113e402a73df93f9461833cac3646fe0f64d85ce0eca0ba5e460e55f917fda01426a8f2a3434d6580e07f3ba8aafc2f864e95bf83fc1d749e87d5cee0aad0f5ec8cf912a7cad9efa20660676b12120ce37e6c1f4cdddf196cfb8bc216e6d3a1a867b42c98c519236f391bbf943408cb8a2635d9715a12927254368ed046b356bad30e29c75f8521b57c482249248924113cf1583eaa3f770a2a91459249a7a06c62e15839968f4e4d95139dfab1d3292096904e9d18aeda939706a32679e0c1b675bad5c59df674bab0c1ef48e5f20f46d83e4ef5ab2cce38b1911647518b916ef5d772ba19a11ffa1536b1d9919c68cfbbeb56ea47caf95577fda14ff2444a56d6b4689095202a6f3dc9076985329517ec919acd88ebc1f6653c4dcac1c1f6fdeaa4fa2624648dac6c43b2096539cbcda0fa3b0783ada200d9f865c430af36dd5070b0adb64b8317d4681016b8d1202b324619317a8a3488ca674384a86caa51b6333386ad258a6de5557854e8a908ae07cb73bf352c3d3c56e8a988952f7d89f650f9955f56298285621cc57454ba9e287d568d31c196604bb0263e2b2999994816c100c2829c46e57989ca194e65c886ad47ab9cca2f89a60e06e4287e16161e0f145487a0d126286c4962c708b78004bb8ab6708494d33d5b0843d016dc10619372b650fd93449f2482d6f4d10feeee9646fa8f4409f93920b0fb15507bff1364836b626924d1a25c8e210d2bcde67fda3b7155c9f6fdea312e63cdc649b531febd011b2e2aa4365cd4a63353f975144b6c89f839d53508aca3547e63e418636c9793af0315be0a954cb41a1bb0e1551b5e859ac639812d93faa4b9ac995b9bc96283afd908a2f290663e3895d3a8d04ce7e464b4aa186739eebf52c1489a0dd6a43ddf843626d57f43a9fe1b932667a8fdb06867c0df95c9db32194ea3f2590ecbaaf2c09ce15790c214713a9baa5331d53f73a269b81f3f6aafbc4a8a3ac7832d08e7de4939b248e5bbbbbbb9c3f13f8d32d29ebf4aec20fd5517e4c39854efd8dd29affa4715a7c3b2c324c3a9fe180a0a6632f93eb692ad68367384f234ba351bda4c16dbf6a3971f7fb5992c2c78d22dfb90c2559b525d536b35200e13c7a9da0b0018b638bb95bfb62dda606958a82906895190b68c67cb7a3ad543f5cf86340d91ea8fb50f51d874c026653f7606bf4a76494122c217915099970791676770e461e14a1c4fcbcad4c2d4a2454b8bbbbbcb3ce96c4c0c36a8c3d0136ba4669f0e4ea83a04551d84b227da3fe3f96213f58b3c3564937254b575764e1b2117643b7d9c0ad3d918ffeebafac30cf8e354964efd4c264ef565f581608a7606c7169535caccc572c1ed60d3d919d005f5c2c6dfdddd9de0cb8bcf68928d5f05feea60e36751c1d998f8f185f5449acdc6396836db43fa4d6d4731d826d604b0e907bf17557edb174d50a12c924452497bb40b1b3fdc8ab2ae7ff53f8d7a4e061bcbb350ce89469172542829a7fb7f9a8685723acda9de48733c476a8b0fb66f2454f96591d370efa552f72b477194ae74009a21c3b64be851e980ba8e6652497bfe94126d8cffb685ac67dbc2503d3343751e1b6c19cf0800598ea33ccb21e5643930c2786a6e48a72c1f22dc4fa72c2023dc914e9d20749911a2c2454619a54b7b0ee98c15b68f9443d221f14098f1746abb5b31b5376386ed93445be4d9a139641c902d07ad8657331e8d497bae35a91c900dd32113d26c3624ed3907562f86adb9c3315ac180e1799a4de6a1000000e6549c8a7322068546633df4001db2e0542cbb62e848dd2bb610547fa87bc5500d6acf0ccacf6836271f2d8e4705c3461ba9b91f31462b5a3162a3f7b746188659d8c8e280b4c713b34b9b57cc2e6da27c76691313fa70a0bca5d974443919c000341b58b3f069a015612a4b6936d66327196b36463f3386ad47086c48094a0e7400da19d9fb8b0a3538a1849248d36a5835a331c7af7ead7f76ebe084ea8f75b167a4d9f02aa428377081c306e3a8fb24c5117b349b297f76f065b4309a8ccbc126835a61e35f79963fd570516155f9f83b7fb5fdea57abed4a461261a46c9d4665e55768a5fb2047f9ab740f44ca5ab6fca01ffaa05ffdea7f3aa5fd9117fa9f37f2407fa43da15fd1701a0dbbd15b1d0e14ad3915c454b019a517df0e05c8e1708055ff8cc769a28ed0f671aaa8c339c18d3ee360b03346b59f9786532dd782a5d9845aa83aa7d9340b4ebdb41f569bd0ce58695959cffde0ba65cb7e6ea74776dc4e7bfe9c4ae3549c133a1c108ea7539c13dc0fcae9b4c701891dc7a3916013da86b6ee043721a7813332ee061ba6c2e35404cbaffc0aed71ea67f9d2b3fcd6a8504ce7574e45a83cf71ced716a1ac4f4352bf4a6a6e987ed54fe8edb71947fa933759caa07e099eacb742a3f7c49e42820182708c6a94d25d7ae3198d83e4924896491ca69e6dc36938994f39ec795321e7eb07d3a04a5d47dccd4a9affbd829dbc75c325565272afb307524550fc0bf44da8140d790721ce50f0020dbc7a9be5f459d9557a19c8ea3fca15684ca479d5f390dcbfb3f50a71a32914443d5ff8d348d24924c1e49f42189389df65828d7c30fb6f81f2987dbd9bea843c3f23df55ba9dfaf9ee6574143aa037d51877feac7fde09c68943f6b35bcda9453b5e78f92c1f6653cfda736f1f0c6f4ab7473052b8001b2edd4a9860b58bdbacf03131ba7c281906206c87669587eb3289686859256b6215415520e15d5bf856643cab1c1c62f8976c66867c3993907c8b615184f3a7fda719a958c6706eb3994bfaa2bdd49676234b81a6c1f2967a53bed384a7ec63303af30e3e154271d156cd67f9c4a487ba49c4ef90cb68f9443ca69cfffa5b6ac36c69fcbd93e524ef58f537d51a77e9c8a24a4fac3ee3bc5e0d453fd49399d8a3e549dd2f941da21f174ead4bf24219d9a3d433a45a45393b422fd908c7c50fd23edf91bf99f07fa9566c3aa3acc9061e3fa71aaef5755070119278046cd341e62c4f4e0030050de0e3074900980007e9819408a46064c0e04a0330a608003748f8004d0a8304a6f5e9993dd987e4e559d53714ee8547f4ea753a6eacfad38234d3383fbd1a92de2031527a4470af5e37caa5b42b4503fee87dba9fe32c2d54fa7a216aaeb206fb6669d1e144055001d02e4b824404e2480ca075b188cfbd1317667d3276793b965460cdb375b0b620757ac28d2a1ca98926a066c61be169cea8fedefc79e9db152f7a3cfce803b83ab34db448cc10dea7e33161dad3606093c3bdce0635063137189e8137f28c366c3455dd91961d1826767f0632b48bf163c1861b1c2a405cfc6f8a314ad30a9fe2845284c5676760677116d8c4f0b4edd1c7618aafefbad5874a46a4927c65994a2eec39e45128c03a6fa0f7e0a29cb20e59c724acb92f39a02323377b725a324f204150c1b32cbc72073949625f789fabdb7351d3569e67fb57fca04b9e20c62acc21663d48480383831c6d561c52c0ae1d57accca628c1a10d69c3487578beec6b084d70c20197a5b8860ebc76407f3a31c6cdcd06b1482676d252a3f4b8e1f08a4d48480d6f6b427578739b5a0a068db2243404185fdebbc3d4c545b67b98728422208bbc0441107dbf68630085811204081bf88138434f14d537b1bc4774a18d704ae3bc992c9d4e2bd056df13d4cbf359ed0f6f5cee628ed3f19557baf5337e663af6559463f1c48d4f9721d35a2f2afee867c6c76362f8ab136c41228797d7bda6a9493b4474a799595f715ace525081bf74248b4b7b897f4d340fd30ceb43f3939c9b2ec6dcccf581ebbd9012b0be52eb00fda5fddc7437aa73dab81b4676d5cb7d215c17d89f680a2c7e9b92fa2f44e7bf497aef7cbbf08ef7795ebeabfbe290f28ae76e9ee9493bf1cf7277ababe54faebf4f2b153c77d77a5f7aea5ebfe1edb3232329ba3ac6ffadea3290d47594338cafb94fdd60f87a95e9fc92cb3d622aad667970c3ad2190de2a2db9eca4b3ac412dc979e86ab741bd7f9735f447f89f6287d3f47fb7b9cde7f6b52baad75dab3de89f6acdfba56b5677d0341f9759a93e4be8896ff165a04f72ddf427b949ea35bc3f2d49da4949408976fa13c4aeff23c4eeb5b7cee2500dfe569b4ffe95b5c680ff73fd193fcee2f7d7f899e1ed0f2a73f9d4e44949e7b7eecd4f997ba7eae6be94e1d0f2888387d89f2e0010504b666891e5010e1f2a5efb18e7af91e2ef40847595ffa177fa2eff2a7ff58258217540343382fb44419c75196e582768ea3acef010511a5e75abe4789f2cec7382270396d4d0bdd1c3504673dd7358ea3acbf640685f0635951240427aaf52bf4fdd00fc860613b4388eae73eb2ebeb201ea29e269c64d529ab71aaf5d68957ed59bf957fdab3de8a5e4852bf16e285faf5902ed41d2e5843d69b56dbb391f6ac97cf40ed59cf40d5abca7a4b552deeadef1ca729bdf518d76d4df378a05e5476dff5f2fa2ffe975dff99aa4b954fc3af97dd07ebf572ce396a203b3b4e036bacbf9e3599d11f5bf6f3b98e6b5a49a7b2b75e761cd49ef5a58e851c653d4ac8b6f5244bcf3d11a5e7b82f51498968297d0bdd66a93b8dbee56f6ab8ee34a2a707705ffad197280f28469488d2b7501ea511fde4ffdfd49046ffb550b5fe74a27d2fe9d4d73bd57aeb74426b743d197d5bdf482c8b89e358ef39eec33a0d59df49a460fd8936dab1be8158cf418c2e9a3d57eab626fbd6aecfe6f3c4b267ad01a30e87a9ce1f75df56b94ada994addd6704b70137eabdf7aeb7d07a55fb20099610efb80bd2b43199900c28937a6351bd8d56671604987ff6247ac31c6998e041bbfcb77fab9bb5b724e93094208654011f4776a6b0c99994e5dfbfb5bd6ed9a82e6f33f8c274306109bb533f009ce19c90c01638c1126a1614cb1edeeaeb71b1318b69ddac760f7d7f68fbbbbbb67f7c9fa14a8c07667585e5b903094dd19563cb168ac3792b9081b6536c66e1921840288bccbb0c9feff7f8c4e31f6f23301f9efbe8cbdd0b045a86d9091051ad547bc60f3e720be3acd4c33c866492f88b70f1bdec0081f9fca44438c584a77b88f8f107c7c563f30a2841864c157f13b868c2adb8b21434a79e1489691e1a1adb340a32cd05e29071b44e11542e95062344a97c3b3f87daa80e62fe1600bc222aa283f040a35a2872a782c1a88a818814e8d120d06151b6aa0906f816f875c58b17d2e4db3bd05b64e031d7b2118c14ec9bfe6100a688fc60b156cdf06d5789665fe43d050801114e0557d49c2f6c9703df90ce59b3213c6e11c56310feb00ad56688788b6a888559cb3f57b55460603a504610f79aa86423f233e192926952ea52375281d0eef84624046d5e8b7802efba8d9640f71784180200646c0c053318a434695d222aa237a5ae86a10aadf16656d8146391c8a58633a65b0edd0830d5343d1aac61f091a3ba38f68cf73b0f5b305e8113237d8f89977b44e1902130d31b1002341a623dcc35d6022266cdf0a0d19ea15ecf866a6d83e191468850455151d0b362a4a700422fc9010abb3d9c4363308e113b64fc6f487f3ad2e89583989ae4c6fc0f76f20d7299a198c8bc9c7d635486a7b92da9d620117c0752aec322ab66f8928cff6795ed1ca58e11729d8ac7708e3d58c025c007e31c2f6c9b03e3ec397fd1898a95214e971a248143e3e4070708eac400d8c066ca8fa0a0c1aef080b744ae5f791e854caaf367f31f829eef759480f0f611f26c241473ab5fd3e0bed0cd26f12262afdb6fc5a6153a6f6fc4febf9e3681a995db24d96a8eba3eebad48571a1919d36c53958cf7f877d1ce5af838c4f276384d3f09007dc7f48e88587de646a41b3c3afc3e876f895966e2bc47cb6f2b497834df513ebf9384daf7c7e6da29ccd67e3c074d7eafa81b15b79224f6c6a4e317ddaf39f2b47f9cca16752facd174de497e9857ea6cd05fd3617ea3f4ff49b2dddacdb76e95c395e31cfae557bfed35ec55a6bc0c461c1f65d446a895e403fed390756a39518462b0d036a2f46d983fd50cc08b66aaf071a6cab4efd74ea4ad21890d35cf41aa26fd8becb7baea0eb48d3ecf0fe1791e9d3298c083ba7c08854c77ab0211d36574e83f154ffd192119351130c07cbc1583a4c85613ad88fd16a94d28d80da1b196994ff9189644b12a404d58dbc6ea4ba60745792468d761ce57f296994bf8fa9a7faf38c7cfc68095393ed5aa2d315a5eb7ad271d5eaa8db24f5ea24aaeb150e4e8dd76d15ab2da8706ae66a065d3a3e9938cdd6acdef4a6edd299db6452fd61745b0329b6447b5d4d546c7f0d557f0219c450571c04c6b643071bd51f2b3bc6cd214d03e3fdbd676867c08740aaf7ecd0c180d1c146313694439b8e802d0cf76377bb6ca5b9d7638703b60e210e0f622890abd0c1c68fe3b0c745f57ae34c04cfd3ce76dad9fcf99d6be019b07e0ca506770d1bf0bba94ebd2fd6fd1a504606324766ef010f90e1287e198edac7a2b4566468cdda0366d6da037cdddd3d467f9b0dd818d56863549dce1a432c410212d8cc6451796a367863f87966965683b75e5a8d4bb3d170a4f963281090b181a13cc086b6dcf3c47f32363a815f234648215f41583fd61db7838d7d67276a3652a542e176b07db0a7c76668a8baca55bd2a2ae21ddef936682bff9e740da71288298a6d6adaffc024a3f483d20f4a526cee991a648f6f526c526c41d86ef733333333f3f22eefeeb5ee733608d68b1b585489de2c2a7ea42ba8f1bf8d4b50e3a2a1460dac0ff1a453281fdf009dda3e3e8dd8e3341b855c3027ab3007fa8828759d46468cf16bbce28c5ba4d27993f6e2a774cec497745ed4b992f6e29f744ed45e7c194ee33d8e8a1f818266ec9ca2eeeeb6a4775317d239edc5b7baf6d15e7cd9b5aabd18bdfda38cf1f752917a6201c33820def291fa03c66388435d4d0894ba746e4c0ef72cd650591362ab4b4f45c89afe51fd3e5083349d8eda7d1766c8220eb535215aea5214ae18d46c5c158858bf1e756ecc641f5cb381283a6a5875bf46d7a5f382975f57bc668cd7755d578c56c7c832c618d73b616e6603b6305f0076aae4ddac3d8790218419f7df8a4b898e548e0ab267c826b72aa34f95454b4052bfb8aa52ae963086fa452355feb478d4237f34a45391a8ca1f85a17e2322558241461564f9a3203882262ac7be1815a66914d0bf344c6ed88c18a2858f8ddfd4556c5fbf9f4fc0c248fd64e0cb604b43f455f5a2b85a51c057ab59db61ec6ed8706780f5ebd8a51f7f8e1d5c77633e0ef8e7b02aec7ae3aca3fdf9e3f388f402dedf0572f0a8961039bc5af3b1ed2e906337467e90cb9a17c891cdf6ac979605a3e5b3ba95c34ac26b7710fea59d2d13e286530c783d0541bf9b2fed6cb0bd660718c1a99b1121d5a56e1123aa5a440766dbaa8a9edaef1263657fabf41c1bb8a92eedf2feec2e5d873bacfe34dae9e7f406fc7df9fb939e2e7ac37ffedac8e1d21e7fdffcd1c7a7df4dc57e6b86fd37fa484d23eca71018bd31fffa1b7334ff7ae1862dfea4dff537ac3fc9df7a536f4c1beb0f5df6a10e21205d8ae5b0a65585155318a95d5d2ba6e0a9180a3f8672f3e2c706e3a1c4d1470ca3a720e0636f033e466f2af3c779719474bbafd052fab1c17708dde9891f568690fb198bf762c8c88610d25310fcf06df043da7db0f2befc79ddd41500fb209f865b0de30c5b578880d1f73f3df9c39a82ed653211aca490d9c5b83d9aecd7a1b432f8b36510ae87fd621e1801016b5b72d44e04cb267477f7010884b0ddddddd79977d9e3df801b638c71cac8d46563f6473a309960e66fda19ed6fe297ef3fd9b21a4a973242e855b8649dd262062c78691b88ebc5ab031004d58180ce909ff99d067a9b0309a5e52058132cc1c08b20901a10977481cbceb0b229eb7e8cce114687b15d7646accd9a7f47fa37b0a035bdaef374d9195dd7f9b27179749e7334fb8b36feef2b3672d9199625652e2bb085819833bbbb333bb3bb67edc11fdbb7451e2110c61e1a6c23222bb056571149b49a423e62133a569214401d39a938836559564ea4220b17ac98544c9ac069d4d66f6e4e3401441343965564a5836a499a0560c1c451a624acc4ba4590a0d298c062821e202a45b612159b17b6ae6e911e2aa4b861fbba457ad89092646ba95b440a1890966c2eea1691420a9a209090e3a887f9a16e11299e5061dd22ab21f57b9924eeeeeef2dbfade19496c75672461aafcb0db24e6af06bb244e40d4486fccda1bb8a99c837f470eabae0c8df60cd01f239dd918ff4865a6f3afc51cd99237e2cf0a7f568e6f25e1358755573a057baa47246031c2e7c840c867fac5282d18234ba8011173be40a4a8fa50b708112a2ac6d9c2a659028a5310bec3ab3f135f03057fa43ba47cf8b0012fde7a6865ad09c11d4be698b5c7c409dce3aebca64557d26beeae9846802defeef62e6f154a3e98d9dbfda699add8c11165d0c577b9306cfbee1e3dc61833eb2d8fef91dd610fef6edc655ee6ac891c82544689c1046fc31a27b5e9f6085c3881dd6b1ac26e25b8b9096f0c0a8cf25f48ef1cee37915b0dddf0bb97b7616fed35a4eba82177d38c75c76c2a707cde5dc89561e38f304ace8263e49f58ef46b62cb9bbdd4b186e8cbf1620fd62c591d5cec27e7c76dfe57f40d7344d09416525b85b083baf7424d8f699ab43c885aeebbfbe0ca13b8410babbcb38ad69b9bfe16d6c85f23f7777e9524a194d1bb3045b195b8a776185dbf4d474eb55d710dd7dcedd6f87338e0c72120873209f004208dd23d436c6b38de1973a5808368613ee42c81176f127a42b04f601c238d9074208b7bdddb803561e089d77f73714cbdd37777722308890c07c7d77d97ddd7757ae15d9e9b631356821b2ed77ffc670f70df38d3977777787e839e784bb3256bcbb2b572c4bf02c14ffb6e8c6851f29ec87102ad1dd3cc03ab8bbbcbbbbbbdb8960db67662deceeccc6bcf0c0bc4b7bfcbebb307e59dd328f7523e4c802feab5b66ef26f3fc6be5e73fea3ec8ccccd12d19a78df42e7aa0728cf1999d99a373e50ec66ddfdd8d69d8e50b075b7f13d65d67de5d76385760f6bebb7177bb07f0b196018420c618e5ee7ef20633ee3abd0117c6107a1d6ec38731c5eeaeef3a1530ff5e15d2b6158124dbddddddfd829db3427803424f02363bb5b066b3bbe7f47f71d30c4980c19e106a42c00e52e40948ea2723f38423b57f8646d72e62c5121aae59aec0b6412c585523fdb57f26a80923b5bb7b9d9979f4a526b6eb4f1e7bd97d292f3feb74a0bcca6ff7b13cb6dbf6b6bba4dd256dddbbddfd396664342a6392f6afcf5e7b79d2e920fdc98f3a1ddbaf3c4ac7d261ab6d07892c24425de9b66e87d7ed51ba1bfb288f5d9d8eeb7778b5b9680eabde5869c00abd91bdd67d39ac7a7d0a4dc2eb45bf8cee8fba8f4473585b77e3e4faafd75f3409af237a02e22bca7f45a14978cda020a476f5df2e1bedc907e1b4ebdbddddddddbf7e73eccf6c4c2f75d998feeda6414cf4ea603aaf33407bfd5c67cd5eef7d4884eddedd9984d7a5b0bd5ee90c09d80e8e68f3ef7064ebb94b2c02063f891018e9eed2a55b32ca8fef7f23ca6949e952baf4fd02063bc6ae91eb8df8ec438c52ca186394b1932e31d20dc58350a45ce952ca13a45b63f52598bb4308e90de8188531848803ba43ffa5d3795cc667daed4599f66eabccccedab44df7877181620830b32902079382a36f838f807ce6bbea45f67bd5b0f9f465f73be35d78fa4e0540c05a742fa6d0b159bbf7cfe66758f5b5352702a8dd601941be3524a098ff85e52eaf25dbae528801042226afcedb02d040cf9b99b58802f5fc6a80c219431ca18a3c2d6c27f6eda98a5524ad957f049a7e4688aff6ff042ed77d927345137186c9ff7fcd64b3afb372bb6a146c6915549d98a9625fb07a7f2f1968c452041ed35d47e18202c2caab32f0f363819c14f09509af0f302136831c61885a4c1045524610c4b38c2179480445302162cc8fe6188d0440718ccb7b7d05d3c4666e6f69e30c2b853acef95c1c86baee4d1e8e5cf39929347c320d963108b0c63224eba2fdfb22c26a09490660926be26fb116833fb7654185d3af53b626f54af118f3a3d06d1b621db965128fc61e4f85b37fce30042b0d9c88e6b36d773c8037e0c9a12932329a59412c69101b099189fcb0a0c94f48395f7a7deb8247c39b47185504a7ac25e62d8cfeeb480add85bffc13afa9b1310f1b1b7111fa338f647c2b9f0e662188339c22cf8d74fa7f1c7a837a2d7dc2e997a833f997a59968543a6f2cbc7641a36ae9675fd1c3dff474f230a44d78feb097bbfa9183d15111f3e0422c52103ffa64eeb67675950fa8d54c2db9d102d282d19a7e4872f21c4e17d61e30aa1fc68fdc7929e2c4ac43aeac3e1d5283fbaece0c70e421fd8ca8ae4f6160a1beff433b94cbad210a6dee087a9567cfef811074cf5ce64868df906d3d3fc8f2b3d4d2b09ae32d613a00005302ef0627c2276c727e210282bd8f87b6a0cb8a990ea80b56930c5143744f6a7f67b4eeccffe0861bb872ef408b6fd888d6033d16d4ea087239b7b50f061aabb3156f854ef93f19d8a5507acb14623476ad74433d46f26ca872720fcbbbb9b3b1d49c01d2720aaa4376812b3423a5319e35161d4b11b03e90e779ac3abbf7cb77663befda9103acbf0954e39836164b57739ac4e87e760a73b72e4b0aaf6b34b228755359b13fa25e155a3dfbc6a464ff3b77e36cdf4ab31626b7ee7b0ac3ae90729d34f099cca4ae054ff52135be9c8667d7fe9c8367f7f4e1d8d5373588e5361241cb08589f186b631fe48449bb91b13df7aabfb9088d2b1d6acee460ecea22631abf548e8689c3a690e8b66edb9c935ea5bf37fb3bbbbbbbbfb17c69180313337e14162e44757f7eeee529edccdd643c8902143e896a5811d1ee8d49416ec38e8eaf888507b8ee308242c30b4434349883ac55f64830ecae15eb0f512d577f7374bfeae543d2ad944b59ba8c6a7d157737773f7c518866118d67cf10df7822d0a176c5fafbe8ea27f3a8a9db195e7e39dabe39e9d06d23c3ca43d16386c1fefec3010e6e9a0511558d685921a36decf610a5bfcc2c6ec9752f053f7f71bd2c05c1461eb8fcf71bb1b76ef0ed943459c5004480b1543813b7634bd21df7fe90df8fd315248d7c60eaefdfecb2f78b0f9cf954e71e5ce8ec42123b67737a8aa68d0c0baa9b7109d76a0104beae748b0ce85da7b2183ed5b28f80f418411429c64108794aca66d27ff7d45f97d6ce8a6a9ebb4079f2d6b63fc4fba1731d83e8b8602fc2d297fce193f7b1bf1339afd4dedbebaeb459d05b3ebb666617cd4de032c0859af98300cfb936e1d8565dcafbd8d7eed6feaec3e58a796751776532f2925c7945b2f5f846c2e03574c2e0fe3b505a1f439e79cd0ea97d875132b36bae0b4e04309218cbb9a50ab59a781fefd5a37dfda9a9697216cf05dc674cdd1c7c73ac6c774baebbadec6d68bce64b1fdf537b5bbef6536ea9b3a3b0d649485c03e584fc331cba5bdf5e67a6ff99cebbb128259ec0bd83adfc6d6f937b5fb8ea83359c47ed9ddd4a502601f662a0b01db4eb5ff8eaecc1a5301b00fce837a85ee0efb318c339f73be0df8f36f2a77d65a3bba4aea2c601ffca1c50c84dddd5db8bb70777737b2c092d29216fc852e2974f77508954068c54bdffd1bd1dd7de19b868023460861840b172efca5d0a3304dcf84052740f9524a990a21f4604c01abc07621847083d07ddd7dddd77fb777b00b77f7fb17ba69fe0b08e1ee3a5c1dfcfc0c1952840940f60507849999b98f307f5cc264495194c9184669314b6b5e56c6f31a3133377333377733777337333333337333f328632ce34c3b41e1666ee6e66ee66ee6666ee6e66ee66eee66666666666e66eee6675ec26449d1cf91a01fa09f153be130b28a9bb9999bbbf9086ee6666eeee6e6666ee6e66ee66e6e6ee6666eee66eee6e6666ee6e66ee66e6e6ee6666eee66eee6e6666ee6e66ee66e6e6ee6666eee66ee6666666666e666e66e6e6ee6666eee66e6519b9fe9f6f0836d33333333333333333333333333333333d3853fc0b69f07256cfc71ce39e75c21c833957d2800e7ac0ffd5f4c910e44958798eb762f86b0f15bd65b5fd83ecea97dc339dc710e3facc11fb3d3ccc9809bca70fe09be6c8d0127486157d89ecbf06e7e3264649ad4f8be84a1911a1f2efde04f8dabe8bcb1a33cf92b0b5bfc1c5cdd710a82e9a744650a8b2a8dc62e2c6c417266cd35c408388b9a6ba65331daf3fe7ba2966cdf09fefec76ef90203b6301fd3e88c860216c000d53637c346994906b6c98c645cf4eb213e4ca43d9f93552b43c646ca3ded390b710b1f391df480aef180a3fc5b54a162525dd56e8450751f9240576421a77ed91559c0a97b0592a22b9024a97b0592235720b1a26a75af40b2aa3cbdba340925b33d1e148142109c400436b1b407988dd576999fa0e4094dfa8753e1477b333b753eb62f41f5487d31d2deea6705e4f342a4723f5e80549e3a513ba81576755bbbebcb0e6a2775bde8a086d4f91fcaa7ce971f6c729a1455d18eca0b50048921458551173e2e5675becd45449cb0dba8d0f6a5ac5e55677fa81dae88e1b0cddfba45a0f85163fd5e86be9455116aa7535d86cfa4d2a934b066be0c91a17e2821a89def45a7ea3815f1f2f123edf1e2c2c5d06f0d0076b688da41f1d4f93bf86043ed380dfcf9281ea789b4a7ce77c153e763336378d1a9f363cfcbb753e78b903a7320da98f92d516c2e74eafcef858807a4a722648de5a288477cf12f688f9a222cea8288bad84959653bf58b45acd918a1434b4bd0923a7f3f97faa5ace0971234a393a3a4cedf2f36914bb8d42f16750b13465245528e4549397f53133b528fa326ec4824a2eb62c3757d93945c2d3a5211690993f65454baeb5b742392924e2d29494722222921ea54ffca0a0a0aca9cdda5f8e4f0e4905429909254357a74b5b8ac7c888cae1e430b9377293e9dc27e7e8aca697e60b2692d061e08238f90a8d9a080486959735ed768c4861e8cd8f41084f50094659a767282e201226488cff66ccc146283ead9341bb0a27836663e46ca38201be9495a0dafa27c78c8696ff2e0a3bd99a2d9a086b4377f34ea0ed5d3dea4289fd56c503c2a2a2b59ca68842282c353d150abf626b692719a0dea877b4eb34111696f964a5ceaa6289ff6260b0bc7955040341b148f89c56432ada8a4984ca4cd6432a1984e3493c924773841a5f438cd890808694a4f0a8fd39c88b01ea6f058f41d357f3522ac8794077c8b9a1c35df058b6df4a8386c5fff45bfd1aa6e1d6160b4aa713d3ba9c5e5f429485a5c5c5a84300076a20032dfe5d432a39a71a253510cb04b014a09d26c864859396abe090a5bca639ec10ec5e3a81e47cdc720335cd2c9f7813afa8149a76eea7c2cfec0c4697e1872d4fc39fa8189d46a78f587a1f6e6a3deb0ed155f3852bf1f86ea7c17382e723af5fdc0a4ce1fadea87daf9614635a3da195c678aea6508b5b333463f4754d48f6bfff7325443a68ee8d2abc5635f7f8bf78ea6a83ae5d5f9d83ae9c2469f8204c39688c247369442b444872125757e4a90d3c09af9d86868b4b3311305a453110b9e4eb108e9e9d4c910944fa7567e3e8a48a722181a00abb7307513686f7ea9fb5044ea7caefb50ab3a9fa5fb483975fe4af79178ea7c95ee23f9e8602d4555e7a7743a20a9fb4c28ddb729a9f3793ec9a7c8537cf0909234cdac998fe9ec8cad736b564c7aea7c92929efac5a213fa9174b09dba15f1d41290d378a4a1f9a4261b90d36ccd0cda84bca14d680b4a59b5913a494275fe87ad7c10c3e6e25fbe3f33e208213e2d2d707c7ec422d7c2e8ea22a2c84448e200c5253336a9532ed19a8d96bdb8f8cf5461ea7e10d0884e653f7f029dd27efe119dea9fbf00a7913f7f08a7b17ebe37e7f48a88efe2852e01c548eb0ec5d3dec4ae6bf4578763b40ae2e269f4cc286a2732894d244e9da89d0e05a4bdf92e3a144f0f6016396a7e7fca6a3ed61d12edcd4f59d599b29a9ff2333f05687e4ad1fc9426f3538ccc97a13aff4b415267ca509d294475a62ca93325a8ce1f5dfd32d4a96f895e92c47a21d1a9f9176aa276eafc991d9696c185a02abfb4836d6bb6263efc518fcfb96da31db953a55c47c997f387912a3f0275ea48d3d0d51659c2901aa8d2a864cd96653d0b198d0010000200f314002020140c87c462b16834ced3104c1f1400117e96527a581749d324c8610a19640021041043000044046666260d020ebda43f7ab3ccbe7a0dc09a74248965857fb3b7d0aa343991d131898af16686613d801f00902eefe80fd6c5e0372a4d0c6021d9337fa777e9e1581a3e59a1887a142aa468729f587169e065bdf718488758729e1c071f2877d33118c6f816767ccf0a992361cf633c054dc8b06a36418a45fd29c76587983c9616c4100d651c269c36ba92290a182d002c0144a9549140dc557b155efad27f1afd1b1743e893bba442645fe0b342e64848372376fbf2967909bddf3fb247865d2221c3de048e98238c84b02fdbe847b9c87163ebb023237a1861b42d4252b3974ae865cb23be07bf0dcbddaf319c45d8ca06aa56262a56d1d08ddc0fa294ead932601153b44d84d419e705880dd32f9f2f9974e6f3bf1e4bc0363f22961967ab22aaeda5641c2717980ee6010078a6af1d456da08184589e6a475d06c583e7db9898c2d543747304a174989ccbeda7c838ad4a8bf4ccdae45bb7a8c1cf2562577f143049c69331ce1b9b513a21025b4b513dc33d2b05f19e89289fbbc255179b60af2ee0272322ab1a91543c4c193c40a235a3e1bfc6e24c1ac6cd254341f70e9c3f4f513d681316d1006cf704103f2c81ed7de523901a16c04c53b72f988064974a6bed0008ab40fd6e61e4d3facf5a6dc0eda9c88a976350533a6b2593c704249ddcf15a8ee99c8fe62f718d43f3cb3bab457891f9e823bf392955050da096e39db6ca7d2bbe3b58831b0a1065fddc33c2f4a4d5c353771f3fb577487668fdd4f3f8181719b2d5c73f49879c426284d85fe6baddd61fa4a08588de3e859b42de9b598d9496d7cfba3830acc7ee3b55ee5698863fa7f01f716b4d7b5caa98207d52d6a27585b5cfa30532395b9f5215ca0cb31ef1aebf758844015b7f850c001ba295326d16d9cea28310d0066a190d1f29288822104934f4fbcd64aca97602f43e6d20691ee576b1d07f83c7c9d48bbf7b57e6769cad132483d763bfcc8e3686a8aca268e5d4e0f47c5119f814054ba41b65790976db62b9869dcae109869e19d07c0fba0946885f02e25444a731052edd7022301627f9a8ab6f4ee7c7890819a0c7726adca0ba3fb4b9d2b5a731b8ab11c68ddf9ef08d80f8cfd3d5bcb4baafd47ee29c0a7ec925747bc739f3294b3481720f3c8480f79c4febae43e1f9338b5e783b0035bd8aa52ae18d7ba9f26308c61646d2233e9782f885e6ccdf3d7b4c2722bdf7077e345a6621e1f4048525e1ad0e5260b7b39d01f034bfca2a08438c73f900c978a1690e8c8a824b2c9cf33b880570f42c5de80621eaf07a60c252fd90e494609a96a033278b72ed1912155c8973f54d930d20bf4fbba751f88fa2932c189c2cc85b3504c7b611cc030da5c884a73e16afea03609b2f04b1d180e1e4d3187c7516e4ce7ca83110129c1b9b66011aaf2436ec73ea17d8e39584c0c5c1332ae202c81bd0e23a2a08fbdfabe66760a1fbc836d8b92a2e1c35021101e68003ba58f71b817b029e39c27a6149f828c10a5ca2b85215341e20ec20ea1829db06f41eca0c068dfafb586e455f775e0cff6b455d2e5fe94f0f09456170b2e210afc840120d7dbb536b6b55266b2fb2a1905b94954a2573411b0f83265fd7287a81e89966f535a2461b41f0808ed8b177075bfe7ebc576cd5b1a3637051df711e3f00f8a6008ea877b5ffd139e7485d17c5460c7673c013c0e58262c636e896aaba1fca6aacf9f7f291bd0243b500de1d07e77901788ca064c6b200618c17c722c9d57c5de683ed2cc6e5f09fc0b0898fcd9789e975802f5f679622b2951b2d68af53b9364debee750863c6081411ad40f2a9ce0facfca042f979f49bbee9ac6ef4439a59805f87522d04c26315c5e0f453a3b02f1c26cebea5acbd7eb2bc88f5577172e53353ccab5328cf4404e2dca8ddc5f1d669c32c4710fbbdd941206a7674ad4c2fbae0b0e2e5c9e54be3e699432f0cb76505fe5b0444f6d3803b6fe063c134227f69482ab9e88b89d537c49ec113d971bf1c6d12b7fe1896d227f07156cc4c91f99c8ab815fef95d3368f335c40194d4e036617eccdd4c102b88137ee004b9c74a9e21fa38c2c0b4362eb44ff5ff71158e7077f5fe18e2c8faff704191102a6eaa4ee39e79b5492cae949a8c66f9263d4dd8ed2249859c99d7d79b0d69f817a658678c95fe43a6dc6b62f3d3b39d9d84abc4e21badbf69aea5ac230384804c3ed0af81c8780da526da55bfd950b6b8e36554a07364999b4701e7fa711bd451ac53c4e1de8010ef7d2c56424bd11de74a56af02bd01317a85b94e5f360e5112ac2500c163aab8ec20d47ac3ef7f755479a96b71604afbed1ae76e9e09b244cefb9509c82ada2c9c5d5777a22c202a9377ad1cf101a1476e9992513c12366321de1fb9b780329fac0aa3c51ebab72f4eb2c9d5a4b21959c445dce49374ff8382d261103fbe24dd001793dbaa765e72b7d20d593e9815e85e9094b94fef5c7f835af572d055fa62248356f19c6bdf2f727be3ce5ccd9f297371ff9a1a18d46c779746c3150e65d90cb88521e30865e602e1edd97ef8aa735a2459c047af581ea64164423cd70faf3602fac4d2b0562f89b4909e34143275bad74f4d261fae702a2030aa3275af4bab4316837f228e98c0b04efb3c8dfe67081cff743e4053179881fb567a9591af0d169921ccc447ac77eaec3eb4ee99e44fa6102e3555ab24d97aac56d25d34c87c1ee37abeb6ee0933168d81d6ada10c84856f9eddd15baf4b29111772a71f9f129151d17b5ae77bb7f0324858c6a7b12924a497fed34905852261cea2b834a77eea7b4a47195634ec62e9548df4cd5cdee36381d95b959213601f0522b4f8eb1ceb66e4c7d473fa5c1d345b57688bc0a0d935d7241f1c28a069e22ac0621b9c01b6ba4d2978ad1ac368ff646a63cbfcc16e383aafc9621e22f57098d6e25134c90e250d68f4704e548c173ae3b21b26921b8712217900292dda6a9e5c02b7ee016d2b35fc604f70baf999e9a3c3fa93d4e2181d0e65a273f9f09aedac56feb291b4679e84299f4c06c6759b8586801354592731238913244c81b2ecfbb30897c4bb8854f958c8b98cdda971e328bf50fc0fddc11e43f6c8e5255c0f36926244a9205cb2c5b5c97454cd43922647e48505a5687c7205dce139bf7c53454237039ad37486e34dfa269ee9c24f608c1ee8a5510afc0a784e7af94dc80a8698685d8dee15cc612d144cd3ff711154ee70a83563b66eb64a14b127ef0ce8077d29eca70f3982822534290ea3592e5242f27bc8d425f5c8af1145278eaa111f03ab4a7f04128bd38385ea27e6d4606922b93e0eac6e5ef8c07da489198d372ebd570c0b97dff7736c724e96e6b7e496e96ab4d55ba921ac26ff8bb563345587caff0af7a9a58433558db0aaa4680be3f2708f79ed3d78afdd805517f9382876f0677332925558c0e6bf8b7939449dee3e447a7cb0daf98721cd625c7026b8f0802b04fd09b52b4110520e32a6c84d479acb0f0b571d4216245c311d1b5823255fcc9affad019f1cabc520dac89ee1701afe3d22c7d4af6519eba4d8a6d1867a8832b17072e702143fe9a3709d2d61bb828406b7a81fb5fc791d9d1bb36f71eba5c29eee17d50acbcbefc7f46dce4adb86519ae0d33ecb4e274ad259d73ccc9802395fd550ffb81c9e35dd97b9883331a0be88d9242518d417bfa81fca9b65db892646b35a415547ec09af411b8dd568ff1128e0eb37c24c677ffcca819e18e4095b531fb5ce0b228216067e30fa6915801a806c2c33060e05d21fda2b7e0159a04dfdf14bae5509f71b19cb6e08344a9f0308a22fdcbf6bea7de189a308d451ac52871afaa3b4734b51040653e7cfef2f28331ebcc361e6f6af1b486f39f5512b89bcaccc84aa54dabd977eed0398242632b872e43aca54881503fba875824fc098841cb07158da3224dbe2ef5189095d1e5dc16073606db98f5a17db0008682c55c6a206636fc0fa8268554d432d34a78a3e5ee15d46c24d90c24d925bb0a62fa8cd58db5c1fb50a530107438f7b99de7ff5f8f60543f800813ba7bf952d277f99e6ef724f1fb5ae0b134856a31aa0f1034b27f5a6eb62badc61346515310fe5a6cfaa71b9e3bed03eaf5e7830b678d245de013131aa7219313d75c20b9bde72494efaa85552382af938d0279ee1aa87797eadf581bd7e92431f90da5a18870ed0278d457e2014ebf204e35e2e60a5f8faa3ab73defece45c80a2bd14f6bc624586c6060808cd8717d6c97338ca65c393a8151e130dc7e005b52262e32d23268ccba10f003662a5746814c410cfffcf3b5cc124c7814545c99f02888c82bed7923243d5cddbfd5f228737b51e9ad9fe6a7179e20e22350b444c5be72517764b53d56b4b2f5fe40d040cbea3212da985a6f5edf808acc80ac8fa064cbafe9e30ba690b8112c2c21715fbcc85d52dd8e2bac597d3f2830a2b2764c09b68cb697af3742a22650e627011fb58e56073983bf47b7d9b637b40e2c3713ed603f78dfffc4d49dfd4227a364988b4f57ead845fed029d6915e759a25a2d79db08af8a553ac23bcea344b44af3b6115699316b7a619d4ecd415c437a5154fab3ca6de13a3903679617cae4b57786a7cb7fb7da079a0759d661119fed38351d1781a5eb154440c0ae5f218750d3d8114604e1c97e3e87fc15d8e3b3ca5b913b3e281caffad88016a2f5a1cb38821c2eb7136a5b06829a25a8e1287c23c677a4f9b9c34c12ac8a800ef2b4f1911872e90e62105d4e9f97ad4471fd202c0044455767cb5c3811757fe15a447817aff8fc0fcdb60f46f9a7c9c1388082eb412ab46a1f418a1d8b8294cdd138d39ddcb953d31303164fc7d371adb36f1bc36a521c0da5ab4ee5de48656e07697fdba0a862a2c41115059e874a27acb8b3d9b557bd4503d3a4ac759bce186dc1a44f8d11a90a1a94b22502eb944a4c0ecda333a8fb0fc75d166432a368f42d3640e0f119eafd898ec285d354d32061cac83b2f4981abc4dbcff38ed48c04f4fbd7dbd0f9d27bb70d37780fc6f90915df7b071bb7c07a8431d0c6cfe8f6571544453dcc1f665a6b11150e6872a13a48fd201e90e162bba6389a30edf970a98232710d9fd9d182380ab346fd2b38e7b0e9411ab9a71ba04412a353b14e4f0f2ec14a313281e78340987f3ba6227f230124c27e620e20ecf4b5022ce579852807a2eacece9bb24fa28cad162d05d07ea76086ab8512a4e4ffefe6d1a2632aa2e7699d64d23e19e455c163c892fa43a5708b778bc1ea82d9ce592300911ae0aa5e15f4b6170e24a808616038c50ee2c5682a9d70628f555ba41503d7046a0709777dc8c8537d9cca28b91868417176d493ee481c20f0d13673673d3fb179ecfa6475b360a7c985a6e2060cdf273c273dc9313c0fac319aeb5430f770b106df17e061e60952555d64caa9e206e21b8f482b8ad82290f1ad06c6bad7d07eac79543b69ed3d951289bbe51f41d9fb1b494805dda2a4d53294ca13b0f209c2f5f87e1d0f245db88024c63d777d4e76b8ca2c645ecaa3894ddbca33e7f42d1d2c932519357e63e04373bc01287857a2f61897db68fc4333d9c5b9c09bd46244d62f17ec07013361188a26f00ffa4ee91143584f5cc39f407a1eea88a886a2bc9d10aac5553ea8bbf68539a428839da65afce98c107e643a4e3e5be02204a9d7735a9715cd93d885a9ec71fbcc357604d444b5c5f7a6c73a5f74ee5d7c260fea1934d1f02b79b9c23547a0ff44474879e263b565d108d9eb735becdd4744a4a5274018f9fb5f5a5b05d680f46699d5cb078e2ae22f09ee763fa0e2e014636ce6579940b231768124b778432f72e2968805b3d726549404f5f88a84a443694bea6f406b4c3b72cb341a0a98177a09d90065c4e24f039980b56cd4ff28bf962a450e0b0f506655ed72fb3ce14065bf6cae925c8b6fea6b339bf5963452ea45192a2d6bd3788300d88b5114909a3b2d32176c274509440394fc0f02f2b414460048d9e1cb0badc2ae049e00f2542c0f38971a2eda721d5f9e08e216f0d829cd68d7c01d720209536bc279a98dff9c1ec16ce7bb29b7407d88cc5211037291b37aa2ec628190dfed98b81a051b7584c83407ed4791b514b33bdf5cda9d7b21182c55fd176eee826b61c4f50db1319ed308dbaf88146e9dc40ca9f1632fb96300ac7b523c11c294ba46e36033116ca7e068f1107da0eb005c81727cee235d1f808463c1b04023013c34f2de13fc588176f171ddafcf99c60c4d55100f53b3a20b5363947871dcb01bb25eb93cf25203eef18991e8d75efca90725d0727260b47d9ba10dd7bf06eebf0b6e1a61bea948e19a49284dd5e60d97c2ef3ef988981708c3e017cde1c28a74be3eaf7f340ea9c00a48098daa434199fc5602546601d499a70db315ae99a04832ebde01e426a1767709c48fa76be175ea4a98e7798e0f1414632b7ce5c1b3696f645450a9b607ad8e787d7c6e2d56907bc33eaf807448bf95bf9fe6c835dcff7bad4963f38c4ab267612e032215a2803cd0e47a1c6f38f0b3e9697632ac35a8406adbc206359ce3a70f0668cb35fe31507868034b408600b33b8c6a598e82be768727359b8139c99a34c5b10e3561d93d13f0beadbdad44df561acea2924d2306e256443550a8a9c868e1e4399dd39509b127ac608f008d52bb78e26a0098a9cb77b9e21a881e880920b444275e516cfa9919e65d3546a81c95da7e780d8c20674e01ec68c9a88c4f778866d22424233f53fa350570fd510e4c4ccba81b42c97bc04e94af3f98b2006cf9ae5fc55951e260b70835797f02103110f10b87effce9e06012e6f1949a0ff87e96871a1cb7239715a661d90d7d617ed5850fb84403fe185de1b4fc4f545ff09e5f20741fbe02330cc1d751d2636fa5b1620b5b22641ff509fc5b3ce3844b5522b96104539cbcc9fecb473cac4b60508f59d090e030ee4c3a3ab8fae9112174cc48c0205f045a524eedd0f31de47ee9eee6f05818c04a9b55bf4ece38a243faf3e474a60f334e20f9210bc32e75ef3175ae5ccab55610282841744df7ebdbfd25f9c5d3e45c8b17cb880b730ef0b19828875fb6dea9313afb0078e1ade9494c7feee1b521b39793d4424115b1585c7600ebd2228a9bd45a42e1ba4c1c76f0056afb64b95a7c199c925e634d5b0c4454783f6f497656f5e1ec89d80b7f3dfb2c6fb15036f8fbe01889fa8d821cbaa607afc79dee644e9b152bdf4c501da7342d09740d9976671ded8f102e6db2bdbad4137c6353d07c62928842f58a1ff1be61f5adb280cfd8a19aed81a96f9c3d54cb7f686b14f05dd1431c4bb279700f03bab229b696597640e3a49218edf6d8fbb811800b25310ad93c0b0f51b09e57876d77cfd13e2b00aca86291db63ba8443bbe49223a88be21a53e6612cd8152ee044803881a9dffb72344132152815d8773ab550e166384aef86ba5d085d71097d6e2dd68ecd6d6805c0e3bda86158390c097b0bf0099580000e4377e014609b921bca7d5f44660f47e0ba47308111a265533d889492dc077a1d8708d85d9dc65277766e0b7a6db019181630e56642978112ccbabc88316be830bb3aec64c32292e1706862bc84be8a78abeaf8b3d3b98ff02abfd7bb31792136e4e08190ba4288126e4a57e5b26b7e933233fdfa5661d3b677caa32deae9491a0e8a1b4f4884121fd40cc34659e511b5f1764ba3869c3a29c609ca95adb1fb9f77fc263a969fdfa6126fe218c91c1a73978c907032e9844bb52c20e8f807428f64258380106a057222a6a3a64b2c12303e3dbf0182eaed2a0924b21a13c6bfd6c18db74ed7490ba92640bb9edc9af0ed2b397ea201d6c384798fffdc78d92d2b633d07ae9e5ba4607e9b45bc9b57913a15dfe283f07096a842e0efaa808aa9ec6f1aec88ac65e84f6f2ed0fed8ced5d305fd784108ebea61214d74ee02d5e3c398902dbc65c8fb218d30709b3278f525c54ba012baefcbe7434353dd98834f71cd45ded711829e28aab2166644f7c6a457181065f9971722287e5c75adc96cae31819a48556b2a176434fa137a9cd53334c0181d66d079982759bee99ebddc4dba39e4f35b84826aaa424458c57c28444350053fc1c9e426fb735868b1974ccf4f6518ea6a272df105882475abcde4de227de0229ed1e4d020a8505a3fa8e21748e26342ffb4071a1c8649456a8ce9406344f99e56a3d110eb80b32e2ba5c26e0fe3d016b70a604239e59f7a9952ea5ed40270e07d648fbee50a4e7104ba17439930510bbf83236dceb5190eebc0a6b79eb99eb8390d7aba7580d3b7c433e94bd0077df3572cc060be2318153d0bf71f9fae3e4b7701e3eb1a52cd0edc13b7cf8a0961afd2c4dbc0f5cfc4b975cdc707f081cf265a710c1b68c6a9f07305af9cc9f5b1263235a34e586bb1a7f31299cdfaa83b0fce234fb8c82049fdc71d78787dfc9dd487052e46b6dfd7690d9bce247e6c8fad0834ee7b0df15220d5bffd2ba6082ba8f6ab1ab498f5cb37b06b082113b79d249cc81d1e9fe7de075565f9aa27341c0216717ccb17c3639b9109c95d72b4343917b53977f2b0baab1596a1b526db8c3986441173ae27b1f15e64562b7ea17e058efeff412cc6160e8357e785e371f17812954350cd1739079121934cca83910520f69df3ca739530cfa36e10c8c4880bbb55bacba515291684338c805df9c117334942e0e3eb671f219ee22f1ef651e12dc8c2451d6401a210f30ede7f15e37940e5a21a67f5774a05980f8ed32b4face018d9e3dfc03a4b596d89bf5524e25cb686f06b25e835c01be43f4dfeb0e0841d2eb072accc26122ad115465e02e9cfc86562e957f750631bae26eaad962b55c2678caa5c6deb81f3bb12c574210f1acdff73cecfdde0a39db03c058116629a4558fa4992a0eb88f02002fae04244ba9bf17cc08906134649b194018f17bc95ee4c772fd1325e31c0b3f318e7525b19191c3ba9a9aa9e6bc80ed27b51805645953b1ff3c674b110f658dbbd3d8a97ce1514867b6ab20ad8625b70d342955023018171b013478d0b610a306399c4f7989d56bb7482a3a28bd3f3b55516c2002065de59a8bed1b10eaaf4abc0176b478c1e576734549fe185457589c57a2d39f3a7ba4e9e66ab32532eef82e5bf80f9585cca5422888dad240266f938e4a49888b24446e7cf42275e72ddbca377e0a102d6120136e6752f6b2bf2c3c5312f47fb2e678b3081a90bdee9060cfce12bc70fec16ed7526ec339f2716ccc827c42b4705ba1e4156db5a9051128d47c3ab73c7d5ed2b8d902a4885a49c395f8db9f5529c05be93311120181fddba14126a03d9ba12e8a8389956687d13a8b95b49e297d02cae01c4a6acbbbe1d59baa26ba0e5371d2b53ea0a600e6add41b6f64fd91d816fde115aa715019d105536ce78981adb49f0c8dad9223229a5144acbb4aad089e1220af4f33a5aad647d54127154d53b5c0f922453e6ee4b1a3d229953209ae6152e10164a2c5c08d965d8d327cda24fe5fb2a6235746881ad15a164cbb8de1730b454fcb3ec5079194cf694d224da933857978617a45a309da26f7999e06ffbdc25308918291d8c48063833250cbb5483d74f8cc1342d372371168954b51a471512b32ff504f260d21d83605e43585723bab69333341799eacf37e9f2878cf3b5c6054547164a75ebe6378672e6432cfc7e8b34419cc72ddce61bc2e5f3761bbdabc5b34c0f7ce10d258decbc2c42bef96ecc016020bafab552d94a2fd9840dcc1bda9f20e37b803a6eb15bb376d59f1d6ca8bfe3332d6ee7f28616c22d92e534d9aab2568cdfa8a56709b1f71676623bd5c2bb670f743b637a8ce599e8046aaf6363e55f2236a82b8761f0b951d19828ea23694dcc9e9c660c0f1494f833ee9ebb82a1bb34a32d5950b645fd7b10a28e3fc5f3bf2f507020fd1cf1620926ac82bd2d856d6a8838215ddfaa0cd08a7f5b51018bc7673fe750ac7ad6416df27164412eb7e10c0dea6f71092f6d2fcbf6e5431790a4f5b336d5f25c1bc529ce9e1b7ae8660a8fcdc927d4de21c4d759209d97838e7c0372b64f901820f1ae18982123fe49cb6fd90ed865055b663e02c60b20f7cc38131fc0ccaa109c9eb515b10955896a9541864205a0219b02d4b7c5792a772f2a4402aa4d3b2aaf1025d8d091ab12cee3fc6821b1946b5340a88387b71479ca5528f14b03064293a68c8c87084851dd8bf65c0b976827cf2c38984907bfc55f2cd93cdcd58b8c564a2b32dc522525551adba2bda8ec3aac2e7119a1879035f35eeb4a0c05b2d018b0d36dcc16d9707e6ab93e6e82dc4576acfddc2068850eb415015b6374434856bb9a30dcdd0691ecb6fee349667050262f9975cd718537b2abe0311d7ab25bd954a377145712e95f6800da866b61fbd3d049ab99455bf83ccd9207de6200d5f5c2cf7cc1cc8136a9d9f994830c0fbb86671155ca451079867237519bed9c5aa9718aed1c6e794373ef68d33b5259a49c3d96a5458bb0e9ce5a0cb98c204f06e8367cd7420a7c902056ed0f0ec7b93307e4e927e754bbcb84365fb24f953c0200b6020a9c2ea9a2f987c6c9e80f0da56461c6019e4c4fec01d6a9a4f70e3832baf290c5d80dddcc0a102ccc3005499f81e5135abeaaff2a40671458e15e3a81196d8f578e24e26f28121e758d6146e393cc4dd3c43be01d4b62f5629c947e6a70c0da0a45ff9163dbadc03057105b46abad11db7c0988958037bd437fb91fa61abe08998862f1dafed53db7ffc9ef13ae2724224d337bbca48ab817ba5f920a3a03418f7c8e60acc7d1bdb94f6e44d747bd0ec56a8614043a6ed30800c914807e3020173afa580968a09762791d0b90230628e02af5314d1741091b0fe17b04e9b6a6d7c62db661bf55264c75eb92891c4d9f51adf5c0fe47777b3fb90d291dd490c059edce9d20161f1f2448d124ec98bf53cfcc9854adbb5521e6cdf0c51835a5a931288da758269fd15b657c0b31697c24f2c286141564e735eded7d8615af2893c7a345a0422f2481e44552b156ab793c9e867d718990ea056418047d7e868a435462c2857a2e7b6149d1e46d834e1898e6fb8fd483962981b1226f213acaf20d758c59f171dbf74661c324de5ca4fce748972ee7147ec0938592359afde74b3be78231001fa065563b911b73604b939f8b1a3ff43b65fa417aecc1012ae5f511fd845bad91bd6b49472fe2ee314870046adfc2e79e9ed6a98ef553d2f83ca8a0b8796ca741a2a4e441897e2116c8fe84907ce9c4cb52d9e60c45330969cb1fb9f3510064b425605313a35362e7f9f5e1e466b5d96669a815f158f14294fc634344984e60787346eca1df173c00383de39c6a7538998a20370176f5a82b47a24632e4cb4e1598fe03ec3232ec5a9740546c07638da69f4783b031c11ca84673091ae0771aa031bb5d962d1091d663ca678a5a6b4d80e22189fe736825a5e01dd24c6eb4aaadb608b14ec03425603e33ef798a2c50b813fe3a87a1129140e1c0df6b0b61af1e2a5f52fae3c1effd67a013b3bdfd0212bcf07fae45e180ecc626bd0fc22acb84d6d2ccd9ad90f7c57d247593bfb440b184999990cbfb5b9763e2cc6099373088e67b8b8577a8f89f3d703b9319ff714f62342b2993ec81db60cb5635addb088249fe192ea2954b00ed0ac4494050aeb88cc196109bb87c4220e241752a8d26f97365a8792cda735bc3f6081cd56ca9a561f8a28c5a876aeb1729934f19f71377ee17fb83d2e33a99e27123c6efc85d04d1545889452b3fcbddb1c66c15bd3916cd730cd6d90a5cde97b8506a17aace9e35f9ad84027436ee0e03ae02025e800ba5864ae68ff4c8d2e9c31d0667e7b290946bdac6e9b7c248b5f4450a4b3db6c4cfee4d8fda1d9ca783eaa9c6bca600aee7d930adcd5c0711244f078374bda6ea47b4fbfa81845e5536429141201c1c605ee75c1545a48074316ca384e6938313f7e4a4bee086e472e36a0349eb2ead70005bb8489fddb5709174ccd05e428c5b31292a6293be6aa4659e0f81ab6eaebe63581f25c610e773ac266f95d617034c654ad7e60ade9d793cdde2c6c7150b2d6044bcad73cda8a473520bd041a3be83b4343aa028b3b02c0bca7905725a15b9e379210f778c3ce3203804ab09414030f782e91821129b333541e2674b2b8e55c8ef72d9c806798c77b44a050e6b64236a55eb8c0b63318000df409f9daaba074a753eb56df52c79f5231b0dc5d738ca0f19cd92a39e128ba787a29e83b50de15b6ab509b817b442abb7668e5f4336c91567139292e7ff0b60e5d8a7c376a3743b8f9466de73c48ac332a5c11d7cdc005a0532804ed5a84593dddfce520f41b1589312535e557b3d1081f2d922d206727b3c8955c2909e0feb0acdb39845368d770b6a2b5c8597d11feae810fc01aefba41c7b7fb4744fa00ae557cceb96878b0d646895a82afe8d63c140694a901cd969f95b7e856e96b8d6f6317b97a99716aa26faebecb7f04b4505e51c34bf34899df1a90ecb8c33c05f8563d2069c5633d21d0462bb072782378d18a8485a74f27ce5befa570688417c24248892d353bf46d8c5e93be7774228d5829f4e93b6fe1893ee07071e0ae50db5dee2940d1faf4f872624aaed2f29d9e2453aa045918a0ce7a0168b14aa370ff5a3c9bb7dea8437a2bed39e410e9310a503eaeac5fc6ce3e84badb64b50bd0afb6039669d71292a7bb682ba47aed37ec16949efaa58ee604c3e31b74f3564049fb3ca50e10d40297c77d5a08d37c7df62e7b5a5249811c9c1e74f824a4073121c5961840324c33be30dfaf3682fc6f43a68174379209af03846e64c2a1c62f5fb21eda84837c50284fd389e0c42dae4f6d69919e8e4960e2428cc2da2a22bc066f496753e72a134d88f8ab46e37edbd278d228bbcb3f4bb388549de2ef746b1c6a7c3e5290a49f96d85608a583996b25e60a9284dcc0e48078394316349cf2e347e2e084e7926366855c97286514614612da0a1b337f5a050010a19b855852f32064f071624a03a29cb5575769817d588477584663dda2047b6999e28c8de5a6224a64021d96f5bd9a502742834af2047d164fa09927d1c1cf551fe6070285a9be888999fbc5a15a5dd105f09cd30250f90dfcc8bc4a6ae36cec8141052d16c616bf0d58face3455172ae43bd003b079e05aea0f1d90454566134ebaf2cf69153e0ca38e0f28c8a7d4e99f4ae5570747bede8b02c8319d5eaa3a8e947a5a5c31b989f17c8caefb2ff331eecb6b90a18b00a872437957a8472802a90b2800dd957314d565b85f62b449f8f67a7082d96a3e46ee70bae9f41b909662178573083075de851a869256da034f1cff29ce605ce9aec7b6e3065fcb3c93363cf20f6bb354e6e06fcfb0fb764f3e68b70d1074b203cde63b6e5a50ef153ec79f29e6d40c80686fbba6ab185553bd743b1661876ab650e60d76665d159e3209dd9106646b2dcae1e490176255ae5a031e6fe087f628b9b9cfae5710aee80adbd831b3a641e0b20f7b0f9b9033bedb16d807aefa38f5ce28fed307bccc071def3663e758b07920988f832f3b82bdb4fb0ae7631ec2e6771aee5d77611d591fc5cd541836e20a1268f1b648935c949b1305c1f7a8a839c143e882e7064949b2805af97db067b607b14fc37f389b56ce9e3402dbac35bb5d0e0ebdb3ed0631fccdbfdee3dcc9be4f2b2a3ea7f84dba22e6f647c60509ee7104c456b83435bfd8a889b4451117f9e70c4826103ec542c142e4d5a02d781e7c381530b4e1008af96913c0219771381c09aad1830f7833fa1a08b1551fc57b954fd3c68e6e0b72e083090a5298d6a255f3b6749397ef2ca95893c1eadce08cb13407bbfbecda46b873f6a1700cedec08f830dd031c9b03cf0d46789c33e78e878373d200e3cb883f4a1ac244fe174e2bb7044a8cf94158600987bac4c88c151327cb224ae0cb05e2264344883a1deb1888c94256f27acf9a07246d1c4246c67ecc60e41d03e4c41cb1f33e38a39c33f163481d9bbdec34268f2057968404ab42ce94b0176074a9357226d8f396deb1510ad776e2d970a682796749b7b0b74ed66cec0e4feb22a569cdf4d8aec99fa8936b985203c3574c0f4d0eee041f25b6ea0c6d3c591a0b81b1e937dc6e62c67bc7ae9a627c37dc1f80b65657eb03b1013cf1bb6a817caf7ba277c371713556fca618e596aaf2751044416e999a9df65ff6c1768ab51176b3c50ba8a7d1030da56ee8d53f820fb276d5fcb3e014cca7aedbd66ad5d7a5b88179938381cb7fe4259de7acd7117ece63a3e25f76445f661ed03a8fafb7408b98c43f3675854c2ad9477de2ba7fa437b3d382a3a8262569ecec1692712e39b9dad67cdfaf533ac64fe316fc86151462628beb9c32f902fd32df6062460c80f7ea4ec3135f8636722a802fa5ef13416b9f6e04714b2230cfa2fe37a1105decc2ccbf1a9e495aed3614235e7daea635383198b27bf1631a4ae3d151015c1512704655d3cf97307a4ac612c56b0262161a908d2fab7683d2355629727adb25d8b52e76fca6e684c2c14ff3117902848c1b42e9dc23fd708c5b928819c030525ed45f1a3b7623b1d387c95c3e5af1940775bfadc8ce2b533c189b83514f5a0650402a95ed2a2a2ca5d6b3d46f10fdfaf0b407a79c2809b8b81041d7d4964251fc2e5f4fc1af34e66caf82f4864d2f61254468185f0300530132d0e003080af5caf801e6e4715d80419a3a486a19a817cf0dfd7af4d80407234841f5dc2db86b3377e73876b1b33eef2145f92fa3dc343eefe02b2984d10ce07069a7e503febcff834f45f7a31d3ca66f7779ecd43ebb4c7c6be4b12f94ae1bb593a17dff9c276e05e9e60c0b2c04d28abbca44e53b5d0e6949167a77283155a20929876e266db6e17f8282f9bbf84cef9b8dea88c38af58d919892617044bba51d20a9534a5d251f2dfbce27d62034e040b0212480d1a61d2a2d6ea7c8da9c028c8da6c893f6c6ef96d1d72653527a998766a11bc20fbc7042a3afaad81e8cfe572699defcf83f0247918ad4da2c1cf702930e2c22c9da491517c28f19f7cf50d6cd6129da58c3f7ea649e75558124c405ed956abdfc4892c22bd78c2a3da204782f878c931317df8a6d53cf1cc36c609116b82b1c299cfa3db033c63c69be7e3cbdcdd415c0bac43b6c49c8f159fa7008013e62200f0ee5cc74faef7e91ebb4cf6b8711fcd39a19119b16130bf0cdcb5867031b18b65adf1a26b99383c7f9120e2350e58d68a0c7828ea205de67aeaf9376d9833d1298e7c6487c2dfcbf42a53aa0a0f99ed410fe26f3e4e5d24fadeaef8b4b80e9428c06f2047845201ed7cf6db84fc93d9a294a9587a8129d60b354d84e400835e7353c2899b029240e0afadd8c9a05bf2677f492f50e78d493e6e97b9c54042b418bb54c293c5492d5e050f62197292d1dac08ccb4e773fc4bf11775e815f8524ec7c7535c87dacbb5b27a4ac24684863b04519691cc915594b038a1b341d8330658d7102a224db2d5ecf49413593855105fb89fc635e447e848a58134c0e36a5412ad69d5957f1bc5ceb2d1edb34ac2df0c3430e79ad9fbae694bd1a6ee41bb4510a57531e672401c082a40b995a33bbc0282cbe27b49abce90c059b7c5be86ea96e2c048e6f16e8bee66361db8a0e21e8225c025d08cd3416e4adfb0492f5089ce506c21853efbac077e316c11c894d09fd06ed7311011311bf5650c282801c6c711030de0fa0c42b683c627da4b650e36224394253e457de767572953bf919f23b904b941e238364c737fe0ecb886babdc69680cdc59529cd0a94ebe4e0b77c9364b3ba54b6f5f508c1b3e4836f07dcb985c0f4b679d937c219c53f800af589918b77d7e896da010e8efde020d0c84fcd85537a7682d3925c81e15bb21403c48cf6389b4422ef9eca2ffdf2b44c0c6c8fb6222506922cb5d14a2eebc67ce634550332e231bf29e35b2fb14209edd16c738821ab91dc682534df8a6815ba338f35b2813c85d2e58387b16be1240a174b0e19223a6a93c1e494a2f33f8a604930811f4b20b9de6d86f928f3c2afe6366d250ef27dfc371fdb7e321c852fdfb0bb6e0e3d754bc2006c0cf9f5ad44a75199068d3aacd6610979ad0d2336acbbc9671dc484b4d53cc0bb0f986e54d3a13760696f2ae2b0e4dc92a52570f2c014d2a4982871474ec4fd1ded035da6ed4369213b63a5861a3df30854679b2c0b96e2b0c4ef1aafd85fcd94c47c5d583c408ac392e735c04509514ee2ee4ca3b290d8879926f20294e2b0c495d577e63c1e822f743c6c4bd2d385662f5a1a872555821ef24844cf4cd84ec0f3d5b66e5d46ba814cf464155b054e0f0fa8f1b1d11780685c6edab535f11ccfbd1eb58e71b9455f8891f215d282b200ff4fe15ef658b1d57f365e3e905b9fecd086b1407e7735507f9c3309b943e1964b5d1ba3ccf0304a3236615c6918e7c9c6cd088dff0076bd07b8cb05da31bb8e7d53be1a3a2f35149d32a3a3b7afd4ef417ef850de8c7cdd2d6f5eb4aad603ecfb00716e9dbd15a52a43066ec7d129ecdb834e23ebefe89dea48fff861303f7b6ff2f2e465cae8b104f037df9fa4043f5562d95cea51484b8944dcff3c728930c801f69856713ecb365d0701ad76051e8204cafcb50add00f9e0d6d0c76a1c3b03b55192d3f9759391748d4f4c136463da8c2734e17422eea51234e4d213c18d3de89db53387a3fcbb3852218f462fe48ec2ff32e8b572659a52b1e7d6e50d967b6aef649f2409a06fc7c05a015326a9aebac06fe839a5ab9e9bc14928910cc8b6379eb354a78db5ed94443c163dd9c3def17870311666446c15a89292e92f04d25bf78ab9503b7df02688a6e902c75b51ea03248c0675ea4ec5ffe5c63b5f6e8ec4f6c21daec248349455f26224d5a6282260d6fbc5249cdcbfd1f902d1aa39b5ed9932375ccb9ccbac28b12c13edfb67949da00f50cdda7d3f0f04804234762547d20c40a0bbd9ba62a49d822048d279ce5f85705da7b85fa24e30153dca19a90cbacb06d9c5c41a633bd65e008fbc6e25d161cd6916195e719146f285b42c313c2964135e064c03a0a8000c50e0c0b638b752aa689eba4aaed8a2fe24493da19dc35a1fd6d7a19e17c82836b2421d4133646315b017b567c9a618dcd4282345032b1649e5cf0c451267909f03b62e278ded470cd43dcda5649625fc33d1c42f0259a30c0ebe838e9a0f4b9d128da068734e25ab3dc09ca0a4cfd6bd9e48b821755c4f5738c96f20e6984e56420778cd13252cb973c74426720c19bc4334d8aae5a5dce625f06d722e67a93dd6f6cd486a2babbde889e32beefcc33fb690b57501d0134e68342e63738ecb3e0de7fb46e5adffccf84b9f7981a9d3de575a9f8bca96517041a8d937c6e5b96ec1dfd634110a5cda48e498b5a78ecb61a2a4464a22719cc9c4ab02298c4a60e5c9eba5917b757d30032a7b6418b6a56045f8cda0d251a0b47912064127e3b74e99c57893359f819708763f1ac698be9f7ea45986311a59999a4b497f00d2658a305133581c99238c7247b0251ab4c5a046ef8b9130feff03cef07fe6e82c3c4d64e126bc5c71422c916da7896f2f9a8877406a62c4089e07987980fdc159f624d6ec396893d000039a4ce7f0594c42cf39cde13b572f6a64f551a91c7088319c74b9d28a7505be9fbcf28d55880bc4377ba34885e1135cfece59396ef3cbe442d99b7992472e89c3ae619048969593ad102b859885742928261329f617c1f9a59d8a8a1a3249130a1c3c94a0e1be8b8bbc80a55eb314c6b8fca99805ac2827e3958853097016010037e731a27d969d4837977109a7aee5acd298305be2a57e78db7b98fd29c050866f8351db498070400d1f75fea1216b6bd85b61061f9956886eb4a1f404cb22a728127a4c8d78e69011c66e44270029af75cb14708ac70e75915bb8d238afeebc268f241a1989cb343d1d181805233b706b0f383b2222890fefbc80334261324cdf22ea9c63f8b659126fb8553c31311138b1a6e959fe5acf600567ee4369883721b4e80d90af4cdc1dd293f938b98c6009481c0ccc1f4843120227527dd00b7f1c7df930203b463f25034bbe035a96d79b1f2908fd5f6bf366aea321e97ce642e5a025ce8d77b39ade9a39a46a5e12b2f3c44bbc5b5f6297f1a3c2a4272a739f2229f9e2fe84fe2609e8e1ab97091e71ff21f3e3856515d0888795425f269650342ea3df80c67d2c678a5be24ce30392c64dce50c71c9e0f49145b7e6033af2c0820fdca07b04d53d90fe235efb6c054237fe620b67652f218b2f9a2dfc0fe4ac199b65bffbf11583d46286474a4d726cb3d6f3efa0327d054a3cebbe3725a4654386faaa0536a36163d6b0b74b0c215bc9150be753600387e664456ff17436986452f4469fd03aa6124e4abe7b974bad040a0f8b35a8c88da98e475cf4832bf71f648eaf68bb88a6029c373e08bdf043452a5abfcc9c37e82468eda73d7e9fb5aa18957b7189bf84b1dfd400f94390cab8e1a77b938205393464481b88c54e28865b0ed0e6c75033fd6097748c8b38287c801541c4c29192d416e39e5208d2da8a00538bc9a654685da6799d022bade6a866d74bd41cd0de98f4a8da8e8002c8421dd05da08113b28615b68168530a79799453721c7065b4f8d49560e908373d5e7f34e112d3387323700f5bf8807b5e57ecaaad47f663fa44e849e9a8862c9757865791c97ab6662a1d6c248cc70389a2b351cf3d149c71a6ea3b2a635c6a3ddb243b4bb2622b75ad1fbf117d4ee21e07db3e6f37c1d61a237dca4dc2396774ef69aefdbe002175ff86161430de034c802c9aa86b227e8a6ba3a96a0ec4c906ef5c5c576fce083219d8e765a212c2adeb74bc79a8bdd59ac5b55e5894d8b5658aa4d061a675ad201cd13caca7c4b27f24c5a245599f855734d2b272251f3888d26184b5c127d3d356a0d763595ac100b081a834fe4ec20b849d59ba6f5961fded27b30a15cc4a022251258da5df25954df1027b80eba515e1990bfea6a4ee228ed4dcb69c39ca47111913cfcd99aae6df8784b88b6598343dae2b7a826ce98f71f90a796a382eae88f16d46f162a23eac9d423e1e2b16151d0b05ffd731c601f1af9ffd3bfc064f98a32e35efc6e3fad756ce46dec4899f66c090d4139c43f66a370e61183f6ee0feee0a0b2613a2fedf7cd65badeac6d23cd9c27f4ded8940141104f81e4f422f6025aca5f024a3d016170a2c325366b7117fa1ce0af85f53c7002072d22c37c92f708f1bc32de98723bfec26ed8fa328ed8f49e4fe939121b779304ddb430370182c24df4be4dfecef7a35893a2054b1b1ff78b8919ff6250a23ce1e1530ce1b88e9b01e019c7ad07a64ecb758fdc1c5cd906c78bdd7b10de453839c0c8281c135c14be3a1af4ad904822a1af63b0d162268317b29760811a099f066dbf85b9b2f262ba5e922614ea63c68c9287c3bdf05f4352f3207c73193da6f22c63500659dd313cbb017f6a421ec38f1a67a2625366109de50c746d0b825e3d67696405dc6f1c31630e00ef86bd9f7b13440e02ae2c49da91f17a28eb1502b5dcb3bfae9e7e9800594f01e615f6d50f15693a41fbead2bf4500ead0c352ea46b9dcd17d512223393f9c4e7da660b27ac8013da763dcde938515efbdfa6839a8e149de287e3ec1526b944f80f3b1970ad68acdcac4f595dbce3b4edecd83e2fc3c897f09bf09f73304fa56473b398e99cc7d7420a03b83c39d262e9ce6f45574904c16385f46e3c1c742307e6f8d1842f1d4586433442a11ef90ce4fb3e4e8c222981c752502913f42639a15dde86c9b99bcfab25a13eb2f72526a183d6b7fed40a0098156aade6e87e89aef13ac03140b2a7536a76571c51715e97c70682e92ca9c1490c4f1b27f7925e0b8af745294fbbdec33ce750e3352f16e8ab56211a3b44e3127b2080b0fd89418dc9cbd525ba5ce5002d213def2782a9c5e9653568efaec602b0c6ed9b6ed2dc4076a241486782113e376037b1d205fa9244ec1a9d71fa1815d1a0b548916ef3d8979beeb1e3cefedab402ed67db1279e0668c7095452b9be3246de5dbee1d0357d7dea7e0b9534a67b939cf8f75825b1ba3f834ab3b1a82473c05139160e4dd69b2616f82927930358f7aff61d081b7cc704f50d62189fb6a0150ada8c41922bec42c121033358f4a7aa040768d3e6ab92c42685e6a50a5bb7089e59585651ae43ab0110db310cfa9524d90ea7195c6e8e3480a2279a26572278b1092a74c0bb279fe8bddf17409e714b7b2297e865700c1d644cfa4b7c7ca200f60912827a4953a88046f7c0f1a9fd7ce80d7f20ab17194f3c8a670085e1d189e8152e151b04d4439bec81261c017ad6064678055089a8b0176f237730dd17bde55a1bdc35a96668abe78b6d2ac2912de5f063b17e49dbf34f0c178ed81cba9f986914b46ccd5023c70db9205d8e07fc4ccb31be44ac88141a5d0bb89f9e2237f9806424cc23e2d4487dc9c99dd8102b0211b6221e20ee7227122e74c64181f2c677a198b908e61bc29368d100c97c95c71e8b6da2e2fcf45d1a83dfab1647d2f2d7682f21f65700ba6f5078a938ea7f5cbf7ee45d1c795e3ccd94923df3e31a236ae7fbfb4435e275c8434a6a1af10e4bfb63bedb3826a48adb21586ce9d3728a743a11511334fa1f61eaf431cc4ba2896e57105244f508eed7b49cb34a2aabbc0bd6283f23dcdc36a28cd28e526593e99c322392cf00a3f1e21d779f10217865864713e08334d9ddd34c5cd1a5055fa64379b72127b79ba5a3f60e9b2516dc26f1382f34a96d3ba742ef04082b0043799498e07de278fbf19451dfcb378657d1c6df4fa6e74fb9157ad287ebbc4f52593cce3bc61fbe648860381f35fa3d92de437d48993fecbfe8f507f3fbd017605aa0c5673facf249bc6ad5b0daeaf8cbf7ff6ab16ce66720a2a44f7db5421b5d7f6bf8449bd8adce853205d3896594be02a5fbc2cbdb50d7315b11c7df3f9d1709c3e457415461bf1f9816c6b5c1c83fc76ac010f9b741e8bf0301d2160bd59234e8cb2b737d4087de22a55d80f04860f9c2a110312d1b3028f66f380df4e684c6c8280511065accb3d4e215ec2542fc632f4f08aab82d2a8eef7ff78967421b0ec19c90d2937d6e209d0318414e33f4890c40fdcffb9a27094bcfa35ebdf607ab9342678279b21fca4f7f4355cbe1ff4de91bc500f80e0807fc4466e1e6f258e8a47727f4574fa982b29ec616a43b1b97957b12d14db8c742af0340c4ecc82ac69eab2ce8fe6858b8fd5d073f66a994050a4f7110188698d355119454f8c79460709565976014370b25acd8a9f11714d01a50ab805886384ca16022065479c915c6d7eb816e6af7b99618cefb716e2d9889489eb6e2f28afdcc14ae8508d922b5b6bd1464b59b1057836d9585ced5ef2183e3a0cf0195f9c9551b8017d368bd8a2f1d1f82c9cd35cc90668cd1dd7317d066946c475bc1d51eaae65066194550000ad372baa2b880625fc2f426f036923b86c113b5bb2a256a447ea77a7aa2991536c423a330d0c758ab36c7bf2bba71d2cc43a76c04f5bb5c5e3dedd7c12f1e73231018177503eb45c9cc38c563bbe40ca8416900503c79d53d8d82f44490af414c849d9349ae42a8126cb2d040a8cb08b703696cb083409d81bc404ab19f260057c33b30d35368e61415f0f6f09aeb26e84b73351841f1d0c52bae47955dbca4a85f8ff98c2fb74ed08220bae0384081352a59298355a2f6f61b0a232048b03e0d267f0260941e6a283661f3b8585371c267023afe8fdbbdfb1293368f101ddb17a0ca7b9fac82183608a7652e39bcb8b59522ba6df813740037c639df81c5479ec8ae65f6510b1792b8d45cc3a5b168042a0e05493aae69b82ce89521e666a653f8bf8402bf2e7595bb4d1daccd216726184138710483e40eb8e74408752c79d08f17ba089640b98669c9d509c4613a52a8fe3c81b4245b19170a2bd0558fc734589df664ae62e2b1596593871d1c14539225d7e0a12b63e04ac4811dcecf95156a4a5766cb0a9e20833607c785f30ffd9ed931b432669948470117172929ee0f073b1e8bf4cde4daf4d8b4a45e26dd9c2410aeba9b08ca3d38268bfc9a450d04c127dd347d8c7a92b7e16be785000de369ba0911796193122bc74184f62922e679e3e89b83a62fa891c5f10cb9f1506d8c18c94a09a3acf0d713cc336c855eb46409fec463d0f9126a101a419deeda53bc8dfb415885541874bb16a7446361d62cd639e8232bd92d61c60356925c96f41f103048e9cf79aa5b9d11e93fbccef538f56e49867efb5c4f751d6b173b6e210c8039ccadf8a9c47c5d8f36b5c64f2f467428a833f87b0c4cd83775de45b87d7f937178c577b17b2fb573c07c9ff5527f3456030a3026992d096cd88a5700e7a1c42a6699ea13c6a92460244bda9178310f6656b1c69c656415752e1ea1a842b4046a649384fc6d6000db4a3b011273dcf5ec7b7f316c9271ea843d7a3961a439064703815f7e3a401f269ed8ddb1bab90157650542c02436c3271093cf3af5127a3b5520648f64d60b30521b8c077c7f3cdabf8ae3c91c0e8a07c620ec90410f5d2b60da632c39e608ccfcb1d1603c0037f3783c44664dc91d0f9a6504e8d2ae6be1c4dd3df7d78740d9638dd59d2a3ea61eadb219774f630d169ba402dd026127fa16121664ee0fe6b6d807e0f857123a913023514a5e700f4ae4387f7c70c3a6c1784a459729de554a7011004c4e89a7be259248c02c3b80e2b14ebc00ae4472538c0fee7e3da913fa73cd6522498e31658825cc448a2267550e76a23efb86f1f21dc6655b1fc26ef18bcb2f3cb4c5d2dded5f5fbc20cd9db7383cc71318da43dc9f30b72f7ea6ea8546e05db1e28683d72c8798f2bb0875d02f35a2d303b26c72a97a7ab5561a6eab92025e85a4b80cfcd5c0c00fa4b831fe12f4da8d874ed434a84bb3d1fe92711cc79b2bf4c4b23aee08faf239f18ddda7bd8cd9b173751d5e9586686ae82b0cea682ee7b9f5d74a03c2011e0d9e0e2af53a6d4a6d0abb692de70ee7be39557710e1c9dc4f657b1d9769103f242eab355c59089fd573220870e0300ee91ae4437f47d8c4b6014419c687746514d82a83a6fb0b0cff1bbcdfdedda33d2ced1f228a6514b400956be971c17aa0ef1bd749a51f00fe0e6f24b0e888c91c4567c9168adbb628f63ac335d180462e6992a8040d6d60ad8c57bfce2d6248f8ed60828614d817762b4f4f62d553ceaaa17b1a266adc7fff0d4d412eacb739c16e68f508d9000f2a56339a5bd89eb16ea0815ae93347cdd09c327ab602af4f1f94a5b411ee133d31e2ff149afb6bc3081f2743beb1b8392510d4510a45b9dee80cfbb6fd3c2ab8a57fede921bc3eabbd83737ca0086c343cbe23eb6f8e499e4022e5f50a860eb377f82557f11bd7367123abfa96873e8e8bdea8f56ce37e15730c63e7435637d2c03a7d553f32583a4841cc8478ad7486bd48d0906c0c330d09277c94c4927e89e185e8e067cc68c205d44914570af084397c4f14972ece02c3aa821cb3c778f9327523e5a445f996593bf8036f1f09f8936969ace749d4d2944b3812e0a7a81d33bb5406699345c03afe500ed4edc87b385cd5bd2f9902774cace66c25234e96da38cdd40c0daecdeb08ebd2f51f0fc56e8ac562c20e459e8a7838be336020101fd9ad9c4e7d49456fa09c39f412d60d7cec3424c115049c76748ba7793e54431b0b131883c3d6160f3440a2d40129cb1d9e1668d4c1eb66729c822d9ffbb36a11c1d9a560066f9ed1bd964970103e1c3246b48291ed6b6d8ddc8450f4ba632b6b284730b465d338bf927eae182a974377ea4103f40f29b31725d896d54ba2aeb4a93be942626680b3e9be88845c4b9fbeebf6aae8d12dda9aa1fab58dcbb6fa2f24f0d1cfbc20c10b26b4900134b4ab4c18ff0e3a3528e8ba697448cb07991ce47f93fb1f1c2c56f846a7715df0f88808b778875b826d6e67b4ee2077f452b67db801439d976ead1b46163df770e18377974d0412263d9c7f59ea91c1a10a09d2d35a026261a06970d0f5e62863df06cd5a9d2601e340f7023db77a90b782ea2d18971585e5a3acf249c8f02374100498f6bd402217e959e58a3fbd3f9df3962eb93a818e8a3b1968c2e90c1240a1ac2f0a7f4b100a4192e4d6a9c789a70138ca587d01397371dd7218588cbd8eddb8e13fb1fd7793e1a611f2a53fb1c97d74a5f7f0e26f2311f958c6df2421eb89f9673d3ee301d470edbb702ee77e9720888f7b3f0b282e70d4aae5a4098b4e4f8ff906167f8964455a4a2119659ce3747a4d6fb86d06a7bbc3fa5790251929220d01e1c26a240ead266d6a761b6eb574f306dff937dfb72b799e0dc07ba72a7219e5c228fc1cce4ebaf13a0d231b6f8b38cb091ae0b40863f4164804cfa31637acef0c491aaa1e0568cf91f967f1041f250abc4a91e6f5615b0421c5cd56d7255ec71a8da04808c343a7748008388d4c20b9b7c15002926f1ab23b00053eb9c03e8c8b443f4ad76a67aacbee3dd957c493ada9eccd552397bf3049ade7f128a3e72de932fbd33728dddbb819a501d611214a083a46eeee74b71dfebd2ab31d9d887686bfedc5c6feec41f40e4cf0c9dac1af4e5f30f7940624027512e02f19a9968203f080d20a9c8d31682e59e8517e4a7016a9de6369caf2240c00144fe0f0e32d1a09fd7f432622193fc88882524558e2a8d0b0a5139a73040ac87cdb4af357bcad7dd3e6a527613800a2da0d812e8d092deccf94b20221ffd33ca528aa0502322e5d0dbae226c5505483c29a1ff439a5c2eea9099a39530215756d112749b8d28acefa3a862b348e1f543fedde7f851c55a0149c714b870ca452dd9b7f3d023577bd5e5c00b354ea4b0c372f5ed28cec8dccc5d210ef8033283ae50b174353fd61436bcc25e1bc9469ce4b039058e8d61f00ead8feda468afd15618016d587b0c66968c6fab7606c91cf8f863c25e9d8ab2acf21443789358489e90ea46a183c560e00b65200aea1cbd01a50dc218a50a886c495e6b2bcf29ecc7e7075b443c88cd823508451f51d4cf929499cf6911b87c8cea2ce8c07b6635b2187e8681e847a7e1c1b3db2f55ac5972da3f02732685cf371c53ca0cc45394ba744cde7a8aa60a79d701abe5b3491a03e07c4c5149cbfa8e4ebafdd7fa3b2cf1cf92af9f0e60bcb9ea55a7540334cef7ff813adce29c78e828b22354a6e144af1d48eef825e66f32ecdfd974e27e938f333cd741ddc7a02ba85c4454e0bab7ebff8df4a0be9ba7e1496e328b12cb6f50b9d81e60bc99d0a7725961c2f3f74b31068aa3df291c1290e9b2d89bd11652b50809ada92d8215e6262d5d330e1c472a4571593f7114228df9b1288f157790ce1c9090192ebff1638720b509a58985bf9ab3bf5bd2712f89e034dd15d6dd05f60b6122d070355a9f5c43d56bf27c5ad7fcf42f5cf2c3115866295cd55ec8b2762bd8d4d9d49ce42efb2348cba6b11377aec20345b0177649f8e14111a2a0cbe3274a4c011b69e931af1f4d40526c10b350108c66a4242852d4d76ea204356fd2df8e8f82913039a137b306525828258f49af9ba1966d9fd6907bcb3687839b115f249a7aa0500f17ec4f831c11f50b0d8fd614be4006b7408eec34d1c423e5eb964dc8d8601fb2cd7f705a01af427bc78fc12d4723d5ae0d043d74d1512fd92f34cf0e0e8f8718fe506c525f7947d5a18709981a1710871936ec6bdc4c077376304a451afd9933079f82126b17a0a68af052c4c02737e057b932ff5ca21e01ac1f6fc2daea40a5b3d669335d2dd83dee46a8f7846ab7a4a3b52f11b96916071f8091abc8b084f4e30bffb405bf34a9734476f4683d55f892560a7e967fb02a7d516c8089a298217370fc6651c29068da5aa851b9ca6e89526714a460c79da5d9fbb570110042d7c0fbc012d048cf6d317bfa4cdd63846c06ccfafb839d91ed699fd6baa116ce3710baae375992541542884464867ea45f88b309b6b353d69de23d4ed51b1b254f471b1754f945e6500564fdaae9b57122dd338e758ad5a66be8bc8f332da8717091d1d18a64ec926427992a35c5f29e0810fdc6e761cdacb1424979e61c1017d1770d9aeb27e605c1e0eb857d399b90054248d9de5dd425569c857d614901320b46fa85e3f02af61ed5b237483c00c788da3acfdd33b728554b49e43f3e35ae160b3bff54cfd44eaeafcda5d0fdd386ae3024b25f898e0280c86716ba6e7d49acca80bfc6d9ae2a6f7b2e1576863e3e10a1481a8f1a324475dfaaf74803da1005d164a9a054e740571a11a4f9cbb68874fed11b6276ced89d91fc1e42df7df2802931191e0138fe49f3c41299d861027186197b15142ff3907bc633c15e6b66bb07ab80bf6863fe1dfa90518b0ce0eff4829887a8dc0c00824bda431115ab3dec2215eb26bad6137065c2cc29a8335be62ee76ec51e2df4ced7010238eb779fb157801b85d86e737eb52663f86ad3d22cdaa19de634c9dac3a115274059d19ecc81e4288851c4c698da92a07ee6ff05af0a4771729290d62cc3c68c66a493eb9763d7ac464add0ca4b6c05cf737474a40975ebbdafbad40bdb5760b2b778cee9a33f88e139acfa116730724b4a3799eac29af292a1cbcbeee61167fdc57fea94f92a534a226fe30452f89aafac0554d5a40af1fc9fbb82b0652b1ea925d151d67d50c5d9fe7ecbf5ba7c8936135a4031adfed82c17137dea5f77e5c2f198e420b1fd8ac3ba3521210f04ef2cbc598c9f73a03c12e0f429a44223dcf903f610e0c919f2bf0f44fa50946800422ce400badd767ba9e2639821bf12aa575feb091c7b505dd80da65f2112440bda30f80d5267d90814a60f12ed8fa8d9c0d25ef069ebe32c36cae60d2681d80dc181ca2d3cd1245fe08b4ab55dbc4fa111fd820e48ac03c9da8f81d934a7506173e5aee824165d58c3a0d5611e8aa73474d5c22916a7f0b9e03152d669dafb209d51d05f7f953212c7e690feffdecad66a97a19f75abd2f32cd055ba1648ff5314197166819c330aa292315882a736dd068640c1aff10f571220e1d8335d0d729cd1dcef251a775551053f9a05a746cf2c4a998c04c4bd37d3561359ff752a65153404ae981e1666d514f2cc171a41774ff7eedd2db667be2b8dbd5d7a49c048c0d97201e6f6be118b46feaac62bc4be75146e02da7debe806c875d97d37c0a5dfa8b014d0ce1f77a918e0c380652d010254c047184d320eabf81f9d4de0061c787d0680ca390168f14aede3f2cc2e1446f2426142acd0e2be52632133d42fc9186157c01620a449aca7e295931ee037e9071781c15f795726e0e591f107a7f0e2cd3503b6cfbaa2374067649f191dbdc9f5d2aab04813101999b0ce54f2cd3b3106db9a43a82fbe3197a4c0b7abcf41db710be10b266882be4b927a12696c38995a57fa09499af533f128f4cdc370a303b98229bf5c1f60e84d500b9af33f732f9161d373889797a543676c9e9738b9a2053e36934878a46234569ccabb9ac51d0c972f16d32fb20f71cb5337318d97d1f3c145a8f31b334a0969c83620d805b5e8d886f875261b270df5592a3238c039cc6336ccbbab2d8582de7884549bc679f2e7501b4791fb35e61845d2b90f9c3773b0cbd146bc017c6e4d638e4e8fe3e13f9194900eb2018ce88806a35811d40f0281b418a4f6e20ce4edfe041a3c9cea7db10be6ea154c92132d697fa53ab04b304432d55c3b5aa49aa6642163b0d7813f2029579a56d2038022c2250b30ef8f82195613d9712344d25e752629835f7efef199bc49bc9bee40176d365055747149d53ee6d2c3f540d4aa45ee28bc104c1f63ce37464ebc4dbdc1a04c54f23937f43c231f8fa5747118a4f8fb78217819a157245ef942b161c3b430b7693e3b638852b8796230f74aeab38a9a0f88cb1d637241360335a58339666e202aef2b33d8443391ddc20965b195d5bde4dc4432ebb965e1a49bb9ffdd647c395a3db5fea4d245bcc5580efdb297c07306327652054a4e32732445a29e657e7955ce39effac409e69c412bdc604ed79110c7a1d4c247d7a896c361f630eb7102ff9bf36c004185263ab47cc6f2dfecd28ce0ac3b9bf47b32290e52125a515a417644a20a231063e05e3c2254b6a199e39c2462743d221fcb132752cb45b7d817a1fc940c362812c07883b80e6c42c6fc7f9c05138c2eed91666f30b61dad593b958cda767e8414d58cded5a8e7ac20680c0e732673113fbc900f746df0e3d8576083795ec9452a4d723b28c5050a261105a05d574eb969d4f03b712b91ed40b802a7af985ae28ef1bb8b46776151ba2e41c3f90c0d34f931a1a2db5d58e5d3f3ead0991256a7b6ac217b4fe1e2ac2f6f355f6a473d4b3bc6971188c71f20fc03fe675481a206c23fd0a9f9e37590579a8e23b721da080233c0da08131caa8e956b8b7c0ca657380883c97ca9443d557d0db7dcfbbea079c88a0aaf2e9fef3259db5e8807085eed4077abb05f954e8e83c9cd5b4f491fc6face21acc841d7ebf2f8163b75af76b0a0e79cba3b7bedcdfadd6295ffb377c6c65b64cbb245cb7a1c6bac59badb1fac1bf30fe19799c35529dfb84e9dc55a5e7f3df31119068c02c3ad063abe30eda0e73746ab2d264c4fecd3cd94e46b05ed9fb83c31b8fffa24c042a830a144b9314164460958e11ecb0f70bb4493fc27b8651f74877787cc55f5abb125172625ee16cd2d3880bd3239f9a9b469843461b1fbfc64fb7ac3719ddf94c0355c19498f208eda70fdb3ad0c8abc0fef27bf18a0065259be35861340b7577550194b5502a4370d632fe555cc1098cc500905b3e49bb3d267869ef508a52382f69d5f3277e05581bacb426c5eb5a63f1ec59ea1dd2510d02219158e98d9e1ea5b6cab047a39909204170cd8cd083a1d73e91517945ea003c3ae922156e4fac1f0a6b6b3e25e21d99c1e0ce606881765793044d63ac18698d7482e9d29aeff2a672e6886c507c356a4203d71e3b734ee60bd4197d947a02b18ef6309603ad2bae5f67a56b3edf59800ce5e59d08405256765e310ede2920f6fc21d85d78a8e76dfe87111582700b289562ce14e311743640a5cd63ad2d3eebda9fd2dba5a1e8fd6844c53dbfd6f1e52dbb1765ee61f73c27549002bbd7ddfdd8362156b56cb4509887aab5198fcf442ecb4380e591ded27ed42cf295f213dcdc4ef640354ded3b3548ccdb21a82ec464aa93b147c1f45118d99794cb79f87381390676e52b6207f9147e402f4b55a77bc4b863409d06d14028a8a6bec2cacf263bee494bd358ad1f1ae721eb7e4f1336564d640473f17aa5702d28e800c49ee61f3bd07c2dfb367a1935b6a28115fb3417186367606f05c16a83316bbb684ed01b4426a3f87f4708e1b983a9c569e952142eb194a7bee5bda5549755f321c1ea968c0bb40be7ec1c98571e814a85585e485253cad90166b808649dd11a798f734be11e3201df7ef0bb6c40e5aec2750a241a2c18a5c11646a8805b9744d804f5824a9b874143dfa4f6131e51a6955e23914085882228bba5275dc0aafe2d7a496bf74e86e36d0d0b7dfebecf08807caf37270e584f2131f4774e2eb2254c79b81b3a9de9b0a08f549df53fb77e43eddf5aaae9a817abc53cf08c6f9c9bf8bf27fcd9561a3c851333a218cbebea89e1077af8e2948d484837d2923362925c22794123e936de5df2793cf7160190bdd4b9b7d7716eeed16af4be4f4c660a357481080d70f74bd43e78a168f7daa3e887b693b42a62228f09fa2ba06c823b0fcf422a40eca3e39010b89c51d2df06558eb01b90ed29759a74d59a7def9247e822b686d42ebf6a40d2dd81ac06ca24b1592c2f9abb692ba20755d91b9224a7f1eeefe2fde0ed86972cca5bafd6615b3f8b8b189a99c2a86554411e41031ccc06de31670625fbe0b04ac0302a4332bf171b62390f79205725df6a14a7f4287b6b7dd9db9dbe9f2ec0cda855815e87337b987ac4726c7798a687ead64ab75d972ce0daff55c424f970f1b6ef7aa3eb02e123fcd4aa87ff6068382939b35a3dd642e04272b77ccb766e37680267f9ac91fa887653ac3093c0c31ceb3552851da5474f8bfcd6b63f992d00884f14f0b0d64517e941fdb13348c0db52645ca1d1b643a8bb24c98e32b793489ee5995cf489e81e54d280bd0b27f939f202919fbdb2ef0f28333ecde59e70c4b0111e40784eec32dbe4a86668d99180253831b1f5aa0fb577333931538e85fb412363c0e992c233d87196580446c6e3a0e9b4ca3af006295dba8181a7ca559b0d94b60528a96c4a1852e4111180bb7315a427b887cbbd5b397175c059e082e3aa3f45af30ef9da26d80dffdce66e1987a7d8fafd578bf83078daeec007d3256d14da1461d6bf746a9e83bb0d69a0f6bdede3bb1c4b99629f0054a81546f95f2c42cb91ace7c5f6094e110c0f634cf12fd89b47ca62463c6f0a30c1260f8026868828b552c481baafdfefc6b6ad6c0b98b685b037c3468e6197dfeddd71c31fbbf4033e451d821327d1310090dc9a13da0895750ada5107767866b95b4d5308cd1ba22300a62d55a48955c6cba6a09917dc4d2f29f5ab2e4a5e8b5a4849f03ee65480e43a1aa37792a17f468064da81dae738d1055b174c7572c27ad3318834c8a7ea41d91789f325321b818f997face9e54f86cd2fd3e8e810b9ef82495f52f002f39dfe79416767c7971ca78f9a5dfa22080b43c39a0caac2880450bfb676ab75c6a24afac0e36826e93aa64aea9935ab04230382ef26813dc68a112aa826867d319cda03725c5e9af791ae074da51fccd6f5c481e1d7cea928f32d62f435ebd73d76d86b35c1414587afeca427cd928b741ab4965a54f3b4f95ef449ebb4a91821396fd671ecf1ac1bfa2b13daea981c3118ed575c0f16bfd41bae96be70dbae25096ad3a41306d00e57b1136ab49308b3989365481e2f8a1c28aae369bf8278fe88ed26dd977085501d0cc146dbdef0c7da70e05c70c89e6ca7c45a55a2adf1e496ebd2c8e62b8187aa2366fcd5e016c5dd74b86a8a6ab0d96ef9ae822ff1df09562294580691d3466799d6ca6af376a274ccbdab583f97a53dfc1ed4e8aeb70638bfca8369a6e88b11255a1998591a2b473854ac02223683ef5d138384a5ffe31f551efd6b012bb6fd2f0abd841a2973a173ae256fdd28b70b033043c042f284ba651c230a13cb07c0fcd54ab4f3b7e31a1064b9ba4134a6ca4608edae5f0216b1f227f5c20d769d7a4c0e80a649a17767c5cec0dbc7e9b29121bc46da0213ef9c1d576c7d94c860eb0e5c0b8bd7f02b7b6d6d184a003d96d37f532e5917c0840fab6e3e076b22a9d144496a327f54d2f13971f9c3bc4deda09358132ca35b0f1cb0b4310481e0b9d7f6f303ef99984c92fd0074e0648b423efa6199d2d9d1374bf0c40671f9d11eeca99c03d976b50406c25b9098fa63f088871f3ba208d65b47d1607f0b56238d5760deedb6a2b8faf424709597db7a598821924a119694beb8ebed553d99d1db023ed27d5909578408d5f60b91b14194243736ca311b5f9069a5dad726c24db15aa9960829e3ff28174f69061b5091c7c9506792fc551270a185480b9b5e0b49104931e99fba22d59703ddebecc87a2ec176b2696de400486e2f800104560229ca9004cbf44bd7fa7cfb971ed745eead24a145e87a4881e7b66f23307048b1c35ce44950ac64496c652739354fa7be895d82d1a2e591062155a2e97cb19c7693db123628e042b51bbbaacd0b5c3c586186b10b5fb09d8b17a8862c5bc6bf04dcc952f8376589348916bc6ab53563e8ca24586f28254ad705d975e76761b2465c052c79fd10b81c81abc576e8e89c2955ce7aa67225a953400dcfa6b4f1d99e3b891685785a7dc1b86d1b0b21b721a90aae49c0c170231266110b3eb44813c1ce478a70c4c96c6e803bcd5658fd402b50b954bd20c6225ec9cd0789f697eb519518003e77444fa6a135788c33c3d5ca193b73283997cac63867514afd9a7e36f1188ee7f6b7b5cfbdc21bac2cda14d839bbc5cfa644343e17396483c220465f10d3a20661f617dd0f96edf9968ec0f5573b0e1fb64c0333af5e97169063d6418598d3e79918fe0d6e2b344de5742aa39fe22f786ff7ba4cfd14d0051065554c3286567c2fbb7c5fb6614b91077a9d043f9a8c930429271e1c3c6e75dee520d04efc2e18b5d36ac84895e33d85ddec5209e9971d6c1479b4ef21f840cb4b214f171c6bb0a9f5cfd6722865d327ac09dab35582a03ba8625a04848a49543c3a9d53790a6b942d3c0610499cb8eef606f80f077b599aaa638b25b557499f9eaf8b3f84cc6be39908f40fe108f626878ba25277816e7622757b0754ad0086e93a422221f795cbe2eb0ade33c454ddde8b4f6fe51f0c866271cbe9fb1af4d1c6490bdb7d04c78e1aada5f6aac296288c4fd20957e948d7d992d75883c1d21c3af091e98a86af81801967a6a29e969575fd442f3ea56e751727d5fa22c28a13de38f54554d4d75f4cfd753ec786950a0556f330265807a9822a6a0033e6f209308ab83f372dda24ec67b314d9e10c7a28bbdc3d6fb7117b7f6780496709a6571bd1918ec3ea7b563563e9489fed1592add8c6a3b3a1cd19d8b4f949092028e8a0cee123042d15cda6cf8928fe429fe6e50ec64dab6ecde6a5b59ed6192823c6b14997836268e868e646139fdcc85baa1fc6adea93b2d8a376b6397706bd91b22d4d74da4df53b937c770cc492fa44dd45ac74f3f431f532b80e60fea0dc77be8f63b37d2c806e88892786b962e023423bbf46bfb3f588f9b9d13bcb4708c3ca27dd98dce6ab2483876c2b2ed942982567c6017119bbda2a45860025832f732ca959bb92a1b1122fab647bf7736fdcd19aef771c4f623b234c25ee3b2295b5e14082a2537f5fbb896259e7234166fd916d3af6925ef06ec15195e7199fd310a2d4b8761ac63325825419e08ee11778723752acab59a9759b73055be692d6121ee600fe207c8c803381fe065f0d94a65eea8f454c4e21571db4835b67a63c2512ca9d78eb6be55fc0a9a64c48609a57476dbb6cf4003b7ca6708730d14b2eaf2dab69d40d920839ccd7300ed6deff6742314b26faa9a176d8391d9fc66ffbf896ffcf6333af5bb0eb0da2454a675efd415b1755aed8cc672c13243414e53d03fbbb4f68d14c8b642280728021abe8a34bc32a296faeae4831300276693c4b572eebc41326768ae655571ef70fb52341aaea33b52e6141d35ed3ea3c027cf4829566add92a529f4285e0f0d41922830b4ea8fa5cfa1027a86004d55d626f6b69309e1c4ad9603562152e356667a902239fc75a37e8a964e2a9b85b9149718807ff3cd2d42a32af01c6cc178dc80bd050740ca347f13ad6c435512f854191d5c94da4de425597f1339fbe898104a487341a2a2ec2298d4fd0c995d408ba56b67c25d81f57f731fd2c997a969c9639995a0cd12d6352eb6fc25059480466118af8c6a01d4275977b3bcf8f266318083383e193ac7c69b5c733fde412db20770efd2d61ad9da611fcc5b750b554fee0914784ccfd2d414a4b7546ece9273af5edf7c499aa11e458562a5a366a756734b8d95b3bae7edadaa78c8e3f16760800040e4005aaa04400e00f1507f499b343251661c355bc5dfbb1aec32300c11c4dcf5c266c240c21f3969e88d5b345b2ef8c16db39027fc4ad4bd10cf4946d26db32150f2aa6bfb6acbaad68bfee66a8cc95f79f81cbb2ef09d86e07b9e59bc7894290db74f7d250e350449c786c3692d30bf25615c8ad13128ac332d222e1cb556424f70deb220825eb5bb9b43887ada31e835794b852c8f6eff5239f2fa301bf0e2b440dd6e4656338078aca805cdbefe92dbb47947de3cffc028ba214784b86dc10f54c849a2300fee4a97af4e3154e87c130b0ef205cb7484a49fe788f29c569a6cab98c112eb0ab3364f9d4efc7ff1e2381a9144d31dfc1d4ee21cca69ef95efc490b76201df50707eba8c515584c48b070f8e8bbd1e5a241d353d7e7404f1b703cfa07c507d9425c27f63d193bda5521fb6c29414ad0099b58140e07ad94e35ec0eaaa4ab57bff9d57430b0ed18eab6029765862b53926b74b1202d3c1fb42a82e114618b9ec2384dee0c5701d02d08fe37494e52ac98b841b93764cb712932e2269b370345195b2fe1a8472b550ce9092a49f835b81f3f03c0536e5723b21442dda5f2c15abc53ff30b4acf09d377046060f2a7a4f755613b4fef62d64323f07ac9116c68c65925ae0b2f9fad7339bcbbee1a4d485bab2f1a00c746f9749a4ea347be3ecbfed92354c8fe5d92223ac4f347881f5525d0a675dbe9a9a0723ba33d8a80cc65cb4cda4e9392d32597f46f54995f0fb5abf65caf02e59065c63242b7ebac20089772cf5f6775ab67c565329d7fbf7cc29fbeff37825432c2cb722a360f40b3e4173ed936429e327db4558ed9f7987b445d84c8c126f4ebe9f7ccddb0f97776709408b89a5e8d46f79a0ede2761ea19bad432e013428298a3bf768301fec9a67e62b628d92121b33923fc7ff1327751f47beecce2b37554bd8d0dc151a67c6c865c1c659cf8ad2a4e854dd0877cd58f6bb5712b1895f1f71e19e03adf3489115ac776272fa3964871573994d2d268287f3d8bfe408b6d97308b95a19672b87c67e58992c0a6cdf390b2ccd5272003fd87ab66592bfd75377a03624a1b0435b14a02969a4dcbd1585d06532fe23dad0e504211a5948b75fe0f8bdc10e1385971c40bdd450c82917ce0c042bd5e8413d233e1e3989f0f8dcd8cb7a1ffa2f6db0976efeb9760d7625e1daef685f4eb2d89e9c50e07c33ba50af50833c8a66c282ca0a291dfe539f050a62d981e9d63900375416e35bae4803937ac851a979400ab04f20b6c6b2982d5771757df59421b4eea69e052e461f767157920aa8ad167a996fa78f96c1be5acd2a77d41dd6e2be32929fb8c13bfa48bc6cc95029857a628bcae691580d119591443224535ce58b11b035346c1d5d8974f9afd26b45d206c0297f1bed225d141786eff326f1907ab4838fa2ce8cf2fdc22240d62d9fcf0be72ea3ad1cb73f58ff599d0d37b97a1b3c887577b6383100a2ce37da58b443ad0ca503499e02dee9e64e8998b2d9c90952cecef1ee68990247c384dc8d01564c998deda6fd9e1a4be855e86667f459f3c3886525ad4a90af2a9e56dee92de6a6856bc8babc22e408c8902799a4ab6a31f44b1155bfbf7a4fd26e7ef1a210fe457760fe75a2ae0231920928ba47efbf807f244b02aa7e1fc181b4aeb0089d25c380072c15e835aefc1d1746720d43aed4b5128cb764f506f8531cb50a33e105a510fbe2f41dc12ff5e7335957ed36f6c692d866eb0042f4859558757bcc97934bd52f75b40582f4ad850208e824113b12aaa2f25e810d01c168f590af6f0a5f53fbb4ae253d2d7afce2d4b70518c3a8df4fbd0241176837ed7f9e7b57ca2689875baebaba7a81e588524408b3ad58bf038e1471febaf45cd33a9264245a5eb9b647d8762c2a700510da3850b0448db40de2c211c89f8c06b64ab82dd66bd471d40b9687672cba17aa2e0f84ac0cd96572d6c2aa9be6cd4c0a7ba8530f30a8a1639f713781530cd8baeeb974c70e7256a5e19b31f0e5270ed5fd551913550613f6c8456f3892922a60b2c35032a30d66f5930165aa5037d958bad7613c6141c0347cbcf13a2ae9d53eddff1b4921bea3249ba41918cb938996f64f8b1e320defc7c7e1febf81e26c6eb0f33b1d4b0ac6e504789f78cf18deea5051f1f6a9092ca46f6de64cbbda54c29a5b5062907f50648dbd7d96d5f0f6793d6c2557e83f1be7f7b2efbcf7d58409b88fb7ac8c4030af3610fc1782aeaab6ee1b42fc6f46bfa587be3957fe39fa5e91df04f1bcb982f2e2cddb3f646f59a969ab2f5bc7bcff6f5e8aafa5c48e5a28cf4f1abc1feee099d1ff2cb0704ff552a4a597bd3dcc4d2fceccdf60d842ae30b0d50b52ffd4e7fa82f9c34a4aaaafd83557bee63fde6b19646fb62b4e7efe13534400d1789b534ec44a8d5d07b42cd009d648018946a194cd87666ce4612c14fec74620f989aa15847f3cca041031ab298555936332ca19f2580a0821b585125c75f060aa80c97dd3802e50f456054311110d57e1c8f431a2974a31d5f358a75701d4ab2ed4c11f1f9670a1d954fc8ae1ae3170e69a4eadf1474a9af96dc7851166333010f497a7288146112f800054f4e7c2245fc891459b5408828e4c42fd2a5000805396ef32487489125929213bf26fd455eb4c8f1af09ff7e91e6076c1b71909f0307bc627f43c6b3f6173bf05fd728fe63efdb433829c88832a2cca83dd92e3d935266ef9ef4ac17734fbabb673273bfc1a5c4fcf3ef27d9cc24866558e69e3d69d03329a709fad52f492489b4e0a0c84131c60954e7d1049a4013c8db316f0692076620b5b0040d690d87b4506759966531ae1817105c98abbad11240090db9daedc2a44b4a77b9cbc8594ace64d6e9c46c6eefae6c99652f9755f7cbb26c6659f6383d33ecd7e3cf8432ac6ec4a4e024878883f8d348d57f6223618dd448fdf29fea44eed32ffff19f8dfbaaec23645f42a4902edaddb1f640e7c88123070a5e9ed8dd23dbcecc8fc9322cc618635e31c61863c464fc187fe712ce313a629b93a3363cba66c610aa67b89ba30e1b2931a7e0c0c13a24588a13473ff7cdf9a6676179adc3c13a58e4e230f5943a196ae9719c0ba34d0e1c93c53a16470e29774a0fcd91a3722773c8219a4012aa5f770a212a533801545975a768c24afda9560c655ce439e74b3a5f0bb9ce974fbd9303b63abfe6fa35f98bd3dbe4b876a1f62c2f3dae74fa187fd26fc6f9b2f3c14c510dfaabf6436a7aa14a1ceffab749a536529d9f753adc05dfe8d7bc207e711077c13ab6ce9f9a79fb0365247feef8ddd418115d9d54e3cf460f3d97a6bf9becdada084ff537c2038f5e8c7c1c70aabf9118f9c5e0420dfe4ee369d47e1a0b88a6da8f0335d57e538d3abbf5ccccfe5b1be12bb5bdb0880eb55bc7bd0d74cd81eb5ea95b717c63fcb7d6b0ef5f0c2ed47e1c76a8ee1703830bb5df85da5fbc52b7c8c5591483bd641dad43895bd6c15ce4d451f7a5a6356ad4f9dd50a3f6a0f5064eaaf3067e75de264cb234407ea9e469a5ae73805c400d7b763e1650e777c302eadc9be5d81e4d1be8ca01ac4ef9c5af312c367e2a5edcd340444654aa08ea9c26aaaa2aa4d47e19d0862a0ea5461097c6555ff98bbebdbb2d05125c84d9adfd6e5e9a1eddc5267999635c949f67abe86d7777ac65c7eef6ee96e284139397398a8d852b1c8bed29ca85dd9dc7d8766765e6ee66ee3e62b9f9c5aac17efd9391338b52b1a0523502a28aa7f05203ea88be01ebb0cc2c3414f217a5c9b470bbea5f17c79e33971ae2a3ead6ad61fc62604055ec5195db6ef9e7636b44a2b5d4c854c53b90ff2d02c46d810db6e64d5f052008cadf2d194cb6aa1836808a49a0c6de1556aa180726f604a05ed3b22bcc838932c68e31c6c85b32c98f6769ab56b6903cbb4658a041e3094e64dc76a2542362bd55e8003486abca0f95eee1e6719ebd7123a39e410da70e554c9ea5e9971e10959f9e88b1509bc76b32293d537ca8d4eeac4aed6c0ca31a4a9e1e2f557e9911112e82d048e5a5914fa36b603f6cd84e214514f760aaaaa6a28cfa52330fdb5534d58843ede91eaa6e18e79e4bdd32cebd53dd26e75e0bad5b89738fa56e9ca96e1ce75e57b7cec4b9b7d58d8573af54b7162ed3ead6b29d38f732ac6e28ce3d59b717cebd58371929d516336386c93fb934f123086a232086a0f68c988e467c4b648b8121db6f379659b7e5eeac1a89dc816dbb156304000530b92c8b312301a67a1778196cbb37fd52438cee9cc4c60bc6d8fe72bb4df2dbe86d443291c844702936f94fe8607bc03b6463c362ad0f2ab6831a87bc36b1ed4c36ebd7dbac04213629c3f5aa9d852894c15eb5371b6dbb37546b5998ba600b15a8d1e3292766811a33d3fc7869b0ae5811e998acedd5c06a66cd3b1dfadee9b070a4a88deb4290aec75facd92e8879c5be585b1e8380111504e920060aa068428423e4645ecb177298b735d790488d6179fab17ca8215c35d118025d7062a58f1b6b29931d0cb1ce2f8c35235231ecb18ee76cfe0c06dbd240d707422055ec6362f0e6b86b00f61a4f2fe42b755ea95ae600ec596ae64d166e2249a70aab9ab1a75aaafb7de3dff4dfbf8f715950ebedaa59eddfffd27457ff61d0c20b680b2fa0fcaeda1fcaeba191eade132337e602176473069105d11263ee7418f4c2670b9672370a8542c5e9cd8bcdcf9d8b744765f6bc79cac93133f7c79c7136a27b748961980dcf21f3fb9aac3859323b434580a4ab5cd15582ba8d784e4d2b95b66d9779dbdd65664dd36280e8d6dddd1d876ac40065df3534c2dc4ca7e238aeeb3a1b269085858552dad272da5545c1cccccc5dd799be72ccdc73ea98220b6d51a9a2601365666e693971a7938b8b0b9d28144b47468a53454197994f27171717e6dd17666695aad3f190687f21e5eeeeeeeeeeeeeeeeeeeeeeee8f99b3ec9bbe72715145415f5e64a4524582d8a809ac8a8251a8f9425f50d29d7993c1c9e874646ca9948ae3182f687fccac52a960603a9d193366c4c4c4c8c8c8b0c0b1c0c2b63468d4a851839959d5f56066d40c957467f6f6f6f6e62d26468693e974a87b8c3246140b5e12687f214783468d1aff1ec72cc3020daec67352782bb43fef6b814b434b88dff103eddf5ca4331c771e18d4bf30522e0aed6fe6e149b57f4e19676c58a0af5a0aa1b60c7ca3378c33aadcc5ce22a6c46b9a49cbc03a9a057ba3ff493b69172c4d7fd835dc22ffdad582122ce37850db05d3ae6ed7aeda055bf3e309550ea80d5a890acafaa3052594334e15050dc159358f4e24df2d51f92a0048402cc6bd99459d8e1b551ca251fcdab53571ad27692a400d01841abe0b0eb4d0012a0b2aeb4aa185901641438d6198c4a4aa772693999c4fc9e248185f7ce1544551aaf66a34c2ab4e1e2c70168a6312c3304c0a2c5e1593ab6ab0478a1045c9ba524831a286f4977012c347082940b838a12f75a5c8a24a104d8a210b2429b2f89182083ec8a288270348c2891fb872a2c7297922c70528b208ca895e4c0f72b6e6a2d78a62a5b801114439455894a02ab300837582185408ca298275e16d41ae1ce911712b496c90945384a506440e608411fce414c18ee05dc020477a445a8b2326ace414613922671b015818d5708620780594da6fc3264a92da3f33a31451fb3970334c4ef76f2e0d7f15400c01e5446fbf4092e3416cbbbbbbbbbb46f844196394438ec304951f820df02b5778417ed0796fc47618c0ed16dbe8567f32d94ad99ae1f0b081880cac175e4819167b72cccc4fa63ad141a0926e612c560e214376f4cf16d9acd11e61b27fba154e1750925622df3f2848486888d5acfe384a01aaf029a25bfccd641f2be9964c50fc62db9824625b30915cf69ed74de37496687d44e5031ab6cf775bc9d443cf62757372946e9f7b7f21585b00a2db3caaa72a7f07f21a1c39fe1ee42ee443ee446ed4ad95e2c84af5f728dd3a619f6201c53e6c1f77f9ca7f4e4a55aa0759c7e4c9da5d47a8fc42cd35bf7b9aa741f7817a51b7b0f7f718b85c49b7e2113df0e18aeaef407b13bfa50506c6aba1bbb8ee835f4cf69b3313933dd7ecfb1ebc148488993fe6c580c1807d31b3725018a2861e84727a64598789a7eef7dedc345b591aff8d89bc2612f9cabf25ffe8c8c888886868e83dfcf06d9c80862fbf08a54157f1c05d2e55dc1091a7330209092968bb62343434448420b8087717b53a688eac5d35ab52c76a54f77fe7ffca5f87fe81dfc76f614820628b306a0f21043f0ac6430d41095c710431a4f0021d9eb0018b6505f82c56945045c48b872a3108630a3c47c076a8fdb409a130784933e15bfc37c1232042153504a5137e650c122481e14c20810c181411a98aa228d5764055f71f5cea16f548e118639312c4b430b1041260d058b7688914b868a96ed11224c096e82188d4ce38bd6842852b5016303ef935d96204282ac50a1e9a4041a9a0b46e51131cd03068aa6e519324a068520294942b271eca8971a2826e484130c9a288899110a722e86907340ad445095a040a53b78849095ca0d059b7884910a7218aaa5ba4441496f0851529585019758b94802288bed42d5282064aa05ddd2225a8aa488921b222257ea85bdd222552a005618300a507202215464544aaa2284a8340c5944c88ad080815a8ea16f534a19eea16010104152e84117530120410485c56e8c0053bb0c0098c270c1c5850610a57c8e0892190d0c4d1912200c51bf088c1846d0b221fa41c77d2440644394e9690c349497216014173917686fc28c981e8880d3d3e495ef8d1ad1094214af5b937d8af0f6eeae2a8fb03d81a70a5cd16af4e70fb42ce769a7f6797a67f89a64d0fca9ecc603118d6d32d2440d5a1748d0f49c1850f122ba1d51928f2702154430e4ae2e2a886fca4fa6351ee744b5ea95e05fb257451778d50f721b0352c0fd81b8de5dbf195bf69ceeef3a90a02dd1ed4f504d45d03f8dcd5f44045420abd0bd438e79c99f6cd7f61ce1e22fed8cf3d7352ba2163df2f0d7b1790697049693666246bcd67db993006695c8360806abbeba7a7a7a787b537daf3647e013508feab5498a669da6b1be74dd40ed989da3d27a5fceab6e7dac7a77dda47e3b40ff9a56d40c07ec76b60bedf86cfa28931ffbe6f03da4f96d76c6fb3a357363fa0fd218b3dc667c97696ec8f95433a2056e740575f37658fd391c56a50fe6fdef495ccd1a0fced9648f9d393f2713a004ea88d0dce4e0e3ce4ef0eb95498c8f7c83cd808640f453d2c38a20739c5951791ccde6c9592d99dd99dddb9c80724f80e2f222a76f0a2093c7bce28258665d99c1a7bfba5fd5269db38aedb5dd3e4eddd35e9b0743e629db4a57b4869686b399ddc770818b2f7f2224306cb40a15e64743a5bea25954aa15c4ea9540b4da5522c2953974aa5301905b0a5e005dbb651e8fe76745358436da3069767db9970c66fccdcb1b12b68482b95284c15312ce3743a1f319b736a9a562a95b60d5bba343adc6f9c8f58b94e87e5b7aed3e97e3399d6d4e9d0df58b869fa8d72a56efed6b2bd1ef724b16c6aa5d3eeee6edc9958680b4cddb4ba71dd4a1ed78df3b86e9be4388febd68d722a2250feada5bb61e3c1eb4acd619feb763128612529e2914c4c8e21abd47d49a53120af99f1fb12698988b691847cc87fbc46f59bfa2a781175df89d8565b41a2e219a3a9748df3f8ca676fb4df69ea1ce84813197f8d50d288e5b7f4266f65bcb0705c894371a78e4a2b720a13f93a7d385b3ea426d6242af1f84ad334a4a30d664b9c7b8b691be71e6b1e637d4a72fad1a697ba754ad2328a3cead6494983fd35ea01ac40f21a2ea93667a5a23c9694f285c54ab3acd39999a675a75cb2aa3cce2835e3897c1a2ce2874bc205c17f5898d0175f4de955bf7c65732bcd6eefa3ed9b41d9d44ed4e4a1a8a092aa5819c3a77f7c2514e4527262325db8166e7aa8176a0a8a0a2a0955a557fd45fc9c7e4e4127a0d3cf29c9e987ab8b42725169258f2b578e6bb7795b5d38f7ba1924bb7b3a61613c588f7c49a424d56b6998043911e2e28994dae00c2b49af2954663665aa05b5b5ad14fd33753adc9b3a1faa28133b7dc0080b82021624b3c4e5b7e65e5e3a9deca3b731aa2397122089c5731faa5c3ba241c3f33a9dd2eeff12e83e8eaf271fdbbecde35c0d3637868d731c5843ed48d3269276a41dcd17932027427e6472b1f349affa5fbe925e0df6f74a29e9ff4e47fbef7cc4aa8a4243f9f2a13ac2c92ce5c24af3f4b57cdf1776952a1ff7953e0e68f654964e24d0d2f06b4192898c815422d5821900e0dc7381f19a40607366683c2ca8f1b4f69372f5f8aaa79329349fcca11527a3ce6f0635d8134af6a16a6e1f3fac475ac1782492275f1a92f6eaf11a49a557495ea325f94ae3e9968685c6c3b634290bf657d17a6a6b47d4e3da05c0b4220047804e47be07868d0270d3e35c4a98785c5083eda457fd5154158afb7cd24bc9d38e8cbca6c74bcd70fc007bed48320d57c22a8bc74bf3324ad7a80ac0b91753b71b9c7b336ac95be2f2d25b3263c6278dda05ea2b16e71e4c95de12978fdf3400e75eaa6e598671a54ee794a4c1fe80d143816ad89f5c0df649eb743c202bddd2302f87e9d5e9c8292c5c0e1d68d7f99855fbb4a3a5e9cfc1030d4f3f3f5ed3d26265f2d01ed5c967fbba4af6f9917ac4d4f590b1030d4f3fd817ce199e14f215d7b89c5e642480530d81aaa25019ec4bb9e46b477bc3c15449b97abc54ec8349e25e724ad7b860fd2e29405382a8085519d288a66846934aca25a9683f5ec331e19c682eee89f6c341d182b822ed27ca506d2944dc14cd489be2d28e1a9cc101cd4c9312d3e9649f7694cd29b992b61d803379ac1a0295e9b0eff443a1c8b4a36e695298744dca957239a14f5450b822222e4aed9411272575c44dd1a6d4fe2de5f21a14955ef5a3aa00a1c6a0478490a45c45a4885e92d4ee1725b5fba4e4f4e3e29d92d0c03e14121898b6a5aa8b774a52a3d3d1bed30f57057b896d1fc53c2df31220bd25311f63647c7f8c979ae1cd97375d0db6922a365458f2a572529bc97c184f26492bbeea57795ccae35ebe6af9e28c96a67f7adc0f97a43917a7a40adbda6d2bdde2aa58b0bfdf799cc7795c556578da91affa5f3ceda521f9aaffbb1eaaca955c9e028143aa25ae0a07867644fd6275683f7ca37f6fb4ef4f29e91a97ef4fb95c3c4d4aaffa5b3892f18243e2a8746bfb7e2e6906cdd7de38e91a4e8a942953b4a350beb4a350beb4a350beb4a357edafd1f578a9d1f595a2abf26f4f4bda4f8b4be5ff013b9df960e72356ed03d0f978a9dbc7e1f8010d39a423fe9043aafd1bd8c1d0b16af24bde4fe5f0f4533d2fe5621dbb377aab9cd2c1b9a7ede0dc9b9b02302f36b8373294c574eab8ada4cd98192663016e2cc09b5b644cbb34fca53ac355b785229a9196de80128bcdc73e22e09439d3db1ca63ba57603768a3c82944d4ba2cccea876246cce7a4d36c3fcd247b32f7d325f500cfbcd9b5ff2b28f5e581a9a61fbf911d9bebb194a9f7d44be3b086c8eca57fe31a734a43494b3d872ced6cb8add4298af7999f6d94744a54da0f4f323529abe72da5ff23647f33647cae77580fcbcb909dbd63135025ef72eb8dfb9ec53fdfd6d7877778360b75836d8c78a159fbaefed9f799c118e3b28c4cc06b956a1ca641f3666562a65d9c65ffae2a66ddbc6a59fa5df7e2b6ddadc782bb13699b3e8ee49f0145514a2a5811285c6fd26e5787797774ed74ad7d206b9c94d1a7d72541ec9a1068d429534720f6551946e85544ae956288b6451b7d6f59248dbcf708a07b48500f9b146cccc3c51a4cb9f5bc39f8ad6904e943fe5f5969510534c501bd5b570ef25541b7b3fd4b61f8b0914dc82f2ef744f813b61c2ea9ff99918dfc683144608cabf7be38fead6ca8f5b4fb972b033fe97bdc1af0a7764d4e885d55b7a30357a18a858163fa25eb02fac0135a4364790e744a12236c49c4ef8b66db387f60cd9f77bc65f4f7fc83cfdd8f33bc6cc53f9f9f9f999c75df3ee0682f3a3c12348768424c9f0b45a61144287f0a8a73258184938383b7264b1356b6476c53fc3d9a8ccfd82d780be923ffd0b553c1afc81ca6f0ea75bed80af210d34094a98436d106401a121870d222936502246c4932bbc98d881358661ad8abb62907330eb9fa592a1b307576441a44550e829a21205231b1f39c6d831c618658c3146ee6d87211c1e2fc810e72cea81a2072936f95b8c33f42ac9c6c8c686394a299923330c0f462c4b8c3737b75286de1dec779f86067798336615efb490ee15b2d3bbb33f6ce20b391ad2bd42768e6818d240ba45dbdb8b2c67724e37cd389fd86e059438e49ec52594e03f66e041126468a900d9a4b5724462ad90bef0c3a388eb0fafc1414285d8f0e307c6e367698468b846fc535bbc6c2323111bc98cadcd0e9bf9fe3b6ca8541435d70ab49f5d563889821b25d990838b099117120ff8fc60212b64211aa8c44eeb627cc69ef7469431e29001092223f64082b8cc62361fa7810009e2cd204b34df231eac1c4358635730334b8eccdc3f8880044142348fc098689f4e7da657fe362cd51428ab5291e9964d4f8e1d51c8d0509006e90f28702285cd9edfb3dfa102cdf86968d0f9736ca77b47080d42765cdbf12c46410856aac418b128638ccdd18806a39d2146b1a84707db143220b69df1191b9d6347dd95e999d6667809a1c187930c19638779b827c3e4048abcae52c55ef94e691d357e02f6c60e1e933f1cf8739835014b13ffa647edf7a9809001b5df63f0cafc8509a83e6abf0bf1176abf4a45296bdedc55fc92b74176f8a31e88fc41bac59f3710ad3f195e20e261c3fac0061d0a54c8cb3d48db81fc4d4658236a7a4da986e33c54afe4cba7316835fe16e7cbdef057853bb583c16bc681ae13a6c2106bd69a865a1afff085aba1c6b82baef640c1406b4824beec96420fbf94eeeece557abbbb7b1047dcde3416314c6232722c35dca1fd6e7534606ff8c74f8655103404b9bde91d3ad4cb13d4866ef9f70be956d7f8de42220d3146efb1cd68faee77ba25a4c1b8d360dc69303e0dfd020515e23bdddace371a21ea41f64625049d5e57f76ee02bfed30bb2343374ab9f7a110d52d6d200a9b1028ac3e3856e953e3e4bd6bdc2d108511a211aeb6accdbf3fbd89edf4825f295fb92c7ee38f886f6ed711efd62bc6ede36f8cdd0609c3dbace2f882a899abefb991ca5f78fa5ee2bbdd7e08f6a307ad43eecde3ece49690bd99efb6df3541e457d8dce3de7d1294ae5a5cf068c1e63906e457777ff21d650649f0e054fae80d453d4c3048924a0cc259048e28314824e498a30a380841090b80071c41149f0988e8c684136059398c9aa457e2ccaa8699f69f363d4a216b538638c32fecce2cfd0ad6ce587d360b429a23fba35438ccf030735046b8c2f5f4974aba38bca0fd1941f7e20628942fbe5ee6a4621f9b204b109c9051055663edeb33747427e24680ce9d353b221530f4a50654744951f4a2e3a9fa5918f79426300fdb0696869e4c72091b150331fafe1a6f44a3e959f2a2e6e0ca02abb234155764884aa2cf90c55f9cdbd1a944f83082a3f2cf9b42c0d9f2abfcc40ed9758a8814aabfc2dc6c8340994dc2c3ed914323eb40e57a86264a31b9b734e1658802c68e3f0701f865055604482969147d6bddddddd94d2a6ddb1bdbbb1ef054fb2e19543337f405c0734e4923c9043b7c245226264f4c35705947982e82c82babbbbbbbbbbbb637b77472050a0f80b3f24a9a1ca08f945ece2f5b2b1a33f768c317ee923c738e78c33462cca18637f38fcb1c006fde3122d51f7b7ffb00912238d31fee0450de98cd90e5c042849188f2c46c6be92174ba552292bcdac548a338bdf0b2c8f8b6818d24595e722d6ce90219c23453f2c51b91891a03b68820fcbcecc9aad09f6334a25194adc6c8900129434b8d8cb1d0d4aec931810f67e605f7a4ccad2ae344dca9f5262ff43fe9801fb701a943645345ca31f0b45ca8552e5f328ea21a2ca5f425f4ff054b92ad59c35dc079ef7a8b44df844ad648a46000000006315000020100a088422a1682c8c7449451f14800e73884276583a9647931cc9711c848c210611430c21041822333344346a01a564d300cea0c9ac1a2203948275aa1d91c069ce227dc9b8cf622de73110dac35c5c5cf6ab97188ad269b41d59fac43d0a6921abb0574d5491f74264ff4f4d1f41379c5ffce0299dd8c009874bd37be50477288c8a9c320d28f6c675e6dba70c040ecdae9c2bf624d4ec16e35c78dde8f728eaf4db2c39b642f668b23d06c796171c43644676207ced8847d2599a8a62a4ec28e50e4d0252e095ed8b786d0b918896ec53b300e0ccb3b2f95cc8990042322293f49b1704568006afc09840eb2ec68efd40015035d05289fbd5cc89c43ec2459d85800c3d27a03d409bb6ff400601c401fa6f7d6809b1204399d1968149664a4f3097a11f6919b1b145078fc6a7e6181cdbbd2efbf00db2848c223180659af3480e17099f05ecf823f63247cb08ec6226bbe892b42d60876f46625875412ea2c643fb98097cb54b0e66ba781189e1658d4160bf64c6f3c990200d357fbf8f6a457106b36c1e9794c3c62915201bfd0dac603c9d6829b03a892eca892d3909fa5d5b2a6fb65f834efe8abdd4a90c3344cfea1c74ceb3e9f7358868a5c398f95598763a15f773177f2f5c8eedeb5eb1620f1cba264dcbb932d633117d8923d53731ae9ccc00cb82bd4a466e3518238fddf60a514aa63ffd82f57b7e45ab76b5ff5c6937e712c4e79461896703df60fc90abe088d7db1e1cabbceaef18c508fdb21cd460c8d0250f9e2b71195eafd761676fe564df88a1717345dcb34a534b5a63417dba9a00b9394a3771503ce1ca3c156762e6b27b31c8d39e402bac0ae1af3ceb09589c5d77494d3528982b050fc8d7e9b6b11a2584a15d28c158e6cd12b0c3cfda0d57b72eb6290845a01de0b6c242b576c2fb80eb767a87a185c3fef73f9718cbb809089e387b0800654a4cf0b0b867be9518c41a46410153de74703e4de6226a8a1246c4f3352767fc9147dfc91745ff24b8209538657b5762e8a12f876150f94f4f432bf777e87e3d22f1204616afc32d5e7a576b1961d64a132f03b7899ad8f7795a799711513eb09398f9710806e19384984533e73e763fa6babe09c371fb6e4d4f69c67958078c129ec6364a80cc1e7c99aa76094d2b33eabd05d027ab064c019273622564a71a3bc8db4cbd93883ebd0147700ad700df13a4b6480391bd1bbbc616b75226d2e2d8e889989c1b087321349e2bcec7409432fd4d5fe1ce324b25b0fc2bcb08699412a92710d381f8f71340a115b11edf1b052fd962afac38f6b5cbadc438d38866289065c3a117721dbb33964f1be7a1a728d68330cd4aefc1de374f17d72a7fc3c73648bb6e1855a2de08f43bdff0cdd2c9635d24343d70a42857f5f4552d03a6f157b2f674328ce4adbd38fb67839225167d478fd1ea9dee1966ea5a924bbad31979833e0170b38e436743ad560202bc6527384df10a3453260a8bc5b773588768f238a156aee152a000988c88f5c5603ae9679615d8305d6b88eef66d1128b1bf5cd11bd6e1de8f3ec59fb839ac431947f6330b17344ff48294b2665b03031f6d949cf6b00ead92416997393774bb5e496a1c08a2d18b73091d22e4139ad51fbc006a8499a4befab5d0857b9f3f10825dcbb2b5911366403ac45694bced2a7fca4865eddb9fcee07d34351028a7b7dcdb8fb699d8a700dd64f25cb6e601a7e30f2afbf76fe581726a5512ded92203180a9f6be9e67b3555ae84f5e5eb828420c87e3e8344205a00dc75360ed32ff01429752329c93fcc3e361c810af2d00149679e648f6ea66ad1c246f63cb75f929e64782eeb53d326580c1208af10d6d5d0ae85ac6e864c4cedc48315be13d71153073004226851b0463ec769754216cc8b2f5889641d4f5f9710cd2bc079b2fa3464f40a7c5f160cb53b6f22d1df294b1d756a81707afd4c564ddc3d80372a879b76a0db4c8cdd5fe0b51d31e412ea84bca4e5282682dd455197534356f78ca4c47b75f108e59489f16afaea6206bd7a0e2acb4eb9ac9c73a3fa4338fa7ea12783283d4b90f9888897c8c474fb12f6e8f01a0b1e2d4a16a476bc6ae3a342b6ddc6e0af7342f898129d27faea1f3af2fc9d932912f6f4fc483456722c3cb6bad492c07b079de5dbe31a200fde8e6e600c2ce660fa4fe5a08ba10000026ad5bba916873674000221c308be9f53721ea05590cc406281602df5876f00edf3ca1265df2aca81f60de3c83cc4808761a17cacb91134134d1097fb12ead24420e276d623409b630e4043a763ec88c9e23c0ca0041604977cf56227164bf69a4ac62cc71d7ba35e02767929761b3a0faa54eb5813f654332781e20f1c20155b855cb390d7304919a6c981a2242847cd0b050aed31cc4adebdd56f02188284071ed7be2a19c6b1d53a230c8ca79e7438e0a749fdd80dc7b896fed30c629ed670cf33cabbee0842042cd7976290c116a3b66cdd6b4bce7aa6fd73baef7afdde2df4761bdefb4fb7abb4b3d9769bf7a545fd58f625f32d66bfdd976c99cdb588b7908ebd79936cbad4cf71f731034cd9036ef03df3f41c15d60363c2152d5280d4674ac66fd2cd9964c92f6a2006ecf6e5bd0445baa1dcf538070897a471e045c55518328bfb988de426167e93435aba81dae739ffd335cc83a43041d78cea65a29f226b4b78f0e83dba7f15cb9d6809fd068b76b756545ee2a17e4683a80728137e98dc56855c78db6abe4c30d96459ac188558c22f1c8558f4c78a748744d10f6af4d565ab29c9024f3a68342b36abcda717813abd15b77374532abdb1402ed3874ae47372d5419115802cfa19e3bf538b627dda8709b330fe865f1af85b4bae2ea4d9bddfdcf2d8a8a567eff26156f3d4bbf4c75a6cf11390977a2d5a302c67bd18e6957bf6ea675b6a69d2794594c547d2ce855c9744739882d9e0fbe332eaaf1ea581369bc22dde627034319a8006fa337236dea12fbe66d4d5271beff600edc7ca786b63696ca047fcd124fad5c40304f66b9e54f1c7adce1ad33d5a0cb61f422a4fb2e2bf8609d16b6257759e5a9d3110a9dff99108bf2f889efebe1e72cb341c430c292880a1310d1872cb866338830a2911537c5f406c4cfa1ce82c882fc21e0f83ae5506239dbf8bffcc30c56b6e04830afcd6b24d7468cd6d683a8e9356471e8da3a0ba9be2eb0a43d68e1adbf4d2dd96244ecf21fa3c183967acbb8becd0c4ab7956406361878cf547dc807106ee19556041f474b25c0b781674b02f1a1400ac75474da0f026fcacc6c7be804938bd45ad7b157bcdc22e1b753101b5e43aeada54e7dace1ff44b8478f0e904e15327c38f883dbeff19b7c5f59b28a1397f904b5d95af7bd847d259bb75f012d88e14ca7b0f029af554ae78dd3a9857b5b9c96bee01a48c357acf09ca582d74e69c66fcf175ebcc2420a7bd6fc71b45e870a8962f6d3f204f5ba72c771769d73ac31bca7e04e6f08b968041cf9ffc61cdd2a38b606a463e83140f177380d6fa9bef68f325636a705a4435ae01d5787f53dd8edc7563c5c785b7d50b458185e60c14920bc05a3a0a577595aa9cdb555943ab2668363835eccdc88573e2dc1aa7182a25d5440ad306489c6cef378daa2bee676e9d84d40f779144ee2260241610b34e5177d6b81b31a4ed2ec61022181e95342342e590e6ffe14269ff72e39218e3cf8a323670ae62069704b38e9f160014fcc1dc8e389a989d80845e1b7c266da9166dfb5ba67c7eb84903ca894204ea3c380fc97c83b5eefa0a8e1c29991163b6f3c4a182da02d0a28b4eca9ff508161689488895cd6b502b5a4adb2926aa8b20f55f1f14a1eed308129ff2678c3b656daa0c7e5971dd80e009760bd6e527f886f914a6cdb04245e3f268b8f74d36ba8805273a67d01e7c4560a83b6d41471d50abc0134c8bf1bdc28f04fd81b5b3483d8574ca04028a927dba0f66864a0542b0c348259bf9b0c2fc511a5d7054385bba269970626513929016b378daf291404ccb9b8067015fe5a5114a94631407f930ed251091ee7326e802078ba9e0ca054b8e7beff971ddc56b7c7e7ce77c943be438f2215d50b3bc8c9a91fd4fb318371e35d97284a54987f2384e46fe73e3fc10b548efb5ccc77495e3d1653e1c91178950725c8a12391e471a06b23054998ace1e31efabada48a5729ea9e7419bd96ee8bbe4ba77be596baceedaf754ca5ac4ad7623e46002eb8f0268b8f9f3ac4f44bd9ea58930e1adc792a5918ce918eaa5d2c6a8d8a34d36e325d4e413adb5d6ad12393eda6d92766ce4426639b644c762f7d63d10580a231c0d2d949a08735a01e536a24cd8adfe77b2951158437b5122956f709eb8a91f3ff38679fd3c7d2540296e60b9151cc8219967eab30528f2f1736a12854ee4464375e1dbfd200efc6850b615457fdb9227d06e81994e094a86efcf112b23198eddd4ed45f744898e95f70c08b54f96e58878f90066f076da291033ad3ef310935df18a1ec1c7184ae9f6103d72f7b42f2be5d7e2d3425954b21423d8c707838a9cbe21c41d3f6405bbcab14333d9659655b9c6b87d3234f8dbba0f5e13a93620671d926cc7d2e32893e8e45ce8a60b8ed555ab3b888b386130e0e60313fc87bdcf96368d33078865ee743b3c979683d08b63b5deaac54491595658f2e3a2f27f11f1390d81bf8148328b2975d13d610bd196ab0eff4199622d60af727878dbc090da5f8cceda7c1f404cd1b34f595691f99ed08db9837598ff835bf0534a10c0fe7a4e3cc8bbe1f1268b816cf9d752d3113caa0b820e2a15deab489750f17103d5903ce4c2062aeb9d0d0e064d03f494d37627e2e5f043cb869c1411898eeac319c4fb06da4f4ddea90e0cd5c970823da5167dc746ad11a823967a188c45f13c7eefe313ed3d49910558c79f5065d22a778c5662a333b28b4519057eda0cb4db07c92c633ddffc5db251b8fd2a7d54715088c015a76c055672f5efaad29436e9d8f5bebfeb45dae770b08a0b1573e9329660e2cfce960337d29a3df98c8bf7bd400f4dee0a620adc0b58afe554bb35acb6a3755dacf3123762221aa95c4c9cec6086a99cd5c6770961f088addc1ab289b54af08379fa764b6783857ea2db73317d286768fa16287ce58397e048a3e5beefb887f4e7bde7aeaada410f1e73a41170fca1f8cac7567f6e31e0846a4766f03f667140bfe7cfcf8f09414ede47c3d1700c4db84c04ddbc242fd6d090dd1a493dba57b74b56725dc0899a4fd555bd3d9ea11473a42c96243f0148a3113a4937e4aa32237873e5a4189b5b32da07e9a455786d60f1ba25935e9fdf0b9231c706a644e2441f9e903f4c1468e164481c988cfe3c022e9ae1287136329cc2e66be825adde773b3e4d6125455851cf42d0d2f869d86af2c9511bf0c0868b41e60a8c2997dc314e8f334eb7adbd28266a4627cab616b4b612d129f0c709c369e4d86abb4ad541bb040cf139692dfc16a3c35b4eaf1b71e4c5fb2027b686788ba15e54feb9ee5689c038f21c00dfee12ce6aef638bbff99fd15c726a6e7f5e022978e43edb748c2f1614232011f96207c31e1d47b388d66b98faa905000b675198b810cae7569055f182ace474034a13a7d4e9e139f9ea722eaa221388d483de40ee5da25f33248b543cd8a280955af06312d05f646c444fd9a4816d2e019302cf3c65f64cbf03253557b3930954c13951f7bc741436fa3588012d5b7be2ce0e5543c64c8396a7ff657af4911a81e01fb40d577c014b5d7298d6fa5e8a06138284bcdb9c9a8dc0e29d6576fba07a88a4007053df6f158462d0324ae6751932b9c0552db8f4cdd49224ea9d9f1dd44fac3a91cfab0fc9194434cea8fc709ea99fe18537e8cf07974aa59cf17e421d0afbc58470ea807a51c6d8ab7968e47738216f050bcba075d498613b97abb3b482b6af2cd7927faa4865c9d3323d35822e23e1ce689ecc1cfa05a2b7964484f641cb1768610b552c4614b5ddd9c09999901c013aca942c660f65c3c706a65da865cd2d51bf42e4ca6d00820003a8d9babc4796c5610a364daa66c62783d7461d17d538e41be62d94c95f24c5fc22e91e2c04b356ab1ae7f1996db9842e9002edaa163e76b36115f23283188eefa3a40d9098c6591baab6c4aea0d023f2ea3324a3deef5942a3f7fd9c338d253424a5f169a4101c3b1f998afffe49b355b2422812b0dd4de06d1b7a29a801b34593a6e975f2dbcd61be3c6b264e5ab20e518b906ef43caeb479749a485d4ebfb1c8ee02860cf9df9647b5a9b6bded8916ae882945cc5b3c6f631b9e5f0d63d96ab46d00677c91fe054e51b27bce8024af39cf0ec3283ba8d59c66ed7ffc0d7982c6c4ddb495a60235655d2bbe5c15545abb7ee6defe23bbafbda0525f0dc6af316bb97b94bc868e30a67023a767c250728f74160163bf5f1f529d7ce05ba10bcaa5b5e80b8bcd59f214beecc431bcd895b13cf528ae56fc0f5a33e38ed182965be713cf073e3162d21b9844b3e6f4cb645bd753974a08964571e163dc5929c2dea5ff5a4f56429aa22994dd06c6e9606ca618dc909f967525ab067bea08e576cd5cf7d8b9b406eddc06ff2c4551b8280bbb1d74f269b5bf2b05eba780e98177bf4432e8490b257f637ed360c6edcf7276e837b8830af9c6163be613a99bd19dac659803fe32e6da22e4a1c2481c951ffec3e2a47536fcb57cd84157418f9e8e8eaf88ea07b7b5ab5af2961f72428f6bdbc31d328340a94a988aad226e8dc908a5ff36b21a5816c8d6b60e8634482ef12040cf17f278e4dafdc3bbec06692ba1ddf1c0efe61d32fd4fa5af59451c38e6cf2480dea46d5d8e0ab0947464f01fd57c0e3bcd1c05d1ab77aa8bb5b72a592117f662a4842fb27fdfbad2dc6639c80af4b9e0372beacc2be5daa1d2b8f27bb077a087ca54c1855665df429ffe1bf0a8dade0305f0f7983bd8e52cffcf09a6665f9687d1a2c6c034af9c4fe1deae8e07314a1a5b36a079942bb9fa727058483ea13f6531d187d5bbef6c19a56ea2f5ba7d90b6153577a6d0f49194f54e1b401cb2c8e60423c24b9485b63697da0b90cd821f378b541d89b65ec0fb582d499a2f5c25cca60e71f204e2aaf0781ea443cb4d57173d4b205a7a8aac01190df9993587eee56af083fc62aa9144b6cc02849412e3fb9541882738203402fa3fe787571b700976c520141263087480d8792ba9d398a4fff8298d35652f7105dbde4996039c818d46c8c74e12e34711149460228f0bf0a7f0b7755c8432d778c8531ddde08c02d3d13b65b0fd0324d0f335bb8dbd253b180c9762d87a07c579b49f2622d04590a308b43f2fcb13235261ce2b3660d86e133965d87f43d88cfb6282d4d80e12022ad90b3ddf26e2c97f3541fa2440631e892c51ad44f41c3e5cc3dab515851edcc81c586b275e49a724d93e956cb3b4e0679e1ce57bafd008694bf36ba40a8dbed9dcd76c058cb81660b56f341579fe8e586c5d259321919738cab8a1c3c1ab702218a2adc6f9917fa34ab750129b8657570576499a071d7640de7c66c05236842e76ec78fb20864ab4e9d2cab4013e7b65b0b94609f3950cdb2b9581350876b19475bd722742b318566319a8415c011330828160646935c4a6ab0023c53dc652b8b6007e200d1135bdba7d67f5a3f8e41b95f542bb24a5df08525c205baf7459372abbea27d8b158bf90a5ef46e12171e80cceb35c90e108f84c168e87995020b7930450d77d2163c824eb6cb7376003c19923b98effa4a4c64a71ccc8dd94369b2643103b218309a3f5e8d551008de9e6151b7f4fb9d9f82993e0223225f35230b59067e083e11f1a9d3e0b80a62d6ab8cd05c33aa3a29ff8f14c2fd3651ec0256737cec9cf8f6216da733dea3390d8b0b74221483010259932829b69d8e7a6d470ab1f340d289b2078e9c33a9364d37f4230ab17b4832a6ea86c2a3cf7df57b0f2bbd86ec64659657f7ac3b79acb07fe56b6d9a9f3b344f1afbc240bd6a15c00cd974cb484cc44dd95e9889b6c9160f6a36e4b326678826d2230a3075c206ae725eea9801df5d9973745729251e9778a99371cd383c78385e79ae942b6c41b47da8dc15e81ee894989254bcd515cde252ae3a0c9610a023267f7b019c7ab7804d02c15414b955c9d71f9fe2ede2d8475b114b913756c37e57d933890f9d0b0c60e6ac690c62ec3b12d5700e71ed8c975a90cdfc3c7498ee04071dc590ccd06d548c5d91b2c9ef61b216560be9c887a4e5c061ddfe04f9c50c46491b4168fb2ce1cb7c3664921237fb1a51d419525792776b5b615d63a5f44649d1de99c63deca0162a7bb213fa858f22a1a294934c4924112b56fe2b81ca0af5e4df817690ae6726ae24c18ed18139dcbc5534e7e5001cb2588290af8c974bc284abdeb0f2741b017eceedee0cedc16af16005cbabb12607273e0c8f8808d3f37cba3c5b0cd5cdbcfddc065e14c4867d660ecf1de8cc0f08185498f07ba5670e4ee206e3e47ab1b10c3384e84f7d40c85b3660164a27e4f6247f24799ac3ac8606c630e0efe8245171e2ab13686fb18db1f8871acb4cf292b48317265b61e14270c443aa88739e86990cbb8b1108154f28da0dc93bfdc3b8315ce6046d3d370620e6065a414e274980bd190d7b65804eae04ef8782ae5b95baa0c404f39a30b7eb32b31d8f1576237cecc85cc04a1f8aba55345cbb71af3b5b8a3c327ba4c51f8e7cd8784efa6049e10ac79fc4718d576eb6ceff8f83c22c394ef5eaba2d720f2fb042f6be6d695947b8cf972a2920fc85fd17a54364aca817c75bda24cd244d81a79c30d400bd219d9723efe424e5460e062846388787db7e673edfa57996dd9a5d83009bdac74db8a2ee116b2c42b82011c8aa32fb283e9b836de14138a81139b2f997fd4521702a54388fe52b8aa1f95df26436828004fe13444a1ea33a1dc3b956ab22b97adb754ffacca8b397dd8f58742841ec699129718f11fe2c094b2a4161a55e261dc3a2c77696e122e379f97f2668214596bef74be4d6a84f20f22eb7e9ca680b9d33bdd65c39461bf902a1c0cd94383ecef820b518e1b57ff583653a8bb9e32e2d20a97c3afb77c3033bcab41cfd93e5fb1bb45f2139a08b7fe1967de43c69b3a541d88feb26d6a4af7fa4d6b32bc3526b473144b0d461eab842cfa0a7470b50d7215b7a6f5e6f5ea1aa26beeef4437d2a4ac44a68031b09b7ba40186594c36674401a0de29cac6ac1b3a92ef0e9fdedd9e44c60a29b2a765844376a0b52612c6bea042953da1b3b4b3bc9343f55a4ba4bee8df4f8a8c99e8a7581491fcb2906ccdea4f82367362e1b8eb0039c611a8d208c1a43c51cfba7ff1b06a698391cebc2eb08a5a8be5ece3fa2a84f2e927b2db9704b90ced7b11bf5e60692c6be6f1218b1a015a15459c4ab88236eacc6f36d58bac0dffab6ce6abc87f5950893a2b524e5b54548594bb716467047fe4cb3b4c32c91c845d4fde8c153564012fb54776f4729d5d7fc6ccc3e8daaeea2ce448be3ce28cbe3f4970c1c20fac51df700682e9d9007dd442a3a6fa514dd21dee8cc3a2c089534308fddc0bf8ee4d6742fde2805e4099d27178defc07f6fee7b474de3fcc11b7aba4ea9476e7d48cd1d40e141aaafff27dc7f4291755223c515f75d998947408a0e2821f86a53caf23f17f2100a5eda6280288bb1028f0000927a3c51628c482a7d276ad8c4887ed5b1c0e90fd6eeb9a965426f7d8de17bce152984c76aef60095ae8c8468fa80ebc0c83ec01f1255b813ff2e564c0933fd05b6716868818a27f138ee2c2e630ca49da2760c5f8ba5766ce264f9e9c6857bf4ffcf9c7dbdfaea63c9c0ffcc96b519a2765d2a0d7e11fce728ff440123dca04062d669e0249a4e6c1907df80100a606f5f6c13a21f755615a000f980cbe2db0e22e04e81b02459e3e99bc2ecf4591753f6ede9701be91e17c5663550c949bdd498eb0ca82302022111e25864b687ed3934aa0ac0c38e0597cbd42a18c83ba32f099cb79a94f84de00df861f91406849bf57d7ea6bb132790fdf65be39040858c8703b529c61282f1da19d2a675193ec74e5b2063b6f0743fb29074e56eb9b8d98fd335719a0c890e7977178cf3b10a8745e6629de440587afdbcbc5b64d86f22e9f1c80e24432d42c87b5ba9834fe0841b0bee226534c22ca22de25498f9f4a12c0ee1b8510e81aa04b82911c4009fc6aca705ee8cdf5c208de15e380c26f501f142fcd92ee96cd33f4a4530b7ed7345405e6f0b22c78fdc00cde1c15af49ce7df831f446869292e7fea6de1ba63b44a1ea3118165501ca0f9a811379179e5efadd8b1501147204535d6890d0f1ac503a3094845acba2c47b122d9124b0b871cff4ec99f717ecc2997ca82f719e5afcbdfcf426ab20f670dfdeb87480152bade227ff7657d64f6e4786338578efb5642da1e8e17c975788e013c3c7190d6aa63b3a2dfd50342fc2e3c18dd1cd5297e668ca9af7d43d5420e9b9b5783b85634845019c62d9ecbc851ed333db894950aad994c6aebf67a2078e025627664a1574b7547adb496a6ad03944be562600ecdeb3b059af34f531e825bbecc91b724004d3a23f2b306609a82ee1daeab9550865b62b8f261ea0a6bdf277a174fe8a61e4c675e4f91062cbfedb6dc15b6a1b9de2767f2fcc02fb84d9d418a882d498d449efe22266d1f17037a3daf4b9431af68acae32bb3af30c1ca79fbb1361e36624a53eefa1e168af7eee53407256ce862febcdd93507662ee1d5ff34cb9b21922bb7c4c18fe5331d2ea13cb8cd04cab11cca0304766ab1be3bbda347b21b2daee5070a3b1b30a4a309120c6acb9b0496c8e194a8c23b5508068aec0518ccf220be97725d71d80ab38ad713a788d29d7c680552c59263c25fbcc933d17b71d718bf3f01158d564d81f3f6d2047cf9ba2b62562c55892a49cf70d795b0edebcefe88b93c369b257b467ed9af22b3a28856beac0098addfe28b868a60eb87cb23e00deedb1497768386f0f4523a3dfdb533243c3db1b8e8bbd96cec1ab8e9a39a112c2449805ac11416b2fb35d5acc68bc7ce8711f5a0c9091f2906082a26f093f43211ebb28fcb49074d98550ea9e4a2f6d2e8a4660fecbd3b1e87040fd6f47159b25a5e2792b12527bea93c59abcb2341c6bf08439f365de145bd360e60abbc0b144643373435fe2e66a5e0414d57df1de383f27d5ebe5aac14ec2af9758964ec76797b1dc27f0859c9c507860a101358a7ac29f2cf479c0abb33835457d57bc508eaa9892716553e31e4aa718040d290dfcca986789e65c7096b98e70e1831713a305a4832f2dc1d9b82018650ea1ad909868cd1ee42fcead511dfaf4a38253bb0d372802087998760f42dc1a288ec8760979079db60740de531118767e0e1e3193c3ccfc2a624f1421e77f82e5a927d14ec6fcbc62b91f6d67688d54ddba655002e11346deef10642640bec650d143b88277596aaf2412f1aac868abd7fb84baa33ed754f8860126085aed77fe6cb237e65f33dd8293d5f1d4eb7e3c71f106c3855666a4070642299977095cbe9b565a733fa7b7a2d0fbf90f5a80ae83226097203fdfdaf9d58515855dd4cc40b60b6f2a7643f368c140c2a68c93d319b58850b0369248ef6f6f265324bf53ac49dc2cb248f1c0c20dd61b78abf1ebca2cb18e631cbf8f58fb540f7347d4eb46d4f560ba2297a2ab076b161f84dd69904a37500993d120035d476b613878b78257ec2284762a44a1e95b3d0691219500e4feb9ec9555626489904216912a61bc8c99f534531c1322d919c4718597053f8e68706a75604008c065b9094eecf144e09b1488b3002be84d6e4a54a35eb124de03196b653810eb42e1473be4811dc57aa05936c56a059afb3b814229abd30ec6a602acadf77aac0a6479c88f559333fa36a1910a79d3443274a36d49b5e7f89b68414cdbbaf11867ebe3d2a36c8ce5b67e74452f47672d07e7d9e542171e7068f1832bc155995a7dc7093e06dbdd5b47b326daa6ad659eb55b08b6e793735fb16b68cb16b88bccab69a6d3600a120c2510b830d4a856149f68360055d52b69bd14d0b2636dd5462d979dd2f16330f513242a6194436fc133652036bd3ec2294d94f621ba1ff4807ae04a0373976375923fa32dfeb36d2903412eb889ae6f4b19f0fbe22020e4d20e56e06629833261a697a8b74a12c86a405cf6903c21856b46654cf81f726b3899d67a6175914850379bfbae2cb240aaf9e4253410b1ac21332caf0eb15b804dbca870670d1591fbbdca4324adba945336004ddc1144c899a165cda946bf11e0ca39c77e0723fff3b81626d8a625f006d80c5d3e1c5eaa8f7c33c1225e6c27b88089dd4ae91f2e506cdf7cc9bef5a3c02fa266e857d9c7aa316311b78408a7c401804528c702b57c0b5d763e7dd97f06b5fc7249635614131992f5e241d215b9fbd90aff1a64c5b729f85fbfe9ca66efdc905bbfaff6e684351d3c3a0634860b6945c31e464e096e5a7d0bd6c12cdc3a3b74595a97c166969bf22bb9b1ae0c6496e07d0e7775ff1466196b7d0ea790f35bb611f2371bac2b7d6b283ae5163039e4735014a890284450b2158a1eae95e2790fb0811ebd6bb78a928a2fbc5b56d9feffe49ac361d90c97ca0f5a74ecd04e1ae98c2945290c2e4bca2e11c0e07e048d182f26f8c1478c6413c1d36cd3ffed2e00daa2d9e929edfdaf4a6fcd99a750ce105acd5d231d3fd77d06d0f6df041f46c0ed9d858cdfa27f1f7bb7c2a3d2c7800255ea1d3da2ba7422ad56ff5720725ddfc2a0db6f5da0e7cd7fe37df60aeca395da2e77bef94700683c70a4ab0c6094bda8eb236dd3c489b5072438f76f9fee25b4480fb07803d2a863150fe80ff365847d76ed606d8bcd8c53e15a30bc0639e36c6278618281b927a13f68111134aea64cac007ac1b9bf52dc938809707ae8b69183972be68aaff327f7169e2d1228069d5fd979e303e650378a741dd747652fc9186e756fb2ff2fe13aae4682bea633d24be85214391e8cfe38aa05a72d4f1e84072ebce7d24603562f67ad6e49bd0a153fb99a0de77f915c66a7c57045368fc623fc1992fff8f5ab008f4b6fa0a283f070bbe04e0ff9ae35cd3c02b88d669b60486cc3066a12cceff85fcbdc6261e0314ec3b038597cd1207d33c1cde2de9ebd7a856d491ba61d084abba56b8fa0ae9b014de35fd6a61d8bfda42b7430896dd820d184080d588bf40d65087167b3b88712c75381b77a95f2a79524fbddd4a2f3bea680e655a4b22591e132deea53f7c44428d7facc772a450ccbbea805102772080c4a44bb643fa3b27f4f05574ffc3d2a3a5310726dd8a143da8c10915b1d5245497c5b30064516b02a5fc8cc129f10ee97896126bb290046f09fd177ecf9ba13f12abe904d2b517f49ecdd2f73e0948c191be4dfb157b17a35a02b9134124f508876b0b51fd9402d2f406ec58530f54e7b7c275e1e2c9ec09e935ded31fe63ff3579b65d91f03f0dadcecfc09b5e710f52d851531a33f07c11f06fdaa7cb678c95f069614dee53992b665846d2a61642bab11d24e7b8deaa8a2c01699a120db70cbfabde42014e331246988a39f2d5d880822190f2fa9b316824197c4b330fd8463d13855a88a3ad2481447b00e94a296c6dd4e042a6486b7d341ec64ea6d29fbf46346aa4e169d4f92ba53385f663ec781ab394ffea238d7ff4a196db3dbd8fc2a149237bbe3504c975d0afbc0e75e494d06d78d3bed49b10344dab1b61d547d6d0480b0a24b10d1c1a1ede9f6b4a71d56dfd2da48d403806da531ca75e24bb23cd52be6e0f86b29a27e6e587c9ff12f2dc272406baa47765c44b2f7601a3879e81c777d82a58469829b74697cd3bf6c8f5166f9a3ed56a43ce47183c7b15fec0ed66d13dec7c7b3497a4098b795c78fb0ef41a8400a42849adfc61ce9c4d9219f761362d65e7e8b26de09cc66dd46c4cd2a34f215cbf26188535a146cdc8cbcc8b87fef353c1bac4aa6e1fa9ded4bb94a166e5666098db7cef8ff689f2fb33eaa5a9be2e995db34d2c3f775aa6a0bc37cebe4ffceb42ec15926d545b2264879483d38a68398cd7500fc8dc2936ce2c8b4a73c49bcb193cb50838050d5b23f46db21b6111ca201b1272b48cc83524d064d36176892f1cf5d80b3bfcd7043cdd52102c2f192be4e558eb7fb1a362c002a2116ad39b697022888d385ed42c192883190c8e156a2cbe91596d3a840d178150ce12de2d6ae6f1544dfadbbc1350f17e94b57431d4c138908514d0d4f482505add3888462aed9f15340d30f361ee83b81b392839923d5f50c24288d6aed45cbe30d92e9918df682e43cc74648352e352891bd63ce7625cecb128114d2a3d8b29e46b4a791f88ccdb737858cd08cafdb9dd612f3b3ad8d20ed12b8324fdf9b1263d0131fc3f94e437698ad9a1a5cdc8a6782ca9595084fcf49fada6cfd7e83e1801378a9a680948197722c92df68fd42c3bf412372163b4cbf1ae9fa1867d3d5720ddfad74696480196b6027e2135ab3c7ca51165b949d8c56005da63d05a910f4efec09b5048cdaa6e2dd64df1063543bbb980ab6f0b8ea910163ecc4255a0626162659af13c036d8cb8c32d70ff8bbec85d758c3d5e93bcdfa4d485d8d0ebdd55f126faa44bd1649c05074c47cde296bef9ea8490f3b9d5e6ad35942fea51ae37d8bf51334b60c4569d6f09657484f67e5ec83999b978a146cdcf03ea56004ca3e6158c838acbc3eae3dfb0fae757ce541d9aea55946b2c2aaf5df747158d2be5879fc4c1bbbb4346cdb5c0546ca03f9b316a664fb0caf1384a16a63f38a62fd11d56050196b73cd734f84aca9de410b9b1d89184f241275d30c189ca7c4c86f321995028a6662757d45c0697746e766996397c32a5f3f65b22ae579f710e5b484c5b81024f5968b49ba266e68245de5464e69792f9a02444aa08915ec0f0375133e9fbbf5c92ef6756169833ecea4f44cd0ac7de5aef05ec7507dd1e06fce39eebd493a48bd47775baebe960493d6e3b9a91a95b3ae396d98d5dc7243de6943d57e34b6b676797d042772c436086c17f909fdea8c2fce633196a3e66b28449640d3f4365b8c6c56c1b7e3f8584626f65869a152c762278e145bfe77e4657688a0d00c04ab1071561d2ba481bdc43bce715723282a5220ca99a09ba9fccdbb42ba2041f814ec406e9073bd6e39df7727c2ba49258a6a54ac5d14e26ae88e626cb0146d74158fc170a2a65fd5b3dda8a0b6007a2d7883e4757a0d70913aa1a0e71673f8300bed97688f8dde82910ee465afcaf844e9a28ef8d761cbf5bfdbde3d86c476a5e4d6ec49066abf4b4380b608c3e6aba4ff0a211c01e679ab9f1a85ba222c6244c83040e872b83a1ddde3329d4a74fde63eca5a3f0898fe6751bb5b20a2d081d34111154fa4c81e5b7579518b7680d42d0dd971255adec5d12a149f9d783eb957f6e4594d5e196d0376b6d5e9d085d16c4b3af8ec7255aa81b1d0908ea484cfb2d0f07eddf71d0e48fb94e9c40cb4bcd24e9e7857432ae56809743cc04e9afcd5186d333d4ec982fd942c1f054e42fc39112c45a8c83f9b5b68a853d58fae328033e961b38821c48365112d70e4fff10417dff0142959c40bfc0472051edd4a0e310fa49fdcf77548ed040f9e1b1ff7894f47fc2dc45e944064c0f338a706a3d8c066c0d1092bb2a958fde4a0c94df30ef134595edad2fcbcb6ab11e2db80a1637a97f9ae18d6f86c2f714daab10a197078817faa3dc075b835d3af2b90c8db2654b27bb04f1ecd4876104939856b8c0b9af61411c753ded80dce74b8eef08d075485df56d9c8ad94c4a3b450501df3f778e572bd8b951cd0e2c5b3e56073387332894885711d5d5340e8af0b5fffc36a115376d6d2ee18baacdb573a52841b96134aecfaeb622150a3363d60b4ee8f60b0918973cd0b260d2234342623f9114dc5abdfec730df48b27c203623fc7e0eff065f8870972c27d6dcd4d5a4a3402403c9d456083cc9ee3ef928736750023cdb938cb1b946e4a38236c523f880b120537c8f3fcabebc11293009cdbd821b4a860f0a359a6a6ca952bc02959a13acf83af0d06123845334897dd1fdf3095bd94e0d1a76acf32c7e14132005953cd750172aaae69e891988d03bf25ddcd0ef7a850cd6e5cdfe6fe5453f1063a5ad81b3b4c3a834091bb912ff48d8e38a5174986f36bcd219d07a510f4aaed8f3514024c321062cc6ce7a2320c1c1b0077de004d01a716194446a479b4b87b40707dd59cc5c0223ebda9bc0f8661f69e5de5eccfadb282c6295e5837fb580f33b74f86fcc61fa3cfad6a91f0b2717783093d61693abbfa5a93c598610b91a1c4d42480037b43b1e51f798be3052dbeaf2373ca30773964cc21e872280029a08fb83304cf2022a7ee0cfb1a8dda7b5bab7cabb053c443387d3cf567fc0718bd44f4463586a70bca1e96617eabe8f04751e6974590b29490fefa2b3e3127867cd3cb629ee02fa9d07ee2363358e6db390f8d0bd54c17c278491c9550d62f3091a9c1213e2e416050a1a8c7585e0057c308223320d817327b8e94f9915d9de5694ecce8847abdd0751417057bb2e368bf53cb7480d4039c36742260c61dd744f453b9db1553283f1d37f75e172f10d9589fdefddaa015178715710d3a2800c5e0a22a1d72910249171ad5458666eaabe3e246d0368d4a2af7e1294622be487263ea466ded9213bc24a7b0f5ce42e19b030a3fd81aab6561b778b1acad2912760f548344f5b1fe012f83249463a91fac36906e69b965834a766456bdc80f6ea8d971cad07cb163dd0bd1f50c41499e311e9a703848ff950c5604e9ecb50f99e5861bab58a1be56cfd56c208706f8af76805fb564fa4693a08265df50c6eb67c7931b488f1baab70773174a5c28a785799e97f6875de05a7774939ea4426e7ddf0fe41f2c0909cdccbbcb788fdbd48d97bc55f558a64131e11c810339eaef0e82770ac45c90b97bc780eaf7b87b73eddbb3657d63b76d97d69747c14121fc060820930e3b6e1e2280c2c73aa7fe1a324a5a96b685f9b0443a806ba3ec547731f7b8f4970bff051c90251a909cb766d1a697bd2e693c36c4ced36c810038a5afc9ec176049df4e1b4e72fbd3c799980175126fc8bf16d06c6a6ea4d3e94c2aa2a3878367635e3326b3dfa017a04c77d24743e53e072b6b830401948ca0b42c596ce9483a1b4b8ae435e751f329707d2f52c80f5a1f72c76f32564af24ee574a157c788c3da86ca747499afb6a091e36ec7c6d773f45581ab15afb313a1045ad2d300262e7f60025b337fcf9441ff9c0e3b4e663b0326265840cdc2b5b4df7f8a519de49da06b5c77e2d3ba44f0cf4b08aa9b4338d731107696481c7666c06912840d68c2097cfeff95df2f60ba0d481b22b9441e2b1c57d4318e89a3511f4d858ece53082dc6198a5549eb2840cc222e86e49efeaf1daaeeaea8ebfc9930b5fcd085c092d2add35fd5da326b4f268adc358e67b8f0ccfb2eebae0dc3c31f4e5aa58f36b4069cb201fb0d514397a015262d2ec36b409a2a2a217dd60ada5887fb8244cbe5a328fa07db48892efd90a0fd693b34161477cf6729cd62a0e7e5e506765ca75a2f2abc1958632c53493e1559bafa8d82c20779af442bba558c0b274cf4f8294223270641a245336b71be32b75cf878c886b0e8e1bfeee4181029303511e058c0bf5ee7ce296e80c3252ec3a4ebbca62ac390cfaf573db8df4560771ca2edcae35799edb494314ccb45d31b7510791ccf11d503e0a7d864fcb3c57fe801232863b3d4f1e4f89e06b85b8332588fae16a800d5b5475137457961c5350c01f2f101f8e1396bab5fa2f288ba938efd6f19207c0e9aa75681a5d5f240930a948f67d352e73d50496a98f672c97aceadb2e725284b99c171980f9b5c1f22bee24ef379b5d673bb5e9f3bfaa871daaf6eddd2bac49c013f27dd36534dc5d3fd0c2d9e5a4339bd60f0a4a09ca7e14d5a91281ec45ff1cee3e2d456f852243a0c0d32f887d00d19936ee37e663122fc6798f7b1dfd3cc2fccbe1b6bf78a31f246a16af0b77833df327b3798083c4d17140dba702f4fb5aaa3d090a8482a875f45349e3186f2805a252bee9c6e42ec2941e73d94db8677118f110fe729cad428c4d123aa907df2bb193721b1c8017ef2370d45296bce7f7028cbef920be8eafd38f5e325dba1517752c2f460b50133ac9ffa264bfef3d570ee497585b81faee653a28f7a9db1d120c6f0750555d17cae5d869419b5a45fd800039dc979fe1adb447e2b4017556dae3abb121c6cd400184fece4076a4b30a44e2c40c860da55ff075ac5ffa12e11c21ac7cad4f65e17c0f8538962c3ab5cb5e2ce0352426416411cf81871705dc8063920aa887a1b44491cae9e78838c1baf6ebd90af718df28c531d09db877b8ce84f49e4d421aa2ce75d16e21d2c8cc284110acec2d610febf1ad579692dd004ac28b35bcb9c54d27c2dc8dadb749ddf83615a76d12f40e48f35def7ad54036075ed09820eac3d504f24b2d8826f69271fe2d40e023be9b792629fae805c2a88067da57ef5d0feca1582a20c39c97cadb8a493ab16d4249eeebce27e02a4c2dd53e822f2361870148fbe210e4645d01923f6e2ab6cf5faadb67af8aa283daa913be0d3d31b886358823edf43bf4e32191dd987d2b2653bf16e8bdd85acbf1320da62eed80ab45ac955bbbbd2dfda60c93a1f132e9e43738acc82bb546838793bc55884309951d726657b38b674d44b5df216efca7d18bff7f383c6cb953c3633b956f75a8b1038a4b3d67b01e69dee7be60b072678be9f790f2fe645f08fe664560e90f3a670e336c36dd147fb118d17c16a8b6c454b310aba7992b016d40e063e5fb7d64550f9458d7032122b8c445907a49b0475484d9e3440e94c6872139ac2f957d596f9b0c54e88cc401022e34d0d4f86283b37c169e09d2a8a40ed931458fc41c08e2f956a93b6196145837e50f8b1d0d5bdc3f888b42ca105ec609000e4fb373a778f0c698a9d4b1bdcf03746f2cf0546bb2333004f35b58256d1ab25917abd93d4cb61675df821cdf0759b3f48b19829e041538423513e913467b4a830ea57e2200b3c932e90fe5d395f4fdac69669ad7b6bac9496f278e585583b78c7b118b725539ccfd0c2b96fb412bdc2b3b4a9519af8aadd3e11ff1614022199e60e739513a7ab567cf8496c7d14fb6f37802562492be2c87563f5cd5c63e79d55a1acd42057841aaba3edef5651b26ca6d43f9c0f36a05b05fc9f159f89bb3bca882171403d2c7d9d523bc829da9b69beaeb2af83f70382ce0c2e61c65052e341ca27e0d2b97f676d1aab539258961e3db1ba4199060988b36833cd10166960c9f55ba2d2a6d9c598151296bb6cd1066a740452eea5ca83a6c84c019073eed6e698da24ce3e03fb3ea60a3f891e7d89d910506b2fd14bba68e114a6b0137955e8a1f918966b94ce6200f471030d5770950002a17aa6fc82cb1eed1cc122393e4d2b537a1035c3a02e7e714e814f70e1a72b0ecc65a5ac44ea4f5a925c16a881266a98f3250e540b72782fbe9d76549e291108dfd200ef590a815d54185498b338a10185fda89c8811479ff62b0aa99dbd880ead58de12dd21b19c09fa1a9ff2051892c79c17c526ec6a44dd2dda1c64170794eb882a33cffe576d7e6bc208481027a591d88ce7e3672056c2d2ab97cc046eedf3c2ca738756b8212e1819cbd5c16091ac44052a0eb44813f8636b24231d54e8995fc27eebc13f7203c756c024b304f5426107a0f3ccd5c1da7dc697adc61bb1f73b348b1e1107cb9635ca428f33025b6fb03220584b0969b418e697093830b7e374e9b101d851b42c80fd0371b7c51bc0ffd53db8be0aa5bb8d58f52e62dcb9474f6d462829fd117b962b5afb1814d33ce7857d86c303f0195d899e26e363b59c82b37d0cfa29c515a96c05c0c555ad94dfb52ad367d48960a2d4b6bf357adf8be01e2188aea388cf2f05b5270f9f7f428002b2b29f60ff63d40480d30cc23cd5c27bb83050e9b7f3ae485ffa68723c61e29ac850a70262c5f8adda2eb2374aaf94b1b169ba8e7b0b761125a84e9e17f992759af0f1ffd9ec8ef95be9523011e99127e7d6140a426e4141dbf404b58c651ff94bb45c181a85ed24214e7f7bbc0481370eb6c168fd0f22388940eddadd960599a38ea50337a2a9628290b2ec40aeb6cb90ad080c1c3b9fc973b41e85d5915eaf4a668ec1de8d9a12f8ad98549139d116f4652d80fd540d4a04da7d85ac68664a90eacd345499d96c72caf8872ed680a5b50c51b19e0bc80fce10ea6e50ddd4a0b579d5521dbf871e99344ba926082aec0fafd10d34da455b09a323d6b2be95fb127b5b63613b22e8eef7eb87b4df949dee3f197ac912b36470eb754154c855b056f230702d5b37a62d95cc2d792714c946379a6ac2cc3cc8214d903a8394ce6175dcec4afb435a6a3e87ba5c57376851cee485c0481ea1547637667dee15ab1c67be1d0c89292664e841de5f1656a90ad04fec771bcd56e9007764167c1f45f66ecc1f6f3e4e2518e113d87c8e4926b2a3ddeb5a7d09a139f6abe70cf182398b9e02e0c44d8058576b06c5303e6f49116a2b99a783f19c9aaccd926009143bd26eab40659bb7ef8cd502b7be8423ff3ee3ca738f27a073ec46ab6be789c157d6952fdf6e760d1d65aed20b4f8b433386e7326e3c736398fde62fd582347e1bf7063fb33e87c68f5cdae66518d4c2bd22f38464cf92fb768998044cf67bd0a6bee224479722a34f37c703bd059c1cb5eae6b8bc480d580af626a8b6077f1474a1c593b20f75f430178d7e2eda4e93f2cc417e524f36436970eeb48beb64c00521e118e88bae00c718a5822ef1c0e44f0a4ad1eac5b8123c0901eb73973024f03429eb8059fa3118143fa4b426dd5adb900875a4e80731665039b82e09bf30b71fe92e3d260ec5ad3cee71ee480d28c69a85ac284d05617f8c05c8861565a3c0ed2a18f9094dcbf87c652dbdf039abed8ea7a185f222e2c97815fd2171931a8997ec9704f1a20b1b9eb6bba32c09afedc531290f2ac9957cfdcc68e75b3f26c1d90ae208b0529d479296172265738e3622261fb1a142e4f65ee6ad0b93d4a830e3149159953a8ae2cbcdc1c9100a51633144b0233ef67d1f684ac61cb60c647a9228753a9533b108dc7d83585e65f4429100db52773cdca60fdc5676807b2c29e4cfbb9374c8dc10e343d9c450bab9e4c0e0fc76a61dbf5cd75a1f3a16b37400f036a9ad6788b33392ebe7db4d6d8e62783b9d868b496484e79482da57da6a3ca8d09a0ad88c581cc68442104bd5cab9c27a1ba9038612f7d774ffc5d3fb28ee32825797e895363d1b7c401bd536c0e388cef100f08e27d75d56ac85fa4e0ba0c4ccc2ab2fb7221814e38ed048ba0a3b1fe20fb1861d45bcac37521a216c6af787360350f44d193f297a7c120311aecd4f4a5bc7b99ea9e908dc6c797e9bffeecc0045b9d16938f867323cd13b9c262f2aa772899fb246ad8dcd001a521f683ba62ddd0305a91321077554c4e7746a4a338a48cab08bac32d5109d78bf927ade143b154deb8dea8c30fc20675c551ab2a2279664349f42631fdb5a5c3a561123a9198ae945ac39c4327efd05d0d5dd68115d32e8ab2da58633e97ee61977bca4d52f29c4f01e38992ec211ff20beb0eaedf4c35475f982e3f31cafa82da8d50ea950ddde46712b23d6b4c62e19e0e855d574fd98c28a1882b0284069eefd8a4099b94d6b50c6e24debd2fa5f2a02ab6bae5c10c2b2a05c05f3ca80f254fd890860aec3a442f49c35681cb2595881a494ba56dbea34db1becf4fac2024447c70201d398f5682b915822f690e407d3c6107402a7f02f50fb04331c70b3397d1a0b03c083b574ab78ea15bf3c508712a33cfaf0e034940e709a2db66b558ad2328074b57fe1c6c1ba5868861d707ff9bbaee664283de8e41f99cb94a64f16b745866405207e7c4c841e3d902d828b3ca09689a443e6140544bfe0b59f93263d7695c41ac76aeb062feb5742c5390aa2ee52443a8a88e25280fa15ceecec44e87243d90171af93fd2757eca2722a3636945796c28d6a1a22f729aa6f6207a34286acb0c2c8d569fb004a5d63424fe79cb6c15d1311fb1899d7b457159895ec05c22d423bf614168001d7d84504291758beaefba84665321fb38a5c61a4ffbe893b9f7ac6e32b69f327eb495e02051464fdb1d2b4a443d8b1b505052e2aa18b8ae660e02ba19b1edc4f7514a84bdc81e20808b742f029e891601e12b95e538f620d1e9e96332ffe9636434a88f29ddb8cef0994a9ca541aa8fc589d1c68cf7541c856d5a827b309b5544fc20ab6075124509434ecc5a16183a836967ee53bc53173cfec21e659815b3b05a5466e7daf4a7973bfbd415294c1ef82aa33a006d9d4f235882fa48adba24add53a6f1cd5e9b7133d5ba23df6d8541740a7cccea9cec83b783bf614cf7640dab7cea718442a03ddffecca23b30efe244522166e863ddb9912d746b650e9243808e4c04cdfd1654a395049efd31c76d2f9cb1d01881f1c4263df0a119bde59ca279d85e23cacc60e30c56c1691f03ad389c38d63d6cfd43f53964260ed610600b63fdde9ee66ed3ba8aaf24b787af1384c9ead95515139f13f2f9fd42797f2ed381bf3817a9681aa4a8a79864013e966635a65d59090043afcaf254274d97b116d2df16274cdda682d3386a28e55452cf911381d2cac3ba07fb004339eb67bf1532676597fd00f6a79bc55e331069cfb77b8be986477031fd79b540d529601ff38ee9230f7674c7487ea436ee15313da24869638342e1c3d08b3dcba7b14447b7ec125647c3cf2c5546e45bba5c4451c9999425b9c48a9dc081a80d90c7811e6cae593544b9f84ba5f93c62e01ec903593c6a7829bf2520a8106f8fce58fdd271415947b6a50132de245731188b725066838f490b3f463fa483e213b5ee2e74618277a691177278aab4528b901ad764eb207147f70ca972b177ce88de4b50babc7610d3ac42e05b8ee7c1b0727ea9348dc8fa3e22c538c41bbadb48a5f6c1a71467568000093673e4649500cd95765497d1897e1f36207d5e144970149fe327884a987c15ed78b13f48c65c802d2733c7bef5e3c6e7ff9da7d072a717d0068884191b85adfbc7621de843948425a16a5ee3f64274e6d2583e89c6773c024fdaf360ede8913d7a8834b0546dd101c196305127687098a0fe1d1f12727463e848453f15a0f799f873960d36353f35898c7a600154b66bd0010eed898ed0537a38908289be2b500dd3b58e6066b1296b679100a8cdf6fef73c3ed76f3d53a99c0f2416fc465c9685c958dbc9c6e667a89cc24ab483d258899aef92d3cea39f3cd983fe9744df868b74fc68b045a89dcb44bd62d4f77997d0b4e97bf5365df0b694e829b325e689d8878c786e732fafc195c227f85ab4f2803bb19a702051574a5f058b746ff62bd64e36a9f19bbd88e8a6ebe9685381f76aef7516eddd92f0e26de040f1e30e99fb7ce1991f2a0b37f037961394f0e8aeeb0a19ab84b4257a257e419c0f497496cc68375b1e1348c6b69af3cbdaf292b474434c91289f01ae3b91fcda47c0e5102136418f71b37b9180827544d83d1c038f0a172c10528513cbf8606436a13ae1848c2b6623b346e65f30f8574571fe28fa7196e4151c8fd6c1f44c7dc900c3bdb93d13ea98f185e5bb9c02724c6fb394cc6e26ad0cb6e0e8c6c7cda5f53915715b37eabf64f7b7b899cab0ea5f510ac84372c007661f14cd3505f64732dc42e1fedb5e8baf47a6b9f44608e7e5b5f17a4c8335b5b65b5cc62e016ecabac3a6ff62b953ccd7e4c187732cc6e8206aef7d08f575d50c8d80de4bbf3bf81b49c72ef2ddc0b2cb7adbb86e0f6abf3a13eab615942580f553ca436fade7ac08893bc57e53544f221819c715215c89d0e83ca248fa98cf06379eac253d938f9aa1522c903ccd980100f1b939052ef9471c1e97c4a31328e17d09cdb6c7c8a4c82ea413ec7abde291aa1a3ecbe16a6ba53ccfb9558d9ff19421e1625a0196b79baa39bf9337d327c7d980442b34f5b34295a5658344965ec2018860f7841daffcf04776f9f1ca6152b4c98b2bc36ba3abc94f2bcf4ab2570d5fe012999123be1d5f90799f1ee075e4aeedfc8abccdfeefb0dd8f106ef426923143ac2fde916ac68480ef35d28ea4f293dc297e19cca4301c8b4aeeeaa8e63f9bb427e8d272ab1880e3c0d010a45b1e4fd3e081516bb13677927236d8bf1dac0fca5eff0706906802adda38ca6330c5a1c310557607d7724d2dd7e1566e56497026b4bd0cbe273824102dd4dc80749c02a495fea8007ae42703374c8d436b606af9c1acd233bdd209f39e3c36b2e57669b2405f6b683825a3b7fe54578da23db7f573c018d650ae2917d925929f2c47d1aee2731038b4fa2b31deb12fef669a232b8fdb1c595c600385c1adb2cd5b484b55267b7e53c0dc63098ed6d0dac39155bb00a2dfaddb57c3c258e05b923812b5f47d038b8909ca3ed80df7ce08ee1e812d9dcac0a4b011105f85cb037f2e556d13cb84dafdd7c0ba1134fa1f9c01686631281970c0cfc98c2f0c21b7d89ef51201c97a0fc9bc2306898bbd51168d82977d1c136370ee50d6b68bd1070218ca46c1c91fff78001e851d923fe9090c34fbdac82a7ac10188fd65311210384bac5ef0896250a4c021a1a13d7b6fab8718fa8f95e8eb2e9e5334a1b572160cda8296f304dace4cb0120ea8d56d4020e609099625c8953cab475048ef868e0a3cb13d20fafe9532f078fe5b4b613b89924f3c63f83ed3bf5cc678270b407e8307514ef3d89058da445d5ffbaaa507e1b60c4e3a69a4f5f01b12a058079e95df1b3ce7ed70389849e5beb0c5d4e56814a5607459851a57f31783c99859d986a718c7d503a103c2678e823e904f9defe09700d6ecec5835906f7c10532474e82966bac9526e4abe781398fd4b8f8e340a8c6935b24386849a164d51cd6fff7383cbd77d8991393588031d98ae211b3436e20351475ab2352ea99e107318d6467ed5a912d4788a3781824095473d905c93c9814b5c88ed23a1a3aedc6c3840521b32ae3f9372409cfd558447bcbd56c6ab912e4b62143c2d93d662d8343b1eca5e2de0ef63366a2ca5bbb9da494b15965ef34627aa35340395b931bb2130ac1905b89eb1a0a81d84e0a747b5eff9ab93838610585be7beb400c53dfbe5139971e9b4198daf5925602cc1f326cf48bd5c1684ed968d90deb892981b859c1965b3600b36468a553d6748e74814c1ece14c0190651c04093e887f299c082ef4e4fdbe56a0cd73c5258c94ee7fb264852f60bcfe1e633d1e212d438f8863181148158aa8b27d89811f4726cd3ee2565e370818a7dbd3c81084c3089dd3c3110e71e1631c55a7bf5ad40b310e3b4ece36cceddffcafe5f2997d0b9f122c9ccabc7022be455d994bcf0120d4fe8182feb8ce702ef8f4b38acd281d94e618ba1fe9f782b0e81f1677d9142c61f8ba9d153f4119b2619751b4adca0455f27dd34d62680737f5480c4c97eeef5c38f37c7b89994382c35cc23fa911ac798beccdfccfd258dc7ae1778d44003330da771b10907ed9b055191169902c43915bad2e6bac38fa78baec7b7e214f8864c883214a419aae09c6fe049eeb954ab3d4bb0e2e0475d74f37c994dc9e4ce30b9b2c55a4c0beff32698641ecb15ee4bd3d4785513f6a8242d4c69d6c56c756071bc38ca4bd5760288f42d1133866125c7e3458ca8ad036f672688741d3e1252074cbdf08c05bf14b1efff540bf549e0ccd1660c28fbb08c5d434ba0cbac0a1c17206a26a8e266f28051ce80fc7e7898972b188832a316380d236a117550db3f749700415de5058ca65bb248786c668e3e514b01cf0c3c5d14d3a238aca4fe98a84798608a836770c6183d99e8ee233f5e00edda2c294ae5ddadf1191cc1f34942e11f9fefcc31240efaa6ad7a4f61f5c3c2d5c35eac418b8c7080da1c4af0233cb10a0b851f12e44f937861914740b46d6a3a98a4b9961ba39c6709ba1fb14e34e149856f52609e07aa867f62213149ab091ca52724ba8a120eed232b8e65f8d90aa517a77a3dc72b3c0bae66d655f2c71c6cb84aeaaaf607117455b1a0bb016810f804608003626e6182015077b317ce5be114170b2909cfba44ef8c626c9c6d993dc19a8bd23b125928ac390044de9cf0eab2ca3922da2539622f3a068fe49cccf5e42fb39db64c228b1f611269ac654ea6f8272489ff53dd2711526c209d6667f7472431dd992bbb4654bf7fdf7bcd6e304b276b9b4aa20cdca0a43024959c20beceba773c9820a6121fe26b7bf8ac67e7530591051f790be5f8e8a9670d89da721973c894c16c521f03a12bafa0688e01577b228da14c7e485d748f38f7b1ffc0b463729fc1911f59666af96977613edec814ee3345fbb1c5db4a4fa7a9df2ffcea36266fa4b8274d489347c85f3ca183227816ed00e5c525e873af678138f0d000febcf10af03ea6461e9ac74bb98e5711740a30cacaee36132934eced014f3d653b1ccdc3c2f0f4c447489dc8b9186b59bd8cc56faebcd42284a125497b3d25beb1b3505d7b9c05dd860ef90a76c771db676f3ad66f81d4495a3247f2811ad47a1f8ad8b30905da319cab956b7abbec564df8746e163a39f8704a36a0786b260d0e2c15695c1ce72f17563be5b8b4c5f7a7235c444d703eb7b3de92c463859f4f1cd78ae7389882d0bbae6381c0963a2c8257bb1ddcd0136adcdf5cae38e10ba72f141cb7be7c2ea4d388048f2c639e520feb16393ecd82f5666c00eabec7303310bf01172de690c366a6e7a4ebd758b775689f86bb5485028a9941535a304ec29a0eba960768b7ab6bd04ab9023b9dc204bf4150b15308b8fb766da3990025bcf339c46dde1c8f2b9b41d61d4375a4c200ed43c94ef93c4e2a6cdd22bab164f223c029945dd3906275374fd4965937215b6e29534a3205ef042805f4042cb3b1d0130b7db29a930caab6bd7a9daafac5e89e76fd27c3c9a1445f8c5aed97d57a88c6643932590d4726abc956b0ed603f104ca8f265b67ebd2895d96ad042c681cdc5a8ac5ec2628c87bd954c8a6359266aa38cd1a34b973fec480943416d178e5825ec0435d6a44b9942f4a49d627a0e10429d356c8722a0637ffa39689af5ff16c9a55ad016f1d1110bd1928c2cf1df30615ce082ba462420017352fa7afd82b04316d89ef6f6a78323fa41232320415bc43529341f4fca28e37cf96f0ffce5169c4ceee10271739ae0dc74b911f2431819c205e2c83032cbc85db7ad24388e939c7495b0662a3b8e48654e6f6db1293d458d5162a1a5a62a4c9ef7867eb0569b62eb245642aca47af5f4143566f5c442ab2816fa56b715d42acc8a86981afaad6c2bdbcac9575253700dd6e0181e6a5b952d3568834fd17d82bcaad437b818ac754f3f075ee8f60d98e4e486d73c0aedcc928f17759b4a62a19a133865c6acb6400018d00f42d85aea25dac028659c738534060aca391413b7077e27a8bc3f7bc319aaf17fea6e366cd86c6c738682d2a57bf428a56cae045c8b6081e04b1374ecfd81ef3e707092bb37320edc1f8fe14ac7996166602dd4c1f95aa81f6881a36127c628a3d4e0e36105bcf73eec3595554c72500395dd0b7bd227085655a759cbe8552c647404dd00990102583fcf6331d10f52412ac8c6f9068f714c972a36c2424a29b986707b985bfdc51354347e7c1677ec1863437974972e635c62ce1e275c4c921a5b146b2c6001b304ac082e2c50ff7accc7fe04a0488d2d8a5e34112390f941fe46f72f1fd9efac84c1cac17ad6831f61df8f042abbb73fb00785ca20c09fb05f10a0acbac73ee063d6fd3531327b1e006aa0be038cb0ae0c21c3d6f702f46d1cf7ae50b0fa59fe0ddb059234a48ccccccccccc2ca3f31be81ce7ec6a00ac134d3c01b70b2c4d0019053f3c695458f09b33a22368b405be36463d3043d785c38504fd5a19d8159cd00d6c4c052f68104a247821a17d83151da3224b141433a8f6e089aecb6504ddb7c15a70b0d0392711745d2e1bddaffdc0892245d7e51a82ee63202a68841c36bed0fda64d9b7a41220ad6ca3ea6555e3e32628f6529a01f1bf1111b2d0c52b15715049514cb032e745d2e26ca347884065e6890ec09259a7d41bfb5cd499b591e78a1eb7275a1fb523fd26c4195041757dcd8c0be49b4b1b2ae96122a5b19581e44a1f26d30944d188db9a02884b3a9c8719ce4a4c7588ec042eea34a67752bba94fc1c47d96c89790f7e1321a1dfec687facd06f6d397c8b565667c90f5b207e8f6174c9304ad8052aa43e5c53c8312a1c9cf3b5e3d28c142b98bafbcc864c0a28e3e89b4977fb897a68b6397ac8c6f6f0efd74285947e0b889a17748b38ca183dbae4d616c5f7fd993f398f8bf9b047a58b4949a58cf2bdaea35bc44f5c5c287c0650fa7afd7b3964506071e9527292f3d85541e72afcc8d2a3092acbb81e7cafdddd63f104ce73d2a29b2f5b28fb7a7900747eb5a10c21ad376884eeeefe157a7777bf972e5dba74bb5b76ec6ee619377737f30658689eafe666ee4908b4051db3dec142d0db333eec757bcd43c70ef6ba0b8ffd915104fdbcda3a884461a13d27a5afd556d6fecee491f954d6b3964829638cd13061f5f149587d8c2dee116b17430004623debc55eb3623cecf5372b0944e8e779449146fcad2ea39865dd3ef89bdfc5ccbbdf7a166ced43f8dc1046e968f1a4ecc2d162c10a8d5e013960b82f567c7105911aaf90c28aa51a778770ba308104cd69ef7c50bdcf64a9fdaf336d6aff4ad5ee2188c4e5073335a31b7c83b983dd0c0f83a89c24dcab40535d56dd6140c3e1afa76f56dfa91baac4e0e5a8cc0e6d24911c2f553e28a145298a891c0225a40a2420843946d0a08a7e3311563254c9b10066a8a8336afc199fbaa9b10a227272a041b5ea5c8844fd8cc1715c99a9a39ca5269d32688ce07c7c5aa6aa88ab2a278621ad9dd7138e1116169bcb8622aba3560e43ba1acb0a9189e568d58484f3c1a8eba2447631127c689a4242c20aa2a6aa1842c295a729354448c8b2341503dace6b89d63d3bcd0c39c25465a6091112988cf8b586467768782dcdb8d951c62833f081d2ba5f9838c3c522135535464e7ac1e1a8c6bd559d354274a8501c263a2aa03875bf8091a203d5a9fb05cc151580e1f284baea7e01b344064cd056dd2f60c8fc97db9268040b5656ce562701bee127612bc782ded06f5f04b0f2f7c2231b51a4f550176b312fe6b1177dac60b47be27c45ff78a6898b1512e9871abf8df010676bda22788649aaabdc7a4a543bb1f5122aa2355c889048b6fd719da9727bb251517db36a9b05d97ac8b5c4e5e4c93525ca55e5c624b5e58c8dca45fb66d5360b9a512af77c726ccdb3ea03b0b2c329f44537284245b0451c0d1a757f831561020bd42fb7889ddda1430eaac1637f68a071beb68869cec44210d47f7776bc6374e65a8ce503f58fd1a1140b50cab7f4883ad43fcef6879be36251ca8f3bdf7b519d1bfd6b8399cbb833fbb3f825c738209fbbd1ef39ee1c77e72674778ffe12eed7c3658c5344aaf06d4c1ff5129e62ae6aa336c24d3c9573d8a1d5c73dd47d2fb59f9b7a68157330b687d947962871543f1e8f6f7cc6dd18c009fa4527b53f3a61a18f972a13b7455c632d0231a3d1b6ea296a0c0958a87d00995ef70c800dfd96aa851e378fd15f3c42bf1eb38f0a5b5d2505c2163190d9cd2748fe745057c60e79ddd354d46fd7d4fe4fa8ea981e17933232344a673dfe1c0f883958ab1c0fc8b1822b10b688a5229b0eeae8d46d03050152183a8c2b43c870e132e33057172ca9f2d76b04cac4fd87cb84d9b50c64236676f723a054100b357c8aba4d3d45dda0c210d56f46349bcd66b3d90a3235f483361b740a62a16e4939992fbcd577942c08b9830b34b9ae38ef3858ac2bae5bfdaa9b93151b817b7ac5838dc5f2c2cbe1d1dda3cf085c4822ab55952dc6f7a6e733e347e7b808a3374bc971124ea4b6b1522b45dda65ae9eba6aed24c68ea37abb7af6b5d536ab5adc38861a18d1e63f46efd593afbc3efc1ef563bbfce0231b350618c2d0f7e531d0f38e4a84b2334f8781ea3d39fb5d6e3a73fb39143070bcd08f3f5b8557dbaf77085976a18b38ad23400466867b10253bb71cf2385a3252bb051a10a1a19e1dcfb018525faed8d689fe091020a4423904c5483b9e56702e91c510da21a4435b626c56ae95246199db9e17250c67c1569384a19b955f7d0f31c5fd00821e4f6f6d8cc62c9282778dc9d8ac7aecabfebf1afc7421c3c4f5d16d2934ae973e4ab8810461c8f45256a7f74444b32b2e496438326fa412323205ff3f13c46ff800faed52d68499732cae8b345b570d116a60b0704d3c5d3bdfbce1582cadf10f6a8ecabeff759b59c89e81ee7b3628ccc1e29fd246d6483cff019a662a18fab180db3e1a60db0d763847e2cc5523ce56dc49061b132847cc41f0a6ae458571637dce82e6b9b1d1e73ee60ef032a4c3a2da874c778631d98d1243417023ed70863f4e89099ab4b45c668f73057d7c5c31eef53c961a505aac760b1e48747441532a96728b3c1b19dd131909225334320c8524ae66676ba72d9730daac77ac4246696c1ef497b366df5d3c89a25f8a8fdeefce6618d6f06df659c5cdcee7e81830d036bb2d0c7fdc63e66c8c19c9d9d9d5d46f7f61c58dcbbbb3d2264d93d1d638c13896500281af7e5c3e52407e4fbdf683fc7391786c1d240080a7737f62f8017d8065c719268d21554e8b29061b5785af85f4a052929a3a6b29385b6ea4205198244901fb6b9bb0cfbbcba905bf6bac7637428fccfed71b09ff399bd4c99c25f5890b7c53bcce3b32177b9b59cc2d486bc69411627cc3cb9c1452a3fdda232bf6c282e191919f130d43c3e4135eab74e61766ad12c9b75032580fac12ab57fe196a0fa41272818664c6d3885864d6de8068853fdd88898faf11227eac74ea6d4a81f5761e22db59d188ac37041ab41b57f522e9c6affab8b26b5ffbb4853fbbd599593da4f54d5a6f6db88c197da9f4387174d6a3f0f15bc58aafd1b88a1f6b740822f90187d41e6c88d0e34375a24d5c040525a0203872630d8d89c9ea2c2a8ba4925b15fde88a9fdb0aa072323314757683fda24c190446d8a520c6e969a9a6a3fdb160a8813940c4e6ead96ad2e172852f5eb6ed95a43540ef453cbe9066c39d575e160d84df97c716154a64b1b281bb40122892edcc8f795ce4205a85129070c7d99b3b13d1ba99723319bf18c6594326600069b2d4f663cd36172e3a1c80d882046d567f68ac2c25ad9a7b3348d7632640155d18f917a88f541ed67da166df7bbabf76fc1e0514323031441f93f3ee2261eb25dece31d621f43a9a1369297aef6a4abd8475f1f7cf200baf981872ca9d9e10c14a91a6e8cd4dcb43fcd46b0cab585c2ff6095ce9215ff62529551f7a3d1166d4739ff84ee67cc851e76f4cf7781bf8b27b8d03f63375cf0bac7bf7b1766dde33f1f7eb3e7f5108d1db1a5b1637eff8ccdee81557f1faffe3c56e0211e5a109b25d5dfc9fd39195fe6fc4e158486afc7684480bf633e7af0f7d1cfaf1dabe7e7980ffe558cee6b670ffdbc017d88b59302f7c4f77a8815dba9eb10ba4b97ee4b2c34b75b029368af14d88bb78fde7804a5a132e965ad87f89b047372d58475c044bf176b86d4972dc6dc966a844d3542a7c9097daf14e2abc0429e26d6f8df43fdf1a58d0323030b1832aa509ac512c59962e7294a46923543c8385ab3b446c9c682b1e76566b8ddddceb0ee6eeeeee62582993dc6e8edeece0cabdbdd8aae12be7280df67c702c891dbdd63c710ea0160b0d3396297583175bfceb950090abf5b9dbac1eca10780fd37d7e97c0fed8d7dda2869637ff8d76603c7cae4dc6547f7e8eeee4210c20819026d4308bb074777778f9ebb7b373919bf17ea42a0fd5983500a84522094e2d1a394125d4a81508afc95fe90ebc8c98e5c6c22471e3d4a29d1a5148feed16cb4e852ac40111672d7918b524a29b919a10890adbc66d0624038c434d8c3af040af57f8ce53583c6801fd3daf6b2616bd1edbacf40f1595d0745e80a93d885c3634c8b81722eff8ea5ffe45e1028ce8898c6b48e9569fbc3efd2c66bc662af014c81b00585248543afeee18fabfb7145d881406c6bd16c345a123b415a20fe2a532acfb03fbc43a3ad3a9801164474e65ece1138e8300dfe8ce88aa90c98c6ab8747472ca93913322d55b7c3533b59ed7c6a57bfa0dafd22d5ce4bedbaa7b7dafd2b4d0eb5fb4fa3a676efa9f9a1763fa31a52bb9f2e1c1dd19c5c111c27ab9cab7e3e952ba2721c1640540e0b302a876688ca6501e48a2eb8dde8015054a42affd6a0a89c994b628830dc99b441051e4550a2b93fec85aac00347c8adc6e589886633f87ac1dda53250e8d1dd3d762c7963bbb7d0f2b8bb7764d9eaeeee5e085bddddddf37ba1e244a192713ad775773bd5f081ddc1acdb7b3e146015e8cabd8ed705aa731042d83b7c822c6445d97fe7d1238f1dec31cfabe5ea1d4a77e868b1375b6e20b7c7990e0b4634e5be3e7b458d4d6a1360bdf82c69c2026af733961fd47842edd81280025c0ca24072ad6ed6d60a419f997744bbd58a1565ff1debe716753c5de4b589785e2d57ef50eac209e21e9ecd3a46570938adb945ddec98556539c9ecdc3326632e1cdcccccdeb284e5584e2073dc2e17f61f59724aaeb9c9b383dd5cf9b3d0ce9db3cbc3f9afd9eb5f0f458507a818398421d3218a0e69aaff0c287260dac085c35b709312c59418aaff6bca98eaff72e1f07777e979abbabbbb03004a69125f468c6ab4d09da2014409e2449431fe19bc7b0d5b6c41630c004a923050caa011801351103de9010a1c8454c741071cdcd49ea069a6101354ddc9069a0d553734e184290547aaff8c484a75a814b8c959410d567880c324c6938caa23511d2a0989ea3548a9fe2e170e8f31c6b864886cd174e3c2cc953137620d3562b154638c31c62825c2c7535d56dda7facfa045f533504ba06002447528266daa43351153a168e0a1e604c1c48e92284cb099a14bf55fadd1d910907e80528254fde98b4c08f5dba1814132aa9e6385c683575d05323740d5e713266114f9a2054b52d59b956f51fd766afc36112a09971ae3bf604318a5355982148962ca0da7812a890ef41bd0a19028ad0b9a60497164861f5bf57f7df9160c1c809974e6701c57436a9dc1b9c2dae9f3ca8d7bd6015728114d565a945a459e50d231d95845ac489a586aab1f50b82c525d141a240b96e60f4d225315a74412df79311d750f9923ed002c51110b24bcf3caa2d43d3ba18cc4e0880f35a823418e38c08728fafa9d2c2ff85ab3441052a0c9e00a4a4388113446dd2f64a2d4efb520c33403192f65a8a84235a8fb854cd5ceeb83ba5fa64062a4fb19a76b0ced7ebe8cd0cf8b2fdba3356a7cefdce5ac154972d9bd0d8e1911ab7b2cd41e7b3e6c226b29420d41431352bf8de2a4a0882831bb60d0a8caffaeeb3ad7b3bc05e30b600c853c5e6cfd36ab8b7d3d560fbf83c918fc0ed6170bb986a8eb7b55d7efba5cadd83759b1af475cb92000eb629ffb204016b40755e7d4d4dd833dff30dce31d05bdca1f0707fab5a89a7aa893ba878985e0df6e51514f4f365b53d30cf796aaee1d55ddbda95c89c7dfe8b97b17a91a942caa6c579ee062a4090c4abcb822c90c9124649808d105cd90b894113414a698fa8206c5140bdcc825475574fc080a8bb64112499e4c11c2840aa6663842a245d055a8dd8af871a44d112d5249182f3c49424452153b60614510242ea6e73c45e8f79aecb6b601221172c311f2551323429e9670d412c30b162243985b91a5a8224c34215626ed322d90c2889a268c9c18934488137777774e729cc338c903039d81850cc960d5a45fb84149073260806881a484a4fabffcd185bb03e068d64393bbaf9851c39921b298b2c3174ef248999d85e213cc50fd3873268b911c8c6c1341c050115c86cacc4cae5853a58d151954ac6862470d9b17c4d0924514394eaca96d704333840c28aa2c1c9c49b385979cb9bbbb5c5ca9fb414423e8d8ddf78dcd9ea2709f27e5270ed7b93f32f3ee72961947ec10216bc611d421ec8405bc79f3e6cd8df92c87edee98b11add1d3ae52186bb3b4308934120d83caea4394a01a0a83f74ee5dfe748f2fe194d1a57cc6da999a626f91a8a08a51351082b6bbfbb7ddcd42372d004cd0ee9e5272dc199dc6199595f3b17fdc814010b6fe31b6fb43bd6342f159cc321669651b0299a6a894adba8cd4b1d100080053160000200c0685c30181409aa88134b60f1400115a80427c5a3619c763418cc3300aa218800108310611638c210421a610111e75f981451cc4099e59853b1abc58b45cf63d339aa5ea852ad448e43b8d6ed186b1fcad85e08d4dc5bbce427949c70002f11a255ae68914ba7c3e195f9bf7713de11ff974e5b4aa45c4b0d742cde91c86af6e818ea30e1e0105a389695b35c2f47d4c320cbb6e11d47249fe4da06793aaa541913fe04275cf05aac4990925861d1ba5817496f05eec82382fbea1692cc07684f445f4769204b913224b130ebbe6fd88b3a36554e05abce84a227498981d6f7bce573538d3b2b4b7fc39daf2f31e36465fd36bb4b077891c9d3696f994f7feb8a30f360faeba432c88ea02d4d5acf13be068a4f5a3ffa7ea1ceddb8e8ddbdba96dd4ef38ccbd61e0db4b744e47301536eacf31809087c7c7ac3482a2864177cf5cbad1c60e5a240cd468c8eae9ea7bab3b2fe0fc582632256b77ca82640d5ad555256a263e563f429837b983818dc07625ded0d0941b05f30cec1d458387dfff9dd1706b3db848c808d5d6facbd9cbfe57c4ed0ba9e4830d9456c57006b3ba51557e21eee864424c556588aea1408f35cee3f454b64fa02933cf4ac925e659660e0d4c81075afb1649d7aa2fe1a0e1c567b1c9e59433d9712cf57180c07acd480716770579a2a98d08c8427356e833b4e13b3d0bfb9e0878d40ce9a3363cc574771c48f2b577150ea0eb163dcb7aa6f83fe9da45ae50a97344fc637bb2c64f44a88228c09f16d2e5a7cb666e5d0ee92b3972dc816a9ed0d64eb030d79e0d4c7bd7e858233240a6a8611f71b2616c019101e257ed917a9b2a90e7c519138a75dda7204026002489cd8755b41a1e7c4e8d12c32f236a8668607d15c24165bc138ab1b941768a95c278eb8fdb0d2db215060229717a70a510a8d8b0d5f4822547d6f0952755fd2c276c1b8d5e1040e0f22c0c98be1ce58a3568145d4edbe110902736584fa6c2788dc5a7c25d50caf419dcd1630f4c52e5f949006f5a024cf17e10dca9f6e6f11f46f83aa138a5316b97926e29d3c5531e18f24a47d84d80de4972a1263675da5cd44269cd7936a1acc6bf8be0d6fcd52bda276df08cec518190fd0e2f7eb50f52bd2341dd79a49745d363bb48709a2dcbdc3d9c0317dbea3b988a57805965587fa39e69a2a54638aaec8dc6dc5d81ed50506a339591a05469f6a406f11fc2e226442cf40a9a34a7d0d992f84688bc22a48c49f173729315410efdda53fc1c7fad457bb68ced758f476cf9c8f0fcf45aa19f072855185d082536569194e649a977724cdbe043914e4b8b45170737758dd37276ffd5dc8831cb335bf18adb887a9f61c22e1734849175eaeca4af8f5c8fc88af74038f33c73ac0d4042ef1086bbc71472c5c31f4476dd1b2a187529ed1d1cba7d8dcea83d3bf677bbe38cccc51c4b27ae6083e5725e9518662ef028dfbf4c37e1037c2b3ba93f64804e9168648df62eff1380b6351943cec710d0fdaab2af7bffc90acf089f35ffc8b85dd105199fc41cd858bf70d42a59ccde8f8f0b63bb8619aba054aacb9b4e4518caf7c9770b153328253a678ca865967a7c476b33a709efa1b164fa7efcf311f2db0f1f204c2c9440314b59ed90d9631aa0652b1c4ab64c39e1e0fa4e38d7f76a38276050d03a7cf4fa5826f5a923c3ab467b802958e7fc4d5e38e95b05558f69d827c39a478e6b03a077d1b2eb9979f3dd9fd30b2444530285bb4ad38ab82171d7dcc93bef6cfa8580eabd88ee415d9f58f4627b65ee0021c3b248079c03fc7286362690c06c15416a2ac09b76ee1c698640a61de511e008b507c46a6714d49d10332053bedda1bcc3fa62f164cef3bc216fdc8efac20de86015c76635e99138cfa8f589db0801c03e9c57b23fc89a285534b1a2469299c444403f91d1c5cb9579541e3efb095e17e66a60eae48234026748504c8db4aae81d67f2551eaf13fd74e4d74d0c7c3ec5dce658ad8407451a0059f6cf74ef51c14ca9d877eb2456c90e9a9759d43ac304a94e99f5efe2535d10a0fee3fc80230575834b6c0a98070198fd8bd01a05caf52d202d460c2f5e25f5dbbf81db81b41b48989761686d61630b07403e834717730ac41e172c737d89611a246cdf8dd4c33f87ce8266e2864cc10c5b4de0a2863e3b76e85ff222dfb040f3c8ba3b1986905da0c23eabf28c81ee0065f65244e49bfcd26959f1aa4e569d01b5e1ae0d9c9c2b0edffa4f495784efceb80d385e225b8f4f507a4655c7b0141445eda3c30ec6edbd1cc74532083534509899f52fe055651c730affbd0d85f9c5784ed05374bea2d282bd9ea85e1b42b76453cfb614b05ee9e86ae0b2f2b1efe15bfb4c18aa592236551199db4be3dee0af527949e2eb0131ae11bca465bcb41aa3f41f78b5aeda853599f5ab2c3291ad62fff4deaa1f3af478acb08c782d9c5770e2ea736c38094f91a2affd6936035f610afc8bfdb19b40163740ec8d5702111f5072393c1422d3f64f15e94bbc3dd0a884126e387d840848658b88d888d638bfc7442a65c97499dc7dec2dce11cce1e59be47b1f820c646d63aab4b12069d535b4da24d19eaa37722b8de0abc682798722890ebf4b8d51a9644d96bc584c5e641ef0490e1c1b84d378fdd3467356e41703a7c86c24959318906f6054f3910cb314f849c4ae883832e27886e0c3f7c137ce17c7dced7abbc4a674ce30f3d85e13bd62d8dd454cedaeb913cbe52627179f15afe721df9bad493525d534eff6f77ad0abd94a6b9742e63cdbd9ef316fcd36a9e94b74ca0f67f64467e3802f7cfdc4faf38406603b52529d4da944bb06f073ce9d3c1e9f9067836930af7aec69a266e48f3d3686d08a723efce7ddb80201a3125018fe9ceb279c1361d934a1de105e4b0742accf77725c8ef9c4b8672679e65919db6132394173a90885f875d60a8660c3d8b828569c74ba5609d13de1535b61d07d63d857dd7c2438bab92ffa50ecc7d12cddd791c9726e3b30ce2d191a7ea1c31d0648acac0d2d4d857b6a59aa6422147e35f1b2cf84bd2448bc00cb04455df09b550e25cb8475bd98cfafc854e16b148f10a4a44e1a55269995d4ffb12830be03a5536249404e3518a3542185986e03652eea3782619286e289131fd2436883251a14983c4062327e3ca09e60f27d1b3c84f11e61095081bd004a4168d841afc68b6444ba650f12ceb9d84a33af744475d6b907c5e915bce9b8f5abc0ca5c525e42155ce819363340a6accc5bee1b5d11d3a25d3362d7466b1b9944987460ebb8f0143a64feedda734da26c489ea5e7ec780af0fd58bf47bf012127482ca3b7d1c75545d6a4e9780449cb20d9e66b83e6c14b5853d013c2037c34e0e6125777e3a88a23eb26d3d3a8bb0db7d8ec59d43244af996622793c64e62ab7cd90d6ac807c88318f45de408cd494d899fecc69f660f460cdb44a62a7e44d7968e0593f9330802875c707f64d8e3497484d47ebee8e91a4cc776b186f9e27c8dcb47e33040279e5af69de092c7778f74e3ecba87efc7e3fcafe4c0c94eb844f80408e712fd674128167b0b5812586c63f9e617115eb031a3b4b6700e1bcf51324f13319a6465fb05b80c43cadd31130c5c8f3f38bc21e290bd54b3eca42ca8b6be342ca2a621a84802251682603745bb441bd04068ac426dfc2cf4ef27b2712f1fc457acaacfb9d25a9bc307e00479d6522443d9a0ccdfe77de3d592f219f5e0ab250b1f2d2e4dc25cb6b3eaa80d784666a3ffdf25839ac4037b7e85852087052deacca052d172e7cc908b63ea598ce53b59623927fa301738c00344c5091287753d2eb64ae62ff8ff550b07e4077367b38e29174c8ce525aa3af7937a01dd80dc033c932fd08cfe742ce8151598c0c1af34a73acc849bfc52fdb6b4104e025a0eb65785037134e6c51e5a51e2ce1ac1a2466cde452c265b9d1f4b0db1820dbb2a34b9d6cb2f12b6d7d7fa998fad2ef4ac68965e8c83fc360f1029061ba7b6506983721c8369cbb8035f2f1c86cda0cb14ddec29a01483753ae08b794256681feeb0a135435f863119a10397cf05834e13d18b3f1c9ce8d94db88dad8c762c9a494dcb7c9fa5874ecefe543c9202115019450c1ebc4e76db369b05b5028b7e698b693e0ef566bad97c11bb6ba14dd80f8918a9327377419c488aa19dfe2a5c89674897b8b3258e3969d14b6bad1dd7620cd42ce80a6188bb45c8dd60cac486f2e5737ce8e38af5cda6c1dbec26a71507cb771e4188334b4d68f5d5ee7b9e67e06c5febb4bb0102ca4fb3e0ded265effaa11ca98a04e1475ffac3ced828dede66d4235d87d6919ffe3b14ef843fe19f30e76d51de9e9cedd62bf0afd67e7177f4c98afac30818f1a2c774ec4a9ff53005300a349a3a91d63e42ec536526f8e05f2a97c6b1380c28facdf340c56d192c84973226df012e8606225a8bc202841ea9748403d4c3b462b1c6d97019542f56c330846f0e1e487e01c4c1f640c714701960ab9ee01330bf9a2009603b9ee00330079a201cb86dcee00330179a200cb875c1ffc4c1d494a4a01ebaaab848263b5201d4f5d047d834e516de3008c4d60d0dc47f77d10601340caa50c03c7b202ee7571d49a8a2cb8bd8c02ab44f263e6b0ea5105c22be2dd517b377b407928aac396a784a1ec1765ec3dbdd8fa29d2da63ce798ec5fd2318c72c2c75d320a21854ed9efa53b930ebf64ad43e3b8f115ed583c107ff9cbc040b4ab192011cf3c0b1dc8ea6a3c32e1480c0848dd796e03007ef87045a80cac00cf8d633de764a854c411067bab5672d20c17fbc25a696435ddd03c404ce85af4ccd671115245942e57c48c7f9f5029ce8d614e19ee30ef6c757d34134ceb2952d64e4375c85eb71ebc9931c262aed672e59256b351e9536425091a636bbeb752f628d1be5b2958bbb08e78041684094721104b65b78592a96062931be31fdedabfe07c042d13aed36769d3c1a0626eb0e573b686a8134f42e503b1d82bf53f0876218020204d070af3c0e3640b080a7382d857c0247bef6fc3801cacb8ae8caf6cd1ca071cc1b25f6c27116601ec6446a5298fe59693b2078d6087e9667a40aa3a25d83464ae43edb28a93470a220e23452e4d934cbdb7a8c6df92e8accad5608377ca67212d2d7ebdacd3a6fb1023d5064dbf1094f062aaeb75e881125a76460480f726314466f118ac45a0d6da91fa8ca971c31a6624ee5e207154cdfc590ad6ee1eaae212dd1cea777d037c3087445b1832f1a121c0d04f0c1f496aa45aafdd6770b87cab1c9086836df0ab03a16e98d7b375e21a4bf7f41d187e6aaaabb96c16d32bace6bbaca20a2d935b1fb30e78a502be0c1ce11962eaf4a25ecb4c191c1c2c7c8e7a1c2e9a5d0b533b498ccab83d8355c4cdfd5379d7159638bfd57ffea1c8e2fe900815ffc20c8a134bae1b1a3693c5bd03f65884df1d89fda934c62b054d4f68a0c145b2ea890662da436171353d8efa49d0797dd8106c354cc991537bbee0319429d95f3f134719336b4ca47e3d44d8373a50a37f300c6ddf2ef527338f014856dcbc830da4db524d05f3c0ef25ec0b7a964d204eb4f604adc8936e1b0bf8d4658c230ff9b2acd642cd0dc79b68f28045ce8fca2f36d2465d387ca34ff590bb52877fc39348c68a24327990333d00de74b3b4834a69dad1e74d7dd2024f9df8084bfb914d3808efdb4bafb2900021a24388b5c45335c459083f419b9797945430dd6129aaf6add04d5c65327dd24f995e2d22a4864178fd4ed136102aebcda6201a59140caad4812c5578555fe34ba5fdd1a59ea3605b907818bedcb94c69c3ba3bec41d54b29e58c155d5073ffccad80be7c09f2d755090c9c93ebb35cbe5017ac2944a5fd3171b62049a65c2a4dd0d91dc8e6fbefe6447b8f78642c7b9955bc2887a847ea23002e1a80767d60c8bcf45a4d004e6287b840aeb68b78cf51d605dc457891bf771f06d6d7f3d584dac98eb5ddb96c7f5f4d1256282a06d39656babe588295d8b122d578c967e0402c01efed26edc358b26944fac9590f06884ea4e9c3c3a4d770167b6999997f2fcc43d6befcc8aa291a24149dfea17bd7a3c491f68e38364d8a6c9d0e8b7f63adb628dae65198169af833885efdac645dc8ef66466e207d829811e0416ca8efa86bb28539354dfe003f3e93bee09f41c4c8976167cd4b854467daf6fad627ea7b5583121465f75ec7550100b8d8039a52802029791997da738c4b1812ae82020b39cde02b4de26dd501669b5332c80d0e9279721635eb8f50a462e5b2b9f110eb817dec4caae4f766eaac3a08ebd69d09fd46515735f7e417ddcd9ba73982da2064f415aa5071235185edeb068195153613f1cb5c4c7a7b261743de29c9c965c342d2068c637122808079a1d7c70e02f7ba49e61d12289c8d1a5b86254c4b527ff3862cdc980f5d907c520400872b1025e75a68a09ae19235037542d228b68175095932394a860cf01cb234cda77573284e3027957a7d349281601a5b5d5534997f1e074a93b6c110726c5febac236adbf276a37f63e8a52d9f4783928c3dc67f2c0ca9ce4c31c14d45052ca385d89f3fb11d004e965ea7a4ac701d93b08ed24c967a304a0be021e49aa18d905790c8dc9bcfb9556380e326bc18f4fc1982d10872f882da106c97a71d993b9f5413031fc4e9f02b78d53461591845c92ce421e44653c0b27613b236f4c0a0a8455f83ab3a75f16a04860bb2255618c91c46725d6dcd0b4b60a6599c0a6fd5382f407b77f0037bc453286451d5bf8948218886368534c79b702c1df12ed6e6862d1516c2e6f946da95377e48d8ba544859137b0ffd7065379c02d75f306054ae98ef0b92a750b69ebf93583b2338f932ac7e3cef4518d64d3509500a8c9ee28dcff756bc28a47280984548b5a1400bdd60f1a1d66b0e3572f4bacfd56fd96db9d6489361d121f1e855a757bae50ced7ae99c1257bc02c7911058d46f6ed96db717229d88e3bfef92133f9f9888cff350b75bad2ff5a4bab88e478c3bfa602122667fbd7742bf5197ff5037b5f5b1c9bd9ebd5cbbec6807c289f87c994cd82ffe4dbfadd964095eceb16bb41780f40bffb7b6dd0ccbdb6fcf6b58718b4d7f474ae47ff7085cdb4c2254abbb6364148d741dd63692e3ec146dfcde511f429ed8dbfa93a784188a1c02783dfb2484614c694ca7b0b25c1995301f150f5fb84f9c3c09e3c3a9ed63ed9a6e2769702f9d0e8da2e524dae876ee7e2cb039b3951ae09fce6875af9456ba3c8ea216fe0cd3b822d7b653548962b62e06cd1cdee2ae62531cd3b60843389eeb1589493e3c550b16bca43931d74bc824234ad2d07ade83966d1aef427f2f4a9faaff326d146e9b1fc9c7145e8bdfcc6b81e9e6b94a247ae824e3cf8bebc58039f3f1af242ef8fcccb3dbe75212028c4d30b03764da7408e2047b499de9b1f625acefc89649387210dd8aa96813e19513d43ef3f2f4ea8972d40d8139cbd34c716c607a356f08ffee08d2d907f78d26ea5916c69d04558b58cc6663464e5b4a8cbeae22b05e8c81c486d43f896eb1762e11e0eb0799bd62687f11e98fa68d616c11f0ad69f3bd8d7f10bfefbc153f64c6a6f9548e11472e650649b6aa8ca437cb9b76f27d7e0f6df561db11661899c287b6429dd35393915d92bd7059a730dcca0e16948f6b6cc06f4fcea32653e88b7f7ba14732ebde082a8e02c8b0496cc7a7c9f1d4dc12c9b0a6c793e1a124eba4990ea63f2e6c121891ada1c7c455fa1ec2f02622761b8747d412619851af567c91a43333616e01165bc9b0e9751ea4f36fe264cddf296328491f8ea382c09523402b5e318d2af8aed68206627a580dc6e5d04f54df6cf17a75223612960cb0efb39c80b72e5557014b9e6a37a02ce8c05e6d79d97faa540c83e1c7c1d52296cc263b5ee75f4a76724da7926c6560ebcde61216407e5af14787523254581d358957448f9406a555fac9d1f4b99ade2eb7931982eb0ab852a13063b7efcb76cfbdea452ded11bd3617950736fef05e684c6e7ddb0a395c0db80c5a8e2b568c49d3af847bbc88fb2a2a38996e55662b487679ef49ff54effc6a1012173f3d0444bba24317c7f7bab2e6a097c3fef689bea75bc268e62328ff2824f4ae3bbd15f5cf3ae7e34f651494dd6bbaae6b0337b93046161b7c2f4a6085793c9e7536d40575b5cdc7e1f7a65acf408c46f9e89f26f35540616d01b10f5d90427a2e8631a90f5f3a7dc7a354fdb7b0a85a74adbc820ea9e33e208de2495260b46f82cbc82aa8de9d0c600a5618e9e51cee8ada6f20f8939f00ff27627fbbb17cdf5b15f16bdf976125a64be5beafa46542a091598c9171fa9a6b0ad37839c51efb1d49c929f81498c2a9706fff988c15c134c918031d48763a399dcbdc8433e1c029ac3bc45495a6dab10211fe59bcd3411a50a46d0de5d1f435a99f2ced0b7091ba29a9fbe45a0c2c4ad6e3d70ba88fd841c6fe904eae6c81938817c139970d1a2d9c3d2e9d42eea1695e5c207b1adf58167f6aa5987d01d81c0239ad6ecfea5993ae24efd35830a598c44ee5f662e47688cc1d21ac241b75d1521786d3a1c376ebb7a04f2c5253017a93815a808cf0424bc837c4a975b30d12d51a611045e1d61d26f2afb4625f42786f5bcbfd4fc58d24db78fa869452c42c909a5ade3958b44406e057cf90ff49bde70be3808f5dcf0db25907ac7d5dbd001740736cd7eec35075cef64f439493c77141e08422df8abaa94726316f058cd4979cde96ae3d980e2e697ae2ab27ffd8bcc3850643cad181e35ee5691b1e50e751e0802fc60c122e50cb46ae1869683e1f3e60a885c4a54ececd9083e21901a938f5042692addfd3799d1834601b72a5729b7f620fcd86680c7bbad7c7115a2099f71a47a8db0f2b9482fdfdc0d3d6a44f64773e033e3f6369f8deae10b41a8715b4a6f9453a6d3db64021d1e617f9e173e1c6e95b6f5fd54bcfd1fcbcc02364e91e242f8fcd335d803d20a1d3db535133a83b2302bd8b44dfcb7ef3354204c2bc5dbccd05cba0189251ae8e84762e61e0d3956b45e51d39085ae63917f9e5fe8836f81f512741314acfa766cf21d51beba4fa1630501479c65debce709abf50c81eeee2de4e00605a071769805b8e2581be09cf840703386efe89c8cbb7bcfe3699304002395cad2e9de66df84a02b26980d82666c0dd5457a8cccc4c28ab88ff0ec9ad6b3c963ed1d6135c2774db0cd861775222c1e5b44a40c64151bcdfd971df05b7e2441555a0b9a09119cb3061bf34eb724b58b4d522ca5422ff0b55520011474362f542ba65d4e365641400b47dec7e6ac363d966c6a7de1f7204d271e0535624d3f16d9b2ba8dba218a4232d295add17b45c2723d09cef6e3225019e8bee4bbd642d04cdacc81a60ac47e0d95db8f6a66618c8e4a06ce4c84d57c137de5884fa322e0e2ab6471bf39ec2b22d8d2a81959d9000597fb16c212ae2603d7412cfd039fa7ccfc7d9207c8f5039b234842792736618e66a0b09115c0926fa09ccf063bd05e896cd55eb8f9b2ba5bd961549300c24dd6285e29aa8d2164a73bbc6c041938957bf72560dfd77150eda4934cd9130b386eb44af3b3882a8ec4a8ae013dac03e9184e3c6c851c4722d3a4833518487233c6eaa01913439f603d5f626568cb5606aa079fbfaa3f8588b7890a772ccd012382db3a221060d81850fc4bbba807a47158f4951dd4590e8b3c4a4d8fe77d84f60ed822eae635ccc83a3dc0936d23dc68ee4aa0902dbdbf80d3cb08c549b208c29f9239d4e5807b989527c1f51b135ae941a5e144d6b4392e166cbd5bac7c4227e0254a7af79afc433089d57cfc1bef8f56b403dcc28d6b0c0d56a044ee6aed730faecee0c115524e1e2cf322980b72cdf5155c56627f9dd3d1e3052125f24e6ca2238b4cb668ae82d2909c1d043c72bfbe1dc1844a7fadb9fa2fcea3aa9b47784d460e1f411ee95dfa73f617c243e45e3d8e0d1719b13220394c806aedcd384c9d3f80f1218cf1bd52435e6be0b90e3d83510b736a38edb42ef89847df99bcec8e9849fd67251b6e66680ff0b4bce3aabfa529a7055e111aad5ed7f4aa1dc1b71415e77422f4468310f2864af8bd2a8b35545165d0c44304d9ea5ed0724015df0e00525546180598e2c30020bb79199a933da29e5737303187d47528ad76e9b577ed13f0611c159e30d2bed0fd8dc9f91a0f00853856da62330ddc45c01969f6df14b1762df0ec2980a0c2ab118de0f9673f9d28031d6f4a6d2ba16344a613820e93d132c1ecaed26f3c6229f518326d34051349789ee8d028ef66806037044ecd28c2aaa0b142cc1dd48a0a2cb9ff22f881dda73b10c89e7c9a2b6fe567412549529e6af19e641b6e3556d5d964d7d6aaf6bc843a179b34b2424619e16372d31a0b2e0f486c1a23431397ac20c02f11ef5ed468ef12f16e4ba92b99bf28e029953e19c7864cc90835d0b802a5e1261d0fe15c7a3a683f50bec72cd9ee4c3a5970acdb09194f4e3a912116167f1404d833944247b44ed863d3d6c7988b4158c68fb68db87774de1bf6d4cd0335ed5a64d005ea40c8c829afa8214251806b023b4863466c980c7d4e664f802c0a53badeea3777278054933b51a2d070bdd93f4d1ca9801cfddbf402efde3de6de175c6f06fe41d28659588a3c4ced994dde075f0d36bbe8e9f681a1a8c8c21240c10201ce052a87c488c0ba552015d5de33f4dca4db4a7a636a5a667a380afab98b0092e4dee52f8e9148a9bef873b704b98ee09463ed5ea52e46f40ca9a7e475a974a7b690cc08d081bed99d0a715f1f5d7b50604d1d420e2433e7ea15647d5d623d6eb194094517a0d056f9c9067f591c5eee5fa0295ef8b8680a6d77fb5cb3284d2e669eedb3aa105ffad86b10c3c0bc8c6e6e06d11f6ecd1752468d6fa16214c8480e746183413e62feff402bf6e443f6ff3825294a9b35acef54e7fee1c7bc07d9c881a585dcc4da53f19805d260f4160f99665645d7c892944ad25039f53c90e4b982308099cfe4831ce4e0e8468c1406c3dacecef79cf994d712846162eb75d1b548907c9ee5f57be83ab39dd022a9904da689e75be7fb080a50ff750c0d5771d9a5a8a993fcd60102c49edb87c3576fc89310213de499606a181e1e9b4da6d923768713e93a0d6efdab3fee982240b710a8e3c89c944d0ee1b06e6f332947d9f442a2571d5a743d9e336b21ebbec5b40619adc2be20cca99aeb468cd31033334c1a47f70e54c54d0ca507300b6fa4ea5db36048d6934325da8947bb6563874e2fe2948cde05c3f9281bb006df167caf6a3a4f576f336528639c35f40d3e5be9d869689723e740095ab0effe6a1799e958a523daf33aeaed05a22acc73629986e5d13546675d93477fa33e14afb5c2599b54c4550e1d57fdd4792a8f5a1da5a249ea8a501b02413d346c17a44827bfbbace15b1d3707152fcbb606abc597b58da4ba948b088ca6dee3c1114cf151ba8582b50483f712dd1ac0de2fce2262f9d984a212e85ecd47d5d5f6012eddccd304ad77315d1b3d588e81a453ae7b9e1056955f78174d7777a3cd366b68533dffc82b29faa47b11e25c787799c46c666b7c0135c80793582e48bce66cdbd8a53a349bf0ae21c789c74066838d3b1267fb6fcf3c0b14bfbbc5220f3cc078779f7140e6729a6662ceed2edba89b24d4fd944e8a47115ccdf54e593b12e5bb4114cbf1df2e4ca41b5d795f9c8aaa408975f60ad4a91b0313bc41e5cabb2d4d46f3be5c6b3e8faeba6b648cd2dd47a8dba1d7b0e61c08c7705fc8d9a6bf4a58de53294fa90276c2e43808321badbaa0b74970e9231cfc1f359c165dfa37684abcbc21c2c2488d14403c54b93b0d35b5eb7596db900e4f3f74b4e9135980ef0b4454edd9ae689c9580afa39861a2fd24870c6c78da2fbee4e7af6d19f35b0d78e0ebe8b3343c0d5f935a36a227a9fe4589891af39646bd1ac3a85c84baf8515fb8b54ae2e2c4dbfa136cb9e55f6075d4fa5e3203f59d0b66eeb5530425249a8e8dd288dcf3c60020bbb4b11629e441b8c458ad606e207df425a0fc312716b11b67def82ba818d37dbf4b874a07aa6c39c483ed8b29cc80487032b9214cbb9892479f7936e6d67ce79816487cd1ee3c8d2d542072ce03711fce740454cd01b2fa3a302a62bc2898b455e0796aa88ab271e8ebea74fc84f2710518e0391a922917bee05aa89649d7f30bb1a470a5a6290ad0810428d90fc2bd647e65e6004b6a1ec009b895bb1a418814ee3ea1db5b2bc36d9ad281320252a2388bb2724a9afc22867c7d8cacddeb200511bcd8e997b4a9600c4706737763b858813b5cfa885b3ca891fb4123c46abaae3eb59d65e58b607f60b785a351b8854b0fdb945cf627f9be28a897b136428c5dc7740778c7d3a570f64ec8c360d13ad42e489df6184e4c24c1fd68584d92ee6b04e789678b185ee9c2aacd5edf2a0fe68946185ea218d05030167520a2e0280c92f38c07cc3b964601070925c9d31ca76e82a2163bb799be7e2d4143a4d6e3f50a3a2245a5842a326277d6c70d90d22460ad2b48e4f4f6a309a1594ec6eb087c8980225d6ce8c1dc07679aa420dfaf072ebf5077ae3811957f740baf1a9ef8bb2384de5dc09df702128e3e9687427f6e3fa136b4a4401adb010c49dae8089384226e53f42bc05a23cc0f0646758aae5dd044909a7c12f8e54dae8013212609906df64117cbccf6340679a237691116d430358b596701f28afa0b2d6dc9584e6e241ff54c8493a7bbfd621dad50ee1f8ed4ae6379841738cd4861778384b86a1d2edd3cef535b3939395ea7bee05c345d351aec2c36e4d7b4b1749c45e4b0991e89707f18b1c9c2787b9c96ea60c7072c87f85f1c51eca72ee6d0d16baa8074e8431edbe829c802541041ad439ed2003b091d21b26c46fea000bbe0711a3ee29d3554b5d5e8f80b62f2522ff6ce0cbe46597fed9f662a3c4265c4ba848d64563e39df79916a6d0739d9b26cb16de58b12658f19faf02f73f9f851d36c60fcedfe99c58e9a9eb50f6efcfd605c77c41e2e84bfdc8a3cba5901cc09dc6c49eaa49c6925c45cdad57cd82295566aa39783365025c27766e9b1359de7e7c0e0610a56a905c68c5c8fcc095058228f39bd508d5c4642a67333a9736d65e7aa781440d2b5e2e25ff018a9dc8f68063a24b143533d80f9b80efd1b41902fbc4d61b1726325f58dd5515a98db07990dd765b19aee7198fc1bea2f80fc15e62898169d9df130cd5bc53b76369acfc6ea40ba3085648234dbec48371653c1dc3a8ea2f62a2ad8609edc373d813ba91178f2f38301185c40aa3728ed82496d01179062fc657ec241dca102619c63b2675aee076fa30a05269a0512402ec9c319fead6b80d8d14dc66f69fa157d064afa9224d111a44193852215eb803db33eeea1810c532d749e81238f094f09dd1fc6ff18b2b2eac72d550c834d38d5ae98845fb7288b8b24561cc7bfe8d9fc40f4e5210a5cd326c81a9ade7e9e2d99192f2b00d5a603013c7472d1c844760731b852118d9a041d1d41f88ab73479b99fc040c2b0bb5c301aa47bea6297e67e0774e72113edbb2a4cbf9f78721982cd7edfd2b010f3b8cda691e939d59ebcb03c2035d11f41e48a66ae3c486110846c864f3d572a48fd687242060322965e0759619b10a4f7c64c13eeec8bacc08c4a0787799b6c5adccac59bfc005cc6084e95816af7e50d1edb74ae53049393a548636c81e045cf2e8d5e7eb363f364edaacbc0ce6a50950b8d2382a9c0d69c11b440a28ac519c3c521fa22c78338ae77427026c71ef40b7f47530c1f5df980333f3b661dadfb9e97f36752db2e2b92896c091d124886b3e8337840397d9a701f387235f3357389422d9395695e2b6330e51d812750b9966b8becc9491fc8e6a73ca01d8ba1946ee40b6cb5f71014889c43813e974f2e353afd9fa8ab11b77cf75ebd995d77b2c5191210021346e7943098e31cdd7dff5becae46366763d5a7cc4350d206261c36c0b8011a193a49da841381a11ba76cd0a914d3336f3d788cbbaa2459a5127e3f9502c4803c0bf1b5741bd3c14088d4c63e32d51d0f5a8b58861156114c81dad8e5adf274c46d6ca16ced8552e4cff3a4f5ceba9006d1a02cae6c0304ef743bd68bb1ed1319284fe8158d4fa66144464eab60aa3886c99c155e6e8ad148abeb315c3ad28af938097083f1bd635fb53041f57e31b25c1189f8386a8d0f3fb6325c5d48483706978cf09fea40d083f88ec0fd71f10a350f0cdd70833a2ed71fc0d686e98a3a8b097459c65b4b20e0c54a82a45f2be850f288bb3a8158d909e98c44eec8ab73fb444512b32804b594fe92114055389e06a91f604e88a2d0e708e630ce4dc8e1b4c78eb40f1bb7b52019c0672ce4b31d14b01e1649839b14fec9ba483dd421beb27f0f9898314d0bf5691a4dd73a605b5ab8fedea9e991a780525eabae1cbbbf182a877a32185af1e864c7e3f5101f1396fa3020015d0f07ca4f30f17ad39469cf4c01b10aecdbd8168b0f6293e0ac66cb01d0082dfe77215f9c5bf5db63cf3907c1ce76154f13ec8d06adff687c19ebf5a1e885c24f3e1baff2cd25569c7d5a1f01704bd2a2dcd32953bc7b3703b2c26b9cf0c8400a2e8f5683d3ab933c4d5126611c72982d81ee1ad474a59cdedc07d42597e05db887f2759f8411fc73bae83c865a55d75c34002e53a20ac8d6ca98e3c064678e5036db699e760024c0ae87f709c2aee93d947019260f3c0a7749e896154d28f017c0a5c6c0927460a014afce47a8b338288c295bf94d4aa9eec3d349b8c5e21780db499d5644b5581f5105ab9fc05f114e3b0c9bcfc99a136b84d8fa08cef2cb223ad8d6af8883fd2d1b293c6fb8c711078695af200830e21d1db8e253ae8665c16fbf7482b58dc9d33a5931eb2fcb001e2d48e2baca865c9518a4cae65c792c6207895648015d0fb1dc4bba326dc608350209a69eb648ce30f89ef32986736b3e4b74bd43db53fb90412a7e053c2ff1715b82d452fa36a2609db78981a552ecdb69bca17a6f4615543dd946af4490068085b0c88f7a54c71fb99369f18c3b77c6099b5782c1a0a99aa7b6bbdadbd8d522115fce6de07531e3f77d3d0f8a780c6c4362aaabe56dabc0ac48108dfdd3061be88b7ab6f89e1f91ab0287a5cb86b59e67025c1334902d9bdd4f785a28bee4b351011038f8810d1b611dd3033f2ec648d86889b10e3c433e66235fe90a69927bac63a2224712fbd50968ed0ca9a45ca2e3f6a2d02aebf8c1a4772f7818ca79e52ef1ec487bc7973e1bbc3789a492f8ff665b378c94cff4a973a191ec11d4819d553c1a2686bd52d678668995a7492e887fbbc12c5e10337d603c1c90878185dc3884c51fbd8e4c09583b8cbeac68c42f74be2b7dfffa2b217d13ce20d2229ae0b452f3e1a45967fe384f794b0912d46829a8d42813274c842e6f8e68b164d40116e774fe36e46779a7d8da1a52d9c3a8d7ca459bfbb2a05b0ecaeaf1fb636cabdf23a40769154d06e6198a3c637d07f9d149067298fcbeac9ce88d8c3639ccee81b2f6499f347d22c36602372c1ed36256a6356cc0391ac9ed7e2bfcb738322c07818315db9f61a3db340f6989a738e7b6390c0347233431de2964e3bc944e3621b08a8b6bc44b9379c1b994334c8131dc7ed096671e950ff90c435e2a962f04b44eac54e8b1ccfdb02d87c5a08cf4688f7217639e772975a3ce9db169d221cf89b3650391dc11857450276ccaf9cf54cb84ac4895fe68e4812141897f2ea075d14ad404e516a7e5371074ee75dcfd4f21d1a65e8724153206e0550c3137f8d6c127cb8920f323e23a5310828dd5eb89c434e50c0f46918ef4b8b0488009f53e713b8bf9b6b3babe20064e26b18a0a9594bf87301721cfa051d30e8e3004fa40a7cb359388f20f6c74309666d5f5abec838d193c98a92db36cb02b31fa39e89c8aec64f67fab1052a9be667f64e9d87862ab3437d2b3c1f31d2d2974a9bc3c71c0938db3e150a6f7cfa0164ce597a27e3e9f48ec9fd826ececf026dce8913af69127705f8d7293b3828001535a24837c38e51c39ace2885678139d35a2470df16e6cc931cd1f1d31e7b889624c6690a56208da5afe799409b54473ce5d9aa5568eb5953ac305592b757e80504d9f24b81b2f8a9696b6e812faf1eefcca5975181bb77bddae5be9755b3f2ecde750750fa33a25d697be72e96b55f0674f4fbd6711ba34f8359f45b860056727923777e54208eb71bd6195802c257a45fb973f3a775c817424ca7c64b23514f3c365ac8e45948f594dfbb37f4355230fd7a3d1b6301a51d5961452c895e2a3ceeaf2914c230f61ccb8f5f4443be32d8f1428ed928c3330e9852867f94116e1920b8f985d0b409ed447d40542c0da572a34763ff6f9daf00fff35338a64e51f929d8248ccd23bf17ca8f7fedbcae9ec452c9f447cd1a6971f35b04fcf7f8a54743596da1411f660c5074812bc1faccab89e7a066cd6fa08374e87f96cf1eb64a56f87495c24bb5d03c4e87f69fab9a50d56110199df5daf47ea2cf3c884da8f6796eb82e82e405e41b32bcfec5c93fc535aa3ca8cc43320bb37a960323539acf418831bf7aec91887a20a5aa7e4fb83f75196fbd98e0d3f94ccd9ee6eaddcd1263addafcb81a5e8c9f4abba44f8f281c77e7a7db35fb1caa55bc604e1751c050a1198232ef98c3d260610e31a95de5a4e0d0803aa141e3b8f607410ccd920886824d10a63b94453bb1033233a5f36981e744c18f7dfeef266b31175aa028499d38eb7391b2a029592ff4dc116832ed78d4afdc63ec302beb9431d534887af1b931bd5e7b6afcdc337e0c1fba377adcda5b0e0c8c1909711836ea6ebca97884d019cfa06a99e18707cc85fa96050107239bea442a248fa45954f305a688b800355910a814aa7e03208f05c4d5f2d9879bf9e3ea01de08d14b56e3ddad4ba3d1d974968de4c0eea7411840c760bec3ea75b526bfae5c8838486027f2fa1d05d2ba302ad75713d298cdb6371ab902d108e164e4c46f86bc6a465b09731f5d174e7ecc4df3a1d936c6c242ac07bb784acc4fe3d5aa6f9a2d741c158c4d063f7465ded55fbb6e7155032bdae673f6bc56686229be1bb26d86cf98f49c845db3fab18203a2493f1fb9a150d6278e025379dad240f4f4e4d3acfc4aec029649223a8348068ee40cf809a316976440f7a607a77ef4d0ff52d7005e888f8c489d487fd129a308e047b133a46da98e0d0c4c2824783fc4831a705a28d07592b228a6f71a51599b350fac68d913519f6d9d6ce44b8e03d030587a6e2d710524ba76a0c9a82294e49d252742b23666ae5f199a034f983494230e7be7502ed50f42bbab28a1e87fb2432b781ac3c25395a6578c0d46a7b0558736d2a81d93cef43c448c51983505e3f1d54c2fe905c1220034d35343a285c856a32c7675be6e409de78b0a783db881c7913077546c8d8624ceb031f9935976581df1a42452ad1bf72ae10dd0407036416689b04f01318f821f57b0ba498eff089bad77d82068b409efd4c8009eecc4b0e1d04861db4f2f0d0df81c43a5861def3c727773fa2fab4dd53720bb3b319c4bc6246a38926f5d0713c3311fd12134a96c8d9c22441f8688a71b436644e4578dca0901ac586f4f41e3f64f72885017a6d19aec2a0013ff77b0ebd6a63bb50145b06b727afce48f83a2df9843acffcbd1a498414a247f3e544e530f453c9ac2e61f06d4fc900c7ade859868875c209c1e2d1994c40f6aabdeced7430c766c90f1ca9c5b1be7a3c0e7a95c19128cd1c404606326ba742d1bb03fbd1fb2a149096fd90fe67b3c55aa73b02a9f972c5a8be90a7a0c68eb89c33a999160e498aa5788aa54d991869c7367ba3f29a1f3d7616bb36cfefc5ea6ae03ff85d758bf9a3da9ef21a7bf813673a99ec72af0c84b60690e9c63299770514db7d9403fb351fb127aee082f4b75ab9656592a63cf110985a39bf07f448c6f5f81a5a8f98c7907687718bc47a8f35c67a914157a84bdcf52e3c48661e27c0c9d4ae85d88ef297410b0f04f6b08115a068a0421e79e3047de12b3296e186776ceb7514c4564001dbda03aa5bc3bd12d84564bd77c7546e88bf7519f444fbfa5002894106cad10a413c66ac43feee7885e8e10fe1b37717237a403fdce3d16bd7677be3c96d073462a10835016fb0a6f189f66b727b9c53475e402f2491ea54a9fee32a9b59fdaef50192e8794c01fc4650fb57a4d0b46dfcf4814293c59fff99ee64ca86dbaccfaa661083dc78c2d246613f13b74d1a154ecc17ffae8ef4b4ca67bd14fb5af73be1e0fd46e48731da8c62ccf9d3b28611adc5bb2e31721f20e7019ebfcb299cfe20f0283a8b384a7736d07f38fac3843ba1db06f93635fa4110992eb445b8a30f6137bbaf2e93b876fc70a5b3005d79e963ce1ac0349c735e180accc9623911fcea422c42e47c540f58abc76a1af5818a2a1c26f02c73e352a773ff032946caaa31766e5f7d5dea909d36992c0a9772b8807cdf519b73125ad4288bf8c233174c6a85e06f433cf7a603ac1ae70cba78ab57fe6be6446b18164ed7ae4b3c29929c9b3822c0890abd4cb2a9870cc7fa173b4b8a119d5fa4595543af65fa896307902ba501a923c674cc2cd13d9067cc9a80d40e9e158e4fad882ff67a7858508a2020dc2677b87e4cbb625ba028c474e71acad69d2295d04f339da3cafd1edfa64121b2b9a5fd509081d18eeec8d4738971ad32dc96a8707f594b0b1e7e08f1e99192b594ce9593091c28b71b946b7c1ca0e6bc8a83d8c63ae5c989a096e02208ffb15d896b091cfd3b4b892740e9da7d3c30d00eb86dd808fcd0d3e7b389e8f799b14493364fffa24f86cba161c7ad8a2666289d8850516224090e5df6cadb0e5c58b9ed0273fdfd1378c0f82ceb26202701b20d2d97f01d24017a7d808f261b04c855f2131025bda47b0256c8f25061b37505c7bda4bef955c5f3b391a2c89e7f2125dc51f7a4b5505729ba6babc64c9087905315f9a0f5ff6a15a1eb16be08db1d5d68e534e02136b96a08b03eaacc48a1a344c838f9ec6155b9e0350a7cc5360b9b77153ab1ab48118e4f6527ffa711d26b104700836a0f4ab69ffb298fdc250657825e4727c86d49eef827aa1cef8483126c743730ccb6ca1717d7c82a0ddf544d9eb6f2a1a981f7a95b0e5a7de3f3b5837d05fad753eb0f6ae4f88c0234228c74b57f28b73f22bcc049ff4e7fed71f319cf34316a18e59a9caa298e10d0df1fe387f07b71703da7a46df5a8d538ce4798ed353a05db3bd81e435c56f0096432cad1da052d16c2e0b9fbf88209cc4a819fbd3e5d2e0c08238d42aafb4f414764c01be329711118632e8c23bc85f3e56ce299d4dba0d95eb07da5e54ad6f2c332c4032776fbeee9ea215b35b45443b24a67fb31ba6414fe085082872ac9ef41bb11e802718cbdb02e8995d4e386e538481822f2eff1295f8c8904f4d2529ff55fd7e39ae9eaaa17794f5474c00b614edec32c65027fcd7e6e8ace322c4f09484373c39ed46a6e5ba631d6d42315041a001c919ca0c9460158a005151fefacb7a2639d60c815de8239f76285d4be00c8e29c3ab8c1c547344ada81b60ab282212ae83966e0a4e14eedfd2542020e0ecac783728d072a95eec10c5d06052d6b24657a6fac4599ae961a0f30284f9c2620d274db3b3397e53957c5237bcf3f901d7557791dcc06b132b9f7943500b332b0bdf5eba35f0dea7bcd5fbba84d1fea3e964d7008f8116a131c680a63bcfb5465d8f8a222acd950ea3d6efec76f57535857fc6415fc7817fdf7cd35e37ceb3eaf735c6df80df2876bf20f8eb6048a9027351548b248c1774030d95323dd740f67b5b78795c10d1dd335f10114203eb0ba4ca960e6ef93f56609b583d72c253f4c3ad59c4f42871fbe1475a238e3673916b9ef9aecb5e8a445f2cc7c389fcb08f08813fa8829702bcfe47ba5dc4a03d6424ec52494e37b9a53ac8f3480cf520899b90d3b3b4c00633eba6b4a851b0640fc80454d9ffc55d306935e6b10382f0e76784813153c86e005896378e03c55103169fb94a93c37f7ebec3944cca7fcd41abe28127eef1bb0ff1459deb8998702b690731d6816c1ac4d0ee8a22a5861f047cee9c7c91b52e3875db85cf670346b6dfea9537fe6bbb2edc09c2bad35a06f4a90797b92bb614490b7c278aff1e08294bba62e9596f079036299f9c6124c4c7359fd8d6f53ba49a7128991417ac3fa36d94845fcb2effa3a255b306447ede4d90a33b1c183a8ba44d8607a7c35142fe39ca90e585f8bf5242acc14036b2e7eb314af0207bf6049c9fca22741b776bb570d8475c90d28579c87325f93d47e8e9e0bed15675e7558498f3d53ec74d6f20a6bf8949506ecaec7b108a540905b50c1a4d4b0a9f744d8aaea39ed44055f6ada1fbff0047db67e980694bc05bf375640a891cf2d8691049610d137ccdd7bd2dbd0468518b0d219bd1430225298f5a12e520d8da1220d9a56056230cd5d87d88cc7f4378c519308dc32459c87cf10988b7f0d173549045afbe7316f991e19c40bcb63118967db4ace0d2713839e299e155fe29645a4036291a448e2a296428651e76b8917aa80f0035bfe4cde994c2531e43b8f95ff7c659991e942a0e84cb9953ba5c692fdc204fbb071746399297388070aa9a38029d770791eb2ea7247bc50c7edaf747a6fcdbaf99be2e124c1c7ff588763c4b9ddb4a823231aff1ae1a1701c0ffd5431f9b197bdcbd6110bc584e995dbf2a3de6485d57c93e5783d13f0eb4a170f77bcb727bb6c24788e78f434a953c6ee1c66048851a4e23351d544eea8509850a58e1268474ad8e4a9ad7a76f3b580b4e2e9621a2eb027a52050a432ebb2c7e146d974277c97755b873bde786b28f82cae3b4315d62162a6dd0853a844c55d7bb556e31b5d8dc3af7583448542c95823f2420b73da7cdf1315a29d08669eb55ab60bd3debf545a76a9af21e60e71ccd2909cb5636ac9e183e44ea9cb285aa36791eeac5cc2715febb458e6d4760a73c89c64b6c72dde937577e8db1c1bd7b33fa65652267507f21cec45580789d1e844aafb9769a47c9096a573a586a77a93f291351ecb1290024aa1b8707bf8ce3967e6d88aa28b1a59f7a50a595cc4878c14fdfe6215276313fc0b39bd9c353a8e78edb2eb38d9dc999bf00b0f3107003df723c27656f850b441ea195c1b86b1bfd5cae5271d2c52016e2e48b919ddacf099b3ac8a04a50a96bf52878d9b62d770c61cfe0d9924481cf50a5dc9c141d7ac4c06c2b30830c331af236eac8f8694228be79e16ba1d848a89cc268e824264c54c4c224af51d94bf22bd99d3c9a2f493551457948298a528758a2c4a5f5452eade6bdc865874934797b0cba892927125e9a22a064dff6f60fcc8572fb8bb28ddb708bf86a2941b5e684949317ef57b8c39fa9d2fcf4b7e55166e23924c0069983fe045a9668a922f14ed9ee2a8fc6c1ef127834a9ccc6395a36e109dcd0ea45c037a60be7c5e40103a52074533bf136b7f8978c5634883d0aa92f485d8c58844c095b2a72bfbda1ece0e29bfa2855fa60fbea6bf6c241357cb5dcb03f4c81439d3129f1d22ea8085405f30337a666761d5d1ece52fd232f07154f964ef12eeb6a54c49ca93079f079d07e1890aa6f99ef8722e99fe4785ba1af649913b84352c46c37e9c61249b77466a584d16c6be862199917233ac8c7a3522135265b2930c1d40084f5566843efbd123168a3d1536a3940184ed00a333457046a206f0d03d60859f1640105900024008a155a1cb1df5c6cd4dc691c24ad44123fc59e53b5bcd58a5affa41d8398927d8ec2921b83e48233c55c135e14e36efac6568186d18a8ebc6623aa7ee540ca4dc8d794c0431c757a5cbfe7223b0421942180cd4f5e5429d1ccb3344bedcd733193a004708533998157e08f3764298bbee9ddd19ce28f18418f9d260c0f585d8733ab16a748d00d4009010eef0cd680ed415c2603de486c9d001a61447054365233b5033058b75d648284ea372805328f45b3275ff859e9d665da3f1cf3a77f959a55bb667ac9eb17ac6ea196bc69ab943e9d2fd5a969619ad71ebac59f636f2a70caeed9a29b83e6debd7ff85108943070ad59fd64b9e10caf5ebd77cbbc367a91c0890fe056aa66fe3c50df00d4b4bae37d6f2e0330223404a635adfb9f7f04162d2ae1a6403b2a5eed0ea4148727400531f5df6a83a5f75c76aa9bee940ee729d03e5fe6ad25dd5b9ab5fa82c3bb9b3e41e7e5385e5534ae9ca84d26fba3329ddc12e937e1c2dc20ffe77f0cd8e813eeee881791c0d9e0092e667f03d514a24a2df3ba4b6dda0f4b79476d520646dc467a64444ed016aae362635259576037cbf2f8f6eede83246136c55e0d1b1f07bd1939e60b363b0f01355da8b8c0ed8d665adb76ed9db47b774f8b94844368c923528861ae42e99982791255286347d5f0c093eee3bd23a36aab0c3866c08f490935549af9abe30265ff8e0ef99eae4b85c1f3cede0991d5b9d73efb4cc92554923a1c51481ddb5c20c2130fd195260fa95ba781c0d7668a5cb5a77aa2eb703d8ef851deb1a04eb1227921ead06d5a02d3467565aacbf6556baa43810b424c0d682a3093386c0167c4a71267db5e0581a2b2c859ca0fe1ad4adcf842e73682fd23ea4d14824b2c660540a2a0b6c760cc73c5b73df279226cea290344f60f820d9315a135a2d442da425e214a41deead39785e4b91c1019bf063c5073f14493056695d6b389a8e6e81a40935d7ff7db8c272b93f249e60513b585211effa7d3444b21fda9181645329d9846e5d2b3eda1f47d38fd565ac63373a76d365531f7ba757fdf4752fd9b556695d928860d7755969a0b0b4ec8fc2013669d77fc7b8694a80dce540bef2f77e9d2e97c3e1cef3a467937e85e63880edf7b97b9fb8ead3e5d9a5b7290aac0307a9083e75c86b5c9ff59571e8a89982bbad37ee5465774b132d61832d1506d90ace17100d34f107a28126f6473e3ef55d0e8d3e6cf4aa5f1c819f892cb2c82db3e30c1eb0efba551fbcc1ee4c2580be3e5f752a01f4f5beea7fe1f5c24b487d8de80b487d8dc81951e096b593f506b2dc4997bdb3347c57965666b1a861cd14ec24265228bc903b0753ef5ab26bcc7c21c42ab5c862a7c7f3be660a76d1c616f8849e94933d7cf4aaff850f7e13ef70d78aaefa6b44c9fd3434a1dafd7e72218879381ab16804528100bae17c7d38c040386e7f1f0b2d744bd5e507fc1be0853ef287bb60230e1d5a4e0f48f5be0fdf5aeb8f2e6134c8fe0072bfc2bed65c758a5dcd9192ef8dddb75e6d1185ce22991954017bb568809a451b5be0cf2ed4ec42cd3868cdfd0c501a843543be4fe9bd7f2b49860aa4cb1f4cb0a95275ab8e8038f3055f0c9ff4d4bdd2c63af3d57d5215b81ae971d3f72bed15e66f0482d282edd35cb3bb30cca9f2d3091c3b09cf0eec35a307dc9f0a3dcddf0d8a4c126c3ff7806f47b2855edd0743213856134c1183bd484f70e35359dff348b385308610d8fb9d7c59f8f0cd29743d928528d7bbf7698466b7d66cb0babc2c1772541dadb1ef7dc80b897576d3e57def7a649d75498a01f78f1074bfea40ebd92d8fe28890ef57247dfbf67d0f9fa31c48694c97cf36698640c277795064e222e503c93f9d5c4e17dc126cba9cd95f1c8134911b72385fa2a8850c22274f45b0e972ba9c80884f7a1cdd12bd08d9df3f140abdcbf8a1b2108d96ba70b3d76f9bfc5ad45709979ffcf23ef2fd9711888b7f791f4117632f718509d86b060ef8c67f0cfd539c2338fcea792f8eaad30c29a1afe34d97a12f8d38ba0cbd3805364f39f4aa6e89f5455f4953fcd187fee462ac29a01274be484c60f33f150a816f431fe660938e108457b8a13822e4d0dfc0a4793ab99026c624f2c51187bfb8703d700c8946b37e95ecd9ffc694016abedff9fe252570e9cbe507eceb02f0de6b2ba983f82c895dab03b0865e3dd93f21816bc52fe4f453670c816f37a55f76794f395491ae6013e31e92cbb686589cb51bdc703402a92e30028af33502810d7a603386c026dd926d55e29b92e9586355f603362910902e4957b05973b2b50fa45b395565ada8002c5abe7034d21928d172deba6323652ba55d350b5b35d22dea53fbc91648492d5b28b7764eb76c4e0bddf272c8220e5a63adbd48f00c1c708db98b852f02b4e534fc902497edb3608fe0b63350d7c72a0f47b2ad43b2fdfe3edcd657f68e355665329064c0a62afff7dd6fa14b5b67a02bfb4370476cd718adb14ff660a14b91096949b7388501bc842580385ff7745e710a598d7569493b60b3c6beaaaa39971d80fd16a08f82f7236a81cd9002200f32775723b007748b06359a67bd9c873b6fb69aa7a3d56cb7d3c379394fe7b9bd7ebd6bad5f1228578f8d4a6068f1cc7a568667b29faf5d5fcd9ebc6f7dfb859c66439ef5665ec8561c51549385358a53fdeb9576793a5fcd284e2814331282ed047939bb2d40edfddc2c9ece5de0965ef5e7fcec0019d92109caad24046312cb7d2e1ba3e00b0675d9f6fb5341c4642a9e60cad6f4d32d99dc3f52c1f74d97c9644beadc25f4d968f499afea0887df4bd8a58971ee9fc41c56464ce18511fe1105d8c44f93095d26b3a920b089ad0d8f9e7e59bfa6128d4618443577b9cc572daa8d58d63433d44cbe230e472ad814d532e8b5a95b4b8ec62c1abf178daab65d2e1ba960d3d60f6d00b0024cd9ea9b9d8371f152c2f4e7c7c5649132fd94ad73dd1233fdf002b64a6debd7696db75eedd7cf4d9a36df07ed583e8d254b776b9d88776db56d59ee7d7509dc4ba63e7ba469bfcb9697345fc94b96ff659b69ecffbd392bd5ad67fa30d5ed25dd90d7d6ae7cc0e6d7e077eb1373b8ad559429c5b16428d3b71ffeecd7f2b2bbc85a15cc25b19326bea098e3da2fb4ddb25dd66c0588bdeeeeeeeaef757fb506207069af64cbfd868ffb5e1cf611a0b5e3e8c4e30dd83cb9d98267cb354471680d40efedbdd7f3bcdac701dc7549eb8e0020cdd4fa90e33911152a4958c9272f532a4964a135069260432dd19ade22c4b4464b3655a80960ea33822bd93ca94e14a7e6fce12a485eb4cb4efafa3199529c7641715ebe7edd519c92f8f55d46717e288ecbd74b9bc8f6bd24d97ea85e91ed7f0d25db17e964fb238f22db071fb5245b32bc49d0266c48e261a957849c341418744c110f4b60fe6574f120581a5f1e043d2c8d2f2f8ea5076f4882791771c47fbdd0271a816128248df66913d9dfbf5e91fd1b4af6779decef51647fbb24bbf7a60dcafeaad09b3789ec9ffadef46cc8fe3714c7d2934694e84d0f4bf6f742d2f862f466c849f61785a4f104bef9c190fd5dc8846f7e4e647fd268cae29b221eb27fca0030d9fe97614617e30e5a53ff65ac345a53bf34d61dada98fc73e694d7d97d165b4a63e69f41fb39ad5a453b0303b4af621b98a6c7fc831a21dd80478bdd027fa767dbaecc7daecf5425fe80b79a7cbfcc75e2fe4edfa74d9f5eeaecf6b693bef5c0835d41e787878787878787878787878787878787878787878787878787878787878787878787878443133e8e8e8e8e8e8e8e8e8e8e8e8e8e8e8e8e8e8e8e8e8e8e8e8e8e8e8e8e8e8e8e8e8e8e8e8f4c36451dbedf5429f68048622c905975e5cc0c498644e2f5029d50f0d666d29d48b938ce9c29889a17219ede67e4c31302e5e4a313e452b6117d28fa82af37662087abbdc3f1ac917b2dd28447e70aa9fec031a996c13045b5bfec9a6c8f613934521d04063ca364130a5b426041a64843139b6049a3b739001142749b0084192173cb0858807748c18b18490d7d521dfc729c822887c7578ddcf33f511414e36f1e3e04da9ed1dd8ea44104129a5f6f36a2d2c168bc562b15a63fe361b2c21ac1b37426e685ec8d3288594305808569d849cba14c2d2a8656983c5aaed2a3f9d68fe63b07d2f6900b4a67f0003f01d44f5eb907bf65e4b29a52010f1f5175a2bf2788810a1a71e77a7b1a136aabd2b4d190095fd3b9b10c84eda20404a8393a29416615194624add9eac0ca59e774d21198d81a13404a188eae75fc13457bfc9a0e1fad6dddd2d08455c2f54c40bf56a1f382a8560682f4da4945a4cf24477775b23d5bc59a63276c0f5afe8b5da1084223cf710cf1801297d118140847fd6412022145620108aa89e4c06bc1411cf74b2eeeef6ce18c1f5698de9326badb5d61d8422704d0d01a188eb9882a8179d425577550824e7b6ec2f83961273d077eaae52914b62a3b3a2521f9492e89ec8b9ec5c76ba24ba9c2e279259c1b11003f2d123b6f3742b23a714e7e9a91a6872628801879033051d4270b7369be1505d210c36dbe9c013f6da6b6978b906aaf554c65dcac080e6595f5de177fb62d7ebf314c9ced30b75ad694dbbb58d86b6792d43a152aa53ef74ac63304c9dd3b099986ba5455ae0ba2548243b897061db796405da01729179e03cbdd0e770b8243fba8a4a7d504aa2a3c1de5391ec565912dd29ba41241315a1aece89723873f5eab448a2cbe972b22f949d52ced8e9224b729e3a990eca2d76d39d5f2d89eef44263a5cd62aaad46434d27925131e414d51c186c2756635556eb08a4cb1f600837f48015680b2cc4807cf488ed4e17e8eaea8da2bac2d357fde628f4a13537f0d86100cf5e4532dd13b9a6425d7db614b943297d8e357091f5d9525cb4b8ec5c766178dec2538708628e1ecf6597c4890475b9ec5c762ad07db51999cbce454bbb3cb2ab744e24eb9611370147ecf442f72c9f863fdd7575f7d49d3f3af886c562d568d5e954230015464502151911c9c2337778764b4abbae95abbb5d5d4c05fb40043a50ea314a3b5701af5f600d53f4d5af7ec100a650a8d67649718c6200438aab2a5789516762c4f0f7d50d54769a22b866950d1a68f4aa7f8403b634c6f5aed6ab70b9b19d1b6bb75fee44057ba211188a24175c7a7101630a29c5499d30d08b1c3df52768f722448555577328544a754ac9c030c1988949cd800393692a86cd2f77897061c3f9ea92f54cc9806d26193d9cc817afe63d95263be510900ea618b1a7da50a90f4aaaf05405064d37ce94ce66887c10d5d67a81c066fdf9a135ed35af31b9df5feeba65fe1341cbfd343ff54ad50252150d7dfa73e5a4729b87c51391836168a85b7caa2798b6b49a3f6013d75aebedafa4115e1bc09c267e2d3eed601930e0fed7d15a66a32971125a81cda6d1dcd54d8bc9ac4d169b65295dca880114484430c3112e08dab16cacc082077eb4d8e554987f3f155b0718889f1d3cdaa69f4609c64fb878de985396ac5a56160d16f5cab6e9b2bc2969dcb04d6d48a34ba7f900c6b8cc5dc2c801bb3ebaf2f206597ede14b6963b9c216926305eae621360efd62dda320fca77ebd29eb8dc350ba55c01367b76b396b433b36731d8273b5722c0f696fba9f5722e50925361a99efb22f6dd60a73a65da4200800a7cc10f07d83e4c101826532636a0259b3813f9e911f4e34aee07b2c203b4e84841051e2348e1c0b983c71052727f03709850c5c94a6de8344e36c61b183c9620bc3c40271eb6dcdf802f54d9a6d4c230cd298c50f0cd0dba8ba10999e526066c7e5f2d811424f79b608899fb2ad7da03ae6db79b96a6f5aba574ee3afdfcf390cc48a5a93c24a71a2f0873de0ddd5f73d71080394d449f0752d09226c5ddd8187d699d344334fb884f378abb203cc126cefe9ee3f22a06dc4fa42c858092fb69d8f87efc1c0a91acb266ef56d657d2a8fd3236700211ae78e1b3812d98f5d9a75eb26e32405dde1d24e794beff07c5a1df45e8aa3f041248a4894b3821f12ac826ddb132dddde4fae9aace92317e54a06e9974d743085aeef7a1b392650bb522d7aadb0175ade84a67b262e8421040f63749c866d5517fb304259098a20228a8642a4536ab6ea436fe7894416d7c01b9d2953f7693010ec8f52925698e53d444dd22e57b7796fd58edb9bb7b3774effb685a807a30ac6f14f2af9b1bb0c9baf1cf9b65034503fe6cb260904d7ab2be18f600c64eb6e47e98a7b1a1b2ff654983bff29f06e64422a2fedb2aa721d83ce5b6d1029ba79920566e4e9c843cfb611a1a5129c0eebd7d5bc9d34b6503fe68116c966f04e839a1e5d0b6e9b6e9ef210216dc5fe9fbe8c356d27ed48e34c8b1f83881e57e20d4c2d41c1b37366e9434fe4785b54d3f49086cd220ef0710b10a4c6929a3027e4e5e25d96f8d341b979d7a5bc9f63d5c975e5bc9de83d56c5cf6baf4ac47d21bc213dc4fd3038c7de820b2e4fecea2c5615570b9ab941bad9f0f6b3f7ccb4ddd4f265ddaba744aaf3b69260de48d42fed53b19361b68e73550b7b2d41a47604a4beb301b4e5272bf8a5a181aabcaa1df7769a9d7f1c4031c30a1a73fa53159df04d70f820986a1c1f5a98f3590d0e1aed1f7519cf9d9e2faa2effb300e91b46993954c610424be90669f582188ea7804b193fb1b7085c70a326c52dd0a3c1a90834daaab05a0c1a2856fb2e4fe1bd4c248a96f6f84fc0b47dbf4e319366f6e6ec88801d3a7b4f4ef5c9fb4b4c6c9134a00861e48956c9900f991fdfb0525d9690bce247b0837dc2f03863194efdddddd3f0bf816f7daef5f80cd8f660b560c894b4e13bf65dbf4bbec807b62ffba64ca2d798a823f871ee660f33b6300027793bd1cbdb9417dc29c26d6dba954d79dd24b29beee273f1dd736fde0073d1504d8f433e3bae567b7a4541721d8f472397739edc3de12baeaff21f7132f0a0dc8bcf16ab4a62603089b2cb7e1edbe10d8d87480cd4a0b6380ab0cd7ae75a539c64018c9fd3034254e9ac91218d6ff857e05cc69725fc8bfbca7342ab8d75a2b49df02e56ad35f83065c491e3b4c36e0930ed83c79df1e89c36f4ad616d06403aeee262b3bc0d4016c9e9e52911d407168e6a1420e3f13ab80619600739a5898edee211a7f68c12e39b81fb603e6dca734e6775e5ddbfcb44dbf8c0c38083edd65ffea7637e80275cbec5defb45cddb572b3f49629d8b45aba77f671b4ebdc65bfafceecb6a477b27218c37ca745019863af4dff6f81ddffebaed276e0725b51426972ccafb23a9351b2ce8c5459cb6739b8caaaac6b6eb0ca3c7c4fa71d32bfb2938c94e5643959ae73b20ab05961a45c5f0a5ca2900b05619fd298f52cbbc6da0646049bdf59820a039d2929b8e2d55f5980bf0fe3d3e9ffe62687525447c18f2ebbd65a6b85b1f75e1f405c60b8878f1f40bae57f7f6092fb617ab8fad5392a6badb5d6de5abdea55afc2e4fab7f62581f4187f903ebaec37ebcfcf95763dacff0bbf1080f54569ccde027fb32ddf0f3bb2182ebb151f5b6d876c5623486498c0fd0da3df4e80394dfc85fceb3e8b060dbd6dfa4d58bc68b45ed11ab69d6e7f96b85ed1172dd72baa002c5abaf5c427fbe87c019b45250560f101410560d93958c3f6c9926956800b72dda2516290cb0ac0b2eb562ae76400ad8377a451b0e47e5e764c41f1022b0b746038ab1025370479d911e78617a52228cb0c2f3bd2279a7881d609273a45d015148102f4b263093878c1004a620722535e1ae8132079612c3ae05e97fc7cd529285e96c4a10e975d521b4f9ec07510f65f78d1785f7bb26389e2d01b5a63bbf4f1618bd34ef87aad31db03e2d8da6e2f3d7585c1b87a2175d1f88b9f87dc695acce1df47ad7d82660ba6dfa9c238823f6fc1f697d4d05631ea64af09055e0c0a60fce62ebf9d67a62ef399db3c5d0fc99e14dd355fe9bae5436829056db9115a761ea360d38d982ebb478ef0c0435eb2c4fa91233ec4fa90aed967eef242a4fb34d74b86320806752b26f78f51b05fbf3aafe60533a728138d88b2dc5f45b8918f54417038ba8d6c99fea8e6aed40849a63f3a425ba72e69ee523dfdd18ce2949e8e532420f2c9f4454ad0160b5de6280e7efa221cc579794a7774e6e9dfd35d309efec5b9eb9fd66009755920bb83bb936d50975dc385c1b4c082ba2eeccaeea5b92bc6bbb03b1f807015504ca1ae3b3382aefa4d8bcbf46d14eaba5705164aa66f9fd0d6c57599b3f4ed6de48205ea16d90b9f5293cca35e9c4221538c2a08feec158d3da9d40e6cd0ce0a1004c51c3d29f1851643a33598741d29691465629150175221d2c41e8066b013f9f44a1486632ded4ab4971a5e62eec188582e238c7cf2cf6452d236dde508b992261a152f347761db8bb194115d5c280e0b25e6e899f1f1ba3065c6d4ad6dfabfd27e9e2ce62b180a282708ca0abaa2dd6c4a9c4fe8ca49fe8196504cb9e59ed16a3652ca26d6f0ccf8a2a45777acb9aac0989898ef1466d1d833339392d5e0c3cc0b0d9602675e8846172a8c680a0bf8dfa43bdc79f3956966a2996a26db12baea87915f48148e069b8981f9c9e13069a27cb2c80a8f747064bb9db84a9ac41abe7db31831c41c234f15049b299b0792225c97fddec8c5222115261e4e10e15236116ec6466ba466620f64b87b214d941451861239138534256d8304365f6831b66ed9a77da310d7ecbf98a3fe8fa38fa571fc53aa448bb50468eb458796fda55d2907344725d4853bf1065126d2c41e68d92fd6c694adcb940846ef25254a3ab16ea1a2b0f27933cd4c345ad35fca0136f14086aa0d6018618c972c1a7b62fc288608370253a53115a335b3d44e6a86140d1505f532a2240005f58496fde78fa61b8c99d154ab8169663a4257342e48d3ec881128c2c58021d6702815337e1fa44470265405c122488ab52e41b04313ea237b6248eca2b12786fc3cefdad148248a628e1725f7a5460bc71bbc6a432484e0832384ef228bc61e95ea63c69c9817f2bd7284fc2b47087e913ffebf3762e0cbf66d50b7425ddb1dd05628052d61ee2ad152365f5d23a82bcc015ddd5b69d75705b455ca829617267367e428fa406bf48fa3d88492d0ffa3b884daf4a746d1368a37d1d640c401b94b8c42578d80d02b86146bb4463f0942b844381394dc26272f6f9a98646ccaa1df748496f012ab23501cf067b426940249ee56ea082d5341e8ca49d32535c34f8ed6f4a76cee4255415739d0e52974d52d9412b42c65e1938364b299b26191b25991b2e54c3313ad4b2535dbedc4f9aa2f89c291300436c5da8747da2528c221c9d549ae3fb99d097625b9fe0b697e455e68c8fd33a3c318411b365e0240820d1b3636620eff5a475fc1a738ac02c0c89844e17a4c4f020da71a431cad51a2893750d74b0d2f315da82bc96433dc615aa9542a1161c3a6d51a39add56a591648bed0a698b94b94b5280b73dd2a01754885b64a33a0e5ce5de1ae44739708e735505748057d89b92bd4f98ad6ad97590fb4f592032dfbc39d4b364b3b97fcb2cb3225d294119568255acc984a8d299aa75c4654d0e8820f5c9a41ca4809a85ba921b40c77ee42694157a958b8452a48ee1f513a5ad32f8e281c4a8473170a0abaa222a773174a09baca0125e565d62d54145af68e0a2b70a82a7041d934e142a32907d3116a4262a29950a3c966ba995aa659143c933303ebd6f78d64c788e7ece46aa66c9e93eba76c5dcd94ad6bca56df9cd951e59ab2e5fe94ad5b4e5237d39872d2ab7e97ed264cd9c41c29275da67c48d9523ed890f5e1fb811165dee2cc471109a18f1f68231cc72f6c855666b430d8cb472f87a6f1348a79d1d81343d25efdc7b8211cba1ecc49a4116b34620e91c907628e944daca56c291bad9c1142080078a1bdd48839441274804dd38c96fb4db56e99622df70c89896652d22db305b7f58bb54a615cbc94b00b490c618023d18fe40737563327e90517edc306174f60dd0b8de5d553b5d62651258a0d86a8c284861a1747aa28994195232fb52a3ba51360520f5c1c81c12c5cf060910c604a40a5054b5061e2a990a008cd0b162a3d180b951c4a4fa8c868c0305b4a3c60100b1d0eb12089620a2ccad4c78929d9fc378248aa88133e3821821438c10376e2066bad9581133b3a4ce1c14fd9f1c0b8b0e24a0904539e78e100c699fa4c49e2862936173698d2838b9d29b2124cc9290dc160a63e392d72c06296cf55f1e5d394b9267250723d44c9e570839c0c380a2cea00e6078b57bc5cd1c41332c03199fa3471440e8b99fa3421c4973605baa769a2872642e0328227525ad862ace4498d9658f0aa927d451794cb758bfa3c01cab2c5ad5c0bda4b530220c7f96cc9d55da16f2fe7e016cf018dbb5c835ef020920053d29230dd7605b89bdbaea47123e5d5c91eaf7a63e982b22c7d681bd36f38dfd9912b8be15805c26605fadb4d97fb1d8a149c6fb802ddd8cdbcb1bb135e9a0cc09c7be6b66199acfeef298e5b99afdace727fe85f7899a71c1a2d2cd7fffe85978da52a994240e8edab107afb1fb9732041d4572127ef25af8dbcf515eab0c9325d87a266c74cd7b9ee96fbdd8a5fe9c15e5914ecc194a435974dd765d735c1fda52d69dc1306375a13fff691d2b2000d858e154e1cb79cdc6fc20e15aef834cca9cbbe0c2738a5c27dfb975cc157adc35d29157cc0377a4e26cdb17cb553dedcdc701ae46a2b1001b443852be7ed04017691015787e2a7df680d0d09f66e4f684d7b365ff57b37a7a585badc2d7bb42e691477cbfd3a52f08a78329f39ad07dc58075c9f3563b1623888f061d990626135ae60b3cc250d59c38288b8d55a7dd411c326d551ddff90ea726d39dedfc73096da7a81daa67fccc166fd01ba3ff5a75bee359f56a8e46cb7263d0c0d8d0000000800f314000020100a08c582e18030cc0345550f14800c7b924272589d0bc4490ee330ca20430c21841000080106189899192902003785e9c5854614280c374fc0c372a733ea5d31214daa140a65a8db3c8ef3f165d8fbebaa82c88e2d80dee085a218ebf4ef8c626a7bfbc47ff5a359e402a0a53c5eca43119a1fffdf1840b80f13fa552b3875d5bc7276fb1ddebaa266a389ac23915075e94d542ba95ad6f1fecd19e6a1de6ca5d20ffc5e2784fbb66386512261b5370844ad7facc07f55e16d24d2e1b03d626cf7a2b14384cca659bce1b19c59e9ad1060384dc3474a9ab6e6bfda666d145e89da85d470614e564b989adf9f456431acc54ad398894c5fd49b07a381ee3d11bdd69008aad52ea247eadb946603e50a4c69c485f8f06550eeca89f0c30d5993f1b7f036b011fb3a896cb93ab0e0351a4a4d6b30825e3946c368548e453cbf543a2a8b5a35bb2c2c1d19a043e4d53bf89b9ac57052d1fe4527592f98e00d09d5da9a0d3848db67026335303313d4d95179bd1bdbd5644cf7beafe7ba046bab5c4781b7222eaad0d6672b99ff1842d65de11c365d7b78abcc4ee6abea7f9d5248aade9c682816be1500ab303bc58be62f2d81d11ba6f1290318e91d00c73b98b9da745d354d4a637f2c078dacf78cd222d27fecef534db3e88cf933a11e0a4cb0114d539118f1a2b1cb4f282f0f3827717b93470d6c7844e2b91e8a8921d8c435aea0a68fbe6b36f04c34e9bb592fbd216cc2f25de031204cd0d789ac7b989715c311ca848f996688075a6ce1d5319457c65e10e824317ba1855d0c99b6faa68bdd62e366dee1f42b97fbc422cfdfc23446b5494402032bb14d62ae2b8cb6f9a482dc032535f6eeaab1d04c803e655993b6533b43ddd1f2490ba8940df82599d6f61bfbe54a03c131910ac65bb7428392b7673dafcb8885e585b84498a2e7d64c3a8fec88808b9d7dea5d328b33ab43a7a2a1012ddd99b14382c399d5807a2f55176cc4fabf52359690e7be5041c6e5277cc92a11158c88ed9c48c7fdd9afb078aac4c826004916f2c58336e192a14d59e2941d8883975045beb1f75fe33402a6bd99effe0eaf5a80907bd909e8fde64ed07530bfb0fd3cf778b508ded21f27018a4c67b3519dbe2c89a231cbaac37d8b12d50c64ed701219e539128ec27649ac6191156761c8354fb4e5795065799d8a3a4de43b0be3eb090d8d31a23ced5813bd9fa93bc5d194cfaf63f7ebe19f31f2a52896026d77fdf7e3705dd12b238164898455887826364440a2a60198fd85e802166fe5e331770d464f25f33a3d75c5f434d31f939112d4f738b8e363c3756f822843d4fff03f87c86e626b33cccc9dc120a462c76ba391a6af2d7c656195245cd9e9832f984b83ef2fe5ced89f5fb15539894796b3b9b98c669ce9e0b4206efa6c6387b5d144b2882e76d7f0148db818415dee683eec8bf24e3edb5a512c86ded4b366c4bd90bbe4dc2e8df1324bd07024e277ae6bf7823b5c8f6fe47381af3f1cf2c26019d4f45516bd1be6660487ca330c438ed350de74fa75b281574b9104bd21af70cbda1a125487749f62d885c20872050b98a6efc2cf2a0ec494a66540d4156f2ab4543506161cf39d7cd382d8de143aebee686a28aeed1697e9f7676c840626efcdb699296ea04f7128114c9d7fad739a83982092b3b0912fbf390a374fcf3ad31c57adb5ef1f306a7ba34e25a729c7d1f1d894b10372fa9a711b330652579b0d246ba1786194b56000b105e7d3c427657b10d89636718cd0f5c39e806db149aad4ed1b701009775bebf6b28338b2d93cfb23b50a558ebe41623dca1b53c9bf666ffad4350b09c191ba980ec6780053b3be17efa12dec25b97f041feef1a0e6a2721d627d911e2489248a3256c319976ed5b5d551e96cf348c35fa61063cae8a3a929277e6fb9ef9c518878cda2b1529cd36d07dabc9218ff431faef7719d76557c135eeb98a211c753618f80e74d49f658b23baa87e8fe52081885eb53fa98a1e9e1348019587251a54df3947ade5e40a0f173cee27ffe9462d58a5d086208950c801732a460cd9da202a7507a08ec5838c2b9d985afe809c3f1abaddeb4b999e971e1ce3a931346d7848aab27b6fbacd591ab6e1d0c8815c52b198a4eff7c84e78f31015226cad651eb601cc9f06465db7550345dca3a18b25e91a79052e6113e6a9d5d00ffd8493ef549a958370ad607ecee378848b0e77090506a77f041b25bb21ab4d2a57256684c3a44518680f17927eddf06928385228612e472e13ab74eaf38533803de6c99cd5e57ba828592995e3d18bfcda49be54e1b89841b7fa03dc0100dd59a65743175b2e0e49e9fecc4973807826b1479a7d30727d02eed00123d0438d8d6479ca6e88e6f7807f714701a2b6864d7af2c9193f499295becf6029efed5ac1658200a305fbae74ec1bb0f2b55b3f1f8dfa6ddfe3f8150876e92947850082c806c4a2cf9c9af519624ab08096c105041fd69ce63e31c6a55df234618e874e0f2f975f7f435db8f4fa4d8a8c177e8acdb1c6a0bd0aed07db1ddcb9e30b3d15359473ad0ed979666a0690e76887ea1f30fa0ebbcdee1a4e1681bc4526979ad92a732e822138cb2c93b4c4c63cbf75f999390ada7c1359be00186929b17c9959092e9ba76a02e2529f4c330355c2a694da2e3b4326c9092945026de4e681341dd997d94340cafc9ebbc1d3bd822624ced2051c998cadd87250692d0a9d1029d77133aa1b06e66d99d4ce6051c19bf8134a01f80ee94796f8082b6a4052effd0da2ead3bd450cf467becb3b65258c67458ff17f2446238eb70b13c72e3eae96637695da7879eafb0c3818e868f23f47d0e143ef3fc778e0ae8d7e94e8171911dd07114897dd3f3c31be6059458887be37bee6c7d270da68bb7b5dfb6a4f7961b12c7db2a53932aa7db110d8d8bc2296a2ffcef7d689082bec549d732025a6d2f2f44b849f602e538647240278dc779b757601222adda73c123cd902a234f9672d44d4cb0495d2e64d9b02d30ea569f3498163f139ef20f4998d89785df54750e6732138d01d9f250ea8f59994c7d21b6151c84d72c88ff752237e9167096aa6c3eb0ab993abd873981d2a404482bb94597a826015778a6ac057f888cd79d29c3a00dfc2fef49282ed2a6266abf456b3854998e0a584f050f78833a885d73eaae359830b80e4afa987c511de0b0cba37c3f8b63b5c6ada30a26aed62437a303b8e62b24da4056b6c0706719454062e3f786fa46158da9274f098ab040677ad2dc0c7919a1f8cfd9860954b93a10c6cf49463a610137c9257c13bae84eab191951044930ec13c378faacbf0594b2260176e01ffc827fa954a914f92e9232a32a419ef8728600d23a160f76420369f227637cf672db2ffd28693734c5c5d2b289c578d3fa0928241c9e163d6ab17ac3b3ca9ec155adbcc090496375b82d6cc5b588e35ebe453b07021eabfb0a942f3ffb378495e61a1d78cba7c425e8c661293aca950eea7d5c1c4afce4883931dd76d2974caccb262e386ce21f4d0b3f737e9a1c9ba23c4cde11bb1747a22bb7c90f9b3922faf9b7d28b46c39de3ca52b49efcfb5757310674689c605a80efcea79bf2acf0751c348326f6599d6b468d9e5568b39fd49e773199f7d84f186967210c134690a6f3356b8d266174e9d6ab9a3e4280a78940c3f4318411e911a9fb00c268d84026b1820304150fc4fa7be227d03992905f6830f2f0e713a638e00e8c013a4ac4159ff0652187bb02058f8d5934570a330ad89acb494b244bbf14fe03a3485b1c18794fc9c0680b083cf0c2421ce65ddcc504462c1767ff2341724d53c088f11f29562a3b14b813dfdf98a1856eff03303a66dabb9c1cc665e32d2c61daa34aea3afafc8e76d4b02248a8c40c765c2fa80901d28a4908ffd750673697d65ea23a6e35e376de81096526b578d188fce5a2d26d316f6e42e4710fca1392ae81b4ff2e740551ca5bad15d04f914200e85d762febf3c92024e440087bbe5d210e573c4cb401ef70fe2f6014f2a7209dc4f15cea393d57b8a97be3794ba0d894aaa83dc7543ef800104f27df09b9027fc27239cd4b3ca6cd0796cc2dd1d06974fc8328013c933a4477dbfcc530ace5e3541c3b2bd85aa4745ac96d37bc9fc0f032718dbfd306f4b68c21a1a80ceb8aa025a940ce6218d3cd6f894f858f78b0b6ff31451ae5acdd47a56546a838b022d69c6e04908a6472d6537673508b35c06eab7b4984bf18a3ff8fd6dc0e9be5fbb4ea6b5bdb423e69d8513dcc60f2e38bdbf843d77644265bed89379461183764c508bc71ca6da83ef059fe83eff967f3cc7ad53e727510b7911bdc8fd6f60a32e346a722596865d2ad6e733d4c9cf7b0decbb8dc1d4eab8d6c68baab08dae24233c4423145cc89fd5e59e2b8ff2af6a4c5aa6e6a00a0c8d448587d4d2edf36271360b74b3d9badce38cfecf52bc96e3a87ee05f38bee7014c2db4fb0d1028c9f0f268b0758e45eadd5b883f9cc3bc0c27ca4a48d39e7f546e6ddbcb203d603f52727097ba64c4ecf0d641ae9ca18a2a09782a5807147878af8c4ac19e73593d0f27bb070fe965ce4d0b25ae3402bbed728e9e1995aea2c60834b4db5afa729e1e100565636afb523df61c8aefd14e3b232b0167268c5ff0abf539c19dad74aafe59d27e6fb024603827678d0e4e134dcfcc0da78d98327e6f1f51eab115d81f1cbcbc5704756eba77cfd09adba1a83a7a95a128abf32c74b0b07585df4b88d735022f8f5e7d291de91661616971cd908e6659e27218dc5fef6e4c82fec6e29d9054deb0bad1246e40a0952b8f8331123e7c38c388ad7d20eb41046385007baaac781461e644a0e3444d5f75943677832983d2da785c6618f3f151486de79a4e24536fe47f9d7c08e631be9084d437ae32ef69a94b1a131aa0c764bcad508fbcb44a31d5b3e20692e7aad3082f838699e45e4b35026d32e1a2af11f11b9885505cb502606a0334d6fbaa6190708389b95d30e636abfe370df90c12e4cde7bb03e58b2268de156591e66c6b0b56ef038b7df11db96969f42e33770d3a6d48e060f76795c7014c9df2ffa934a4ea8622c1cf8c6c661c12158f2a66fa63a26051fb4ec1b4e13c221955a711544c975290cc74e95dfd63c0563e359a81e2ec46ec31b1cc37a5745b294ea6b2f046f4b575783ceb26a18b90170fa96100904c00391b761768bccaa6e20fc8cce9554ae24a06fb2af242f00a2dff5ebbb1a39e4d1eb8ef2059f1a4989bb1d443e43a4c8862b3ffcfbc596542ea9d62e4c9e1999a1bdd194d665522825b870a0d45bb2d15388966e38e4299e39d1a167fe1c222af15bad1f0210d2eaa0bea1d2bf8feaa4650c5aa849b2f9ca8033ab47264aad5c6f9d492c0e11eb23d502c97c32120c6eac4170ddd40de8b48808291c0887cb7c81ba8e8402ddcd824c29d267ec669a30f75fcc7cf5daf744a282b238d3ec29aefb2c4302c2078c15e7a114ee774627421e8510574234ffb4aa2169ef554f8cf98dce22c21a08995db4be16daf63f7c5b63bd562e57115a5879c62356366a6d31b81343a8fe94e55979c1ef1b8bbabe4d29a72df86ecce58baf078116b9f55135384d2f21f1aad51fad6d96ad1cc0c9e9e6ba71e9351974ffef4941ebbad658c2bf1a880ce7abd545b7f71823249b02ee6043b475a939bafc723fd99d4957d0d4ee6da0d22692d83a0e972792ff57ffd811fcd5d6cefa89846f39821807e2960250d117dc55af96c8381a94a95bb9cc6f0d68b0a8dd64a084c1adffdb6f64882b97a140bd0599c67d84b6db3d059dd9ba8bf630533cdcf5a575cd696991a578c4de8e0b265ae33474dd4f35eeb6a4e85565895a946ef1d7ba91eb3c839a0b1a7081800ebf4c695f9ce3cb0acb96d7675cf177ed4ecc914ff7ac017f282c4cdb787bdd72f04ee1086377823c51833c795ab3e61d4e3c6a97b0925180bb78a4764180b29517b40bd12e7f9502bd442ce3e363006e28743a9f9d400a0e06aca09a9568aaed3126855d85e20927af35d8af150c07b390350b7a45a04fb1d1f0b62ed8e59980d544ef2445ca50b4d8184173527c5a3791e1e7907288d334751df5a94f1392234b08af1cc38f09fa963067c9e3ae9ff45d7ebaeee5a35e8e853aeb8aa1341b231539bf4415b3c3e5d125f139dfed0b1e7d0ab6a9657634f8ab9120fe46e7c6992fdf1c9d66b196898c01766cf908c7dbea1506b8a5326ecbfa05aa44113c1a2460963a82edb608d0f05eac4d889abe6d3a0d214be5e9c17f3367e2a9319df1663d84aa187fad452a38adac4d93305e439f5fa6c0cc84399f0e076b87a0940213c1d74d58befac94dbf9bec47984de8d7597d212d48d2e4493d22299fd6cd46e3df091f0217ed0c452a454b0f0a7de412300c0c5ebd40548494d6986ec84d007bd603d85cbedcd8392f2ba6360e89c682180103a15f55132f9bb4ff1c42cf7627b56b7992348843be94c6905247924aff08480ad485a479032059268ccce2c772310c4afa9028dd1b539871a3d91e206a1767fe587eb3b6c1d1dfa88505f757c50b8b444ee14dadf8bc2942f6f4be2f2d6f5914f890091f0e74bbbcd359c10f1a17027473ae6ade582950ba30cdf75d4cf2bca3b29d4976f06da1226a2be40565256648b30ef98709663c5d51ab33628745a004dea0bacc5074ba0c8846d2125b24253043cb4a2b413f19e84e965609b45012772b468ae8c5ae6ec8d45c308186104744f747beab7d40e048d2ed5b00076e68ad7eb6a7d368a91ed5f8a0179e6c5f370011f84d0d96a8f9e5d0992e27e857f94cac80b1e1b2ecccb8ebcfeb3db674d9d139c2406279645e97c8e61745f24ed84426c0d9a4cca3d9a70f479e02e97f8870e13671597d6396a74bf8ebf0dc3c2bdc165dcf30faa4a7fc8ebff9ddc7ef30dc3b21a4328b453245a97722d0bf9e782835b00d86de7ac8f19ea4d9016fd7fa7c24ba1f13148f893a5200ca57595151cfd531fabc78919d192fe076cbc90a4aeb44fef43a405772a6d32bd90718dd6715dc1f3d77e9c3c2445bccb83b8c21c2d12b296423a292703906165757f254a727aa79e6ca317090ab5ef8ad615863c7236369ae6f62cbe1701a2eb491f108fa60cb88c45b7612147c14426d3b1845dcaf654b905c2cc76a1593b269916814b5517ff12d915aed7520275b2cd58f19ba76e9aa415df5a0a701b122f4b036fac4878ded1e98d3dbfb6846cfb382dc903c139135206ca146d96a9aac5634391c473021d68b8e5992fbb7e78f6927c9976aad788d01f351f8fec5c7b3885e5eaffe99562e113f2b74335734b544ca998a37145e9d21bf37872a8b9622244ed4b0aff631efb6f1897f1c3e991ba016b3b4c906a2e00d80ca37417464b95b16fbb1685deead4b5ed146854f702f0452a0e9e71c89f11093cc42b5f091f689e329936b5e110fd54a22829e988369025003cbf5cd80eea5cd16d2c1acd4aabe1065c889ec7b4094713904973feec14570da958a9d39422ee7181dc275c9752574633f786882560194d41ab20550ba4614f311c2375b367695ec11a4a895e96ccd91a9af80c04ad39d3216521cab34272e4331792f61ea4310d9896964865a02866646bf60d710d06960b826323171ccf1a8ec8dab68d861346b054c0bb65d27b0ba93d06a448df7c55e6005efa2f992f9cb561c2396de3ccb4f37d95e23605407b3ab9bc14156b80d02120575b23e208e015151c5b96c40ce48210e185b8291fb6b3d38dcf10f4d3aaf906ebe3e747a8f76bd967fffec37d3bf12a608822d8592db7eb0e4d716871bdd2084e75d1e516c45bdf0008bfd1bec9c220808da493218b2add20ae1349e6fe2435dad86df1a54963e6be2b2120372464c9d1737da475485811edaf53741cd904d692dc03bff926e5a2a58485777e5bce67ddbe93027da8e1b933d3cc7224d30b07d133b49d95cbd1ef8be823c325429ea733f6ddd5ff85e75726d492000f429ce268336342e9c45f7eed1de0b546ca46a761582af681456d41612472e55f158008eadf6489dd45db5e723e9479aa10e5b0956f10c3c33751a99639f44401b0f741cb84fce33accd2a79c46363455fbd97fccffeffaf07b89ea947004c6a6fd34a81221342b8a889aba2fc8426e868bd520736dd71568c7f213bc6909b42802957618654c85d5c22d4f67f09cd7428fb6a99062f67eabd0dfb57f63d9bf6b5414e8bea770cc63fbe1690dcf295fe35637e40917225b2e61083d71f4a214dfb218c7866b274c366c10d051232660e92f795aacd1c4d1a9d2bd62633166bc5fe62256fef493a86ab15f30b4f54f797a4f5c29011bdafd281126cde266747fc823be87e2c181b81f20022aab95142113aea228acf8c1b5de5b9d7ffa8083b91d0f1bd7b7a89b155d403efd49cf6b887493330ac1f5d46f5eedaf40248dbacaf40ea86296885da2d36a1a4a185799124a0677901a867f7d4e9249a92c8db69dd3adc889a47174aca0e4052164d423e8c2242c69a46439d569b6e3dbd66b1499c859b71aaa9ab240dce7601323e7f821a0edc859b97c20c5bd1664484ec2955894074e1330e259b8b4a207f9bf62bc8d027108660c914e4f4196f06ad7a203510ccb5795fe328711d49e9a330c08651c9de00995efd35efed0993a27dec350786563960843a11fb8be9751c6c4aa168031a0a9dff4ce04c0a3cf3a3f9259557a3814602b72cdac51976607a98b5169b3f33fb19fdde818a31a1bea268cb970ccd7ba043a13d8eb7bf2979eec28a03373137ded94963ff0248f69e8b2facbe4f1cef16b5468ef942549fcb4e75c9f56221cfe22952c9f367551ebcb032586871eaa89a833ef5852a7628aa74e51ffe426b6f1fc82b503f1494ed7f519f3a6f138688ffb4ea83a10c8c783d1aced63e3ac964ab5519fca6137707febb881ed6ae553468954ccafbe86c5804d65a5fac98889f67c0d4cf33b6bd4db19409c9049665ea13e8526f9e82f2e8960d8a4f4c3dbd8b8a5457334f922aaa92794d490ec1859b46260948f4e0ecb620546d66af41efde01b8c01380c337345d65c4f878bb253b9140bc3b281cdd078859415840217336dee8be3f53fa4a4dcae768fae88aa85a58e25fa82f86f521383675fdecbfe1dbf9d501a1ead03611ed0f57fb3162ef1ba69d8bb82b2d118ea5395a7bb49f2dc98e0b8a6757db57aef90201f74b948d4af693fa8327004d027f4e8970f987a5539699b742d09dba0d2f628aa05bd922168af21e690c6a1b7afa598490ec538d925971386a66c422bb6432e043d6783de94875ca8ba830cbb90993822e51dfbdc3bdcd87e30d5d45bf49c3d3e8d5d3cd05a8e1febbe96c0b3a6ef20f758a9b89bd7e13ca0d6e5eb03df79299f5935477f6920a7a920cbe8d2c4bf736ab4a6c1c9354a7c71f058537a86d9d76c61a8e1e10de7c8ffb3d566922f4b64f7ba2d5b1c528db5e1b8b3c75739623d4fbb9f580f9d034ce1718ccf4a5094cbe21d589be0e94107c56db4e495474ac8e305091a60a99a81dc2a8dc437ab2278edf4ec80b97a67f6afaa7a6fdaebc794f1b27c449ffbc9cbb997d413aed9f84c5c2df9153c907bb9a95fa3dad358000735857b722b08de3d60138f80deb2129a2ecd47e9e88977148166ce7f85f1ae4fdd4921a541d3a83bebbb8d4cbb75d6cbd86f0ff67029abb506ce4c6f61fb6325d62e002c16686cd3dd330c694054d7023674a5ea56b1d19cafa92b3cbb8e0ed47e656d0dd6e5855fff01b27f4e62fadf84da4b0af8abc583db43e24fe8091a98a95bd9f41082e982de3e4ebed784ceafbc1ac6d7ea0f4cdb41fdef97e210dee561128ecfc0063e46f0bdb4b7ced57273335a91f5985204266790ac9f59fbc161b4de2bcede896ccab90a93e7613a280a10e4433b5aa3f08e8cfa9be9fb2780608bbc376f0f0bbe3e4137430994d3cae90274a80f37c9dc6a670b670d60da5dd6aee52677aaa2bcbb2011ed40785be1e5e03a6520583a09b542374141399814f67085ea702ab4fbd0b74e2cc9068b0a1296fed47811039117a094c9113d873a534fbaf110e3808bd3f5897ed58358773b820e3be1939b5792dc68591fe1ac49a5ed19c2c01c8bdbb40bac3cc0577d655cc47a56765cb84e5045101b6b92a298899edda5dea35aea5f2e912df72aae292436f5b6565c2dcc7eba47c64e83e1e9adeddfef06a5f1f44c3259bb1453d4e1c443a73134b2d95fc9d7a6001e4d41f72bf5ae44d2f4a5f08277e36011d9997b408e1bc3090d2c269fb73df499a8f80f6e666ab4e1dd3a9db7dc18b4a048f754a9a4f42c0c6f3f7c20456e973bb3714d768018142bc3866ec3702504447002f85b4a6eff3ee6c8d574371672a15bb42deef1ebd5725eaddd5fd4651d8d31d6c74cf10602f0dd7290b78abcb39afbe967f3fb244f549c5f80c80e2f6a516e95a1801c0eb1a6f98eb6ab7d37b37624281a39985fc34849223c2eca64d6f2a0748925bcf7620c07a9778603a1ebdd3db433e5d01e6853eb18a1cd74f2d318ea1b63588e3eea3039212b78dba7c85ef0900a8cfb7fd2b8a9d86a908a36cb7b0eb6bba8c6a787d334177f85de93a8268358b7043166e9f9442f08c426330a0948fbf6065781a1fc3a5907a74e9e308c420735d77fb5e12fa3acfdae5ee7a798bea1295d2907a1714328849f767e8b09fe0532731af798074420d91d4c3513a0483b0f4f9b1c834fb3de15de6a9c5017800bc2a79bc0efb954fca0f716a4abaf0a2415682b946f9816dfa861c15f0e7ff6d726fed29998e0e91e9b688442977a19722b1356d817a616a299acaedab5452c9747474376b899390caa7f962615ebfef12d84fc96304a8702fd9b5853a52fdf6e27efa4c679a7985aaf9fc215a0a556a230e8c85b3ba225234bd3b468ff3db649df5df4606ec90f1dd8d233067a112f36f1a24a5cf52253057c9b840fdf11c30135ccb4a6c000b05075c103a9aa23ff08003de9a38f15d4b15d026ff4fd0873fd755aef41abae64cd37c43037811a26962f517b399a625cd4f911494cd85177ff89e844946777b8388dd1fcd00df1efe9fd0ee6da0de8c0616bd360e580592f6076c969f4762a9bfc03fc0f22a1e2da3492485b98d4f4747c0d857598e84f0e010aa5116c29878cca6a335ae9a0d28f757b6b9ae813053b2f8e504a3960119617476875db735459251741e3b1ccc79726bdb2509d54485366488e45c12507b5b3c08658786c61081a17d992f5cb05da1aceedcd4fb5a6896fdb6b9c393075aba19739c91725aafc612620f6d3a6cbf0c4be029cf66e9641ee10d08f11a3d1f3cc11265dd1a19a792970f4d6e48cef91acf8ae0fffd4fbbb07d383fd3f1313dcb28d1814b7e2b5c6adb011c467697a070b2d87ebf758284d7c1ac4cf43210c8e3db4557e30c2479c9cbc5164346b9235bf8177265d5ee73ff6c1606702e56b0aed5776f9715bb3faef7fede1ce2f34351f93f68efc5d3333d840c1a475e9fe9648cdbd0ea459d30c55d229c69f70de3ce8c65cdedb86e2152cd6dbc2c9f033e10120f662c964858bf11d49e1a4e1e0b767a83516b15e7f127f7a0091a8c37d7bcebd5d577e64a3df6608d13d596fd2c0bf4d23e3e0a4a58bde0b5d230bbe6285b139452c81de28fbfa2b089687aefc2bf7e2549cc73cabcdd31e7101d6a93d5ec6ed151ea5d43ca6874300534315baf462c6fe73ed9d26655f5df0a2d959ab7d8208fd52e343d106cac15824948412b112efb41926fcb264df3d69a005df0fa15f5264e80bae42abf43a95b3b14366098805dba3877b86269ceea948e94529d5745b33910f72d2c42f69e42cc39bd68cc26b71533f191bc3accaaffecc8576c8578c96ae94c0c2df6c77fde879a738b92ab771599dd6ad7250169a5d85710ae4ae07360fc16b978e898962666d8873c1d395ec89ab7f3fbcc8a9f11f760f0f0f06c0bb3cbcd5cebcc2602a41f2ecdaf89d0fe971edfb01f42f35510a7a8ff59c33a3ce3a65b55442cfd3e32aa1c0532f0c6358e9e18fa30b8dbea43c02c4265055317046cf05dafcbb93f2f783df4c1286a247e89e39a97d7feead71d1fc8787cc7784c03b6a445e22b590d1bc9cdb753a9e96528d597be848f2293590233e9ad75696c10133ac226d50ba7c4f24ceb42d648177ecd6448400d5327e1f1226f6d2b67d641734ab6a19d41937e9f62e6c58890574c6519b750d3daaa2a37332a3fd6b418d1ce59c8e221c29e0172fa3e3e38db31df7e5c27b0f9109c930d4b435c2db02b89c24d4c18c777c13ea544b9b39c3704d40a67d959da44a69818455d6cdefd81e2a96d1ac1ff9273ca816ffd40e6f7ed7fcae7c01666a4e84bad372e89be07243757dac2045024d53f67a8e2c8c5f21249b800a4a2dc94030c76da3d5e5230cc7ed85a62d32ca9059d1c8ab631d65c8ceed9f82e50651eea96595c989f3b5c0a590cdbfe7070b6ba45877effbf55601deae5e74a17771193691a45cb6d0ef9c585e560954126a58089a17a9d948c059cb1f3f401c25d85170fd5340d45f0c3454f98866ea6f6c51ba9193d306da27ac01a16cf7d65db31ee97356da2a443e820cd8d21077723dce0703f366f87c3dc48ba7cd74e8f8485dc5d639bf43af23f0c1453448079bdce6d4fd310f52ceb9cc99bb6af821fe46f23648d2ed002ae1a175ddb77bb4197a713d9a2457cbaf4bc55e59bac8a7e30256693d02f8deba06fb0899f8b38783d24e99c9dd37cad4c943be73e74976a27ecbeace08e5df25c8aa329359ea810780fafc61aaa29fd0a2eb88647b121beab9f8a87db6c17036a8da67ced175590df54a0cbde86a9fad33392f41050c588972bf87d6f929e09d19b1763b9ca643afcf164d22358d624dd174ed83309df0c7733c8d2ae120a7d9f95eba0c1070a0e8d78ced74075e230c9c10b6676f3975bade8314e84449bb614b335386a413f1b256ccee631b24354947d198b3db6ce63d750abddb0189711236b54a0925cbe284dde377ec713b77a47424f37e90140ee4d16fe2daf2ad34c0f36806fed13b4c5036d5202ff573052267e3605fd0c5c8b7718c45a51a43f844cf2715a7081a1f31ca57e972db46190ce1a460c539b9a990c8dbf10da7ca21d1deca717147a5e00e09a6281db6e67a5456724cf09faaa580e9447c44c0b0516a2b6342a10af1ebbc303899a63268694ea78588acf623925cd5ab1847ae7ce40256280221b9ffcc6ea8d7d5322b2e05055187efafa34d418a79e34d4dbc166fefb32b5579ecb95f009e49f138793a3770ab11a450187b3a921fede13940335f33078f4c78c0e1df9170e2d47c669c2112e319672f5ecd6a3f76e7ebb635bc9114412172e0eacad0f82d5e59d0fbb52b984fc6878b486022f1ff3a2e750647f8a56ca0786a6716775febc434080d31647719833c0ddf24d1af2f2c10e4e1bb23324c3a7bb872e1d62eeae5043c6b888346347d05af68066e914d2b95d062ff17f981532606d3d58a02921bb6bd113548f20c273eebe8b3ecfd52907d37a2e4cf4b5a88b7ee614a6aeb5b3bcdd9a97881f00e13bd90e57958e975851b5115e9fc2ff020d23b204c20e0e53fe7021254f4234dfba033a382ab275e551ed23bae1fb6e9c394154cc2f15c049061ea519b9f271cffdf1e042075821b41175cad34b73c5acd2403fd93c95936e50bfee1b19aaa7420822f31bb04949a9e5275e51d196b89fb4081fc058daa70343ad5e5d1a48b679b5af06a340743d8d3893403a25f7cc0084e3e51fdab5be0f3c95d5c2b6638c5041ea6d91461b51843445fee04b0cdeab7ce47f7590436c6bb54f0e038228c5d14c904aa74f25b561bc98cb706289e355fa107bd65154f0389eafed7a8e3b03f59ebaeaf79d5ac4b15df9478b15dcd25f5257d9f809b2ac6bd9dc63a4900965a69f8395c08be5c5df61d4155c6ab015161779b418cb6a07766f06f21cead971ea3c1384de3d23c0073fa514b9490f9a743c2d34a92756564786f920e21247a9cdaa71271191e24e3505802636e518df2b478e4b53468ba20ee98d961c0bad501096a567daa74d31600a136ce0af8fa845f538a7e28356bcfdd0afda6552ed9b86965f9e3624fd26f05bda58357d480bf6304aaa88e7287af3865edaa79f7e80031eefa4cf3ea858193c56552e3c07aa35e145022c057c8056d1f01404d1464ff0e1526aeabaf0eea805d03c16a996b1a3e3a96061dfdbd5a045eeedd4dec8d6b5ff4e85190664bd36a94a17fb213d5978fbe49b35ab047c1d095ad6e0d230c40be1ddef54602b467676c4f0d4c6fe8b99d0025f3b7e4c20633d16c99d9ae9322963ca548f28b1486c2d0012df3947cfa5557e8401d516c98c206591e3cf80c6bfc82adc4251df6a32fe5379d73e289e4e8f3c85f8ca836919155e3ea4cb0500bb8880616046eeb987ca46d26fea85c1d7af1b1d4365d3412d7909ba473f2e01c8cb097c542fd78e79b4d8c906bf86d38c858a094cd79d329de47ac2218fbfc8928d30e7cf5922aaff429b7e2b831b7282bb0c542944a221b888375722efdeb7568f0462db846219acd3a664e946074ca485a86b8c2fa91dd4d7973840bcd4410f08d3b48d5852fdf37a5bc207ca5b88ad8094121c0f36afe0c272ad136600b2634a6f6f0d536657819c3e64854664c4b48305d8929ac9a27ec573a5970cc9478d18f5a4470603c32e19d2c697ab0d48bdd82a11c885daa7345639de560135fef034b2ee1135c03e44373e27509ca7ec2730a34625f4b334636c1b956b9bbd43b25c8074bb358e0f968c474f7609f83a25275b37544629a30a3980d896356ea0754a14010c3cea65d1ef63a6929cbf87bf5b8d24655e00ad191235da6183b59410fde12cc5a3f3493f2c5be9918650edd71564d24c880d4ed501c42c753f2c6068013573f004a927a6631d38b213e42137e5500b18d590767b07ec26b5d7b7f2cd6743bb25c8ca7ec2cc6b55a398e502e0d73c219dd87e1af968058a288face77e64c2a1c29c8f59cd9ed7c4189694cb4be2772c378907bc111b505288b1d60005ffa0ec2ffc38df58b81200680136639ec5106ec82cbd06668bcccbe5aa5f0a189773685e8ee7f341e834337d67c91444c3a4041c67ff1e8ca2dce1c890e31403aba71faaecc4a13aec17a533369f5a9d5c7f96ae467cadf5da62cc767bb061391328aeb4f9cf93bd756416f8b147fb0f929bfe0f39873787ca09e5e6898a6042ec6e7f29502a9aca6a7b6d45e18a73cb3c61910c6519514b821603e7a9196931f52024056f49ce6a736c287b10ca4d3846f1a0799ad3e859ca369e331a2a5ce3f227cc15e897ef1857740b6b5bd841595f02c1be3ed19ebd5011dce8698435d29369a73b13be78c4878944c79c08d6736ab32ab93074fd7ab6d3534c5bcad82d26d1b6498f79eb286d9759998dd40cc6afcbb98e40ed6b1ba17ddb64751ddfd2ae73eaffd49521e92eabde9a9ba31520c3d4aedcf14e019e1f6614eec622753ca829e5d3ccf19a0702d671a71bda12d4261862505b6fd511ee06c8e287c9d22fd0cbffb5a3cc77af8be89c3c77feb65c0999e713bc17d5c6f7c36546c1ba22120253f5e3cb577c91221fad4131d94be7913f124568a52862e275ac9d3e4ccef35e22d32d550e2545e34c4126346c31185beee26f3a232214b742e0989991179a8d5099a9e95b46f5244e1f1e75b8ddc3ee73e4b075b1ef9176f3b3408ab3cc5c06c5aeff11d789e3cc054ceec2c0c627f4cbca3e4d8138f7f5aa654ed3030d6199fbda0ddc9370980da0d5c31b368a648aec51aa71247ed1ceaaa70b2af6313e0140dafcbadff3dcca92ca02dc62225654e3675f39573bb1ad0701c83ba28c16fa4adccaaa9bf8d3d0720dac23a5168d21d4ed5a1918c1563d93d773871f9cb3b6790019c68c61e3fb7d9850b31f9db695ce5a05b1c92b3114305f26de01a52ff66bf88fcc252e596fc6b0e1e3b3c20dc07244dac4b70275c6b0f1fdfe94e7d0cd2371b509c8d674b18fda97d47f8f9c6e47b8659862b2028d213da97aec2a26596553146af8ae3066b66c09272f82dbb44f4c9bf7fa06d6241e2be2189eed52f1ef59333fc0b1bc7a11dbe17b3b6eff47edf7d2735f7b77fc9e0edbffac2ffff6fdd0fe1d32eed5717b3f6e3f47ffc7f6efd0ec56c0888dd1462c65f54061a78ff4c71693ffb1eeaf9e3329434145b6479efe11f7f8bd1e76efc3eef7d4dfaffb3d7aafc7ddffd4577ff73eee7e0f33f6f6b8fb1f752f4bff61f77ad8743750898de84b2c6e75f0d43d0d3189bcaa17631f0610896273856a42938bba91afbfd3082340e6cc9a380665e7acc4635076664f1c07b3e7b660c19e73503d5daa4ae776a1e2bd20ae11fbbb8125eb91f032f6a63cfeeefbd0ed1d67774b41d9466a01a7ef18d212c50c0537ab4c815e937d02092af0caeaebfe7a98f33ed7dc513b9888d3367a13a4ad6faa738e3d39b14fae203f6d276bccd1b517cc8134673026432895f8469e358650be8dd89f58f5b1c5e67fc3e290e47dcc3a231b91b03f1f05fe9fd892a75a3545d71dbab56f0d03158166fd8fb5b242a64b63e6f44fc9092e69eb10ca2d3b0b2a4ea23fddd40c1327eaaa55ffac7e1c0c175f406be1cf5333a40757e71d50b8b12aa965305ba0f7490c0b8727464e14c9668ab1016a228a37f1dba7d2acfcfb36dda06a7f1785711ca444e5b36534528c7a0c73e20024b65977faf2db57007c2bb2c70fb939723f9130e27e63b2353b598c1ea9751313f609bd537b65407bc35a0b67be458b8bed69d95ceffe79fd5609c2b801f5b30671d46f7de16a501aa75f5a401fc888b5e23308d0790d2bd61e176f6d9592e13d7956e5008b01273b05223e5afa905f2cf60affbec51f340fda1970ae50795d5f3176b856017c96a722a283a3dc1a5053daa206696238a08693b926a45237113ec8e4bb4f81c9da1a18f69cfa19d2d2176c851264b831798895b7f10f632c39d086e1ac2a7a91fa3f79ea8e2498c7833218ee62eef3ef6fd905893ef8485426ea672d0c08bf1e3f823873165487e2c5ea851c42af6cb01cb881046bdf9c0d5a93c95aaf471688fcf2dc2ccc02562e0ff012981a72036e34fa0be194e5b1e467f0eadae83997b6b3c3fb3f5a73a4304333da585154ff63d054e60ff9c835cb5c2c63476669d6f780b3221f16c32bfbc93131b8734f97b530fcf91d9c307b8d9454468b507bde9ea0e37b5aaec885dd9b5f0e9ffc01c0894fd4e785c298cd5ef32589b9fdfb23fb1618ec1774f85535a2f800450bc2209f82de7892b0a9d39f630f1665ba056a3754199b4238f67a874281a9ac8e21f802878e97c3ff482fb978ea2c5be1f38e8a3bfd092ef5cef7a972e01ee31bbfd62f7e89a1f0cf0a9cc4e9de107672521798789a13f7215d0654058007ad62cf692fdb9cdadc2af24b2bab0aec98803509e077cfe40b2c2c754d5d86e3de655d31a1df2a3ae8fbfab52aaa79be4b6f646428b6c1fd3a457433512ce0178429d8af4a0213d55a11750931f4410c87659ab22a0102499afbe529c5e0a2b2b14f2a99343ce4ba39d7ce64370974cfae6f2f06c227d11f49fbc32c2631a4082e8dd12aebdac8a70afdd7448c833b3bb0506082423530af88b7d46842157ecf560c772eb1b51bdf1b5da777afa660c2c444c7459b28738a9894d6578d29aba146f552c260291f241fae56b4035e8cae26993784aad1de9be750f43b18aa9d80311a879e0f7f6b3fdfde854816302b3613bb008bfcfff5f5704713145c742b9330d59506235c36934471b4a9c94b12524d6a971bc2c0b64d60b942f3ade7a089854028916a8d2c5c599d7df5ab48b4999e005b1f520b85e8486013a761d84a2cfbdf5d9d4a338f83fd66df292aa2a8dfd6a0b7425bda060fd11b13fc7d68c80a3177439bbd04d7a9c571dec07def4143509fa06351480425e8a2ef171638943b4b23cedc9624e20c5829b9e30b2e37337be212fb40b0db2b6d5c35733582d304faaa14efd971add0cba1dcb58c3c8d51513049d3168c43923f79dfc3d26c5c04360abb096c010672a6d4d3a1cf78375ea791d9700225603d8789c46cb12f7f2327886e34f888f1c2373ff48f1faadc28330767fbb019fff4d203063b94f49ce07486f5019acf6887ba9d529acf5d57b4fa8d5d27abcdd01e2b5fff007a13bcc9ef6df22ac558988c55a65089d99e476fbfdef45a4ee9d0852702e5cc5df283ad7efabdcfdf6d99ef93cb22aa1f015af1ae916d72fedafe97891e6a56415792bce8acbcc18a1edbf1e24afb1e130e51faaf481faceb3b8d73869e5dae08ae75bc9de06bf3615cdadf58c2eb9bf23030418f26f1869c51ffdfedb57ffe4d6461d777173952108b984cc8a73e3d7ecc4d5e53f10d2841b035f122f16da1e8438994a4c06e9179afe44fa8bbd6cd0128e1b7debc31626782fbd4852fd5f862cef2996c666867535adf5bd482832f47a2da34eef4dc4ed4913609d2b9331ccb9bf1028b5093815711bba33c364263e8e4ff3fc8680a4bc68153976e49ebd17b34516134c128ea8ba1357d88c91da2f67bb1a0e5901be444b74e38d9a77041a303a2d54fd0e58d1e4dd4d4e4fc063b62dd7b4d4b6edf3d89c838bf945a009910e5600ebeb13572cc0c98bd8ab5af9baa339d490fd5c6c15de46a2771f46cc9f0366e719d911e57a358826412b4fabf0864bb9d25517dccbb36c07b572fc81b3b5a52e73a33e25b3deb908bc6dfc1996434c3650d40e068d16ba1608cfca57ad614013a77cdc24a33f2a1a62e0b88a562e6e1090b34f445985da48ef65cdb255f69e866aed60bd133cd129d874acbf2352a19c61a7c2b0d81d831f0cef6c2a5d2acf4c283c63ec5ab274c94fe4db62957c745b021d9919739d9481c7022e56af24d3636aa9cf0a4b0cfe233b1ca9c5e204959e6d48e13a6344de3b33d8a158175e14d263d981da7643b19ece08b962f3ee097a4ef0d6681b48d7b3815b7bfb58e73728114e5179f01f18fc54ccecef11f587cd7ee460c68e8920b3099c8f2503324b74b4678f8ee4aaed721d16cbf64233dff433d555b1efc426064c40442ab313e48660583be12e8fb6cdc04015ab99c8691abdb6a4dd14fc29b5e848c7bf789455e69c96e028ff9fa2a38e4a4e3f42c2bc80200459c89559fc51061960779a632c3f633fece7150fbc826b4eb301b1e5f6838fb142580956a20abdc681b25829058b5c8cdbbabbbda26585f24c28b3bc7adb243b609fcbfd76d09b11c6f705f9eeadeff866bccbe347389ad29be2fe5c797721c1a073198bb26cf7b99e407d146d508ae275036b0c69507f7ffb0b028bcb3b9f28f558c27bb16d6f088f629cf387581c3dab679065d4b4b8b7598bfa8dab796b6d39aab61d1550b54b0b734e3969753178d479604100109996972de361b64991a49b08b79b1cd248f0784c5873c0218e4afa9cf2f43e7e05545546ab6647caba3b06e3055f665756d114f43a2f6cccb3600da6dc3b2c25ce1bcb8bfa5a752a45ebbe0e9acdc6029366620d77bc3eae5c05fd495d72abb6424b9466658d1b6777050be21d119149f8d8fd6c1249189780ec55e2e5bdfe8c7fb58fa2358864729f46ef9a46166881bad1cc2a46df50c9e043cc93baf442b3bc75f0a1e96db3c7e3bd1dc28873b271aaa4914ee9cc4ab1f400b9c801a430dbb8b240f80d82f6c98becd844640e8554359653d2d42c41dbd930f302fc524b7928e13511f3a135b01cefd9a55424396fd36d228a4686f87f69a161085de34d071c201f3b3ba80a1eaa7cc918488d337fda443135ac8ea84c347d1bfd22206b96084a2a4a756d378085f7733f83450333693bcfdf838c62054ffd32c21c79beb678286d0b51574def42003b6ca583f58d5cb95437a0fc2c41d1c3d69a9ee0296b8b12844211828a9e9026f5536e413fabef906fe8182af1c5c811fbdeabe300d6f2358c0bd95a99ad0f7435bb5d59c7792b46aa77c10e5e016670ef4eeee5f37d102bbd764a77179e061d317d7080d626a0aad29f53b8ebaf375933f1b403831d87ad272720c33580ab174de6c961cb644a03f5c22c7a43b13decf8583373ef12ae8a70c40ca0190cf99d077fe09f0452ebe55426792c8201479c8826971ef0bea342e5371612a678dded6a918cc7bd5a0b9a5e6fd6f3e39e0c3b0ea2ca0d8d42004bf6319b02452148c0c22131ad76c26743bab6a5795eeab94fe1d9056c404bdc76d14780d428ce558b97f8228235577bd181a2dc3dc71141bf089d1b8e786213de70a80e5bd0d420faf5f6bf7704b61ba97164e1a4bcded88e2fd28674008cb1356b892e45d402bbad7012915bf76f1a98ce802f8966e74629629a575928d5ae412de5d41ced84ad18bf4eb51c0f017b6c3177678ef9ebcd15ce616bca0ee12a8ae8f599caea89d2eef0b453685e629a488449f0f65be50a454f8ad50c32989d540256300f4b6e580e1854b880c9b137bbdaad06f3464e2bf6f513dce03fd0510715ebc046a8e5777b8a695ba9bad6d6633a21b36b4b0eeb22af91ce04a9dc0d51557d2191f12ff34cf93ea87cd47d756c37db2a81ec4ce307e49dc123aa1f4f741078f6f2aeb43de00adbe2b5d44d0acfbe75a1e76583a0c5c7e365e2d160602ed2316a4070a88c4161fb28b5516059d6aa6a22b9c59c4f3e8c6acfe2e52eaf46317fae1b623425974f7e85414d3a0ea46c41b263af5b22264e19b93b5b7b163085ca418123a2c3f6461a1f3b4a4386ed5a7216cea36b844bbcb7b6ee251c0c930010fa65a6a9073e818c7e41bfbe0e391559f61e1f6e22b87268b0db8dcec5198ab377e5884dfc37156721f92128ec27308f8a0908fa07e823fde2c1dd16af053a1d112a08c565d5dd7b327540d393572d5ad81917ae51b39f1bece4fd01009badefdefc6a8e585083bdb9db266cd89b9abda94aa2bf23e3ac1a8be7c8a2852a33d8bb7c030427a983c8bc8bd8b5d068e4d388e881e59c0356b99e2503452d2b746b73e90f348302d442d45d8880eb95aaa6b9352ac017807247bc9b1a8c50b165d4215c7a37e2b7f3ccf56657eee9ae67dc29631cf22c68a623ec567d0e5367334217d88d8a55040c6efd0991d7c879c20f82e6aae7a843f7146dafe4843a378d06899875514aaa832f6b1010b4bbdda6bb1bb009ac885b1d2015e82d2dc789e8e138c06ececef52748214fa74dad2c9ab24c55e36b32601f0148028cd5df91fc00451201ced4c08c6eb19993a283e636b2a38b49278d866ae920e9c4f16fe7e1df53c27bc23f3d9cf9070fd1ebe56a7bf88ffc38f8749f48d403790785924d6b7f1b048384e5ab4b9d3ca9119ea26a02109e2efd84abec599932d917c5dd98dc0ae5dd552883e8c6e077009b7203dba00a53ad2d8703eae5016a5d901d9ee1f588a827d9e4eb5e241235f8ce57d60a38c2bb0188efce5ef99d3fcc01b107dd40ff4193f82847766bb85b130108866420a54c3386739b34cf9c8a17fc020f61ce8106fff68db26c03330b356bd55ffd62a6fae11c1ba2b9ae984ad3f442cc3a50236786ce28df9ba4cd5d83f106fab5205106a1a395e054d7bf228149a140e71a8f44d5adf6a72f413cefacbfaa7d7d595e400fbb504a505a42041c48a13b85eb4e3278deb36536e32c0f754cb9faba6a16d076176fb11317e8077e361e97ddc6a9687e34d20decb900772d7ca1eb8715c4dd6569f009cfaedf3b810f3dd07400ab03dc69431f2d48c37655f00742d8e1b959f9972fd37e8615c059a315606e8c07495765f6d4bafe8d956fdf6023ce6f3fbebf289ceb9ce40064d78503bc9d26014cc25f43c461857521a695becd8eedb30c9b06cb248cbfe4bafbdb8e879a092932913adee89811103ee65bf36650a5468e0f3b2d504f1bd38744238bda47497b5350a7349b7ac7124ca7a75e165e3edc7f61c8a7b870ea841d1d1baccb90a4a0f50c6169901bae0fb0b112c5594822af8a20a5637eac0513e1ed07ce595c2a1386977a4e4ad207e1f1c0b2bb026699116b1778c837826673e39e11c7a44d9d1814c43abd23c968044d4324e6df216110c932bedfe9ada727e6f862d12b6f06f42a0cc8759408e3488b0831861c5bd33d82e71ac8fab886ee9c5081d1275b1bd6ef8708d587ec29f1a8da6782120c302c6aecdb81d22b0300adfb4d4a625d818c738c29679b384afa926e5ec687fe721682ddb2d2d8b8bbe59c1e163cf1262d976d6690dd1febcbfa9d021010407014adb061a5c6d86c2532f8ef69bfe79ca88ae41a57a8072056182a749726eb75d09c70b7768956bce57b4adcc2ab80efe4a2332f8b314605d531480b9bc51a9487e432576ff8d8650ced370d68c5255285a9a676b8cc10e7842c2efb07fb2b4d7067ec404ca76abbda7717c9db9501284f1a49b158106c99836d4adc48c046e28647f08a0f8cb4221232c1e230437a166ceb21350cae41959014de91274d92b002d2bf889a608ca8fbd5af0ae6ef5e6d5d135900ba4fc0c2f95363e97226202697c3f1e2e16fdb7e384444beb28303c1c291413a43643b1560cfa4be2f7a2262a557458c52033bb6202bb40c8d5fff41e2147515240e6d75f3d1cb1613f80cfd7bf08efd87709537ca7bff4f725424e825633a007a33c8d72d2c3c9e7ea3ce9cae849736ce6468bbeba45238394586779177c7ed39c42f29ca6ed639d24cf8db322a392090a919b76b8ad22757c07bba299712be6248a09c176ef84adfff6c7ee3ff832c9cdfd893838b58baeed7e762da42acea787d7d5e48de889ca8145187f0a8e96dc0a411f8897fbab1dc6d0f8eb9660cea256e61e02f18223e5089745d36f915de2fcdbb53554b4ea8fe1e6f672efef7639100bb71d670b9bd9bc5ea5ec506359197768b01438d43b82435f985da2cdc090cdf46ce477191a48e0da8a730c22a9ca82b6020cd6c0d6d09886a7138a0a3c874f8e4a066ff9bf1c5c8b40a5ef66d1c016c9fee06781e7e78456a7404a40d317fded50d120d6f2ae7b762736608b7573d692d42daf0ccbf14128836fc6be15355a65d4b00730f8d36af26c76d7087fe915ce63857ad1dd5bfe4ac474499ebf5c77b42d83a955714b95ca266511c4a919ede1e4c92ce99d3cae40ee7ef43e56793afb58b5f6b97cbeb1211e63e5f525a1b6f62d9d7568e8c4e5396b1f19bc07179e15e4750380985411f376b3dbc078d9efd89f7181d42f6b02234b4de0f279c3a3ac65f172c204a6bc666324e3485398d8da4e5bdae6790efae72f891730ceaccd73bb17d6fae63fba9d052b6270ed9b6656f0a4a965e38255c447047f40c2bfe488c1a6ce6c60fbfdd47029f64c3cda3367a288cb292ba374a2229d4e04daccf04b43fa9ad6db5677f69dfb111db405d05de7cea443b12bd61077b1d609f305dee7139c06e4d8e3a95d7a74e7051e039f55efb25c20a56d188a427df7aae7c117b900b65ab1def5667a7a0a7f2bc457fd21ebee29f09056bac5d8331d70f97df48af3f3ad627e8933fa3d845800892f41e685f1983d289d407d01ede813f1ad7cc7c8f4142be8b6106874035f7114806b938417d0deaf7d112d4511ce1fb194eac1531331bc06ac3df18e8c7a61abe266a9892bc4c2a8cd1225ea867b5ac14987043a2906c4f3c72499e8b05b767db9741dcf079d60dba789442eba2ee276c6fadcdd9b54330e1c23f91b3a90c193e9c130c9713534a44ff0b00d90c8e094bb2887206408dc61a5549c60e81e62d76878c50360620bbfccab2d3f718dc20a7b43ae445647aacab43bba855c8609e9df3b02c765650db29f1095f69cea93b102e97709d11b465f5f163bd5efa96422ddf3739c15d6b02001670ab94b45ed2077e3355e652a133b01bf8b9ee05ed8a3e43454e1b0964781fd01b541c106f234038471246e9bbb112d26faf02b8a3e0f8ab285fa841375887c7a2a4aa406e18294a84883013e7b3f11fd65aa48e2b80662e87f3cbfa7d85cd3a1e113b840a2bbd7696807ec3db07ead9430989b7da1d39b7046e2ca156b79666382cfebe577f15949ab98434695ba8fbef87a8fc3391e49972466bcf46eabd8423b13320ee5a775a31587a5e41a5a447c0350134b4d7902f10d1d172cd928e1d7b8c16ab99aa470d118b2e3c4369b8aad2c7bdb61cb095a026a7ceb4045cec46f3a82c09108568680c0c594eb83252518ae625f34eac77767fb86bfe86faf8af2aeb3dfaf0aa4743edbc04bd43f480212b5e8c06d616d89d749ad0818bf2a04f9e1aa14a8a718efcc66a5b04934017b112a4bef30eae5f729f6968ba10e68cb11f4a40adc120fccf304849d8d1ef8ad9612dceb12068e4f49267db115c10773ccb2b26e45d04dea31742e23c8b8a200a73082fe7e0746953f9c5c3186b46bacf66641bffc9eca06fa41eb01bea40ad1a4dfd17aa510ecd3571ba26841b069ff34af224700ce7b150409153116b68f789589d409dec1cd98192fd6a0af48f9ac6ce2ec3a4de26ee75893a2f46930e7f07b03235d2c234a185d7b9c58fe58d13e83e6752675a1e3a3c2da9132b5f7e55a593af69b6301cea52bf7d6f008fe769dba07fac9e808d108c2ba752f4116c09fb166c02fa3d0fba63644cff82e58381cf547e703343e14a9680a2aad15a2e1e66c1582b045a317347afa81467e659bdc517863eac777f044b1ea63b2cae433c48cfe49498e989ac9679721e63327d816845bb0bb7f664b7903dab503526f6d24e18646385b785889c0c61787104c21ba18d3342a64d1074d3ae240eeb4bcd517f8235973fad20a64a387bcedc29dd83f502accb831341812a51e62012d4ff3d460f03f07145099396ee6606950e91a09e5a7faa590bbac57104facdf2d5848fce0a06f84c4a9af0ceeffcb28a4e66144bc12171cb66d62a4139321eff9b30da104a1c85538cd838a691f5d9edb9be71cadc69fe3c6d3198346222c0805ee34c7293102dfe88288170cd1efae85e4aac8c858f7c64093228eb67574f0acc13ffeb9029d1deb37e7501400410189d3a4c67d70425e005cf1685b0b69d152bfa9af894f9ab7c15e60b2537f864b37aeb03f9b3f2f79a353f55029a6d9b1a66039ab53d53fd814d21b43b42f7e36a7ca5e3dfb0f31dc64eca037ad53c091d65ef52bb391c31d5c53f7a73af98cc3753b90b0f1e5274d91029c00cf0f1d0def063be2bfc4854ce33be0fad071dfb6c4f4ff67e372dca1af78d013a909949f9ca9aa069a542a8979f25d4a115d7f82c5b9977e5a135bfb1fca5c97badc0562346e9d269c50545bb3166f526a9f1c160956b61797ca9d2dc3261d063ddf66aa8368086dcbdf4c59cd0ed778eeae4dfbaf6a545cecd22f849d89ea144673b1abd2bfd81c8fa77ed962a51a980ec63fabb68b4d7e94ba2ef8474ff93d45b431035efaa9049927c4942d4f69f777e1402c54df2a4531d52da29aa63a6e5bcc7cd1ddba773954ee727105f6154198410f17699af6c4b079d59ef300d810fd90b6ef986e7bf61eccc79c593702e480004fde06fac26297ac2de1e16992dbfe2dff1a8e1d5d0e3ef416a07f898af15f189459692c3e127a694cb21b9db05980028b90b3c2cd16e8ea64fea6d667adbe8dfc323609d18d7bfaefd8a538439d71e68fd486489748d6075f88591ce4225bac8907bcb750942503d9350d4faa4103fdd1cfd9f39f4473887637a577d1854c9178ba30c8b4c49854c7459aad663bd05d3770f5c33c886f9a395bb71ba01d1eec2495cae8f01d9d6879ec471254821e173d2b193a4f1321f3af2c47b5b206ba5ace0360057f06a5ce252ab1480ad9035f5d58ed257bed9972fb20a92f4568b05066c1ee149500323798a26f3132ea5b1f20f76191161f0a18d7c5e9c5534604622e03f955e97bf447fdd9522668a988b10e37ced23597d5e1a1db78045baeafacf6aa42f224317a859bf366198c84d351fa4ceea3fb01bf5139e01c3a51fccafa0f0223c30d18ee2dbab4a6ad60723ef61ff2d1362999e04ef6b2d0eac53bb86e3858e81b9511fc26ac4090a5e0f3991807344ee8c076b25f7706077ed846c08cee490f02b74c3e1db3bfb0bd0514c761e30264746973477abd44e7566412b2b5736d5134483ba6fec687fe258e1f5b33a0886e6917d5cf96ed1aa3f879ba04700d204c90e99c1e1a790134bffd32d3ecaf686dc878b8e716beda618807472251f135123bdbedabdcf8562536fc1febebcbb29ab7263041aadfa510b4fb7b4b567ca1d3e86ec871410c5053e775f35b7442615e4d79681f1fd48928a7760b18a77372957010adb751b8c3522609862e6ff1746252c2b630bd79ff4a15c3507d50239f9e3f9a0a7f47032b50d52bad4f81ec0a517c528b2d4ff5772e70d0eddb24ab4d6789d734e64df70ba97b4f0950a98f8d83c129f1f6e3f8f238aa48a5261253aaa20ba0e184e95074347796eb87e9150089e3fdf2061a209449e5ce5fd476f98f68bdee78281a24fc0e6bc42d04fa3c9d793dbeb34be44752711b0e8d9f57877be8fa08cf9ebdd07c1d799a10d01e58fa9890e1089cf8323842ba0cc6f14c6ed111d2954e722c31d12e170181f6231033acc44d35546b5cefd4bc950ab68aad07166d41d22908fb8545c3600ae26d4e41aae7b7da8b5a228acc858330425ae4a8c5a17ab55f08d80ac63d5334d2e84ef3716210b3f1157108057354db868345b6a4f648de524cf444f192b58009a1e8ae4e69eceaaa01f60cb83bcc109905d619f8ff52ba83f9b527c83a7d9e2bcfc61b0840198e45175d5f17925c9449cf19d42864e4839bdf683a11f1f5452415f78ab151e412911e6ad4ac96a08473b1517bff763815b3b1684bb3c8ec4e65a63587ee50cb8492b67f7dfa3259d8e18b4008563c0f31a0c9065b12091576fd7f1a18a52b62d6ab4d9e1c3bff10dcf822ddb0f85268b66e67abd079c190e2f4cb3a0fdb0c2826102de806b33fae2027f2f697b064b594165b3909deadc2545c0b710b985640adff85c96ea25a13ebcf751825500cba7740f35c128faf7c2944190831a36a5439f0330b6e3b25622c4110570a2387a09c3a540e53410c3eaa8d3a8189477b75259fb66cb4bceadc8231214d36e7ade98d972c401731c932b6cec9b999874cdfb30e647748a7400919a542c1ca8ffb97489f20ef5011d81842db6b0f2081d41b8e74584555b255edbdaed78e0b6284b0be10b48048a27f187e574538cbb588b31d6b3bfde32aa96b4c13c9945cdeb04b339ff836e8e2689e5f44b52c6263f78f987537262691bca385f161493a3738b6850a342852517e2b90dc337cb871bea077839ff63a6d6bec7114f2c3de0a46425c3e22837895ddd230d8c1781a4c64aec51bcc510b2789dfcb9a96f078a45c84b8f53a0c39fb54da4216f60ce70d65c0d065fa8e28ca87f46b1b9ac4e41cfb5e535fb1dc5df7085b1332d3ac0aa244411b14ca75f5e0ef10107183052feacbc5cd500d59fac2a24d04416390470c5d8b4d3b22c7355b4a76cd206954ab94755a6757d143cd3ebe2d2db85b6cb1633492ad8e1ffa3c5c1b409a9cad893ad01ba58ce1001aa4882353709851377fb7d6ca63f4d5f8ea166fd222f2af9a78192b89b52f4b5d4d84d360c9f5bd43a13e487450db116c9fc5c5c4e3624da965e6b5ef90b59504f15560d9f10a6c8a6b5ddfd7edf706b564fd624b9949a60d0215024702660e7feefcd598daac56a156f4d9685f7d8eae0a5fef49141786b94fc5bd9f63ad47e28f7c42edaaf75bfd8afa5dbbc8751cd79cefd7f67b56b7fbbdb7ab6775bb3507c5f7b5874d3b7d6f57cfea76ab668da94d2ac5798bb3cef524d2bf99c3acd13d3d45d7b9318adc003067a5e4903b00e6a894eca4d791351ab9eeacb350fb84d784d52541720b2871e00b7f1261d573c53428920b437ff43ea4a33568ede719bac28de54845b9303c05bd7db4868f7e53ede74be9306cd7e6d879fadfea57ddfadab6f441c55c850a60faef6f70ffffff7fceffbc1eef7f1e8fff2d8f67753b1edd6afdfb5eb73746d973cce6ce4c1f379667e6ad574396643f4b9c49439944efe7eabfbfc1fdffffff39fff37abcff793cfeb73c9ed5ed7874ab5d66f7dde71969b3f98f69cc1a4d5c1a1305b326cfc85acfb350ebe3ce4a21250e8769cc15c8a5318988cc9a9159d3cb5ef6bee699ee9dab47751dbda146c85a9ea138ac248e1f640dadf1f3b57986da2885a0b5129767a928447a564c93aa02b55181ee68d115da5f4f726790dc17be5e8eb1ba1c3bdb1cc3e707f9ef6f70ffffff7fceffbc1eef7f1e8fff2d8f67753b1edb4d8f5a8f205c3b53d80c705569668761d34e7ffa512fcf4e3a6c3e1583fff42ae6a19efbbeb7dfb3badd5fd79cefd7f67b56b7fbbde756dfdbd5b3baddda73abefedea59dd6eedb9d5f776f5ac6eb7fad4dbf2810661843770a20a810757e6e67c65319f988abef7debc0681ad20d01a0466cd3914857428abd920175f39efdb65385e46977b75cb07abb85c977afb6431cd19ab2eb228430ce32628bba5d2ae490acb5c91462adabdf796f4c5c149e5fcc24199c4f0645269f7de9b429d26a472cea8a6dd7baf5649e16d50dc4146a07d237e3ee24dbd2d56c95b3b6471a539e7d0b662dba8edf27db3411e8a3a720e6d3ab076195f34edde7b5f21189d412f214284f85e381bbc38b2abda89094599e6622886a18c3649d9200f6932543636c83ffc859355461733f8850b181c883bc8e52f48042ecf3767182f5cccc8c862623098178c02984c5199b033d639e7101f6185c0b1b91dce0d2677099d0ab24cdc54d095415663f86c3d6cadd6add62915ca42ad465348aba88cb931b7daa050a228fe3c8669d46ff57ded3746ce981b7363ae45da481ee6cdd4e15c4f9d524889d3bd5093518752d0ac29bbe9c3bc8d3bef75ddf7ae33699556999c66da3d5808fa84bce7d89a63381d373f76dc9828d5fba9283cd5c98599e5d9fb3ec77edfafab74afaad4cc954aa55ff77d35a6362a9cea475785fcaffb8efaa326b56808682dcacfd7d3b55f89dfeafffaef8c2ec748bf29ead89a6a598bfceafdfab5fe5a6b57e8234a5571ea9c3bbfd6ff94f56f0c4fd6c81c7e0ce9767880d3fbee755dff31b569b56e3af4733d897433c76fa247d66c6c3820bd1eb9963894a75de81765dd75c8dd300d6a248a8a7da7aff77501a653e98422a152a3146aa2aa52b5da2b164bf681d997ead435ba5a442d2e219717d0cb4be505f3818189b058188bc931320c82604a8a11948138c7e4307641d005de19118b1f988fca4b05f4020ab984442da2d135ea54939c5c69891d842b2cac1283cdb257e28d8925067b97aad244cd516a4442ed08545036482a9d4aa7e9349da653e98422a152a3146aa2aa52b5da2b160babb542da2df26f9eead435ba5a442d2e219717d0cb4be505f3818189b058188bc931322c930965643232d90c8c730670cb28c2c3086b56f49c332a4db73b54915f2446f6052e24d103880a282021c287871432a8e28a2a8aa2288a4cb40144e0d022c2fb19863cf1392011410d613886800200e8068c4f6787e8e160000bdc3eb1a27ad92438294ef8f3f99472e00029b5d82eea65718c18b580738508fb44c28ebd92c1b4b15fea6589e094b71d532f4be48f46b041fa61a7ea65737ee0728e309bd830ea6573a48c96ec560d5b14e925198a30b1a7280285a4c446dd28d1b06136438030a2bc0108294639e8944012a2c3c3c94ea997d5c921b1a0c3c45ca2630549ca669550e6d8a41e7e972b147b46bd6c477244c7c23cb2cd1aa58f8dbe3052b24f41f66fb3d4cb1a516246d92bf5b246a628996c558d12093da8388243c49125371ce19940ec52bdec911e9b204820adb055ea65839881c4ee967ad92070600e24b9350140bd6cce0635542f5b441bf238e710c45ac414a8721bbc3c8738a4c959bc6208de9c2fda006c02d00b22fdc40669a09c75688b6660917a00e6613449d92a291b7ca1e61cdeeb420d739e516f3d3d6aec744dda1476685c27072cc0c42f67dda1061c2106176a3c089c3c4901c4d598f2fb0ebb53819311857db9cf3a74c1b379088e2e1aa8cae0a9b4f06601d8535b3eec0f5669a1005b6cf07f605369993320f29b13c85cc502b0a77e5c32421e866b786bd721320fdd120fe6074035db50a3a447c5393004b30dd3d0c29a1e98e6d3230c6fe10e3f15f31087694e15f3b00d797806d610de609acbb23ed1c62ab9a1d2429d8a9bd0c29c8a790866916400ab68906c005433c7a4022c1238d1539367618d4c80450d6bd85653b55051592cb1a24795f1edf886695e9cb069b78f07e459ca315f8086067926e223c724c72b9c85630edef26c735c72ccc135cf4c8e39d849257e726c3aa1f22c85573ce42c8e790579d6e29ca73c6da5ac749566ae4a3347334fa14ea5f4e61829f39199779a394bda2353519a412eda073a545ae616803d8d336f5b9dcefbded715c4e19a8c39d67109b898e3da0ee43d017bf04cf75aad56c20cccc129bf3be3b2b91c0cdee01d7abc9de112590cc3dce672638364089a815c5846945bb8863715f64981f0c1a51d19508663722c8489309f97ca0bc825d422728da9e6646b655f57be2c96d55695686a84229d4aa6f334954e24d42865a22caa1532ebd6c84569c8056a5171f9bc88af1026c3702c0463ae6c8506822a130228cb31398c81a2f85379a9805e40219790a84534ba469d6a92932badd083708585b569ec4d63ef52559aa8394a89e2af742a9da6d3740689bca804cb75ca96d188200003190000c23014c59128c9d11ce894071400144a60505c54309c47a481582408e23808a11806401886010000821800aaa8625204f073b99f25f659e99ff5feacabff2c6b41efb31443e3feac82e1d47e634d3e4157188fce5c0a6a87c2e4935b61f7d820f73a40b91b76f559585be0c6ca1afb2cf4cf7297b3e83f6befb36ecfa2c7286e8691955b819225b751ec8067d83ef7e9b81b965a3883b10229ebaeb3ea3f4b8f65f90df24c3f2bddb3dc9e15546b35fb049d61c8722b3e9fa87b61e6b23d011f4b69911e791852457cb296bb064746562899f4ae2fc4a7cb3724b664c87755b44b37937bb67eed99fb6c0f4fa0b929e4c90a49ef610afa0e858ec10ab7d1ec0970005c792ae9717a39d154b22e5639129c797139f44bc987eff6e3e6faa848043d909df57ee3bda299990b8a13a89758f6add6b6d4847341c809721052b5410102e49c31a8c279ed209ae90eb0744d0c8f3649c659541519cdb1ab85c2a37a907bf6758a1370507162ee6b52df2adda593f5a47147d8b5a4faa741407e674eae16522444cb8ab88e6e4b92bba65d206895d8d48c4db0ab13e61ba543981d360e18ef3624630566fc3da50e58bd704bbaa88b900d20e1ad8ddc424411726e49ff0da50c170df0a34bcf4d4ef699ce91f67867df31aac00df7514f5a330e708919c9737e500f412cd60d312b5eb11055d0b611ed5c6e0d026d89f2447abdfe44480e8eaf252b7adab16ac10866567d03a1183aaffa1eeecdbb19df785f95280f58bebf4f7b2439fa89cc3d498703f61d33fdb3dbc735d0e34017b1f9ba8001715947d1f07810d8ce0911dc9cdb40b9f3aef732caa3d2951d496abae9179755483f0a9f518207ea7cec31ddfd55a2e18f2827a076f2680a8d3fdc7009bbec40d7abefe7e91e9cf730bf96698723014b33fa7068773630113720f651ff385b15fae75e8f25e2ab811c8447cee128c4b69a47ccc8fa47164f9ad99d51ca1a4a83ef5c1fdc17460076643dd230ba4814cc9f2f71980a2b20049158aa9b6c268ddebb51512509121e9eec274001b5cf557a6f533af6ce213c084af1c5a87dbecc9773ec7693d3e78820daa40dcfc2cc10a9b54a905bc5a7ab2906200d09b12c4f206a491bf84d3068228db2bd09ddf90884ab413d624b88b459a3f3188abbe04d7ebb70b3c6f0eb31fb30e02ded55f8ac96ebf4eaa8f76e603d5419fb92ce670cce2ddd38529f16a728da8331388da67aa39d1b794108248670ace17d9455ec8014741b9a9f27ca8e8a96cd6c31412b087b5bf9d76dbab519e47c57987087510080e1df2baee809e2ebdf659912fc455c7ced439663c051769f237538166259bef75b49bac1d731ac8fb550223e9bb237318d11c3b875ec114ee4266de1171ccb4d8cb414e6d7ded92c77e0b229be3e42164198da8426adf0f2882c8230d5362a663267c9e9c714aabec7492067d4f3a895232454c0cfc547cd623e1c26e3b841d4dcbfbb1d39c52c8238771ba121c11eeba306420fceec5e2b875925079a2b300ad3d4fca846125c49c1d416a1e462ca263469859747641184299bc00d6a3536ea2117fa3ae19818c4cb3e899720aee5905d3087df33e415c6b99bd8d68ebfcf9857107336a14d2bfcc4c75448f82d96d48cc6add6e6a460c25282a5f6ccd2ba63ee450a16e0ec415d30db22c56ce1998c7e1f30a2d1c40103bfb53f52caf2cf0d7d3b585d9df589c900d3b5032a0309d544bf8b502d99ea927e932d19d9d80dfa1c25d85339d7038b671a64c8f3fffbad2a3ecfa89fd8f8b4a57da6a577653c81b1ab19c1cac63385bc5d88ffcf129495fef7b46cf81f23a4e635fb83b840147397fff4ed7daf8bcf33a38ee5f1391e26862320479e7fefeb75f138a3dec4c6a797f6322dfd959ddf7fefb76a16373b9b517a88502f5bb483c925b6bbb2da1326e001e571f3bcae94c23194f2038d4e199a4b547247723a7b5e574bb8704d66ad9901fa8e6a91c2d49912cfa8919d4ce6adcd955a5354113fddcc89618c8c90006f9f4cbc88662db718d13c403fa9618f189da1c57b984bd97ae8fee01639fab814baa6ea01a41492664ee3586faba53355014000f00400eaf0500e0762305ccb0e57169fe279c5142cb5666e4d37e3402ca18e8253cfc5c8650a73849f9fac9779581aeadadaa419ded0682a6a00e60d4539cc732a5e30942caf6472b29c1efb76484d96ad0c10d68a4012641a008a21bfc80092ee17161e3b6137723085ad97086d0bcdbf18050396475201983c15f9482a2c643a15adfa68492a81a93150644038028000e00900d4e1a11c0ec460b8960186f033de5ce12e27fab0b51607d2895a407b903646b5eb388a65d1cf4f2d14e4b5844ed73418af6eacc7fa56f277a040cea06921deb4954d5cb45df152698a7d2be1a542c710557f85a6ad79fc521dbbd39aeec024f655f6b9d45408cc632ec9b7ed08194ab12820f2deadc10f5decca8482549961ed65b251cdc95704fcbebbe8ca875878a95bcd7db0a5cab940a111a8d8ced2931279b04a8a13082d27962a35f3304d97be057835157a3fd92c104f13f300654bf066c61bb3eb92df08a3e43b4de79211b0f01e14bea4e9a421ce4909a00f2f0fb329db19a85235e6e4fd25b55ccf0826e8d8cc7386613715830cdf6b517c6f1d066aa0e51d58e06440736dc87a62ead5058b16cc37a62dd0638aa315e25ece9ebd1e54d7746b00aca9f1a0d478a3a263fd2152d862c228fe0bee0f2af6b953e1b9dba8baaf93122ea6339c7b1b710bec055620d26cb1c8c6e01a0eb000a10f361f74e3bd0adbf060c101d8df84c67d3002406cfe93add5024194014efa20001ba688fd566ac4bf154280e27fa7c38b4424c59f9495bb7e37175cffb671aa9e0f16eb53704a0035f129190cc1c9e9f8e050dda4d01cdf8cad4be4e8f442406237bd676a6ec41d1128da561dc28fb3a272f26671ad888ec451c0676417e0e57e04bc9cb9bae1802740a370063ba09fdf321eb2d1594902f6802aa88792647b8d249d8dfbc0cd00d7b16e3ccf9f583c391c393e60b0646c7c61cebe7012bdab8cf73bde8cd24024d625a46a3c50b7143ae1c158bb8a3f4ff3bfa802ea2e67243749b612da2a13a0d5aa08ab095b1526439bdcde6435147bbf7e8b4894b89f1cd4e233944e20e6c4f15a425ec05f2cfd9b276440941fdd8f10dd1b2e4339d9403593db61500dfe6e1f23a16a204c186fcaef47d38e60c0a0b53411311e7a3d450bb9cff6bceeea0ac35e63d04b4cc14fd4b09f21eb7383e9f34a2d568d5e08c2cfe8194dabd06911537d0777987dda7e2c10ea497c221a7f03d733866b3fa98a5701b1e6105dd7e78e019a3f9328f98298f4972106b88f519c488aeb390011085ef2281d16a65e248c288417b3fc03929301359ea22101a6651ee0d2a4ff37d2ccf00d885b17673199d6df2883d521e5aa9824a726c76899d8c1805df0c53f79fe375d205bf9273326211352aa2d7d9f44e89a84d77419d0f02ba7df69ba57342887ad129e7ede51d32eba5fd2ff0f5e00975958fca5136d0e80236fe2e53185faec9797445ac8255ba4b4e7775e18835fa4c85426311c645a831a4de9baec4f9af240e39a03726f605bed660c0d485b9d8fbfb14496f2d3c1f6f88f54eb5ddd112f47c09235ce7620900325f94a369a4be51837c8f12fe6388aaf438e668168b60a803a019c46b1300afe7d35e69370c8960fa302468b9310a257d51d17d02cc7b444793db54290ff4dda2cbd2e75a51f80d2283529ffeede9a2be23fbbc3873887b1d319872b0c2dc04fc3fb3e867186d5b813447b22ce74cad0dcd23749d742b66fb64ea3a02b6614ab79e3a1f50dd425e94e3385106621d43b378ba161a9d10ab30aeb65fdaf4063efc3a150005afaf8335b5a5628b40c59bdf1e07be612de1efcae842e1356f8713424c412a5b7cfdc644c361e6dbc97ff612866d28a93bc6fe7f8c5d183de1e3300d36fdc35332d73174d007f6e99df3e2d57c85da6bb98630234156d6f71d65b4de7741fb720661eab47a1ceaa0d8e41496278065aa010d53f65f40fb382d028fe6e886d561cc613fae6bd25f3c9770e503732bd26fe3d4dcfe0f5c560bc0d5e6c03b1cf802cb1e6606dff0854ba441634d5c52222c261120b79c85b149767128564600702af15d2d26dfd427d8e3b86d726501d3b51dca405eff10fa68820c8bbdc7e6865527b9a52a656179701cd20436631ba3c2d586ed6e9ba3d197f93fb9675112b88367128ba72b3011254eefb44e85d2bb7e9d789990593c65b3712a88c9faba86df71921490f54b9596fbf2d13da30eec83da707c7a534665fe6acf0015f4a1e75085708628ca72d2f626729d8db9c9de6616d6188e66b3933347d740516306d25b908a93d850005dc46c5adb75281c9634da958c96cdcb275bc0ba34ee6901bd49dd33735be09ccf6131cc352f293c67af564cae86e6f61c4dc4aa06c0171588ac62311743b2e7412495e5cff92bb214b79a29eab1012b3598da1883b225077e9b15033dd854dd659d9e7a56d3a9a842214f01cea8517e6af79316b20fdea939aad24638dd20f959f8c8f5220b780699faa61b15401202a562577dae8ad3885e84f8d64997142f53d59b34b0053b9180d4cdb91d7f692eb585a758aec6990d711ff31dd459d723ce2c2bd1b822de3730ce7d32911471dd892f2a19918a92c19ffe38fe2a54e2da9d0a1e6b2651511d49a602acf2310f66caabacf9fac0f5124b6c638c9a4944d39262169df4564998943842983e9f02f047d34f1dfe2189ea8782e09e2ab451247b594a4f9ef8826d2a837d207590924d72c4d1d0e25f6372e2630dace566e75bf0a919738e324a4b6e3a0ec19989480350da41b954a12921fc184e1b28fb027411bcbf3e000ec68ae4ed7f5664948c914f28c465ea6165f7c58183c9a9a1ee03edcb4bf1f0a3d24b6e1460ae2ed58bf22e3768c66917a465de49e4f9422e273dea03ea6ed06193f2605a6f95d343dc0d47d186e237348bee22fe0caeaa50b1437a1688477018abca869d6ff4933cadc22a2d869d624ae0cdc6712d34d2765c93768ada51ff9def9826d7cf123ffa83b1df6dd114525147307cf0b99f42cf8286a6fd33d951051b75f62c1699e454706466d88caa8f0e21c16dd747a0b0cd894287efd1c7a200a477951a507df43743f59ea1cd7a167baefce5c780899fb657cfe9cb53edb8543e20b05ea151fced431ef3fc79bd58164030935d85b4f778eed3a9f64d623176654ca6acb8eca10115da0befef1686133c7299e9f3cddcaa28e159e892c50352fe31d4fe2acb09cc80c01ca9c6c814a5498d0c681835ae33ccb02553bb8f9d3aa8069997e1ae9cb397fbad08005ca5cc7f5f4bcc8febb6be24ab8b98d6f1e37e2c505d80e576635f0eb4f854735facbdc1b3a9cd54db525ef9477b7c875c07a77222774683213ea4f3260b9c696566cb7788344fda9e21185a3353a9bdbe644722a1b338ce685fe245d47323a90f2bfcec6ac3fb9837a36f8dd7f1fe3cf2bf021fda97e0c5ca3ab65bbb6eb8e64ddb1dd7a53004d8bad4d108c7479b462ec3227a6b49ef924cfbd11550f102b116f0893410203ae04f81ba4ee8e5230067908119ede282d723145a4d481852aacc9fe1987ff01e61a98224af8d011131614c595e839259401954d619546899aef6c3da75af2df94db59659a26823135ef4d9e53fd3dc3e663e42bba19869caec9c8d05132adf4c873da8104de1ae07f05fb4b2cdabb9789a782ac01de833ad061feb31ba555bcc84ea251cf69c777f15475c19782e484cd7b1a5fb5070ade3c76bafd6753e0b4d35fb4c19a1b527dd81bd24f1b0151728153f0bb71bb8611b201c3070ecd54133fb149e08d44553d2963ba1331cea40f096b32e70095dc4001d68c97f46c63aa5e1b56fedf2db182923906ec4c0ab42fc191030e2361cc2a4cc89ecc0796c55185118a3cc9bb8d8a8f1be34d8121eb58f3a5e6e2fdac780eb4974f983bec3302884fd632207fb7096bbb0d170c527f1f3d9ff043404d4aa5f17fdcea644df84d21990d2746950522b94d8c4b68031f4e18d276185c7d4d9e666153b9bd0c82802b3c9a08753279706509f2409e5effc6d114546a8ac3019cbba46bc4366060b91b8fbdd22661537acee98df14753d50e5b8f973a2fc5eca63c1ef4c108198290c2d578c6238fa6a78907c00d9164e8755b7fd4ab2c7e9b0c4cc5e4049176e7272b0796c10cf89c4341d628ecc0fc3bb27b5d7955793b8311458a33930531b652701d24dba22fce50a8915a33a5c643288c671b5184c616e686c6e9a04e80a6782a65f84bbbbf7b39cf08a6eba1257da6e2185efa0041aef0e499ece72246705f89bf2d71108ff98f397a2a4d227f29509fc69a253fd74a76979597c4cea9dad6fcc99fbb81fb35700fabddd55438addedca1ae20b2fe6bcf089c2966f21267b81ecd46156969fc5fa46dbe5b9eb6c494fadd46d4a4836b5844f69d0c398ba15023a546aa868750986733b20ccd2ccc09ccd1419d00cdc88c8948d9c52a96cde412f95431d4071a3be526322210f59cea70bd9a206190b1ec6d6b335922cfe4081c8f3f4bbef06057a02a559e824e99d8378d21b578a392d1156b7f41bf6b335588aaa5e4298a12919f8dd123f5b1561e612841e4cc8e2441c87d531f9524b53b7b88590cee5589ca9393527d1d99cea25325550a0cfcf43683fd748ec8bcc8bc90cd3d0c584120194e30622883719396a3f0902403209140527a00250b7102b15f01a703e6dfb815fab06d8573f43bda38969560beda3df42d29248ac7ee18a49864de6ffe81c0d3f83eedabb6bec87a3ae80be2d01452d86d547d5c886a12512a6b5ce88ea58ca41141296e310e2508a291512967310e2e69ed492429f5ba866f56354bd27323094aec9b010a0a053a6050ca41e3069ae9b91f0d2df454b9cac393b35fcf12a408d7c30b6ec8e95eb450bb8e93c6e28e59acd003c277f44e7f309200089270751600476002618ae11ce7386bd37305085a96f954135dca1ea1768981590ae05146a614d9253ba594520a6d0394039f032e5cb08cb8dfdc8bfe467990d5d3888b08263059ddefd0ce53710fcf50615f8a7bae2bbdd535dcc3bdb6e7e3a3e1c4fe8665124c9027a0005340ab00fbc0bd1bcb98c7adf5b6b72dfcdc9e462ae8b49f998fac87e60224f39092a4a4a41c3a6ad4a88123870de61e8b1eaff40f6cbc297aaafee767ee2387dc877d57e896beacf4f4e451281ae5103a9208b66c2107044def6dc3a336d0d96c46e3a298c4933c0dfbead8b95e8f61d0e9743a2174ba5c2e97cbe5f6de3939688b2466198dad7387e97558d1630a685f84ed28515040fb1b96c362599dc3ccd19c73be330f9645010318f7d8dd238a4a38c618176157e210048c3590c35803b9f36c85409b6d869996dd174eddc7dcd8b33d7aee53d78888a036bc6d5796718f4a64e3e0b4d96c428e64e2dcb3aa75ad25c4f9356cdf4b81994f5156653831ce332dbb50a7d6b0565667bb6717e95805f9c985b2813a553e137950d08062a04150b481731c677befa1cf078d1a47435608cb830a25dc6acc1d44004ee9f1c4023d2a500505128ef06eae3f4f14f5616518e3dbc379c4d84a1d7f3e520787a2349acd66e6741ecbe575ac9563b0117ed4372eec4b8dcc0ccaa233342550c772ea3b6f31b8322907dd64e0628e62d81c84358998bddafe9b58e1d4a004fe0cb44e0a67f6a6cee665715e025f386f31b8affdadbcc5e052d038f3eac66130dfe23f5de34060d3c7d777796f7cb9dc9cbe35d3defb8816d6e2745e0e4e6747c551ff168393096619a89f5534c5260ff7fcf41748d331ecc92bcb3e39e80618c341d30fc6679691ccf05fdcc2fa3724ab19037fdf47c979c3bf8ea79a88e4a0191c8585b80903f3f8b14fc7b43f6b60372c2401fbd1a0b01b96fa9b48fe3330bc94550d83f1327e9966c6df348f8606f59a978374c8fc1a1e41a8978eec3450376928edd3437ef634e8ccbd4138f7c9c740ad8f7e89a3ae90e8d5248f8fd02c27d59d62fb61202e2ffdc6087f5b4e7fd9cb5ef6b2972dd2341b4cf752bd94d5ec6552ad43f3b26a82c7a9613c8b97cf1e06366e2c17274b99eea95d756559eae79674031991eedf1e92ee977437a53bb8851b6a47a5fb49ba7159cd4739967dd2a874de4075fcec759416ea981eea589594d4389e278a7aafe2df6f99e64fa6f88fe98df257a9e8d39b54bdab6ab3d96c36d5a32766a5f24765987a797c62aa1cd33c170fd5335c19ad4056c2bba676c7c344f211901b361bf96fb0c9d28719a24248858a5e433f2f301e355141684888e5cda18ed3eb2f8b5f59be3227449c70d0644a93274f1ffff18421a2a1a12541fd6f7049d3fe371855e58956101129e9059b44406cda28c678c50a9611bfb22b53b20b3a71e167dfa37ea58fe684e7644742c67983514affdf6054d2e757e8a22cfad0d0d0100b16324c56ac58b1a2458b162ca3162d584657967d0b960bfc8b9864f99bbc237abd007242a118dcfc07df42a4cd7ff0cd37d2e63fa0726cddd6e9b4d66dddd6e9469861efbda9dcc06e1a8c7d1fffd81ffb637feccf0985241b48f64a1cc531971bc5205641ecdc46b28398a103549c9cb0d8b0b1d9ede4e474c3af0792edc4ee1edb89dd3dc4511c5722942dc289fde8139db638ecb19dd8dd636f91dd620f2428e472b95c2e972301cb0dbe9d58be9d38df2cdf4e54b0006d383939e11eeefdb0f9e69b6fbe3710bb0812eea9e963784f4b4d608c9198909890989098b416e680c007507c8b5bd4e9c421f6105cb73787da7c081970002218ac4a4ada9b236d1e3c72a408d30f28be85489bffe05b88b4f90f51e41b76c5e0e63ff816226dfe836fbe45a4cdc51f5039b66eeb745aebb66eeb74bb2adde642a4cd837b73a4cd834054c590140c06b11831826ab3b7b84700d7706f2c815c5909443830a6025c72de7a8bb3de7b73ceb917592ef68ae562244bb3b346b49c2e2f30b48ef6b697706be270c7cf3d0717872fac01e4b5c9d55f20f8b0af9b1e96bf9da6a7a9981ac07b5a6a2285f1c0706238396a4e89203b3e76a6848e8e521f518faad7f65d46706f66a86b781ccf13457bbd9b382c3fee85a0b37399f47627b60387c7b31135df2bd5680108207b592d62a4d98001553c3c26521e9a166102073949df5b5acda4f93d12fbb8b5de9a30f4e6df70ef9421c8cf7d97d5191d643e20bc58f63543e533b399d15cb49a9f13246e1a43efb0206ad435337d110110100af406458a2099a05a8c2b58b3a90802e30336f556c75c4dbed577d9055121b4e385d7db2bf3f1704e0011ad70419162058b73d758d05ab46c1cb457a4ef6d8611870220b708c5a6a217b5d8039c746b788795c42166fff52e7708ebf02ce35e0ff7b08e07cb8205cb887b165e489f47ad217178336d8e7c595d1ae221bcc29df1b8b20c05f74e5f8ba21d9dc7732529a27d8ef46fb887f381abc1d1fe954441a311d96c2c584500a041d0a39a10f4d5e96259de1bd10260e21e8a7b29f6f95c919d70626f4260d425e849ae5d7619e92dd083aed477596d03ae1a9b6b5323012a1cbcd5b4b171bdeeab063bf999c05bcd57cfe98c55a73356f3e6e6e5f2d79fd9041286b0abe97d2b75d9a042b0b79a69d7298ec23264b7ea1a1e885a4d9caed39c0353d36ae6749d92547a54ad668912d909d207bf1c1d1daccb409454899d1d221fe8c0a783d551c8841b7a40dc8e4e714e31c75dd9f65d598c65b47ac07084d5c0d01bd488fed6a08303c375c6aa733a963e72580323b320c214e0e2fe98effbd817694e444eefaab92f01f86bc07d9fc36e0e107e195c651971d8c5b979d9b86a4cd3e45bf3b486e4d0ae39bdeb8cfd009dbd2f6fde5db9d572cd9e844fc271244b9e1e63a93b9cff76bbf9af6cafad6c75ed5cc14ade2e5b9beaf282736e13a68219a2dfbb057fbe5b9dc74bb7e7de7c6119f193f7983e8e322f21426c5097111a12352ed4e62573b37fcadb1defb539845204ae34b639845204ecb3afe4b910ff86711e5f812f711f5fe22b7c8c2b7d62d9964a7c4984f11ec771c9394b6071b5d2e93c31af5c5a1e6db978746c8db07375dbff9b3b2cf29a1a6ffc79ba47acfc981a5989e98dabbd6d794cf4c87cb637c132e23c983c73ad6c5b947bdeb6c0f376661a5bb642eca97f79abf27634213ef6b7b2d5b0a95fa666b9f4cf1c9f9f3dbf91674fe7cd8fb29f7d8ac379737ddcf6fc7c23d7c61cc4f333d7f1b38bef72508c9fc840d8c879dbdf47d66362d6d2ae720a0e0883688050f455117dbd1288b106e7c5000a3f19c01a267c54e322c29b99862461f2fd9ba485debf8d49987aff2661dafddbfe99e983eff26f920b64f998b3b013524ef262a8e158c53c01e5c4300a8a45479ce9ca0167bdb9b81ac9d2ecac11f8babcb8bcec92fa0b0b63fc0273009309066db1f6dead9818991819254d323332334ab2cca03328e7600411280d4a23228d66076848d09058f281922823891a1235bf254b4b6e2c1901edbdf71ad79227238679a1a5a5a5c5e635be6e5e372d31e41cb5e0cd5e8b89d2185c92e5c6df78263b26bd8dc2c0c0c0f8f4c96e658989898949614ca69842ccccccccc070c62e858686860627c7494d4d4d4d4e09f209239141674767274ac798349e3e76facdcdcdcd8e3a6e1e6a4c8d19d929c43a4e4bf8685671707070784c942851a284899ee06e1902b3a6643e321fa82c50533f2553a2a28c3d8acfcc67368589d3c86232a3cd68bbeacc00ed87f613958577fe9cf839a1046a65036977703e9d003a61026dde09840208484a6c42b33a9bcd6628502c9932fefcfcfca448b1e437b2a66073879aad5623973a8e97233615013b58bf76cf8379736e5ab8a471fa460ebae529dd3bd2383607c9689f06a293744b964d992a6799fa4b1ad74f8dcb41471aa793344e0bb1f8eed0dc00b318c4c841b855ba21030116f45ec0b8a0c895836e77a86e1444fd244b1e0383de61318632eef0ef715fdfea07f3055996649973ce3973929324274b7e018c171d8605305ee420b62f0ee43ed3e45244383007e12fc3a73815cf926bd70a8f07b9902c7f40f8901ce4f3730fbf6ffad29bb963d6b87f5786661567507ad3df4a6f6eee6f63c9819cd6c753b5115d2a5f8b2c39300711d9ae2bc35ef4636ac276745f96b9ec300e1cd23def7ed5d3ce474ec2ceacf6d5b87b99c70fcc6bde2e05f84bd22c49d324733b944265966404158ce379a2e8d5365acee86fa447b3ca928084ddbb5d0810650839f6ab5dc61a190c8021833bc220e07af0fdec6394e408c1a8a63bab3b58bfb2fc985778454b8f585ee757168c05d2934841631c61991c4958ec0a5dd2b35218a351697ad284e528c1d89561df32f388e5f5f47a7a3dbd9e5e4fafa7d7d3ebe9f5f47a7a3dbd9eb6dff7defbc3d5576bacf1355d449cdbdff80eb68d805c38d92bdb5d21f3afa47e96f9f966cfdb2b74c52d8a9cf3587be894638174ca1d11f9dee5a09cb50fbfe48940ec763b24392a41296c304a8f223b1f8218af5761c34652528dbd7a0ccc7b6f947f7d06ce5839f1c537aa2e141fb5d67bdb08b94d7b1477705929809973367767f638c689cfe2dfd4ecc7eccfec3d8df6e457986fce7750a4a10617e201e7ad53d7aecf037d470e538173ce5b3eaee4f8d89fc12b3ba4f36c5961e10a082ef01882340941b72e56723bcb4fe8851d4a3624f9546822c62004288aa2288a220c4520e0ab4cfd9ba7a28948d9061c130ece3358e58795950ebef62d94562549950f14c15db82c11e67b56833c3cf144241981ae1de561ceee67ef8b5e1c75281ef8d95f906b98062e188525c520ddc3f731e238cd40f43dab22cc8be6e83b89a288b3f4254c2557beff7437c159025438b26ed5acf98722bc75c69ad3192b962a52ab39769d6e283c44ad26e9036f35cbaed3adc394a8d534bb4e3194232b00d77d8294e16ab2ba4e310e138032b59a23ba4ef31086dd6ab6749de61d4ca85acdf3cc51ae00757ad7dc825d46fd82aeea0452c2436a3561ba4ef3ae06a7a83537d9416abd35744038b5e6274fb8354749a169cd1b80f25b3307a89a569344d769d68146d48a9d503549ada62b9038a854ec9c8aa963030000802200e316000020100a864442498cc228946c730f14800d597440685a3692c943a13820863110033114c3500c08500c8331cc38a39052790b06ef7603c01189de3639f212fdb5dc99e0418fba4407b7b7da5f49f0ad1c3216c30ca8372cfcc5293f042cf120ecafd1298e0c37e38be34b145e988f1a261dfde866cbce9d510ef9944592345140e2c2b2d67882ffed0871468ebc3f1d4e52ebe59e1155df5495e284a8104f74975fc0a1c3ff5363facb2b48814e9e0265ce73267a61383036da3097386e4cf603fe9a8426c620f88bfcef64c038e0ed0c10ca4da507da3eb5055dab9565b2d06eb55bd072bb8a3164ea073a1459a96d957e1aaa4ec39a3c3136db725bab9e15233f79d1f6cd1a84d6019b4ad7626ed377830228626403184df78d591d18c3d630fe456284e2ed64eb42a0416a598d9e1043c80d75933e8e0127ca19a13fc2c49bdfb134ae52508eb1b3259db0cd0b68b5e10c07814f2e0f35a7d495a5ba0504b2ec4db0c06889281550f98b54e8572041ee1674b800a40fa81510a82c0537b7a60040d731165398a0653f5b39a2112524e8992276107486fd383ad377711e71ba5f26cf0c378e024a6786cc3ae0c2003f78c055bab0e77fd577f27a0b5be62d266b5f1dbb2533eeb82ae63ae6b4045e1aeaf36bdbf2acd6968749cf127a640d110422e145a9f8567f87ddf0b3dc9af5da6335754a5a7d10702865e21aceb07c44a6359629fbd079cf12070b632d10947f73f33b40688b47889049c6597004961af2c4fbc4e5190d490ff4b39dad438a50d13df70ae9d611236b9135b87a24e0de549fc21b9170583ab628e03c6b04b9aac3b683f85542128fc733f78a30d37e855d61c652cf46c5c738af38e7d52d05caaa66cda3a4241b12d4b47a743e3c53568ba83e5f11de6476d3f3cb51bd441703310efde96eebe2883f2046eda54d0757abd1f5110bd241401822175aee07188a25a58bec87427b01c49a0e7c350d88b715661b2a1a5ca34036642d4334478963538fd2eb741d30d6391807ad894a6e15b6085f74bb0173222c7fe5dd28a2714b31ecda409b85f05de20baeaf699bca83365b90b65b8e06c62c01ba02a1900897cbd335f7441fd66e70397af49cccc2ab4f2ad8f3aaf9260cece89f54a08be006aa23b8c6b1f1f2df343508e5645f2c03918cb378a2ba25fc6b6b2fecd0752b77d41b7b65c5be11eeed424e2a52a99cfef79f7b105a63d573e1615d0ca3ab03dab0a11a97c5e31bd0c08e068944e8c3047bad12e47992b540a22ee240ea8de994bd3e3924d35ba8358a529211abbd468633dc67fb908d7fe939e7998f73acd84106136f0b7d2c7340b04f198677a3fbed8f0ea8dc5a4caca1cf08698b89c905daaa711bee8873649e4cee4fdc0ab1293a64b5e04374effd29205209fb24cf411b0d6f1ad0b51c0283c230a2704ed3d1f8fb1239349c5f9ef676d0c8498d2a9e05645880b1efddb0bdadaa247e3b8dcc98999c8aa0f666cf76de890481e69c985de28dd555509d9442136caa14f189beb91a37a71411ee4d7a3dd1050a369b1ad08cd0405890a63dabfc29717cf4c035e1b28cdd2ccd3fbcd9d49eb4cb850d673f8da36e55ba3be954968d31cc1441b5319b1654a3e6438c6077fe19defc2c041d9a9645317defe1ad867a4818e98305cb34b4ab05e94c99a55f4ed084ece3a32ee938f762fd96bdc15623798927b588243600b8f312baf02b2c59ade03a0bbcd98f2f71ba61cd5a922719cc29db93217fec9ea820bd999cd48ecb4880353b2d0f981f72daa7e81e8a91e464e2b75ccfb5255104447401063c5769df2defb646cfbc48b652c8d1a2ce9ea7a20cdf11d334b6a8b8ee96d12c39a7e01be18036a7666e3459cd7704f40c0a1847efc5cc7e4e797d9393e2c4bf186bb8451c40804555d985d29c188f4284770845da1521f8573c6f2e01ebd0ef640e3183bde11cf186c786d3760e650d07c0af7c438976f377446344e7d8861b90c188dbe6522dc3e4066224029eab3dd6e5cb4880fbdbbd981c9e4b3289bd1fe9540ab0fa92be9f668c49c8e4540eaa71ca3a13c15872ab5474361168cf7eb1c6740b008ccbeaf8ca1a003e484539a50a8e93f4deb95722dd7e097ac3b23c5a7f5dbcc3ad1851b286d50c19f05e2cb81ec6ba545f5456a03b8c91bd3884ca5414919a4463270f264700ac496731aae2884dc858f96441b2a0830d2a6891127a3847436c9c711420af1160868a48143d5c6c8574467eb61e6aebc9281cda3d60400be2df462bee755581f7ace1376009ff3e0944d6e8c3ab4533040a08521a1ea8f05d63f6464ee397281179bac42088fd2870ddda680bb49feb0f366c3e40bac715e30370072b8bb9abc4997cda8cbce00ec6a8d4e290852fb872e156950592bcc018c63166a25f4e13c7b2b012fd7471819715309173592ded240f4e1fcb3f81c0d7704258cae3847e64ae6755fd4475f5e2125559399959ef9f05a70879cb3feb36ff91ce9b94fb09510e59953397d7eca689e7c2caa068d42525cb4b99d81f530bde6c723fbdc6d5460dbe769ef863abff0883a04444fc103626559785513e74daa99efe444bf8afd19046002b9dfe8459a955ab1d6de82cd170572bb95d48827a4db3986d4ade42899851834d889901351039a64bf15e13f7d3697ac1ea4294a52bcfe659fb46afac3cd90b96d31caad4af927095f249c533901dda18f50207c5a1102412b36398e5c44f00758915732916cffa8289a6956ee0ff6cc040f5034b71ec1b15858d0f2433df36e9ad95486e6eec0586db75d76d4035d61a51d9d9872c5a5ec6ab103f9e95448790368bc9fe0dec075e7685df800f21612d137378baf47c1d0a875d28c43c1808d98a99c62291c6e0369358f867782e8d13cd322331a7459e0eb4808ae5daaa40295f9ce09f3dd7e890b38c887c037bd09c1657018b03e01165528b1ecadfc8d5d8c5cf6b44d5a272a4499a02523dabc1c37fe4bdefe6b3dc8dd00d4bbfad5cff58a31c86a5d8eed537885666cc7d1efaea7b07145b3eca0a1aede8a8285535ec50d5e6393f4fcbbd45061a8b8a97e008a804bab46d8ab9b6a1a713160ad11d4c36e612996cd86debbdc7d985d7a3b564b34ba991d7c66fa40af45c68eda570c4a9763a36498a00a1d20ad8ab69b549751c5de9328bad724a11d01af899a529b4b6523ae2543b0d9b1444243c30fe9a7521e512fd2fed35291438d7928e2d09b13c0a8fb3fdeb93ac14c78803f542a56da406e7fbcba242935a1a11297af8fc4acea70815fbaabd94f288a4aa9b9f3629ee8f1eb5fc8c56983c73c0cf45ea2f21639220363b1bdff0b2a09a11da681e90e149fd74fd4e64257f39e03712c6312e9915cf71097d4277ac8197896446ce1bbc01f3b152c5705c2f36ede3899aad292e73d907e110c7f9d28af43751dd6a713f6746111e4afdf483568b86390e108b0b455852cac457d8dd2eb4fae943ce6a29b0aad0a05e78e51b7e94842fae3d5fb71f3bc14ddad31301b471f3372f0618aa8642025d136e8036495f4aa40c7133bc1e82b26790bf5cc9ab4e338cd23accc0b40c144d29a342f68d337fed661118c42238495644b67afa3048e19630281974655109f14c107099877be7da103c6680f41847487f4f102f8c7a1b506164677dd5126855128855c5c1a3a9ba2ac9b7b19540b537d751d97b6aaf6de20e9395f5ab502d5900b4b9bef97f0ecddb2f90063ae85fad578bd31dfa01a0e9aa55111b88fc70f300358356d39c47c166ec9f00cd6c3dabca188f39c60e9c0fae8e7577a73459349d21cf4a4317398314e618de945f78865b6e080db6661c2982dba9e40cfc8b0e678fe3ece8863f9b76f5c13886224278fefa81e320cd4df62f436af885dcbbb85bb7d05a64bf1f4cbfd3c4ae9815ef6c23ec75551652e31285c1dafd02a9f9318eaf656785e37d8b704d7dc9b8e410ef0cb6eb14401f0d0f9e04757175a7e5900a4d9f60a3800631774f9a4140183ace0111f87daf2afc2c1fb5aefd461828e5c44add667eb423a843d276770ec4cf540939a08a9d1cc10da9aa5579937117acae73f8884547c888b64021dbd879bf69a6b131db28217b44a06345f45b4fb7bd2798594e359e0e6c9ab58417a8b4843f2ed5d9b33efec45aedab8f9c389ded7c83a8db127aa432ffeb4917cd0cb753eca9b556991655a044f3213a0e982d59c918460e8e3f63fdec6e1654c666eeb95fccf16befe3cd35494546fd25cd9307ea92829a215d22260d3b4674fa82ec121c42ec5a26da4bae266b8a8ad0fdc19a7b436ee4377d689db1bd62ba76a130c65b6f30a3da8cfb6cca4881343457f245aa460d9e9d4e507ed37216ffae2c99310ba467c9be26216afa5e72708b07fd6cc52a21b19c3b6c3b57f8afc19ed021a37f02ebc8acfa5520a29f2213718f87e26eb88f6d3075644c368365cc3cd2a1f132ac369da44d806b61d76167a883292b8ab781144fe68881ebe3cae0dd4b1798695ea1edc8af02c85583be0672e078073ccc0c0e40f2188990566e4436ba7ddda43fb3a556254bfae1cebb9412525a52a35ae24fb702c5c3c04f2f779ced70e904940ad74d8a671f3f59787d5cf23e8d3a06ddd418a25549beb327faddcd843bd2e877a9299755e9718c4cb128d79ad41eee003ed147f658ed3609a03722d13205a5a8a808d46b8cbca88528d403b010a3979099e5fc4b2e73e9d75f290f97199c94adc83f49c709618619ae409d54675875b2d6192b1266c9b6cdb498759a7032595a2eb325fc1f6a85895c6493471559285d5627776d97380d24cd0d2278eac4ab146700fc96a062277c4d47c31fb16e12cee4f1397ad1b01fdee3e9c56c1fbe3a3a3433f8fb27346d95a163bc7cb6a01314f53efbfed9386251db06b5ff6ce46468469b3f14932fd96a571d25d1bd6282692488f57878b545aac98fdf649b0fd622bbc2412b29feb91e67f605121dd70b668c304fbb2eff2c401dabff6289fba7dbcc7a717263657a26f348fb8535143122e2b356f0c1a03236e0ad7c3951ec273c90811ee79d49e131052c29838a2b4219020cfbf779e4734e04d5dab73a2d517417a4659a140bae010030fc5099bd38785a47a7d3c94a34560ad46a155f0f993eb783764e15a106ac06f75b95e7bd507a149361a3cb9db8cb376516a94214e5e1f0f08e2dd179ca6ae998be6e74065c0f240d8754d792cf93b60c249a9f9b1a9090af19487c821358d2e61745a7899fcc0386abfc9d49b5d3296900363891971d7f240b3e19e98bc4e4abc9febdfae996c3a5f4bb6eeeca7881e7a7ef196db941da0e63941df5218d0c46455ea32d9b0fd745c8eace3c4270318a967cdb673d2ec2b9467d386765f3bb3e479cc234ffd75b3b0e28f8a8edfcee25ca8ac41beb461b84a395f1390fab48b3957f5453d58f5e0f34ea90ea6a87a07e87e9918f60b8a55f7b26e9a611ef69195a6585aef990177c3ae4174c11c67885e987c9043b9ef3cbcbc40b911a66ab700da64042fca38a5ef822d3d743738d95d5ab37d2fd7fae491ed30f2097e7a58d0a1288635669e569afe98d017245bba24e0c906b85868016fae30e332f5e5ab1fbb9da5940a0d2fbf37c709a5233b0e778b29a39bf1bb5068c3c904a9cb4ef9355f72ea6cecf7cc3298b5773d7fc4129b029440fb97525b0c0c8ca77946f0661302e41dc3980d498f5c4a209fcb2024d72d8b9e67b0751a801da389f3e512186446baa0234a1dea8e7d8a188e52e57031a39de9756b1a358880108aeff696c0816e115c073c9ca5b3f5920438c55fc48652930912d8c48f4b262d41d49b35101ddb001d147a7ebbf53abbf1490e9d454af6faa95d9bd8ee814046d6b172c919bdd434b7cbc7d4966acc95547a446b575c31da3ef3e20c6d754cd3a3c7ce2f6c899b11f4f621c69a036c1bdf8385fd14fe98b11ea6b30004b56e9305edfc10d061ee9d2c60a773acf85374a47e624967adbb92ffac7d66dadde91b41a4f469786a65394754c3613dbc4b4139f3eab7de0e9150461d1aacdc5aae71f1b7e9d15adaec458ae181f7dee38752d8f91a6657a5f24cf8a86f87bc3a36abd4085a5155769367f6df0ea5f635757f91afc4675402c135594bdec4aec163f9ab26634a58cf475066625474af6e6738f409924d966d9e095cd58ad4819469a2cb2fc6258dbba630a9d7ca597ca03775ab2c114204c02eaefb6370fdb55276ed86daf6ca6587befcbcb0cbbb631c2f2d5f07c1929c6e4f81cc20d3e9c3d7277ecab4588ce7712b1b02722052668dd9d7fda67fb19e67f16b28ecec9c9cd7d712c7a8b483eab5bc7b4a32c71de778e5f1c786d1e8d7e91d8fac5d6dd2bcf3e206d5a3a2b7d947d5510246a22283ae3466a3cb095ea2c8e1891680d198866947c6516d5ef2e2cb75261b282637e338a1fb899752dc88a7acf8affe0c586987a3e570c8007bb7c31e89863ad576756470ea23d8593b46c12e5c03cfe1f8f99e0c8378fe64c35c676739369e5851f6ea768a88f53a355b58e5124ef3b4e03387834812c6de99058b589c85c0143ac7689c0e0d744d32d859bad781f00ee5a929576a9282949e8669121a0c5718d1dc17b05760049428feb359fc1a0cc5786d3013712edc991bb512fc4b33756ae06673278894d2eb7371114eba9a710b9151b567d586fc91805521b9e5b6c88da11d8bf082488b049bd5eae46a6b47000fd546a22861faecaee776d6e2aa9b839ce94237b67a44f424ae1581b29824ab12d757b546d9df77a4dffa3b88842a7488efc4941ea834dd96ad7aed4d91b8dd628038e06602c06ffd56438928f27c654500df180f15a5d07d6bb27e7a04d676b34dd92ac5541f2dc7035644222563de2a7b4fec251439c0bca10cd0a9185ea003372c023cd5cfcf769520333d964b66e02077d650a5da27821468c4b59d15c684727d2497f9109cad73f4b03e15f35f8bbd081905e09d9a673b5c045a7c2b61f43087343c32183ad79bc3fa646008ae17c2b1ad804c58cdc3f3206f9d9083c77a66cbafec2a8654c7b24e3ed00ad002b44c055fde4118849d2b1c84a949d58e2fead659347fa66fdb4103425653f8837d37c2c9df451fc57be161c5c3821878d93c462178d178b8e104ff2f92704756284076c0c21e75413e49ab5f64ccd820e22a77b0f38b14db5adfa63b4c3e1a0f23340433cd09a3d01e8cbb310014a45f94d420ae680080beb76598aa9e26cc5e6746ed98866cde4183063c80c1925f0f11ca33742f1be3627c3e4812b1b08bd390ceeea18fc1bb3cb204e11c7df6b82c2381b112db7ffb5ff6ca3c0b6f5c893e8d5090def747dc1eb7a41219b8ca3e4ea1f7fceb2648c628a68be780fa8ec76a5a26c75dab720c80572d97bf2e16bf73481af76229674e0c4d0a592236cb2c61c451c61e12367346150679692875841f00ee377589f22f7d6aab65769ba6bf6c5c0197372aee1bd0510eafddb36a95f1930c6341962f06cf34e786d8a4699c04a38746a0980c8b017a71cfd9341773a382ef7b32f3e93c62f8b128fc72ab03c70a219b0211e60ad3dc2f128e5e5738b7120a36416dc54be5fd8931bd28c671e5b2a1a53885947c98b26b3268b818a4171502e44156617d398994ec18ba6b18ed607c59b030df119d5a711a01c69f9ec3288a711f05cb3b2e9ea45c76e475646787e1fb79a2d9b0e83789e01cd2d2b9fae9fe1ea18d7d21639541c08c00ff0031168a94fb92822527a0e9292c9f93aed1b256538df35a3be10382533c113bdd4668833431a567c7dc367c81fb4ce9fbaa0fa47b0a5cebbf5f0fe181b253913dfb6df1bce060fa741b3aab0ca78cc028466a55d8290af101cd516ab5f33a35c2cfaedec43714138bb93967dfc33add774687c189ac5a365ea356aa5096a91f035d9b1fd446b563c88138447a1f3c803ee81e9eac8d7da3b0d5c9bbadecd13fad75dcad055a10862a91fd4c60dcd0bdd2f8f756c2ec2a423e1ae59039dcc62aade18431125447af59b50f38b4c249243deb1cbcf73520a299eb07a25a13796c568988e56901bfb8f4dc967e7410ea310db6d89e059bc7f37b37e125475e052db88dd0952971fae45c33b7d58875a15d2f3810549600b2f027e1cd95a51f80a56dfa4c02936edb3548c9c3721019ea63578a1b762912893c19c7583c204ee73f45764e2f9859c78ca970e898a3405da4a09e97e4311a1319442aa0c78c220f985edf9a42bb33e0a42921e3e5c176038cce86cd496d4067c2af9c72d8370b8740befb5d58e2cd6f164c289e9fefe4cb918d5f2199f3a3ea05f033a3f5d943cccbce368ec0e2adb42f1086a8303e2dbf2565593320d88307c29dfb89d459ab1cbcaa328a37f7647b1b18aefc74d5b657a4cc73ce100d5e203682fce4134e1158e3d4d31d452c83aae9eef0ecab1bd17d0257157cd24bc1234b833a78a418a36b804ea86adcd50cb2342e3f351c1cb7bcf8a4a730ed13b08b0154d577b5da8e61038e629fe43a3c8f4254cbee923b056da040469476a02154e278dd7f96c175411f5e46368a4d981ff80c8665f167a310207a72112433b59766bf9081861ee4f373403e9020a201ef8211821c8a4d49aa2432d76b15a024038854d9620ddb944b822a031dca72baa244c7d62acbeac6b86f835673936312e94e634881c98c28821a28365e1d68180a4291d8219f3399e993dd0c2e7334d5770c5fa5c91d3d4e2cfcf25ef49c5f4951a1c39f7525e9042789848ca98dedd8dbe1fac51683d5c12b528287bbd4fe1777923f310f0d5cda469902aba7b570f88f1a29e6cd995ad8f0122358795032df7c22e1e57e4af13fed5f7c47d92c9d565accdd6a20a040dad5485475e251d9cdc6fee1c75fb3d7efed89619753ec3b330a27d98c13b61d2c2d5c10e35bcb14cc9c890104458a07933c1c421542d5f0f34b81e958b4ff4d89c4855ab44846bd7f3c65bbaaa8833da18007ba323564a9f25f4f907597317542d6dc4b1bef6f40c0fc76562707623fdfa4408f57108383fe381e7c557cccb807a054daf76f14a284d03e912973847ac5ad6e2b17336b75290dadcb7060982eb081861a0a56e71cc747ca883fb776be2c33f7bf14eae6fc2f6c84eafff77c0a4844abf7000336708bbc9d56201f74637ebb6c64d82747f7720d342a65c7a27719941a8c76f62487fe356f7511d6367b8bc40c5eaf58018e1266038c6704a0f4f05fbc5b2c2a5334083712c29e8c670d9f1fe5b88afedcb298e5b11b2054e72a8413c934170b8d852b4027bc9594a10893f248bf18d0872e12a7341c5bdec5c502122edf1201074019815ff4cc922a44a3339a7441c38a641ff717d3260f27a2556b4ff39e5ffb080ff503a91832c03f57023382840d08b1ad007a3815b384e2991d9853d060950e45b3e085542e91c060ec4467d2b8eb6ce0b463b40b04313a98cab4b35c46074e77dc031d1f0be4903ca3497c515630ce2954cf2bcb6d96c234e8b1f7e4d54b3681263ce023c27c31645056b5cf554c127ccc7a67a13e78ce2c8c4e3b812d02640b5abaee2479cacbc8e49346bab600c57f5787e4c65195096f53f380e3c4cac3c86bcc41c03fb69915f226eaf9285d4e20d54652cbd317afd2fcc9fd2ae951c54d787b23db52fa599a089a35c6ebff455e66f97460fb1eff24fba4e4dcf21d66e5355702e1c5cef443815643a294498b36aaea5c4d74c1f4735e9712c6087a1ba840e2acc90ce53cc50a722bcb7e8ac0a317f5786f1911810e116a890f733fe4b1b1a6fe5288c1ea00b432053a5086b254e24e4123b055c15bddcd9c10e1bef4a17fc5a523e96ca2e5e9aa2cf609cfcddaf2a3dd21da014c9383b763d233c5e918ca53afcfdddfa0b4f64923f005bdbec8915add863820a3526c2608fd78737145281e32f7bc3ba8b3fcbcdadc587fae3d6c3f3d8f342e0706fe1b223cb4a2c11fe0cb708c4530ab8898ca95ac4019a09904bf38e546b00fe3ce8297441da600f72983e82dcd6481a12b4ec1b9555b8d713b7402b1dd21a92fd3d1e5f86e325658196dca06287bbbe3afbfc77cd495e33e016f1edf7c0513bbe525cf5e3aabf2a68f252b23844dee24bef730bd3e9e20d29bf687ff4e55ba1b95eafe032340340dbae86dd3d4c252bde881a9600678c8bbbc6ee3cc03a974d870358a9dbee7d5c8f947404f673d226b462900dae53a3982c5a036da11422ea637990f79ebb59f9e14cec5c0e50496c58b323ece403e2ae8f7d4aa9c4ad1ce38470654c7c9b6e289d546000298d974b4cf53d457aeb4e77d1a19be3c2496a1d3f83113389241d1df62c3aca340eac2c755a644e9299c5a24272a2091ce00c1bf75c80e8a0b783f0ce4b88297b3644daf8fb6c40706572a1e20cb12ec7fc836fcdf51791876e19b555b849f03f8da07953c6aad1daa4389388f7d9561cb8ed8997398aa69851d932f791cf7355e65024d4ebb68d71be2f07b9de234ea467269276e20873b42c3fd184cd10051bca36c6f4d0e135e75223614688c519192b32ebc04c0a628fbd6c392826a4d5cffbec36bf34b4f99f64722a686b7a26507781c13bbca21a0b06971e0ce4e933372b1bfbc64a3971a9edc1441f00bf9a63fe22e88eec90fb4077035ee0b90028ea5be380552f44244419de250dc14677a5ec79bb013d848f2365105a94a8b0bb4423244e86091ec9f11e3becc0781ccbc6824d378f15b001578d6ab5a81ae32f0495b3d34276a92b040c1e26e7162d7a96cd1a3b3b5c108df8f71ff71a71bfd4ab412d4798930ba85ef03f0f51dabcbc72a6a0193cd85b372bf250cafad866591f8e9d5cf364b450a0c728a420ea1dae231bd7dbc7ebe2fb48dcb1f16a51e652649faf17417ff57ddfc1be04117a89d87a92a35c63a10ba56020bd5fd2a4b89945507f6c947450244ab9c31864c3aa16e04a2b6c8d04abba3891e5f4c55d3c567e83d13e01d4afe8c731e9ae1a8dc802fdff75f274f51696573b00ba1c58cf31db41d6af7f2894f1529e800cfda9adc538c7671131809590acca53c909285f43d7503b1c35448dda743554d0c2f838812aedd16ec57070b36bc920523b49df50c309c526e2f2c552674a1146159c48d8dc9895030565384138c607feddddab9fd30cda96cdd557864bbc787b534e58c865a42d47dc8dc4e3555b6166f0c3d8ad9e3e7b75c6158267921be7f936b2326eef45bd36f50a5b4c43673de92ffd438492962a22071c73bbe98702dc14eb6695b7332032b70b879c81b41724e986067daf86401113985c8e81587965bc711e46ea20de702959f4b24a60f2886f3f0422773f545541493cf2a071d11b2ea6d47597a5a30bb0e0d8611ec6e86428af6fb06d5c02ec4411f5eec443391c55659fd2118f99b735a4dd9e6fd8350719e9a06ad4592c0b45e4e87b4f4a8ffeaccf393a36dc906dbdcfc501acb9d877bbfcf661fc8dd305407c9b95259c877123fd1b5d779812c6a3e26ea22b1791f2be18cc1e5f4eedadd548280c9d20556f9e91f2642031b33094c5952ede49a262cf1e909169763a39015024c40039ab0340d2c4b0d887ab54547909f0f84186470d57b765db22856bc1f6f3f54fdfb12597c9924080d6a07fe6bcc8931365858165fcd80b804a6ec07f7de0b3ed30ab6d9ac37edb242afa77f2766082c4470d2475401a7ea0636cb635c5db8988a8dd3457f756304d50b67b883ef85b2a0230d2e396220d289e741e69feb9d2de89c6d3afc64767104588f128aa3001db7d6d89ab5fd74ea15028f731b48985adb0e43bbb9c72c4046a606ba4cdcfc493302d1e12c06761376fbbbb9833f13a167e43a972ec09e9c58f83063c366e8611c380460f49c0bd417bb58576547b6020a1c8c416f181037d16cf78fdea557df686496c4a6b25b70ee151762eb6a68587769dfbc3870c58820585093ff769ff325c1c41105051d1f37c70a7a796a7b6b69734fed5f876bf64ccdcc8731c1dff8ab96941a16518332dd89bbf0038b3c10c09788ec39270baee9f0daa256271c158ac251fdd386220380efbb3f9790754eb8fe06e406936a8680ed48749068f2fd9f62c80e858de56a756451017bc8c6dd503197e96c932b1bece4c08737952f14ee0a1f72ca74fd5bbf8ef239e0c7f5bc5fed4b5c67bac47c8128a975f23ab0740b2556e704429dcc6c86db15952dc442a556f60d4bd5dbbfda072e307b66c3997744923a182b73bdcd9b67833305aabe53bf4e10682e92ad9745083d02eaae8a24daa9f4e0d0d60c7570d2b002c4385c275a1e6eec2c9d339e20e806f71ffaf045d88fca113e40bce4ae8c822193a6da803a6d04961e09dda2074387ef89e884304da32983264fefed83a18d4da39ac3b9c23f8d2e0bae7e89c5907b3e701fe490fa903d8355503c847a9b8ff6b5207053c074a60ee5f6bddb94fcc7450263e2cb48570e6c7d56ae13e14f83746176c133ad4765ca58afb2c0ae7931785bc49f727ef411bb3d14a1116c10d9bbd8ad5a997463e622bfdc96b1a09d24fc4227461c96412715812a08b8b8511792c87d8740ac970796e070e3514a3643b0685bdac05531f4395c38755e7b6f0178e604132bcc73cd84147c1a2fd1dff083450c060b58cbe65247879168a886323ebca3d74d3c9a1b3dd69d6c5cb9a4d0ee2d0ae49a57d8b81d660720a2d9dcdc22a3958f0f29ee1f3547b61bc77924acacec3de0b317024c48b8d9ed33568fbe59f812e4432098e505dacd31aa8a32c562e0cf2829eb1eb2f180fa80199ba6dd05eb63efb8e0d61730b18424d8db3f395dd96cba7f85830e56ec24c4bb39dd5b9931110306259ad161362fbde21b4bde781cc1853c59ce4e2d95849b0852a6c391c7c219763653a12ca701a8dadd01a0861e001748d3e6d72b40da6b78ea70bbd8dc4da93a75a493d85b5aded9fa19430b653bc2d12f4fe89f21d11767f9a228b06381e8e240d62eb9aee33d79249b5592774e70ab93d88845d5a0a102b619940a9285606709bb3e603b5d511648042f3359a52ad24e11ccf0970e1175b6d25156f3ac1053c3e32b2f7fbe27334ac20b561d6d08414ded0d9b33615e8036feebbbb13bb374bf9ed2ea0cf1adcbca023d2f511bf5ee4ac21f687e8b21cb0b20cbeefc74b04861b326deba64e5400490b0bf0ac88bdb42bc410e8da3b4f3419b13f34d5776d675b2e1a0d4a50f0f34219bcb72cc617e62fbb1e33b73544d6820c823335b3754a4cf0560e3513212678a741a46c1e368378189239e948dbeca3c03b0374209892f02d8c3f9dc56b30918994b5d7b3e0decfe2032fbe32f8298bde9d95de3b8b9e67d51b2bff7b8c038c0f04fa5b63a958305c554d63991148ae5fc76ebd7cbd2f157b5b3d1245acb403d3331159e94314fff28fb89d5ab31670fed31d43b0660004b349f606bca974783586a9c1a680491ad9abfaab542c6d326ab4d42428a4edbdbbc9bdb79452caa00aae0a490a28f7b0d4dd5bb0aac474a12d5c6fa60b3d22c6a0df58031106fd66a1a3ca4c01411531d0335c2ab8dba5e9cd0c294eac3611460874f7b4d1255c350a8e32474b3cca524a29f3a669c93c143c85cc9d2f5ca9eff7dcd931efab0d2dcd0d841e1de57394bbb93385d0c7a9a2bd93f63cedbd4e58b5a12ef49ee7551b53a6a5fc791fe1d13d1c67389af93abcbf4ce3d22315cc1a7d27cc2a3d6ad339ccba8f1aa575528b4aba6bce8d1eadab64568f95b7deca3f5a5f794dcc877ba85c25b37ab0fc5039cb6b62a287c4dd309a75ed87bad0b360200a6365ad4766ade7cae0e2af7c6b439a528fb3992f0d67be52a7d794982ff0f45ace7ca14eafd1d3e9b520d776342143a84d8a2a25653569104d9fb252a5703d22d60dc4da81583f10ab08625d2286f41fbda43652485231437a143d09cb25272c9960d9044b27339457b0c031e638c6a88c46dff7893eda36d2e54758be9b43f3f57df48d46fff21c9ae168f4296638121a2519c9233c464eaacffaa8d5de3885cc7024f1f79dbe9feae974fa4e790a99e1e8f5fb88e8d149df3c009ce04ebdafa70d31724650834cb1935300a93f1ff0593d58ce3a2bff609d25b3c846e4abc4af8795b37ab06091593d5a677116f9c7df9ada90b624d69841f487c2b2219422b1999990c983c371ce45b658f419e53da347fff0e99399d1f2f2a7fce111de215dfafb27ef8d5670c598daec90c30c29ed41676e6ef7ff2bf1931919c1825932ab07cb57bec2f295d7c4b0302bb37ab08e8f59972bf2fbbcb3708c6191a34fe63da3695c2c38c6a0e01fbeef525e05b37aa8ac5ce535312dccead162796b8545b2ace0a88410bc20c607ad8263cc87496fe118a3b5708c51c1318685630ca95ee5736b8ea6530ac5a61085d523a20b7ded800834fd0934bd14a2361106a59741e8a8dacc57d5f4f50667be589abef2d42313f6a22a315f5189088e68aaf5ec6a736a44d36aa3e9479fd6335f7beef8ccd7d69cfcccd79e5b76a7da0f3427f3b55d4372d27a343dea8abb23130fab38dca267e8d52d473563b0d0c5962d492fe13da34b29f5abaccad553d5b15dfad65a6bcdfbdef418bdb81404e9f2a278adc78942e594bc6d2bef7bbdcccafb379fb22c7b2ce1a84d78d39cf4ecf4ebde451c3dba7e3544d7a7de75d49330a9b3dc5580956a835f79cd7b74d70c294c10f7a7df67ef2ed6aa8987d3ef0ef944977e9f91325246ca48198965ca3ae4135dca9794593b48f9e491504898446a995e3a09e524d69de1dd575fd3f7bd47b9a49b300b7b58a504bbd1a572a43cc37a9f82776907f0a6fbecdae56f0e74f4bdea5d74c08d4a7280a3eb572a3ffde23da3ef4f988572d24fa66bbaa67b4ddfa1be2c946c3ae9a5df77f13365160f9329d727a8a7bc46cb6f070fd6516ec2f289969f77d3c73aaa54ba572add74d687ad174bc9fb6ad229e95e269d23dd848267f46997be5df0b179c29b74954ce3884bca1f0fe06f0e02bc2907a1fa7d09d3b84899c6a541958a65b592eb28fba03af54ca71e9b29528aa479d701da54805d3ae937d7196e1f54a7f22e427f3c6801482593eb7b75c1f8dcba63068c4cc27b7727917e3c70fafbc5dd5178cf68d4a9e5b26b3063e55da359bfd6de5beb492927bda33944cd5d85a4a34693328b07e9adef20e596d7e8d2e387f2127ecaf74f8fd4f4583a0a0e5bf8250df743a8e41f5d7fcadbe67d5167e1d153706c86547f3f25954c8ff3f4385f5779db53de375295ea27817987df0e97aeb9bbf7d89c1f8f2eef1a7d5138769dbca7c4c890f66af48cebe5250c5b5aae33155d7fca5b65c2aa9f8e9255149329effb9b4cb9f4e3385d4a281590da74ef79f7de8c3efd94e3b312de529b4ec27b46d79cbd62d24db854c25393f046c9342eedddbe9bde872fdede2750b3bf1df7155fdc8204b77ebb74a5716d23b4f7a40509eea67169eff171149b5fb78d60a2e3ce8c8ed7aa56a74b57406af9f8f140e9d664b99941180391c8d2217b7d1629508c5aa5caa89655b85e85c50cf733ebea27933d93d1826a1c2ac2bd163a5dfd0493d1b77fcf322dcb3222e293212dd2b493888aa8888a44930a3b3f459a26d2ac280301811e339c9a46339bf4a8e1b1922602e530bf03cc18b0db2acdec335d7125e1aaadb522c96222c48896a40b4490553483286148880c3f9a977d386a510e85694056742fd0c33045fbae3a39ecd0c3871f1c0081194e306b402d125ca977d5a9475c393fb749932baeb8b95162c9122aa898620a29a4883fbbea4424376ee85d7566cc78c2dd2a1512eef6f48e3f780a77c71f70897df709443b275708e1d241d86f42f6c72601a230dbd3730d4b4dd33491a88b9ad470cddba5331df27b46cb6bf45adef276d36cb729852572727aa838426e0eab075064932b8e6562839ae879fb4361762ee9a1301b346d108f0ddad6c80e8e90f9da556708939e263f499c00cdd79e427a8a88ccd7f779d19177df22237a5a23f3657166b839a130ae676b325f5294a30189723420518e0624cad18044391a90284713e57044001244935f01a49e1a67e4071a9026a462df89724440443a221f5192098b45101205011224e211f5889088764444f4bc48933aeceb0e91084c5d95f21dba52709c2bacc2291c2788e3d42eca3e84163d36e96c8de04a4d67bee2ce7c09d186cc97e8f31a118d4733325ff7289fd78ecc57e9f39a8ff6a3e7536638b9d34647bd2bca84429d640978a058a2077532f19827882a88b505918918c620d622d1063107319cd76989e05e4728800ce95d75e8123a44a998a1d00ca7a0415200e5f0e8e9845e31614eaa8d0a0875889437b409984540689329ca594191238625b47cd14224ee0e592e3d63eab864de754885a93a570051583d4174996f818e819eaf36d0f341146848eb44185394b3440abdaf96b10e6187043d7ae618a17588d6a4284714b4335ff3558368103d29093781a34a1dbc4a486b820ce7352548ad09b18613220ed500c0ef2ede487d6b52d0b31e51e1aa8248a30a1942846786291ca38cdde95d75620cc0ae2d10551d51d509d205223bf3c5fa9162c2a21080a2921134e9c978443085ded5670b63d0bb261102d2bb6a4014e6d2010a9470c203450802932d579d19e2dc3d8574e46bb4053c3ce3c9b55a7b543202241badd112fba07acb320919835ec319a61c93d39c61022e20710288121eadbd0b5d9f6298c14ea4d24774cdfe419ac5a26fd8be8b32463a6a228bd9708c11590c435db416ed9aa196e590baa498d00e01950f3f4cd8d6694d7e8a61063b31f50e91df7c992eda8b2b9c2104300d6e0864fb5462e2d870640e04628da837eca22e9ad4a1301b254eac11436b3446c5a3356d9e0c97ca4f4cc3f9cc98a1f6d8c42f3374cd50fb9d5c0954bd43fdc9842f132695d0244e8c9dbbe390d6b44360beb44b9b6d2f8f9ca2336801888807a210715c0c871d54aa3f0c5daed30c7f00e2871e333b5ef8ef49c5fee6d0f23036d3e38717d73dbd7859e5b6842dff9998c1cef4f8615b9dc30c5b5c9ebd21cc0d1c4eaa9367d1d0c59d23393f427686f800493105119e9d0de5000992a3b213d668ef9a71c3bb3b42bc1a77763222d94ecc986c67a86f79b9d6dbc187192e9819201c407323766386cbc692c0e6e472b976e0ba8e9b9d887bcb8b8a9e5c3bcc970f335c3033403880e646ecc60cd70c98192e97cb4604303652971dcb849c965863c63a918beb3a6eee300383e39a7163fe25c258c2b510c001f4f16387991d7088e190030e3eb11c0b05755d3a63a9cb0b17dc6d339128933f0061bd1f7acc582dd7db3317239a6b107947d471d3292a82134e54e184134e38e104e7012456a88ebf51c4591b2a924e5d69b6e1aa6d38d3d18b611655a48d39b5efc96dfb2c08fb7a297af6f1b0226bb126424e5a33695d8cb6bc1d9152564943459c66b78e74bdd187623aad5097b36a96f370628de94d15441b5ffc5850e3e48a3566c0f4f0e1070bfcf882c41a25d3c927d640325fabc7cb9ff94a79bc4c3261f2474a0184684352017e4b2874fcc489b0f82796d0f172c88449cb132f55106d48224458a0e3e5cd840d415de6ad123afe02d1865442c638831d8a4f40b441b3243ac17563ee00e87819d1c60e32c654d97879b14be2e78d8e72c87e3a349db2faa142f5f303e63de737941c923c435bf24c9daa825c9243e466ce67a56f3404138972493334a30894b7bc02887a9b75ede072752d235232994da68bb421b71063cccf1b3d6fdef28a2e4839d404ab65e4ca25d365680b57d2bc9af70b2e4dd3ba00024dd3344d4819e896820edc0a28e81d6afa42e955ae4f89d682ae8bb18696553a638c519331b6e0665b8c1693c5c4d0982ef3342fc332a65ec3b5661f8f1843841798e146d3344d13ca2c0082279c30022f6ce10c4bd0a0488274821c22594f8d01bdd94076c3031b6c47dc64200c1e38010a88e808610a5a1038214784d8ba4f9a42e1e998c48a1698a08bb1463667cda69453b3973ea3534eb905e982a9f552d66b236cd63cb740e3e7084a3ee0640a3f3c409041568324341d0d892ccbb26c05160615891f114c4dd382d0c48a9d26430f9b0831d1fb2e209db5314e997dd68c6619cd599665d18859934d2a24e5eaa10b2674659206755d8caff83d679d93c658278b2a98b833c638c3205a9519d56a9609a95a2624d32a95a7995629955a568508c9b4d065f4b2cec70afb1e819830d2631cd26268c355b5742ed1060e32c6fc765bb31612bed1a5eb3eb98eb3dde43a2eef2134518843186e39871764d5b66839db16ad5298ab3497c37c6597deac56c6980f6f74b197d102f1610fcb8fc7b543e8ec32cb75668719f6982e93478c311fc61a3ad81ca206e405090de59043fc91999631e97c6081b41c820b871362122d5fe3e34b45d75a230c4ad029a402c31531d65a7318431164a024890baea0424cd57c741dc90c0c216001030d74f609c4e4092f4a066bb76ddb6e96183a818e4c1a8630b14107e6197a2a131e2c21abcd9141c3b6370913242421738621d590187c443b505911cdb4acd4048e1217a56312211374408808aac8e5744c22d4832b849b6c43a209614990ce3e3f996c0731f4240943152d55084318b8508528f59411c35883859e77f900043d05a0a77c78d3dde32e923afb24961975e18e8ec8abcc97ea93ba44179ac30863867a5291d5726c8a7013dfed773b5ff865bacc7b18266cada0de52cb3d7e228e8a28bdb62aad18633ec32c114675852120016d2e8872f2882eb3a582fb19d2e80aba72db9f1c228c5a3f10dc6d6b08408479618cf89a32946136271d11b155ce14615e8c315fdf45527c65af755b293b49e98888add61861ccc3b0e26699969fcccc0d975577c6e6a87bb43ec004901d9b982d47ea427514ca9a20a4e95542d65e88c2264f37c2ac79d4f7d17b7c1f7d7e947f1499b9c7e828d67c69f41f2ccdcecc3a0078d47b38bd948b28fd54ca3782df6b62507a54a7df872c50dc7a233d33a4983583ca31ea623a6bc6741602b4c7a0ceea41a2324bcb2c05988e7a0f282fe5228af4301d7594bc7a0fa8a31c251761ca45b6ccd261fb3cf77954661d20f5d5595aee21f515ab07d559579df51803669676f03531a78c02f38f22de7fb0ec59d94fa8ccaa67694779b9878965b3047a947e8f801803fef41ec09f72fee1a22ef4a50cd4c3a908943c73326556cd1240408c291df51e4a47e5fb2250f28fe71e6e06a22666471fd4b74b65fb7cd41425e531c6948bb0749841709f2ffd84ef5118e526ee1e16a58e8018937a0fadab1e3f96762dcfacee432b1799675df59a9854ebaafc43f5562eb23df59a1895eaa71701be08551ee2f4d45ba7918116455de5e04fdfd2063cea0a385d25b36650a8cb1893e932c5d25698a565d64cea51b3ae927facceca45b8a7e653f847911ea9aff2eaf745e414b1ca32e73e75d6696480b52377a80b3d0d9b8bf294977e3fd6014a47b902ee53326b06e52c04c41814560f32867419437a6caeb08c516119a3616964863c33e43992f5f44c9b119e3cd36687080f4fcc24e283c427cb49eacfcfce8ea5dd41d4e69d4cdbebbb8cc36f9961f68b49f852173ba70d75c146ee9e3e538af9da936706ed699364be3692f9d27c7ee6ab9edaccbc270e4b02492926909ce23368f250589c757bf66e66d56aaf557bd56e758561c5ad7ad78f87f62ddb196612a5869ba728ba2e723252bba0707564a1b7c7628d987dfbb615416fdb5d3c5793c741149498e0054aa2b022667bfc666ee6b35117ee568d8edc1dea6d7be89a2fd145f6f122014497edd166980863fb86d11bd7d222da04bd869bc0b13174cb700ed485baec8e7a30acb853d348b71bd365bb8c485a2bb8e145d7aeb2df1e3faea31cb53a7ed6ce70b3dbb7db5b9b374d186e505cfb6db5c7d276f3605871331db75f1bde5881e1da0c33c3ed5f08666eb224d58be55c397461528855a31015f72e9cb9597a63bee86dc6d932101656b8da3c0ec1ddd24726caa26bd7524c20ed12775b3d69de524745d5811825121a4851bd9a5d4a29a5d462ac5ee6a9722e0b2cdcebadd230d4855ec57387c8fc87a14b661655b8a2580f341cebc18e7624f395d97c04109c416f79ab8e645ab63da6014161dde976859c17af0a86745da3dcf359ebe5e82273943d4d709738cb26d385498a09ee691888a0776452bd252efd86e51433a417e18e7ab1a094100124e152fa23d227cb9ecf0ad7011098af5fca79d901b3cb71baf468203c70e7c3a0efad91de6a43e242175bba986d67590b7343a80225a902289caf0b540513edf3850fe81d97e87aab6da1eba53e8937a64b85717267cd3e5fb3d7f9f99acd2c7b3d27fa107afe2df335b36a86f599add5060a95eade189ea68b4be4444e93a647a9cf267d49085d9f692831a4476573ce7c230cfa08505a4f831024489021e044a10a258ec00132ac2071b72bdb7b2a98152656986829a1a584182874f1b1a5b5843c809c4e24d04edd7234cdbeef8ba3efebbe6ff47dd4c5388aa3388aa3910fa9a5e7718fcd1396babbf7799ed779ffba4ccfae93ba9bdedc11b5cc02138fe5313e70a5ca61ad4fa59589e8afe33c0e732132197da3d368341ae5694344c49100e93e6c6cd680862b0309f7fb0b2e5c16ee7387c25876a6102714c6922791397764f8e06eadc783b1e400992f9d1deee3885c16967bf9f47a4d0a59efe3f28a07f79a0c4fe82fdfcfab6424e172dfd566c89371840bdee0ee6a0364f475124f2173880c1edc967ac251c7f8c08d12d727b9fbde7f08a3b8dc4faf36347a1fbd68c2c3342d5a9e3e77bee32460cde707587b22c6a02761ed488441b51e1947ee3c85694141228bf2d8ec3a2700f92e83b4204d2f6308b7d69bea12831bc40352bf8eeff686f3c9a4ba579bbddde89a335f52ab5e36b019432b104d2711ed83884d7f973a4618f7376f0a50fde56dfa5780efa3a1f9ba99afefa44f14d67ab4a10a50ed08fd64525785e09a2e35ea5d17f3befe24cc0d15d70c55f8727acb55a978f7f459ed7da31178d463b36657fd5dca572f4689d834782f89a0e025fe1e3ff9819fe0e7a743045f750ae78a33437a13ae3755460fee9e3b2e98dc4dd3a2ef4b2f7dcf1db0ea6b844bfad67a6e2dae7d32f34984215fc39282bbe7936ab372f97a53e5e713d4593e1e1364c1d448d7cdb0eafb15def7a9cb0f9575dc8394c7c7d4b42786ebe5000000086ef7fa31267533eb00381e7b0f39ae7311fa391ecb4514e9a19fe345c48ee331a606d963034f0f8f919ed7aed12efe02fc8d1dc77b88e51a7ad079f6e4cf68172e729ce18b6cc365dc701b601cb4010000c0317e830c182f311e3f19178e811f3f19182fee22efaa5dbceba153bfefb42fce50b31cf5ee395e1373c1cc3a008ee7780f1ccf917f1401ffe3f6c0f11ccf917fcc9e5c1303e67d7d4130b5fa05ef7d6ca2f07ed137a3c2aff006c0e3c7830d076b780abb5cfadccce23175ca593c465de097db80e510b9b3ad0eff02bfc0c02fd848cf0ce29e6a1004c120eea5eea4b78dd020b5a138d486de4c406a30752f792e963df75bf218c9b2678672025483990819040787e2f4e0ca203654f6641becf404cdd79641b444f2e2c5bdbcfca989a9899136d4e002b5ba8bc2a4cfbdf449ddafee512e6cc8353cd2ec22dbccddfb7b1c38034d34cb490fd29dc8b3f32575f613478f3103328d32a1e803e352b17c7b7fa1be4fa3b7c0516bd28694dac6d7ca47ef364f7bf1fb22f52e7e5267be5232f66df5f78da1301cdfa1067f0acb71f03138521716d73886237569f149fdc5bb94fc0b2c7352974f3d3673e048a50f6d61f14e7e291cf52abeb8bc043261f3052c761cdf56a73c46588e14954e790cd31c1807ee3a4f7e3a6696385907575efef824992fb9c1cbd5ea5dfc529e3a885779bf6c981efabefb645cce82b74ba679d12c0f0fe2dd4387e7be1d3395f78c4e1dc41bfcea1777abbb9af1e49e82342e0da29e8277ea3ff7c98039ced045de56a3f22e3dcc3cb0749d67e74bbe1be1fb1678c57a9536c0b3be7dcc21e05509dd200d3e4630fbc974eaf63bfa6c9817df2e17dfe1caf735c802a24030f5fbecd381d3a9acc918f7f5db01be45de52b7b897a3576923ead1bd266d447d6f87ccd7e8066916964940242b5db87bf6f0d01c7be543bd6d7786bac86f382e735c9ef2fc32e5f239e47160e9435d5e64177925b364e93de58f9fcc0adee035f87bcf4ae17d9dfa0a6fa957ef40bc236a10af1e3f03b0ce81df113598673cb9acbcefff1a7dcf721466adb2219a856b58b00f4da7644dc6f8102dbfd110bd82a50f964f3243f91969bfca3b4ce57d5606f3cddbea15dce2c7a34b1f0aa3ac15c7b558165488a2f2b67695f7bd5b254f7d6cf649e5d659795b0be67d75ebfbaaba3074b95adf302acfb87e32ac633c8f7c3b52cef215bc6bf44a4bb3acf2aed1ab83785f83acbca566fd9786a1eb3e5ed655deb1f0f4c1b3070797756486d2874a8e4bfd79db548a5b39885b4fe1ed23d32c3ca49c25df8ba35ec1296fe17dad52b9042f3f7de62b46fd4e05b3de494f85c5e397957d68f7b70252df4c93f22d75ca5578b8f4ea60ded247da0c71b2a3c10bde1c21219fa11f26404e34784110fc8ee0370c0b6fd4538f1f0f99a357acbc81d0ac8378bb34f88bf74110bc8f5116f69169162b250be1d2abbc67342af58b55aad4e3e91195695cfaf44f86e65098175deee5bd776fdea54cf3a24947c93cdc4c22e1a8e956f9f8ec0c604a1034d1494969b59b96554a27a53347461f4dbd69b32dcbb250c7586b16c30da67e32f14b60e20c6fe838916434a3b9d67a15fb9249bb5197add2cd87c4057b6b085d30330870c1d80c8380197bc31aec4d7265b541304d1f83a4cd4deb8787bba136524a23a5477411565d79b7e8998d10933786dea9c49e418a2cc48082115d5f6b199c98a109339441d7672ead033838c117862021899e2a888081347c810a66b809038ece10860072416751e80c064e74a426b327c86c739be1ec2c6745d6e63d71f8f9968e1385d5d62da35678cb9c487432f3b185270b4fff1bf3154f1fd68a5be4f78ba62e40f252cb24b2546bedb4ce098e931ceb834854451c57ad5725d775a2ee15e54d986e65edbe75dfba0ee528ef5070ad175dab55d41de507944e7a9ee72181132541c5894c2825530945b4719c674772249de4889ce49fbb5511a5643afda084f6aa3e9a26287ef241bd984e27ef74328519e5a9984c97524a29a577530666f40411367ba80e127a680c7a680b7a1061f26742f163842568bba14820f9237f64920d054802c91ff9237f807c482d530f3efbe9643aa1502693c96432994ce0515906fe844fa719339c59169b20aeba931eca83c00c29159ae9a182e791e9d2a3e46eaa33031481a80e153ff494baa378c7994a94b0d4e012a80e854d1dea426feda53a54676849d0143487e2cc70882ed97a50a8959fac3eedab4fa6bce5c1afe0185d4c6fe118554c288a02df81f87bfc3cefdd77c1eda7479a32d5993a5487ead02054c7c87c39892e518f9cd84497a869eadb82df1765fae99d09d31daa43754ca74152795b304754de2a2de9ce7c99323565ba93a90ea529df5693489f38f3c53abdfcb9aa873fa64b6c329d4e4741994e28479d50defd84c2cab1199a1e9b2816309d5ee2984c4f894e56a654361dcc1b954d3f491c5346b99438f69784f75442d38ff0f6e83faf53511961862f137663862a9fd55c9dc174797a2c5de2929452966e32992e4d2553b6a57cea58a5d2a594524ad9bd843aca095d325f92c2a20fa5d435831b757c76d4bb9bb2c4bb9a2ca04fd57af7a43aa150264cc4a651a6c74f9a72407da28e7a099770133646609e1ed8c4942e4b37954a97ba54ba7c290b313f1de44d9986ea8dfa046ab4e9258c797a60a3e3c7032a9bb2109c96a72ed3ba8ed52541f9fdf6edd4a328586a104805b56da753ebac6fabf218b74a9bf29647bd8555708c2e2c5cca5bfe74c2db3f19d43b14eebc2f87a8bb771f09b5c9922c459f39c4847e6418a22e517fabd128459502512753c974f912962559f229994cbfa79fcce93ba66b3afd74dc7337612122900ee2a6e4d8c55bca952a6f9bda2a54de0f4d32df97280a8efa6e1fe1cff391e9ed9c3ddd22dcadb255ace4d6bcad6aa29ef006dc8884224ef44ac72398014813410a6d6f458f40a54c8a264a8fcd1d1a5d1fa5a089425af439b485089b38d16528d650d174b260e89641531c307820c2a4cef72481a629f88e4e901952d54ecd724747ea5cd70caed440d06abd7b526d95fa18ad98bb868960f5607a13a76f5ce5b07cfcaad444d73e1de493274f9e70b8ea13b6c946986e7a67b22922847cbd114584903ad4cb1c048dcc42d46f99866ad11d806344a5c78e5219847384bbe790c8e62477fbc8ea4707957087e8adb599f684446648a708a1b8532868863df3e51da13e2938dd25674da27cc2265cc231ba6c79edf693e93e7ba9f1b8bd28d38145eae08c1e9b232673e889fb2adc3d876ea41077200313a24bd428585e7e2e2961157591b9f42df36442edd01d9adb521739850fdbaa3ea98342736c863950180ed3075a3fca41416197f7b91c14459d150887c24a0f5d30580837cb7b0ec1cca12115ada51cd20441a00981090d54269068c1151b0d133b5dc0c2133d3a68424332841b021337f841fc028dd48a2be81566846d769b73e69d5dcbac2ac67735467a2db764af59a5f1f392c9a6cda0a7270c3d3d3d3d3d49c0d1b21ab716d52acaac4d7bb769ddccb20df1456411c31923baccc718f32c303d75b63d36ed76b91f1f3d7e3277341a4d3cfa25652323f91d2140749498574c2755b89175f549e5a3126356cd1f0aa3534497782a8096e8f8f9a3256da2e32792f9ca320d9a619c4ae079933343392d3ee53a5bb26f5038821315bcd010895bd2c4008624f474d529a64b10159c0469a9679e3034b8f80af17b0ed15730a1b0ba2397ccd7bcac416628a7132de58f8e653c77b2b0a0b22a251e30c2a3c4031990a1b36f314802063a9b223b4326850c67093acb70aed059078aa033559665d911c4e08328b4e88343025deda3a86208a719051b9470800aedf20125480ca938b00da159544d6a2fb3aaa94e5ae57470a710264c6ce68b35e3c3aa0bf7ba4908c270b2e076aaa6549943e1cc3b0a01d52a536515eaec7c49e98289dac36dcb3bcb4851bb8db0792d6f5d8d68d9b7565a552a6baf5d6a3eb22ce480d614b73eaaa86879d904152b6aa522dbb56c70a9ddaaea12a1cee738435ae5a659ea92595c29d5e8f4b137945cfa184b79e2d2571f9ace68165ba816bacece17959e66338b6326a7d452ea99c919f442ba982d626cd19b524aa96591650d6fb72ca215aed5a978ac2f65b422a96e2ac6186946a9e97432a14a204aeaaa48ab51cac7f254ba16b72262b1dfb0963316b585b6982ee40b17a72b5851e59e6951c52dda8ace25214aa26294b658034364d1257bcc2680aaba6b4175e7e245c5f2059d3286e128bbf6849db09bed1a2079226b5fe88ebad05d6dd1729166358d85eeb6ac3b8b7527baee3816dd752bbaf35abafb547437225d9492e9b4610d0c112d80fa78584dc1544977aad5d55d0acbb32a9fee5add0a0ba7bb8b7487adee328bae25d35d0b4b75f7c2250c4734c708a35ed66453bc085b842dfada98f17564e156ba96a7f2b14629a4d555a1a44aa00965ed7c9de27c99c0520a457557a494519c2f8fabf15a465dc418bad808633ed688cfa690ba236559969dc25c97c26274c0051c10da4015a4f08498ac083ad3c117862459f244154ec4643f4a6ea84045a184bbb31d3ddf52c2ddaafa1361d4fa136bace879fa196b4c4d35c784b336365f328bb3db9a45d1e767651c71b73c95a24cf3a2b363215e7410f4128b5c226b39eedb6645798bce3d362ddef27ed1f219d621faeea1ad102f7af7d041886e5fb4e8f7ca2be5e595a181bba30ff7d97215d5e95a2b3abd04ea8bd64e3524d45a2ba694525a69ed6a27e23c6fce395fa3299df1e4f3bc53cfeaae761c3ecdb0562c825aebeb8b6bb69c46336ba2748e7b57c291bac48f873843940ec8683442c1bb6a94df4b7afc64f78b6bb4244dbc67e8ee31ca75254e8477d59604a4990de9624ba7da26de2ff3a6c8943e127b323270630ce9f6db7624dc75d2a7ebdec9fa1a3d31f6f0b65ace78c27da34c7f79015e9ca1a7ade844c231467e3ab65bb9695eb496af92c1dd61cba6a92d276f6647001909d236e8a804075168baa3dd256d88ae69a2efaab78f88e446ce9b06ebeda21cb77cc3b763bb2b461a81b48667cc9967282c6e3493d99be18a16ee0c9df1aaeb5d631891e83a43fe066943ea7a4da1e8e45523b89bdacc781a986437e16dfad5947ee061aff07ac109f52e26423ed1241074b95cb0d761e7e22743e3b159430035cec54f06e6b1d9ad1899a14cada456a8d4bd20085ebcad0653e0bd57fa701cc7711c7765dcdc1d817c64dcdc2d7d3477f999f9ba0765b8d541bc3a0a53cde58a936b37777884681a466e348dd300d2699eccecb6515b636c6ef2d3913a8dc7288cc6258dc7e6e452a9542a954a499e498586f9963a75449323906898c3fc0829524f3987edbdfb6eb55aad56abab7d9cfd643af9ada4cfaa5a9b7d3a52efacbdf4d3913a4c8da7602e35cc53f0aed130d73e1d52c37cfb74b8f1011c85f75530d7e5135a7b1f61d447287e3b508e1f9b99a77df7306cc3868d6bdf3de9dab88da75ed8d1cdbb76351f79f3c5a15071be502b142aef1a99558c4d993b91770f8220088259eed44f060473769cc87162078912aac0d171093b377acb1c1634197d3a467ff10ebff8bd0b2a8c08d1a37752dce8d1bb0c00516283448f5e83129b2646f4e83084e89177b19da18bfcfc1a5e830a8cabcc9717f9f2f2f2f252447c797979f9510323b3c0951aee8289d021b50d7fa92136b5eec5bbf8edf86dde2fdad65af3ae3615c7e7de1b10d3af5ebff8f8b9e8583d3be951b79e92e32d381ccff1d8ec3a19f39ac380917be4784cfe117b8effd0c7f19a98f8b53c0766c138011e637060168c981802e42262cff19898965fcbe3d7227399e3525cab95e30c53f2b6dbd3f6da57386a174c848c0d7fc93af0af76f114de57e7b3c04448dde5d39ce30cbd5c430ddd47effeb9c85bbe902e729c6198bb987918bd7b0b2642ea01e46dc371b5a1b01a037e325cde3f340782e0ca39ec63ead1519fcce89dfc88909ad4a285c5ea16af8076f4e897c7468f6e933cc9d151c90d80b44a472537e8d1499ef8e8d1ef3bcdb30147dd227d361500b46deb34c6f118ce7116586a1296ba05961a63fb162cb50d586a2a44e5673a2af47d715cdf071c59df871c43e8d463efe2e79af1e47e06c417d3b8748a3b88531c88bf5f7cbaf401802a8bf6332fb28b94b972faf130facd2c1dec511ef5ead546d5caa8bc02f3ca47b78f4d8d6b622c8e9a058e2d70d42d38ea18ae8941c1bb468fa4d05147fd7d29af174bedcdd7aaf5aac177ad56eb2d14eaf16bb5f2aed1a82ca37c32f771957a07e20d7e7e05e85e81a98300bff21d2b0753e7704cc12cf045ec2e707c849bc09159e0713cc757f0b639959b886516987580398de7c8374560afe3c837456a9c038f292280cf4fe666160f98d338cc69bc033d2aa0d031c91321dab3826996c1bdafd1570798dff88d1cc4ca611e593ad0f8fdcd41ac9cc6a3ae91f34e798dc3c2bc5d1c76f96e05bff80f9dba5dd101b6de516f05db7b100461a030b802ef2a5b1d20f7ee377ff4d88c510ea73c855dbcc05b6a22288dafc0d4f808ef1f1ab642c305f631358da7601f53c39c08f904bc75fddedb38875d07b18defab5d366e6ddcce97eba28f87ed49b633743d7e3a6c64e9534300303c2e1896485e34b04cb281bbe54e077ade7c814fa59ee252a9bc6b34f74e469bf271f776be6af4bd277d321c2777be94c76fe53cf375b3367b5aa36ef6485db22cb3a71bcd60b97da9c4dd960e82208759403c010982602aef7bfb2d7d58306ac5826b3487a35ec568c94aa158436a397b5400c8f2a24f06cc1b3c98bb8b5b49eefd59dcdb098b315707c026c6900f40d42a291cdf2ad5e384e9ab3e43613ae71c19474e459dc255efe220c0a79e4a5d45e59de6a9a8c408630740effb14c7711c77f1c63f3478ae9b1e6bc7bd8d16d360bdca37c818a9c7a814923e2a2c75d749cfb26ebfc2bb46af568f4d2e5fbcb1be07f1668006cfc21d8727103f8efb8ea839207a58200bd768f05a955b9c7ab7b27a27bd152c35c86265560e51b3be236a56667d5f106fcc007dc14b83352adf206304405b954c6d52b96706c51ab2cbdb6e1a572b6f95765d1967f122df701739c6e5c7438c9b4c38ea19784b9dc5788ccb8871518cbc695e7496675cfb0d8fd11932f00d78ab740054c7fb94695e74dd3db4466f34ac0272e5bb4a52e153a1ef49077fcf7a17bbcc8bdf8efc56eb5dcce97684fce81b6ec33510a46d38d855251ae8d14a34c0a3e525120974df22ef1adde2f7663b6486a3679f4ca355a8742c3775514c1dab11081000002315002028140e868342c160280bb31cdd7d14800f84a04c6e541889a320876198428610620c30000010000110a899d90000eabf0d84608d5ac7964f775033ba8f6c277d1dcdecdf172299baa2042d9c30339ae6b09060c04581df86fb93bb50f438a49f81cc7caa8dc7ede65e4e82d856e0add177ca0423226adad00edd23c9fa5f248843d893b6b2f0a010835df8d94f0d053d06eff1c42d56eec92e1d68279f27928652a62a378653bc850defe6979b8313adf53c4442ae6aa04be0b6445b7edffcad9924f362f44d52ab8687feaa6525009d4b7638b214b05dfe047e9b1b52bd14c8e4dbb044ae9e599e1576047cb0f82746e10b10fe169ccd54254f7718a2ef36d9b615ea8ac2fc73658cbfbccabe2065cdd5a3531ccc5decb56883c9bd0472619980320663848de8cc061560c62f40ec8f3cba6fa3654256ed8ae6ebb2ffca3db30f82cea5aef07e9f3ab1cabf7abf94a60662f1061a162b0141e746494b12a8c5d824053acb5b3d75c3eb37d98e9517c2542cb01950552140e637299c68a9e0bf09cf8870caa114973fe645201545801647f6e3163c76a8c1d480097c261d814506ffae6aa12adec5bd29003022250e0068640b6a628560c3c255e58e6894f799aa86dfe1460cf72c3216ef82e85d6439ba943c2b48f92a76d4b0436a7f062e3498a6e958314d6c80d6dea3b4bc7f70c1b373f9288763970081eed6f4235da91da8bb75f67f923ac9f2d183cd35f695047a3d26a8bd26f64b4880873635e27b684b9db80d4318b71fabbeb8244aebe7d6c90aa5699a934cbc3e4c6c45c9c9050933c7aa0f3061f5ec4c555f1b63a8a42c48118b0ac54405a4013d5032e03a4beb26e5a49064336f7d88e4d6c4f42ddafb23d58a9a47eaa0ee938c75ace53cf9c9a678a1dc004b9d73d912f9cd6b74ebec01b93add9ea057041835b09ff55c3801f26f51722ddb8d7235276fc3676beacaa18fa03cc94b758d03b59e67f2ccb2279733131efbba1433cdaf0abae6f86c0b7c386039c4f3aa1d5d9de99fec1619d519593a656b59c609ed65eb1abf10e51e28f39b3f0f434f5f77f1c4b2f9a467c75e2f73e844f109285995712891b1e40f338836bcd88c01ea43471cee6e1940d3a7c46ff754289606c17dfa9b31a6cdfee2eeb0710c0c2f79f101f3feacaa37b605cf339f5f224914ba0f12de6463c9cf30990ac5fe616e5ebfca919908a86d6120820253b2387d8a628ac0e2cd64183ce9b74a57ff244edefbdf62891334896a19cb3fefff177f46780be06a788d3164a362e255d75a1bf1f09b9ed27cb840d83956784b190365fdb8728e392d044e38d3b760c7b706960f1647d5247470a5bf33f9dd5ffd144c4f374fb970f2b7e136e72be28d2321875491bcca6c41290e68f74b4d182451afa09f7dc155b901ae568d67e1e5c0be5b7c10171a1825ff31a358ce20a17dac13f312d48a13dae162f7c3dbab7152e3c27c911a08343adf64410d324dd81948af66e867bb107c30e084339a231cd4cc3a38f5286558ae503ed0ea4e4c3ab51799636eb6a9d093710f92994477158246e7e034e6ae1accd55ce3cd1783d243fdc63e17a3aefae93cf669ef3df4b2782f070c8e45a17a1ba81f2c71b5e4f6d2c9aeb10002d689c1e01916e01cc1c93412798ed68b0ec9cfe506655bbe1fc85a5a710971a011dbee2d3a44c3f12095f33906a62fea8e4d8739425e106778d4315d387169233154dccc92d64a18d476937b39f1b3301e40d25b4cc4405e347054f1508955fb9780c92873c2f104b3ffc1741766c6b4ca28df042abe8421a0f9836a50118797a6535281e5165c4681c5145cdc02c32db89cc28b2b588c85c35bb83c058b53b818afc0efd5d64b508a976ab9706baddee362df93520a04641a6fe605f96cf9f646dd83533818dd3f128530451b637a4504da8fcf69bc935138d98583af705805265778f28507abd0f00a4ca6e0640b0f56c1b0154e5ec164170cae82e12b9cac82c92f18ac2e28bff68caefcdd0cc08f67eac24690ef9971234385c0ce8822890ad94f92dab4263b5184922a4531919567e279dac37df0ec4a842a1ff885065c49d00fb9586ab1ea4c6e7aaff94d0bd0eb42e145735ad486cc04027794fcc14baf76604f84a00923570ce98a96c87699736c63fda28eccf42e7b5e6cf67a0ef678c0246ac88b8adcb6f499d8d02eb6718ded95803a6b2a252f6b03aa8d8f3cd597d5593c075ae7cd7e5daae35515f6e469dfea83bb6dd4224a9dcd4b97e907b4c3f7e8fd5881624703d9d111ccde91ca9b4079c93d59efb020d672081d58afbfaa99d33ae57dd7a7cc25262de657066759d637c350415c52c76d5f2d749b02f9639642ef4c0b3af3784c99d966e41e570f1fd852ea2af29309b279d445b65fcac6ab09770ac8601b63949be2a0ac009be718135ccef19a7ec643c08f505b8e37f79d7945449f8721244e0fc382374fe0f68c88c2bc70ffc29686f5a7374dc8206056104aade846cd89d45e4caab8b62358f2c9919291c3f3600e00dcbdaf4cb56ed6e1b6c817c77171127e59983d31e77c952ef4e51a18536fc7e01a7afc7c3c0154dee18a011e5d4036ca4148e5ffd62884823976dfd8ef62b7f9f8e0e76de032956a99e55a040e017b5ec6b0f8cb158be7fce8b3b78c4bc234232895f5096078af868fc3522e306d6ba2a0c1183d5d5368354de2723766c25397db5b6759cb2e9fb074a5db11245b43d4efa3a8eefc192d2a9e71ddbe00cb594a918c0b834a8af903c377009d020105ca61a72964433ad99405960e19d001d63eed0b81c8dd3ccdf903d93e0e5e0ed5c0271ca527be5c59c2b860eaaeae2a9192941d467d96a700da808d8a3e2319504881ef548d8772e3509460a0477370670b9ed353df6d5f64a194045dfe3947528564c124d477018950226b788ea125fa573cc630a249839a4c40863cbdba48306f1e4adf7d1f63789bc71732f257fea37236da1992f026d2dafdc86595f4ca883826e1d3b8bcbbcfe894945e7108fb028cb02cfd73c5b8306cd62034ef25ae9dbda19da7b9fb8cfe6b25fad0388e99a89b1d3ddd459207cdd09ad4d1f8e00b85516da2c31028a46a695ffee1ca379b7ad3d2e92f4a5a0bc89edf1fdacfa1144d046b403cd2f915a553df420e41eb653d99516f92d7a2dcea6c2bdd84f16e2f9cd27f7706e05cf49442dba291671cf4fa66384c8dfeb452505bd2ff76a931277588802e6b4ef4548d638169ad917605132d6eb25ef6eea538aab61d63b12139ff020c2aae17914118c6ec6e54f7b77df69d58032b66f284ac64f161fcf7b808ad0b5ee5898010f8e309475e362a13bf52381c69664e9450dd66d7dc1b525a011b10b889f20f8d57f5324cd0f0b3b969a103696197f5c69a191c819761f9fd7415c42e5ec8adf7dffe9631946cb2c4554b2b2a8503ea54c425347184acd2198c9abb663bdde78c3dd31c2469f09fb6b360cc8b71862612daec322861719865a4f506d424fe51786a82d0711649993962f9d664b462084816068b0e3e0c1d02b1ed8900b001ef8301cc21cf7d2449d764d03d311c039e52404e97a4f0c42475aaa095a9e4d47e1a394b71abb1344d04bb467a9642ee45aedfecf027be36a611f9f1070e96659ab704c0b036e45d00390d15cd1a98ef43b25bbc260378bc3bfd666c27bc7b0aad2ce391c2b8214180925593e669889e486da8009c58ab5d677f920f791a56585424d8c835f2792b78e4f53055994769020ea1b132a3ed99c5924400daedb9df97c7957accf31641200d040e035615a2ef812b4817440e01fde026babdcf2e19be236ccb2d44b14968b5ee0e464f80ef2329b27f4e132615bfe48b3af6d5c0fcec3d647426b65cca180302662c9481cce19e4ebd9c3c5b3ba2b4bc1697bc38ba4248f1f0b81d47fa62122beeb3ec962e98b4266cf8cc0e7d678d2be296567bb7a2af1536383fd89b0314058a6d7b8eaa6cefc87dbfce5245639653ec5db264eb5e3534f7c1e3b9092677943b8f4b19001a7d0cdda68518824fcedd85197b677483fd68c898ca5df5d051948ddf83b258a12bc9ea803c1c8dba2d8c4f40d9c4319d9fb2ba03f1584c40e43e39d218f1adb9c2897cd16ac96202b50e4fa97c18c06175bb680b2a9182cc0e0f363ff4aa73fe883bf2df8c2c1a3ab50f44a56edbbdeb8ea1d300d911f38e55bfe2b35ec842a1a3292e13a1d60d0280ef760ba19078dead2fa9e700a57c556046fc7153e516365a85a6453504767723f06891f2cda940ba4d11ba97795a746f946ec4b2ea265032abb866dfef9db8206d63f4897ddf0a2e43111c0860d2cb6d74ed86b1400b80582049f2d10f19188f4746b278a8089e365f2fff068ceaf019ba006a4c69950fb8e24607f2a843f0b3cd3ae121631bbaa355edc1080d8671f3dd9ba8822d4ae3412ceddba504d87e000d505660513b07f03a00a2ac469bbb38e4191b3b83e0f9b0320bde701d1ce05da875e9e70a0dbd4585087145906a05f68018a2e84375755c9d98f612b2ee18792ac2529034b9a055c025c819e490ca0a989afb0caf6d1f9c18573996dcaf50d620a8d7bc3d7c20e639faa6a826e16ae312ab05e407b9bdffabbc5eb0b70d5e6f4b56c2ba27775a7090db623d674c9843dda9c6d9c1ca8d77a02392be373be9c5c1fc73b0fea84f4a65e90da3d852e09447ad1e44dded3bd38fe1434fb8e9158776ed14ab6d7dfb1f18c5bd6564f78453a6ab2cf2aa3eec2d946cb79937ee7b757fb39ff172f578590d9d9da5fa8ad09acd4195688d78be9465b156cab88b24800d59b2bf0b95031b525c5638be1e723c624d37b5b8d6d3c628c0289d41cfab5cf01e06c4e0b44e3296e7c2ca9f02596a4b38c50bc78610eb607c431d2343191c4bd88ca65985fa007c09872a6744936d3620c430f583473139be0929303af06b937bf1977b556c81a3ae3187227606f2b0811ee4eb74bd23c4dfea329434f9f909630eaa11c3cbfe65b9c41f39d9becf9924284face44e392577bb40ba7c3d13a8dfa1ef665ba663e26a48783e388851003445f2330ecdc5039ca6f909d286ee03c2fe5d923bec3a3c31294ff2ba4bb1103a00bfa3001a49fe4e42b26de928d76c86bf7ba8332039da7bdde103cb7331740c51e3ed5be1c0426779f68367afdf2986222bc1d3123cae8680fb78dc88bd4fd2ad3bd923c4267b4d0a516315ab7ab661586c46684751b9a36fe8b0efe7ad92d75b9018cac0a829fc7bf18838410d3852c20161ac83f5a70eb1eb4f61ff22564d5eb28b3c28b3a1ad79b0705cc5118516e15ce045070a871df102ad456cbc70bb6283f406d2b0ade04d0ef62bca8db5fc32ae9d2c5e4de6af257e6698b77212cee17704f947109151320989a9910621019448d68a80ecb11e6c239eba404b79820c0379fdae2c46f4d1eef6e67fdcf04461e720c509353ff8057616494e02d254e444fbf8dd662978e5c283b165715b617e2a948db84bad6537c7e551a489243c848185e4d12859f817dc5cc25d5b6d2e0209c99d45444210ed99a7bec4ec5276a12751efc5fa500e5afea445ff68d374210d40215cd6480350b1f60f99eb4fd49e068288b62781395291c0b59c247de60455fd90c8b89061dc5e5b8d4cfd965a2e5b6214fad6819ce9a6c82e1d12926cd78bdd1c5e81400aa608bf2a9d297a2f82f055080e3064fc2a6ed7bf8e648cf7bac947e9e32f8a3a6cdf8efd3616afe6d65b4a09883933f20f23ed03f574ceaa68a8e55a58d535f60006e8b819a73b5e2331cedafa83340bb3e988bfd0d9b0843cefdb5141be6db89f03d42d891a353cb6035865ac924ee1a32ed2c94f11349d9cc8ca28e3cf13a021d8de0ae55a172e9c90eaecd2b8fd702bff22864d4c8048e4667ec5f2aa9031b8cc4027c37709e3ad592a0fa16d5de61222f8145c09d9a4b0f5de193095ff3467d03b28bd871df9c5f45e54ccf99261d980d4f3f31f7421c8eba6d6af3ce2a45b6169caf3d3dce7119785eb998032dd5db6a684ef9cf01462c7e6be5cd638941966cc0dd46252c48fd169df5ab3391a31c3bc7cadca0f34df1bcab34e3a7ca2eb016169219b8d253e2ce00a56fac9f29685e8823f54df12b1f754d3ee65ace0884137cbb1c65df6e8a4010f454cc732d32dddcdb5e96ea0b3e5efe0c6f0c7a7b705d4e15322aec0cd58384fc8046b925b3feb3ffd7601023c7abb6802246c19838ee2a6d608e2df5f3b82a23cb8fd378b2164574945c08b6fecd19dad9777a26fe385a1ba8d8cc3f0031934802c1ce833939719848380772045005e253864fb44d5a6a2e92e70393633959ef0a41c161a56734ca2f9f018252c1b7987240ddc8b6ccdda4dc7a3a40cf10cbf96e4a7aacdf2575f85aed526852e92511a0182e295efa668ee939dbdc4bb761bf95f0509914eab73a065e0cb1c4ff314e573f3cafa67cc3637fb5699194f1eb3f44ab088dcbdd5c446bc954eb20589c80289e25ce7672d217345c4897771fbb9cbaa10ca5a385c9e781f1bceae67c408cc76b307fdf5cbf984977c2fa4290e0d7792043dc5435de893cd52ef619282fa1ee08e7082c2ae181063c8810c29d68c4e48b2cfdc590d990e1944feb0d18a7f3804058d3cbe703a4ed6941f11567616c8f773eb79417a242c879280a7a08809ad71fa98fb46ed88d93c82b227eac990b19cb7a3e5547a9e5d6912160958ce83f5ba7aaa16659db47ca60751174c1190fa21ec29a3e4f6040fd56dc8b2f6f7241cc268538c22003b5018b0a6c37069a647654254a0c0e55078409740c1b05eab2d630c757853a76a108176ec2fa8c18e6e2a8571228f341c9083050b5ad93abd465d92a42e1b0e5b8d18233c59a7678c3ed026235009557c2b5d28835bfba5ea70e1ee640b275bad8201465cfaf67c302bb459c0c015a14f834c16e7d248d0c9a3844c22c81d97195e08492999da947568ab1099ab74b19d6a9f7a3df8ce97ff45114080f390e016395c00f2a703628da9de9b331e1cf6962c0e4e246a5dc60f01f81f19c33b266a80de13d44b23150b753c7fd447dc01cb5896648236cc50ad20e585ee5712ff61f7b3bba75d9752ee3b7fef42f377dab4fd132bd6ffa425963e3680b814b07b80735e626cb1798a4698a456c5642a73993645f40819c03a6ea32cd35817360eb88d7ad1183f318b48f37c3dcef7cf9366081c07265daf28d4b1991b995d15d474d3ae9db8ccd3bdbfb585769f670b9e1a40d748fcce11252e1a1410bfbdb00358cf997b0f4e176bab642de513e2ee9bd2b3e67f3976a5c6e5d8d15683b9f72b0eae996ce03aa337a983bb00e5e1ce534d074f2d9babeda674dd8d85eacd7679e409ec3b5becceb790d3717c1ada7900baa17bb5de8165752936079b2b3de650122c21dfff9963553872dfa894280ff734f00eb84648c823c28301bf754651d9d2dfe77c868804ac24ee3e14541cde65ae0a7d3cd9f6a4b6753161ebc527aef59625e716633e643cf66a5a76e1aa7f14a3e4b880c435475a4f864b61e13d8ce14899e7f7cb7424d585374cc65533598df71c243f4fe95fde2632340931fd07aa158d5dd09e7d38fbea2a0ce023956db175956ffce88318396ef3966967ffafe522ac55306610f3fdde3e6a2e587f9442b6f0594aed38d19ae6496161fa270615c6a81c08949fabaa352e568036b13f3aaaa3e48b8715e494ad456ea1c63f51d02d9760fce38309bd5bb9b169e0e72bd5987ceb3e16836dae54208154e01f0084ce97970f2eaf8e1322ac891f50c88afe2b2771cfe2287fd35820c647140fd45a41dbe88ef3eb564b098ec45c432f7889613dafe571b4f51118a8a86083bf364c24e5f602069173afdd3c3032a5fb4a249a13d1332264b6b8c52b4017c423a17b2d1394e25bbeba7fda1ca85d4cdf6043e86c6eb67b5c658cc3ddc635b95a61ef62ae89f6849b3b939eede1080d28a6df1fb60260448778efc4a002a28cbb002cc212d11f21b59686a2697561c87de63a752a260ab8298ad9e6d1e1afcffa504e43dd037857f9fbaf9605b0b0f7f7360d2e2f4f9d5c6050e71aa028584d7c632f5abc391b595c57d0006bb3467d1b87f722c27064ac24860579443df61c16654dc0af949c1b41c339736fd6d988834473e7c24ed4f6ea9bd8c3a7a2752dd86f57e98010b691ab247fabe5652b82fd328a55600954335130f756bb002f09555fd0192e4cd8dcbba4a0d3035ce0d42ac055ad6f3c31d9615513fb18a89506ffe6972efb9423f6bae3ee075275968bdc82793beab1d9f0af63da829520d21e992091df1b3b9c40c3eade108ad88ab815cee264603d40b5752e1e4cccf3b98516c5e281c974fe9a51ba906a570f93c0f21e26494f3fe8b24380da8c86ce38107bdf6cecf9fc7d71faa2e6d873513998b24a9d452fb46f95edb724e08df2d3cc801173a00e2361f6c51efb67d4845c997aecda81bca85ae65a44e413c3f1175d7e9a4fbd73b04f5fea47daa40f00ec64e95cf27d3c39a5baf45e336aab283b8f0b2fac96fbce34bb702ce2204921e1a3c7368f632b6c461de9fa5e524d3609b13f8538fdfab3b1c47acced0e29d80ac0b84cb4f33cbe80a1a3146a1822be734d7b8edee2afe1c54d9e1cc9cc1462259fe66a01bec026a39a4ef079ec27cc025e39aea2ad8c15a063b3ed8f25af47c94d3dc6bb8469e9fdfd492b288192613662bd8023780e6a88dfd93f047bf885b54c17e3b974ecfd19cab37458b214904937a68d1976e50af9482871e97e97f4155d7b20abfb97afa32cdb66a88caf716ba04eb1ca044de6fbe8c4607ee396315e1a5c1e1bdc40faa99891e9b3cc708d15c1affc1c827c7f0bf28c819ad45255b0272a8cf1f78321dda886085d958bb08845529e0cda7c563c5f6acf106e982407e6eac31a6366d83596f8bc61792bb0a9ad0dcdfb8ef21063ee017d9f30fd440bfe699a64e1958af259b5e30ff9f15a6fcb75ef7d1142d56c5209befff4d9a44a2cd9fae95f8241efba54b44e966678925fd6872532deeaa531c6163639a9a2bd0d4f9182c09ebf9f5d71e18027f57ac086b352601eece31bb41e3266e5d51fd8af998a8eed9f4e88119b010734ec8920dd66d88fdb39cae2fe4760ceb3990ad7ac3c4a7f388231ef888fa721fa75beff8355b900511120a4d7ab0e51ed48d3d24a5bf81faa59ce50d55e2151d5e77f4f2f3391552777602b438a04e9cd15210b1f4c9c7e53d48d2504e3bef8fd10714f13cc089e4a5157132c7a821c0840aaa51175c9f7cba0b7f331f16b268805d232885873c020fd041379cd3ab02c2413e7477f59f43e7b0175bc643403c24e7bdb67843d69773972a73b01f07234fe050728e979bad273f0e7c75a53fa02ff52184f8785f0fc77dc4d3dcac5094de6a2924bf13737d81996edb5c59bb2af2ad2db85201f08507f40061b0385fb580e1fe8c3692677fcd0594ca9f837243fc1549ae731133b0e920160b88601c8cfb6dcc1580a608e6ee58a0c28c33103443c9ca203de01e3e4d691f98119f90b5692af0becf8586acb38ee9d8572485888e22d2018318ac4b438ca9c5ee1622434b5e13d43fc874b72e58d47344d3918c11965df210275e0a7fa2ba0e3385cb00ae836d9259378feb67d8c37ac91f2cfb068f29fc5504cdb470cd12fe6237de31fb6b9800d357ecf59fb5656e2b58d8f548c579816f0c120a34b1f964e688935be94b90efe0038943f014df94bf021a350485e7747ee725f7b95b300df6b4a3a86e44db6002a83b24e661a5ee14fdb7f92505054bac1259828de9f9e58a10446c472dba4d341a1751df7d307d997857e9c6c9a328dcc2f40f63a5ee0dda372d9b163105798f2472b7d5cbdd27da952490e2b4551de821d68ca8205107974d4e60d946d68a9f7e65d6919f3c6b845e3244426cedbe0aeb3259d6c4a9d5b3008b000c2341670fab29b669ee436ab70b9259b97da87a320f7e349c88fdca9b1b7a78444432a2825797e37285481c6a59f6a7f7c52f6ab4bea00d7bbcef4e4f6bdd7537f841e531c894f90e6c360cd02ede301a3b4461ef3e838b7e7dc6335c87661312809dfef1479a35ab989ae0d0654a8ccd68b8b2d62aca65071675128afec0684913712b1773b800795cfa28d55960b0bacc874ead99453f0ca7e68519008e68100563f681f0235fc910cc0c957c5666d14a7046914dfd13f440a1519c02b3e03575b3beb63904efa1854db459766a84fcfafe611b16dbf78bbe81b3e97c1921057b010ac14eea2cb8356bef0066fbbef12a357c70b416c62f46271968e13d8f99728971b98bf9fdb45f7b7bb35f0ef44d776d1a5dbaf978313a76c7bae5fd75ef79363e12dbd6f2e4f2eba6a135fc8baba2263738a8227184829ae772d1a1ed07a8717d58b0729bde6b73f670511a31690fcd2cf534d7e1c6c663e4ce7579ede364cdf63084ed4390149018c2f867903bf67a7e79391ee95c534ca6d3b961ccfe2d7b8eb474f3116fd99f4e8c49b5118e7b0dc63cc2bc59a521f2c2e656c2f634ba01e6cfb9826d16e6a633135b13c9a6f81b651fa7cedc53e4ba3cfb2e21a95be59376bc6e16f69f70bf9b5f2603a98b181e0c569d479510ea08a6c4c43793646eda580e69fa20c2cccc5e60b00f1d3178003ffe842f06b8f57d2c67967e3d4c2317347a440e76dd9f8a6b50c4ec4eb4e518abad7dcdf1e77406fbd710f86a0b730c063ee9d2efe1b41bdf4bd21da8269dc30aa0499bd19c778eef77f5e6aebfbf3a5d3894fa6de962ddcc4bd398693e5ecb737cdb762759463301ee4e74e0d0224c7b6476d83002888359fdba2c721b70fdcf71564a291bbf58c411acb9ade0a06cb82e494bbdb4ae0c1bb270b108e18d55081f7b37316e3ea6f0e0b285ddfc951518e103e045abb10e2c86ec6bfb66d34c88d4bf1e3edff9bf9be0ab07bc4e5c632a7cd8dab1d24f94795f8591e1deff5d4d7a721c7af7e540e8297a30879cc6060745c5e65c11f9a2fa3c22be5a21782aab203160cec52a48b0f3a4679844661ec7a43d34e9a08af3d4061772aa460bf9657d5c064ab712d2fad2b0af887f7090f3da1237d3bc7748444afc63618fae3806e08b3a3e086f386d1c390b197c7c02505514542e560cf74cfc6d5e3396266784489901284c3a2fc4e102b829362aec7af79b68ad99f957611c6d1895962eed65e83936b83df5a9e2a2dcb2473afe7f37a531b16a8468419ea7e2e78f9ca42062649b6d8d1a8e9a3a92428ed26a140bc98e64a67d469b328bfc541147b382ada34aa575948f8eeec71ba0bcdd08d808fc937993d8c9da698c9af976f66922a045a3dfee3dce5a5d82d149995c05acec8227dfda8c1f8e99e2d14985b1b5ee26506a31739ba80f37916956263380c9a38a12843a1f9ad95b5e85472fc662e70246032da9be6ec6ce126e2d63e3229531be477df7521a3bb8ff6202195ee0443a008b6de142053708bb207c601ca2579a178bbdb19f7998ed86500946bea597a613c751de216f5ba35e823172124b4cf165604a9c89afcb98bc5a17ce10d22c6a7c983fe2e8a3e5a17a62513562cc660885481f905d330d51ea43b3900bd81b101aec55df07c54caa9aa409e652e875551f1a0edf6230ab7fc1a937f2ed4bde598a29fc3d26f62e7f0cc81f1868182f089d3e0e794657591bcbf43f03f77524a97f3b4e927eaa06bdcb04b1db4ac3ac410a81a363ac3afbf4f324af2c25bc9b934ab0e785051a2ac895e6bddfc75f73ed36f23e682066ccf583fa2a60cedb94973c5d87c33f16ebdf5c5bbb86c30eeafc3b76c276f7391de7553376f74f03a000914ad41adeafddea589153b4c89404b9d6c5f1c05879d5ac6691411a7673528a3685118e4d43449189a10d711a0b335701f528a031597641e2f8aebeb00b91a0aab6e6b6b1dc79b81b8bd70a05316baa15076fb3a26f8f68d530d6dacd9e8e76f43792ea1df7c3ef3eed9447464aa01325fbb596c12aadd0b688f3c76b345a94d083773cfc482f5dbcd86c40e09db94e0a070f0dd02a843d6153c96e1962ff03923e5be09d412dc50663256a8ad74092ac12b49b8e0f52bd0c12723e439103848119871380a61a531e45749c2358b42203938f2aefaa338ca5f1ce42b6a963cc72b3cef7be1712f83a5c75ec43401fb57c106bb194b8f36fb94cfcde7d51c8e925a514d3b36d4f54d1c7b0fb7a62f63632b869325bcddceee48e7bc03675e446335ac874c555715bb1fd15305499e23c511f106e7c02e257c70de997eed5a918040a911781dd304d28ee10ad4318af93a9602d9106ff3c9e6f74c76a74914b408909d9ccbe945d4123bff791903e900505e63417134e87f74193828d659ae0b35758acbe73e5a7631c9c72b4d538631c0d886960da2f60efdf0402993e186b0be88cb3a1aa3595b18ce51261a24dae766dcbab4e57ec190228db74c3f3456f495abcab449a106dd17b7c42d234e4508d4745b32c880bd6499c84f3e46f994616d644f4a3c8906de4d555332b6bf8652dcb0fef2289b1e4120efab8b780bcc90e896b0d62d46b0298f2a8866490436dfb5d1de01e7b3c3362780dc9427d4484b86776ee00612a3dfafd5969c315e53425fcd770fc2c07eaa4c98c06e3c9c4461e85148ba293713d764af5fb403067ce587e883c422ef24eccb1aeca560367d5ef4201ccb8c20f239d9043fc5a33c6d5d8ef9d3c8abce045ba08cf0e8e66862c119aeb380ca8b87b4a45bcc4841ea0cbaa0f5218b861e80f71fb91686886d4e2d2d9f154568e9d264ef015dfc3fa061358a53b815affc139b77e5d5fe4c6f1b6d7fc8ba9f31279619a097f1f544871a8e9dbe9e3cd0abef161203b1bccc36d1013167e327c8962f28591bde1290a2d4b4e667aca5c96cdc520dc14a4fa510ad6ae2cc6e917c3551dd268f1c7451cc0e30bac9899ac417bef95cb453d61114f7439aff16a078b386ad9ebfb60437dc0a17004d62f10837f0d62c8dbe53a4e95eb59878f52f42cf76fdbbed6aaf58fd73d31524e69a508161243102fd19e1f136bc196c75d7be357096a0b528e88bfbc62d3a9b0288f9718c32c59e4f947f78dd8a0267570abc58a7f8eaae7b0269d5b466ae4eadf0451ab36a83db8c2d06670db9468663aad0c3589aa0cf94d6b29e74e36b998337b04c9dc673be2abd59e42eea680bc119bb281f084f1c7eddc6c9917a527756a0b82b82695b7b147abf6b0a30208cf8e104d5a1a3f282a54d248c01a63b9dc7a76e9aa4dc984140d0797912522ada42bc1402834e86b9101a18de38e15bbf221a9f318670cba47d5c4b3539c59d743bdcba68bf21824c6839665c995f3eecc3271b2395281ef0c07f803b27a62b3bf573da8eed0beb9761a511cc5b1cd01301de621f1ab280c059070be9206d810610fbca5cf4f03c84f453e94ab14425da72371fdd3cf8e6aa06e908a46c68c2754aa6d6b084a48e46e9b94c9e7a2e7c759c389927967ec2b5b834e0695e70c77b0c620a5f7e25910095d631194366a8f4f87c106f60403a0fb43e03eea59e052036fe7720ef863cd52e63c51b4eeb072ce8b5d0ae9749532a543109f1503c6742ba7c600bd87d532d8d3d45612706050b852677c200ed123f8eb58178757ab85bc04ca5f859d14689c202ec3c3ab5b4312f58f606430b8ad506c72d6a0c83a4d739de4bfb736cff4520a8b6b1efa1e0ed09eee56c4610a8b0384b282fac8f997fcdd4aefd120114d4f5c4ab969a15bb13104d035610f33fd07f7110097d34f3cb31c6a84840d77516c07bc567f24dece5e12cd53d104ce412a3306b0ac12604e8c2bb97a5c686716548bb5c98af61685c8aeedf7b96a9694bd7dacaa1a5694e21be422be132a91963e3fed03e5f179603407926bbc0311b765ec22afe9d6b1900a9a622196a099d89ff5d7df9df1cda22adf21d6fb28978e874a3d6430c19578aad9fb2c0d43cf1c0241ef71e8bce834e8a59d736c1b81a5ebed419d1e5cdca74123c50ad95376bf60987b1633f379bc8755041cfe03c0f6a12c175538f5525e00b8f8ed2b29c7f9d805c95db2cf1dab9ef4a71f183c1c1da91dd14137e0e52f426be503bc781170e67db00f983381c953b8e874987023c03b17153b88338f08d9a87c1bbb6093b0d8eabcc47f13a7227499890550ad4791edaea905ccfa63895e6540754a3e5d3ddde05811c6752b65fdcb55f537e4884ce6f98eca2e43f41b7bfeab44c44ca1881143ee26451f3d43af7f2a51b680100ca5ef7c04ea57cc23db699b8feb4e7413f285762b71297a25f1ebb14179c734008d7bfd9aed7765b4ef940c1bf0e912b5fe5821e7137b93466244e5ac308bdb8c3ca9395df43190f2c619aa1a774f8cd3b2115d44736d66ee402c20c5370d8e4f9a14fd7f44fe0c2b6bb37fad43870984abfc3e7bafd4907ca5e6648fcb2f0abef6b3ca2c25dc3a6c37772a2374b79435155773a25d5026886ec6cd0d8996f96e9433018f771da17b45bfb709f540104e6006233b9b64f827fb00b785efe3eeb641d4a2b3845ffb34912eb85939ea6ccd72d089f4cc0126b41c18678caa80efcf667d88bc5f52543d95bed461638569e4f0098f29c133638c91b00e6743579a89e7ae13be0bd02035497b4471e9867a796bb49fe1e8b760ff1b8c307918288c16d1cf635a824fb8dd364eef559abf1b6d629d18a6c537dc413a0c363c7e543cd86d65000bda0ff2b2720c318b5c48f1589da2a02a4134befdf688cc847a66db508c3ad722cb1ffdfb2255f6f9e6779ae423fcf3dc660f4ae885a9d3c176429e242442f4324d77c33467345f066fc4f1c2df629aaad786d948c97b9d8ccf966fccda172300500d180e95533ee9c59af9a94ad95dfeb0e691c367e7266cf3b84bd81f63c92faee9afb1280d63bc448f61657fff1a18761cdbc3ccde2242253ee12f47565bf62196a91d292f4c22d814e98e7f3e9f92b57f41f37ea6e303713ea457f35b1fe14e80edc32efb3974f033b8f9bb3dcf743b5aeca05837a76b9165268ddddeff51f9914a9aa75b92661ee8a178639c465a422baff88533275aaaee54005c6951923400cbf79ae59a502afab0106d9757adf0a75a35aee9bb77449768ff695bcad682291b1ee99648dd9108351d5f2a19c4a9a100472d81b09f2f402d6797e23c1348aaad7ccb0230baa28e7bbe0bb1aa0a166f0314ce6fc554ce62237e26f4ddf5eb68f7a1bd051ae025ec866ee8099c8256bb60c7aadcd713642f28751d2f8995ab3bf905fa8e086ca008f0576dc0f50998014c6f91ebdceca1a3dcf4ddd8dcc9b04bde7c14ea93234442c3ae0b224bbb3e37290af11f36131c75c6e9bd9b3835442092d565ef6275c48c788bab26181f6cff0dcd3c45ad61cc16fa5687b472ee9ffdd0d4870e30b5cbab039bc9036f2f9905c6375627e9029bf0f441e7468f008fb76e81f8b1aa65e49df9b3bd7b921dad9992c34d5e1db2ccfaf140d8b18fd7128c74f175fc248647708b49ede524c0917a78580ad85e20722e5d466f4148c505c8fbf600700bb94cc4c8adacf03424192567418c61acf45e03c5e5e8ba0f26f129400c44b1ae339f11e3a93ae199735920c1c63a96c9ecc36f033011ee04123ad0e4a948f6742a33a9e124c1a06adb9c7bdb525037e2d68064f351f98652e4c2ca1cb3dec4825dd48ac4b02ec263172dd55fd594123a84c6126b1155bcdb1dd03f5325a7b6e0aae2c9a2f8094964acc260844567d0a04acb30b365be788775f9a73a54c89b586a1af797e4a212585c2be291156aa1b43536f2945e194d29c3ce1332f265ddcff2d4bbf9d2f6a5dba49b405bd05397f440f6826039aae370461617cc4b6f01ef93cc16990673d2b6af2c45d6de88d306a407836a9a0c502e588d0d8de0b7aef80a1a84060821a8c49e75c54215fa9096a12c089808d2841ba865458584bc5e751b0bd003ce692c979bc6ca82d8ab588f6b47c61039a43685c13eae6b8980208781eb145923a3586a1c9d18659bb6efa3078b4fe817182420a1a987ec64fa257b5a9f859c4031021c3a251b7b42c8b772c125e2233b5701dea1db15669126d61fa27fd652b1f8ef2f68bed2f32083901e5161fb8ce9001e069e745089cb056813a9d68a2ae40be437f021341926e597361edd46e8371b78e12095c6653f34e32737dcb9fa7ff887708314b4756a78be91935948a9d3fb25767336e077e99a8856eee956775bb78767896a3fc7a49834fbbd018eb02b6f5da9e67bb9a3cd9fc47ad45e22ed60c1b433f513f352e002c096abcd2f70ceab802d55a99af115faea472258fa7727073f266b5f51e09e282dbc93e87adec84a942c1c921300acb229ed21225215b2377db715c5c1401150106bb798c0bde5f868ea93fcee61b8116183799e2a7046035a1a4b012d9727fece2f93165df497cda2e2af581c7177b8a6c123a9ac7f92036acac3f8d153f49448cfa01cbd1a2d87c92896d493b487552c3cdd66b1b80887b7a48fefda1e1bb32d5d5dfec93089949c69fd5231feccb3c21969f50cbf36419d6576d3f0464e19a77b14cd4461e87b46006cf52d08d04ed35d862d23276718b90a3f696293d0d2f3e2cc9537fcfca09f4fee8150beb6944dd9a138a1ca3d760b3b41de093d50f311de38244a9a65e9cad5f058e77cb1ff78510bae6ae90a6ce2dddeb8d2a26e231a2c9181c044708d0a7a1f1e05d09e24d1165b1674cfadea2c4e79e95a93ab4b15eaa52524ba439af421bb3c9e586eea0848e5b5c3c33766975b2f43b60c1ff368a6152df0cf3f9868a43260e851c8132708170d3abf286c3e6032a086e9c3e32f4eebb0a703c39548230d0415d78f178a05ced8e56a2e506f1b98e4dd65b990086d923691ffb0b184091f00e1f236171f636821a70b6a0210e494fd8424c26cffc3346d88b417b8a389f3fa041b28aab92464cf279b84e58084b4e421b8770236743df1c8f2dd8ddd5c7afdd96e4d50e23d766553521e8bcfdb65661c9d6d595767d7846d3f0021c797981da37deb06dcf5de78ad6a9666d182ba1d37e42e2df5745faef7592f6f4eca8e1ab252585d761d64939889af92e789a8279697cdc5e77016e788fa26bd6349ead490deaf6eac687b330297759139c91207c245ef7dec1e64e1b504920593b9c3570d911376343574d266dbe92b85faab5bcd4579915b7f734dafb6c32498aacc6ded10d3c2ec7d8d4719146781b313d411042bc2ec54df032a929b711e8f59aaa987d678b9d9d3dc0b77bfc22a743f04a0ccb4e9e88a9afad39446bd21de91e53907a70948ccb7991ccb01202610fe14c6fc6976ce63a21643c2d536bd31bd42eba848bfb4a6683b5a9cae5291a35fd0c2a728a350bb639ce09832ca5bea781fdb74005549b5d838af5bd199f3e65ec89f56e7fe090575765ee2a92db30347e571555507df82f78a1aad66962b54fcf9b9cbb788030ca7761b54e7e0512ab16c044d6f9d6540bf00a59c7c13b81e154ea3ec960ad24edc73a3ec83a1f104ed535a32b060ac0bab1d50c0f596b88f8f1dd0e533d609aa44a2428adb0549f20e36cbbb5a9d78b0574dad38b2edd6a2b2f4f155eee59e4cdf578bf00662967e5dfeedd80941be83cf6f4dfe4a19e1ac9fceeda87e0604a8ad677363fd261645e4e44df3bf5fe22a81cad9a2c04a28aa70a217f12cc867b0e458e3e286365d3ffd8fd349b7040b9c02b849924cb1e2ccfbbe2ce81b92b969d17015b017545d7dd35f9a1c80f131b51fbff2076b4c802e59abbd0f1630c8d67abf190ac0e64fb0bfa9017b5379f185096c6acf4f340dd2bc672b51709d78972581de129d847dcb5d67623e3ec20023350cb7d1c0ed120035a79007f2112de0cd4f8aed861b530e3fea630c25901045eb3bbba2ab06dadc237ecf7feb12a52f48028b66329862343d1b054b648ab1cb0c9f179a9f16373e55fa2627ea9e0b8f5c797c861d9863678e6458299cbb22655606bc109b3609cd7c03ba0c739515defe3d5b78053ee117843c93b23daa7191a448860c7d066ca17a4a702c5153c81d84f60bde9e2299327a17301df0d65650e514bd836680042701138a5f77ae44a90f88ccf47bb55313b619e1723537915f3f04d363a8e5738285b2ee85bff92166beb329ece20fe6699b3f03aeb85b13ecef64a0f14ce5a09078f84ab8c80ec56fc23f2e1444a6186e35198d1d8e52d58e0e32a34b83b8a65489387f613681e5e383d88c329ae080d270cca47e307c86978cf2c0d4c13c6a2adfb797f8b99c16fcc0bdb4074bdb148f612df181aae16184358a850052b5de4e57894faad3c131b17386c0e793a42b593e82dc75a2a044ba05b0eef757a3a0be45c92560280499f5b14ae62380a9d2a23c449f3e4dd1becf84dbb4f369d3c1d26d7876937839664439741930db1ecd77b6fc2366ce9bad0724ba2a007875b99a7a4ccd4c5785fee4b15179dc30dfb69b1b16079cb6beae37ae639dc1a8330ce862764668abf1123e34692d8a18663499544614c154753ab42f897324575ff786c1dd3c6511c9b8b57c8745fb3032186cd505b67abad1866b9b87e742bc0df940e287769234aac4c043a2615c33b74a003a90bd62bb8fe934fef2aee954c38be61df7757c1432787ac5efbb752629798dbd662b48241a66ea1d839e5c2abc8d3a101001686832c47174b5202cd73166e5fe87b5b63622ace49a9585ec11eabff50289457d012863dcea9cabd1aefb93c6759b939d926a7dc5220ac8dfc7c67b80c670ed1786ba08c7af2d4ae938e503f4a32f06dba2d691f954c6d4d2b08225d2662c5e8f842ebd87c117e2626fc54312272707073fb16b4b009507f07c14cd59b2a0ae3f96ef8036d681abda3d4e5b6523287675a997745597d514df1731d256988a9423eb896611bdfc6aa08f271c654419e5b11524b2dad1a155d2f8fcea1c8bab0a3a99810c15c92071ca1689ba2a3c75f7aa26378f1e3c596a6ba3c0081b74e6c2fb9f804429172a70dcaf38a104ae8e1a29a4bcebe89c0970947d278508e19c787c07d0524e02a9007cf793db53b263379ba675706006591a154d08db1f210e0e0d48e4b9979d88599b9cb36191b5444a5e7b2eb8ee89f5b847a847d8fb2703cf0047c1df1fa50c938c4403aa8851299c30f5bf809e6521c7d58701f6ec9297e1d477483b5690c1946a9949406fa1df4dd3995280e8859d63fb008ad11089f4311b2eb07c45a82939430a6e0acd86f096a4c89d0df5e75fbea80d0a3b24d234dc2b3296c182c6d14c26e5091647e6635f3f7056d57513dc161c8d9868081cd92f894152a8fda6d90d7969a4ee786b523c240b0a60b97d906504d5840a1a0af8552085a81a29e980a37523a5ad25c0fdc12a231e56f71a572f2c6ffbf8413ddfd7211a2a5d9fac0a1b5242ff45cc30920b65e8db5945295649550892d4c3609a122903ac1cf62cfba1f50a7abf63aae2696472a2180e5902e3bc0010d5757865ecca31190882a02ed626176e1717b40bf1f91f2c2eece43e565fd0a8df7e08f3849ea23f490bca9eb2865fd1b37da2f3a958cdfc72304ba11236b1b399e070194fb2cc8f13a579ba8047a779886f345aa166a978283d0dbb65f0f114cac58d244f3a09ff9613481f05b910649b7b31695bcd3df9b099f422c825a0836b20d47eeda034f668167077ffa89e15db2fb46a9b6fa8918d6da4dc8a302639fb79f945c977f3dac11fc7efab9a45397bd16a7421664ad21140c109e4859122d963d0bdcf12fdeebf53a66e965224e69fb4a1f63a281403ee8ffed384ba3ad7014dc5466226ae5e8b376a515439a8258b636d7243790fecd946c009748e73e7d348eadb412b65818e8a8d101a8d2b32f888a6bd63cf88fb729bb7c868c63a67916fa803e1d2249617a81abb457f882d561853c38f09f28a3eb54accf9f797e23de509a41ca890b22f6efa421e817055d2df9838fc228d60d3b4e3f961622f78bdd52ac232b5b35b83b114c9203b8592769b704bd5f5e7ed5a3d97269ae8aff99341b4cd8827d3925d6714ebd7c044aaad172e433cd83301694e93532af341617714cd6a8101b394f227462c34c1ef65a7f634ca33f3fdea16629ebe154d090e1e84414de7500abbed835fdce108e606bdfe0940da74def081bf69f2ee9c41852696bbb8410a08c7763757d35d5ea6f83b157f22f6be803735a9c9392bc1232182025b417cbda5b27f5409c468177346012fe76caaf2dac5a551831eb0093b88bce8ada67bf7d738b721d99b2f271ba9d99d9dd15527bff7137806840edbb6afd78256913c350326a1c3481c2abbf260a619e03b08b835306639b1911aad689f794bdc489064829f51beaa7d6e2947e01e1ae9a64aee283729754768d6772462aa0a3dd70d341af5fe51ef321b78f652a0ff0310c0486bcbd09d9fa9ad00b1a21f9501bf9c58316added8b2bbc93b4a06da9fa5c720fb87633d0295a3a2cb2901ba9f91a96c8193814d637fb877ed620d414403a8548a9c63aff47db75628c660a72ef44e306d4eba348c4e71c7c025c49cf81ad5095f4f9b86fe0e7d43477115d0b0a599968a76714b8e55814920dbacbe9944d154ac75eca9ff6f523117e9547cebe98c6369edd3addd3d8b8d0d5351fc8552266b4a54ee7055a4ea981e30b5ac54d849a4a4728dd2459af28ea5f8e9acee0e60faf4caa13e12ccda61e740908a926c62cab00132eda366c9a62e15ab983377787e56f306444f34b0c94200f5a9c05d43e129434c07fd8c1fc66df6d192454ead4c3ae53778c71e0d919f4ae7d9f0748c63adb80094ba99113a1017d7aed0807a072e8b1a71b5d915efe7478de22d90f097662bd9a614269295233d3c8ad26ea33a0df37c8de331467edee87360fd4cd5f1d0f6f54902b3e7a875f6a04fb46820c59f680d5b2032258692d97165d18deb22e1245602ef9b156a09aba19ad133e51c028788e87a57d00a2f9664ff5f56fc7fc90848a1e5086b7aa3e1c4186a762e299c2456caec5b062e064d49526a14f1eae45d2e2ca96667b1ee01116ad1a93272a3776e535a5440551c10556b9a6fc120ec5d9a6591d6ef3820303ddb65dc9f2d660e1bf58bb5d8a81e91a3469fdbdbf5cd2b7040c206fc7b790514730bf3e0aa5e67ec37985a7ce84fcaa39554ad29b5472dcf23d207c17964d7a236b0d8326f4482f45d077e9b162e299b0bbfb3b21679094d6ebc545d67bcf109568496d6da06ed8dc6c5ef358252a4a1840a5ad5f0c4d7ba74eaaddc2c6a17c085c266ec4eb0840441c57d9278f92008875313b0f9c04b3149ba2ecc586423ac70835f8d59aba4f9c989032d027f9d615032cc47387129b3081681f4091bc6a1b3f364faebe9d14a61bd99e8804bfd6bc4b3b9b9730ee0db7ec0ff1d8a22fa5f2fd2eb6b7da7b032de38ce1cb03863374d3218bf7ecedd77f1b7bf73daa10eff791c2fdbf8b22b37d3ab5167e0286f75f1521be6febd4ce92ccbfa687542d482fdf23251a19798a474f23930bc52261948204b2d41a27cbda83454b5dae6d490fb60306b5d20e82c0ecc75b98c4240f11f0f759a745b8b262fa6bbf8eb7d1d52864ccdf77594610a572630d9e0ed9f871c5c36f76180d5a5778c8141fda976506991d2587a1029c830858c6d97ad2442947112a1dbf401ae435a81446c4055dcdca12b3ac08b9b72479ca4c4e8668716b1f77a35512bb4ee98657138927425815e9fadacf63a44a7f7ced7e87340ec8ff9dd1efedf7c9881c192a1359c8f41ff9e5eec32eb7de080756b5bde44f17e9f66de8e63416ea6cdb22ec722d2158f512f060032ed8b0d8f4ebe64fbc7112ab1692663ca37559c05c17a6fe3fbe201c2a1c3cd7108a6c715e8d8ef1c5771a023e088a8ced037a168f8bab7ce6b48b61d1aa05e2bc5801e31afd3ed5f9a70bd3fd4c8278d25e6ddf559a015caff8eaec2475040307a8c7c3888af8d03420ce5d71e4ad8d43103879f0041dca8832e6d067bec8ac3103ee20a5ccce39ab6b546e1fe7115f7bbf285a57d21a5d5ae408ed8372afa720fb50b04f47f7644e3058b6802fded8d18778a89db4020a7b9cc192cde6044d2bd5e363693f47f79e638b76075a0cd7b25eab66ef4e7b6f5aada2eb9fd860ccb1ff10766eea351145a9071f35d421f6d06e4673d23556a2e943b54811451b36b8209fafc81d908b44fdaa6000ff35b45438987c46f853215cdbde5b74a2743c1b82649491cf2a6885fd2e76b1f69f3973b06d24cb3b140c6ff62ddd4f466d9e394309642db6dad8cd313f708f018881b53ae2f57f202bb2d70cab654630b1acfc966a3cb87a5346df5415dbd968eca78a355363edc503dff0c1229b8a1318755e122a3ac84bc273da1ed33171c8e78b3f8cdbc42be982c6036ed63401b8e21cedb6199c1bfc5d20511830eef57b636493b78c81425a85ea985be7b584cc914a5c11e4a92dea29a2db922edbb4fddd8160d60a0ecc3f4f31e9e393f18710f05c272697905f00b429e2b369ff2d15344c6a1e02442ac583c0d2d1e04e52697f3d59952d39c0342fd09d7536bdd42df2112c5f4e661ee5af31432ea12bc73f3e36301f11a6cfe80ea3782fa988ede35c1a88869564970b972e2a003bf2077eabe33b28c37865bf323fb02bcaf6e7e20d58b3df0e96e38cace6e79dc0786dee70e200ef7fbf8ae171c6587537b72b16993c29d35f28f24269b46e083f8206d4d6986623a7a26a36f29a3e115067a5030218c3066839491fcb015cffabbb09fb505f9067057f7bff7d1332220644a6f14b7d6df1b56138d714ec1263f4029cd4253b0559792da586c9675b31a02b6a5b15a4bf4e955e552f01a377832cd2c56b7a8937426569a9fdc2076e707cce33282149faee8df2dfaebbd5786ae3e769ae8e4bfe8834393d586a0cf3e608551ff051ffcbf28526f5fb6c67b2ffa1c7bfb1343eadd97930a5485321025c9db16d13e1521f4661b0e2b7892b9670e960d7886a6c829792e3b19742fa1ee61ab1c38310aa4171aa99a1c05ff33e14d423e0a0f754fe2425285345a32907450495388047fdcc5a6141c32067d5f39c93596f08efccb48b6c5c07942432223fa289ad9871884a9974e6b405783a0f05392e008d0eb66f6106db45441561df11d12ac9fd095d446afcb977d664352c73390beea618cbf0a86d7b2de2e90d5d19e6f59aa3fd9baebb495c3b7d93fa876aaa0a41136511682a41f0dd38ade123846aa099429d1cb53c7bbde95543bfe321c346ea2581f9d51700cbee9d54e611ba985b3b5a598f43ffadd5b8ecec0701912e97290869c67c515103f370e83b365250def0ada58ef6993f3154330f269341a60a168255d70aba3b1e0e2cddf93f33861760aff0ee2f2e20f169f5e3f7f0b7a6b29234d7de4c513427042db2006ca74518fa2a0b42d4a24051270381072527804ecf964122e5946865061299c91f1d46301fc6eef16facc9ea6f092c951bf48cb936c4c0f5ed219949203dbd956338e42c669934400042078188c40e23957bb62ed3ea331bb0e47cd209054c4c2dcad672def4807009969de12083910a8baea698319c98bd8b60b76d49de5b6852cc2421b0cfd32dc16a4d66004382ce171908a088d1055d13c6502d08b271cfca2ccac54d9237722cc33298acd557404957467fd5d8af9e6b1a3bc783ee4de908ff7e51fd350a9eab6ee0c0a5a361a008e016316c9cacfa2a20ab363bb4b7959ddaa5f124285c91b1929349672f09ff381017db232b6b0953346b79436b0d5424ee46b4fb276a6561e8fd1eba36da9a5ef1da9afb9a59d8c28adaa5dcccf1d285c10571bb51a00cc8232ec4949e324a0a32d82c299af7a267ca35d69cc1368565071097bb02ae2589c6a4479d92769957e0b7f95d1134e171df55823d0c76e90acef6d2887c201fba7414f0fa624d520a1f854d4b6495f8ca5be04ac7711fbf7e096edadff6d99fcc254401cf84db603b36204a27a06fe41a9961e1d5dfbafb322b5e2f2ee9b2e44ae7aeabb40c7e7bea9415754acbdccba3577400ebb7413f2009903acef1b54c940def66b7786c55dbee7abce22c75f2689780e5edd420a2397e95fb63cf30d1276de24a3dcc91219650822b92187effe31056f9e54e71b2ca0ab27d5dca6d7a1dc6618753d1193bc1de8b5c8c50af580d122b91f41e21d8e0bb6a9d002eb049d4fd774c021680ada83783d4eac09c6abc3a4753c6f804e85ae832c12fc789e5f3ff031c18fabd908c86508a11502fb4a0e904fc1d4c337b999fa6b715a6a60ef5f3062b8c09442ab1336b07c52e7abe1d279bba2dcb71c70849adeecb37a5859cbbbdeaa144854e0f2332711142a81cb17e636af5c456a57f49802c6ea78038f326b506c116f971553db0af7d62380ac7b4629646a89c1743f62aedfbd27212831ac8d756819c3941ffc21c59ec639a75d41840a2d55d808b75917f65c4dcef87bb0d2bb3aff62c2d758cb958924976288d44930eb0bfa51c146b3356533bab23ac960aaa4ef52deea04a2358e9f81defe5213909d05e4293da2c5b5e211721b1bd47fa370fe7786270a07c3c069a9e98bf20e5a318848e527b6d6511981d19d60f8810fc85a70876e6991196328b54a4574d27d0cb4d16d12176f603b70c46d5d33bd6e0e6682f79ad09193229fcf9c1747c16e0041d45d77d2bbf35a49775481d4cbce302f80f4cd4508b6a466e0d13ef386263bd4197a6defbcb34c5245576edbeda1f77c05aa07e68fa3fc8f0d68c50c65ef0a952a15ba5e4fc0e20a6a96a85669a4e86400ffbf1f3a3af525c9d59e98e8952783f0d30d3a55e64730019c5d1c154cc97a8fd0eae9d047c547e054a84ff8fccac345961fc3407afe5526ebcee2777f166f6518536d264765c38f12757ee4c5caeef632edbc7aac4b0ae21a10a7f4b36e443ac4a95f223eeb9a3f15a85ae80ad23b18cfd0e9cbaec1f812df7d13383e048ddccb6834fb177c72974048d6977b4ec03482d2dc1f721642031f230fe8442017101626d84ba1477cbaa3698eae40709ce2fd79c9beed74084c99020b4bde94a868094419328a04e3d294fc5112d6a44f4c929e1a69408f56341b321c2e839e2e1d42f3ad713822e27ff27041bc45be05043f3264ae4472e973c0f01d5e2133aa02776584f2cb0edf152df5ba39e39ab900306742c42ea7fe7f3692a5d8b06f1c9c59f0f677d52c3e55f6661f2cc6d308e47e7a007ef9739132eb4a0afb60bfdde35d7c67087a87677d5d568120437a0f29994b79c75a22a752542db469bb1c4c9a0de0e3749e042ae36d5fbb5196538700d1f4884d83d6bf9b250adcd7bf0e89c02057e656154f0a3070c85db69c3d1dee29fcb198726e54e2a58faad9afe02e8dc5774fd614a0761f13c89d7f3875ebd4895c72386abf3e883c8ad0b2bf0a0a648a1bf3b7f0e20645e95829f2213781ee0766ae6c7d6586b8540ee4f99c8cd655a673d36304c59b7794317948a9af2deedcc66b87042140ac0fb33b47ba95f78a4a147e679f38203a4764264c0cbef7b07967d50090c2de6f9de9d39a9b3820adc7c059945d3775e90aaea6340dd5e5e444caeebe572f25ef0b3eba292335c0757e1745749cf24f1dc23e4ea117596bb6676c6a0859c670319a94bbab076577700bd042ca316d25645f8849957bf4c664968a0995449eb735d7ce1bf9cd852e45f196824f6670bad517f3d1efd2b8e800cb1207a5f8702072994a504a9832126b450c251db7a0c69ee054aa406aa143f9c467e872e751744bd72eb9e6bb5972185eb9930c4ca4e8bc7ce2b39f6c92d1104cf9857c2c0762a81c4f363dc5d16ff23993a4d448fe4c2f0b0d3352de4d1e74b5bb3b36d989ef6cdb8920b3cae4995537721616aacac9bc2223c698373843350249c0660e176f0d4eeda1875b249033d4f27584ba557097d092d8fd3711fb60d764529a92587e89aeccaca41fa86433891f0aac95b22e99a3ba34c5bfb051f0ee019cf6c3f0559b62cf88528a80ffef640a9ad737974afc013e140941930bb4aa0b7afc1a3e6bb016c1902a06a616be7a2ebcb73672ae44b82c71e76785ee4f5fef5ba61b612590ace7b7a1f5a6b6eb0c19cf910f3a7f1ebc75834275711a14119473d9f2445971df5f849780091d62b24f360e968d0cde95be78b47402004ab02cc9e64c2cf0910bb4d1669e7cb8a2591f0ebe23c1cf8074f0cdd9c39b19355d6771a42e591f1426dbdfc5def188cf931421886087218266318582566d9027e7e4a3ffc928754ae147f26cc23d6828207aa033b43c33342460444dc68a34d92764e3a761819e11e83810e5e8c4a7d8f53173f86a0f34112212e11a9d57cdc51965324da0652bc456964d260242938c0c58db7fc38714044a59b3d4b4b1a41029be1164436fce4040a846bb8e4686a84dbbf1ab26b7c43930d80a6b3e34e461a670fe8970abb80c9198b0f0ff5eb10f60572944a5a20a1583448adbe90ca05391385d09627975e6d3636fbda76519c647547b12079e8e5890a1a5e5466d220fb6670863b8b804fbbc154857b9ac5eb4b5f8b7ce57271bc0becd0706b8e381ac1b25dadc4da8d348dbe1c29a10291688cfd89284e55a957d6ea4d4440be63982120b85898aca798553b00c50d5cd69bc512ea4064a2c45af190cacb6f8c7ea6fc2242d47234adb7861696a2c6dfca688125b033acccbbf15b2b665211389c373d921d3398f09b01dceb039e3f221018320ffd3c1a9b183f1f2ab88e804e0b8781a3d1b12278114e58d744b96dd9c54a9f42dc6e6af397a8c7edde98ea0344646adfb3bd2071989cdbdcbf018f2eac62f47d900aa4b0c3dde406f5bc1879363cb5ba592004fca1adcec3b239106d8eaccde125ff937d2288df7d8a95f075a7264838565eea004022c99bae261e47bc40b3b5eb83c4a0a75e5a37e478ae9eabc307e40d234e9fdb3ea932869e80c9b9a2a0cbd12b8b720acd86a512505676627210d242ac8347d8bf50c7a558ecbf7472e4a9eb2a67c1cbf533ee81892ef0022f2d2126d6b5a42d0152478b172e42344b1ee55da21f4fd82ac9beb66ee264ffb82acd835c598e8b9be24edc482c1d19c1b9860546c532ecd9252bca20012e77b0d0edd5fafbc4bffb4aa738e36fc2040628367d0a1b03088320614535e074d3a69d089567485b92b79eb50597d3b76ceeac071b10c2bbf999d31a31133534d3e4c5f1d204003254b51b161a1cd13cfc094201dc9d74977146f2676f68d6aa6270da4fa4cdf4a95d6c64a91ae8ed9132bba58f8303dbbbd54183c8f5691a502e9f49c55d7113b48db972dd4d99b46e845fdbf8ea8f1c9b82e4af6761cb1cfd4cfe8aa8a441b4988777f30ba1fa7dcb12b48041cdbe2b7e378d1c2307b01678eec11f2cae287b6e4e0c8f72ed094763251ed35ac903fc14253f63f344b1d4a31a5bfcce3f11307e1c5128c23bed3632da95f879abdd066ca7b3d7719c8b47f445980a6b546bc29c219507b9a0daf8e6e1f8333ca733c0c99b1e5ea48090bcc40328e526ece835add38ed077ee518f3932d10984e36ded2e646f947232ce73003e892220af241e3984175158114132468d5a360b534c5a69e503efde54a3684a2cfac9596d69562220723acea5ba39547e66d3a91aba498c38f2e6397b716455541ad0dc754e147135e238419858826bc8e5c61914815edd1880b23386e619daa85c3ce7d3703c32f45ea0242fbe1d01239391153a418894fda3f4a05eaa0004124da06c19657f18678d63d28e6ed807655b42c495249d0e748f2f80238e8fca6687b4413e6941212d1b001dfd6397c8bfbdf2be85a1adb1f8cdf71ac8dee0f189f33f59d44117d09d85f8a018894332677aa8599fb17c8f690ede161010ac49271e16179074c440615cbacca31c0023b481ec5ee4f844778534adfa851254d0b158a3245562bbb0520ff33c3c19ea3bef2122bbc4abeab0cc3794f93a1d69b10041a514b40534b705611617ca1143afb75de9d8d68fe73284ba29c5e154442173a977ee380edc32c83742f3a3fca861cff9d474806a29cd5ff61bf60c33c42f1b5914c06e8c3a6f202fc196f75c5f2ec6bbac9848b728e730f5b58f6a7ce9bea71e90f721e3fcc26026b34cbe032211236d1fabb29ce1d89b35f3bc655155e4587e6f04ce540cbb97cb8d94814d17ebf7b065eea08125a35352aac884c60be1015fee6f001fd57985be1459f08314fe1f875660b6ff096a28cd45cc93eaa9b3fad91d718512958788fddf6624483cdad7248f8e8008abaeb9731a1ca66304824b69ed02a11ddeba860d08d2cfb825f94b4772da9488e247ca523ba2e5be43c2809deb8c4f082e5184d6d0382ef2e36a8f3ee9e7a3823480553317c3809bf011fd533297770e856c4fa4c8e6c582ea605caea30a7cfe5105f7cbdf310f83dc6cdbf8cac876f74b73288e81ab2a34ee50baf65806de08d05aeb0edfca43ae8a88e4b34795d8dedd5fdc845b1817e88c802d0ddc8c92d0145c8b7090f7fa1d04c75572bd817add98a99170da53bc8bd1a8f223c83ce14c671956ce4615736bf246193eba43291b5574dc2e4327b9504a5eb66c557677909c0af9e0960e7d7b5cc7472f26c8bb9f6618ce5a870c7bf73298df4de28b3db38ab07cd4da56267097cded3c0f55a8a3ea6aea128cd14cf3c457a34f5a05b0078a03170157f4ed97c18615a6240ce3f650f40cf3cce84d24e5423f5c508beeecaea3f2679d53ae6e9449cc731f5a5b0b295fb8dddd6ac86535f1e52b6e855c7056338e9dc9d907d904621c269931b59a3b26529fd7e7f3482f84082d10fc5739d3d1d16a1e465998fa3b64f67b95abb9f222f722a2ef28c1f139e12f6265c809c3d14400f503a524bb89ba01daa174b59d79faa7d4e809b7aa4fea9cc0527e610c13e84ce5380059c64796d70c0c7f859461ac1b0d55b464f969fe3f11aac8dd2daf909643802d9902f20ece249a34cc82542afe6cec71a325d334981a4e9922f743a53331d8e520634f1f6cf2234338487e9beec3343242150dec47d9bde477d6a8d26229af131f93e8af22cceb26626bf1e1f597a2642e9e89c76806e0768c36fad86f7f1370d4b84967fbc85d0aa696c07cf2f2a704070c834414a4c38e8c9af9674066961039d49d528251d33f74966e990260670ea0f1d63322bb833106b1676898940e9a47aa85a707a288ae6b714ec92140ce35987050744c4da9b31e6692726ad27f2498e33d5548805e9966cba26993978e900424a44127faefe34a7a7c8ab2382f3b293558a2d13fc733d242beff18c1aff096732a95296225a8abaa54741450f05c3ed182b16aaf1f5336908f66b797fc7d66c2bbba60e37173c68492e67e9e066f3e30f3667d506271334d52f143180ad186be6d5bd667b6efcd9c140290f5a837a3e7c76a061e6c67f302df2125d868f5d9b36f33d58137843e58cb2c29e3170ffe0e77618b1dfc2e1f1b7a887ed5280e6b12c7133d0d0205b1c9c1bc68b89c0a104499440ff5acc4e89ad83730690e38cc74c6a692c46cbe4579f2398dd77816a0bf78363202ee9dd96db880b4986048351476794c29d60a1312247119a3c31ab1226bc865864e01191e4b99fef7c088ac14af5af64b2c18fc868ad279752689fb71ac63936f12bba685e4b71c7e5218b153316f0d7c265114cd618da57b3b288801521a38b26b4ea4f4a9fcf07261b1bf8b6ef2bce37c1fc3a0b4d7bfa5794e8ff3174626ffe3ed87116072227b8bc707a0500d26338543d562f74235fa19a098baf41f45a747842fad7dc14dd5c8416917d71fc737d16ddabb3c65ec7093707155e1ffa90cc8928e5f410ebcbf79cf49b2e468a16119a0af3799ec8b899224b45ed52921be44d01767dd22b27ffea362e6eb850f1dd8c495309361ce4a67d1d8e718a0183f52430688c3829f26314d178a9dca21490d141df3e9423956bdac30c370f436b00488c12cd31c4ba9f9c781400ac2d992862a421c26b29521f92b5ef90f6c16ff340d79a17fc8d3d23d44d03e821162c416f9dd469d20ce2b9ab57a72b6570b0c6706481ca33645cac71889f895e7ee77cf3f248c9804eebda606b1cf51a27432390047d747b2a70956811d3943718b7d38fa9f8eb3f6ffe923fa8710140b58def944237115d66f361ebe2eb1e5643738bcf689f0ace66ee584ba5c2df54a2108f457198ba0b25e9df96697a691cc7e996a4128cb5dbdfee7f83ba0fe965432d7014cdd8eb78bdb59857a33ce00b9381e9beea4a245d00005248dbd037e45f0d81c0d967b703f7a7316fea3d097b395ab894ab7db1326a32ebc06d85fb83351113232ccaf79fa6b51005c22a2fc497a1abad27e1b29b25c309920abff357404913b3fe150429c20e703580c61af7d2d9245cf722987493ba9030139286f7c23e7b03e1fa36028c1181d5df446061ebd1d7e7496b7a3369d8a2f6c9115802a84368dc46e0d17dea7750d6d588f118f2b2a44e730f9bc191af7a133a969b656cb9eb57bee54f41bff4c3f723267d392495764b146b31df5f43ad625ac25188f8b73521ab07b3d33abd6fd04261cbe110ac52b7bed057ab063e7bd83c33b8b4cb37397153a4e8ecf39d3444cd99632f33215c42741b03b23ee4f5d8191503d42d70f3f64005b88e530aad170dbc649d3782bbec4f1eb44d08645302e4b50fdd8f0e48206b91ce7b4069f715576f64401a18b66f9301f6cb2fa7fa6eaa803914ffe563f58824d43d7274ad4ca93f0c09d815687ab8063704744426dd0a6928c5bd4fb5a62cfaa95144b618e33ee1d39f4cd4b1fd66f8509949c0a5759d00f00f05fdff0354b52cf4157e44afc916912bc90a41dc1800b8b6ab7428be7a62506d27190d4c00e7af8994b9c59546100021a894a9431f09d135e0479ec24506b48284150ecd160d84bc4934e25330c125445e92eab7fdd9f8e90a8d2efc7d3e2b26e11f4a45282bd2fa92ab87808e017069ec0f57dc0a6edea0005c506a0820a2ea8436886cbb6e44036c7bc9c4d64fc9e4b8b20c5de14d6ef76d8b56fedc82444930f42776a8b6911b912849cad96a1c8b49bf7a65152ad0ca947c5fa2731677140415fad330dd3110e0c06c1121017a97ad0a9f462bd6d8078045273e08f5aef85d40c397365af87f7aac4c921f0a58de95330b50fa5b830942e847dadf0def84a85527483fffcc0e07fb6a0d6433a05e69dd6271412832a8f6cb7ff2b126711be20d37338ec2f730ac17fc507a1710e5d0804e1637dfd3ea3b334cabf7ef73ff92ac06d25a79ff7c568c621e1ce5397040ac5f271336db3ae10146e6092802d1b4fa44afef373d71166c9761be273ae375e469aa1fce49a9b127a187bb7b1a2c291ba020319855068cfdc8f61ae96ea746800dc184cb4a3e5d8e12661707c4671da1081e22b150c09943846337c90f656ca95c11fe34c52f1723eccbfe2d2a329dce9e3e341974d781fd327b3c7e061a6034ceca28ce8781c695c724e1b15d2be44f28527c340b6999950354ad62dbc189b20ee290dc9c133b09e89938c6da876924f14a22452901c96a28c9e93b9aa4de4fddcd6f30f1814638cf026b258862c997de8481f6b56913381a9410dc28f416ef7d3ac2521a6d802c17a53fc24801933957015cdf030af802955c30da15a70365509f835539fe1c56cc348760c19dca557aa499a25dc5aef1b507a31c165d01515d169a070a2f7cf1777a5f8d0a82b480a0bec85061cc06d7ae801f048f3887efd746a9c451542b3e35215bee2d775bdb7bcb94a494019509510941092fc82692e578ae56a2b0b285e5cf04ed7745d850fef8f70bdf3f957021c24e2673156fb84818faf1cf7819cfd223a3cc1897ef16228dcc2b34c504764b0c182f002b6c287f461777d86a9ab7ed60effb1b6919d048cbc41a6de886172fb1f3352ffcbfcb0d65beefb27dcfc0e1ccdbe37be55f09f6f36819f9d3327088cadcb7448e43a9e0d0e213c66109879f3f1c82d95f905ec815ea85abcd97e937b47962d1e3687b29e05be4fe7a2516e611a28bbd0b37d638d6a266f38cf92591d267e718a359ffec8632e7597b68189c207d1b13a4efb58786694c706682b4878661b216658835281d226482f4b5b0f4c3948df6fe3b728843db07a43c7ab025ce0f7ff79eded2fb3f37363241f789e35fc469b47f76ebda436689634cd0bd719dc286f1f52f08c1869fdd7d74e7f42013f47ff93ddf85724175727339fe96e363a376e227cb1cce57047282a5d349723f86bd7c0eb9cf2813f4e2f57a45d15c264865bf1c9aa03f99607b90b75c0431066682aa48d3459499e957a429405c92fb073b3f3a6c9a9c32882fb3098c15d6c86c39aa5b2d335ff1a55fb356661fb368de48e9d887fc35c1f6974b94e9afea437e4acb342e857dcc3e84e7ab02d9d005e3c1862e573e99600320e3c89739d61e34cb973fa3c8216f656fcd1839755ac67bfc15f40a7a05bd825e41afa057902ff1d6cb5ddeafea5f805e02d332b38b1d1d66155a256482ddfdb58b201a2e3346ea783c9dcbc627828d1f270d70c8c687a9c28a7ed3bc0a3b7aeea58e639a1252a765408a4154ee9b39fd3fbb642257ae448cc13ec0031cbcf2f429c13bd32f30db948de54ed5c2c60fa58ed466c6648b514a29a38c314a39250d369d4cb160e9c71c819a10ca320335e1522dc9f4a5a494650a560033490f99bee90524acf4628220fd6e07762e99313dd388192355ab48a3d332dad39f3b9d096c28b3fcd024caf1b31cb51a4c58ac0613b60613d6b3be22bed00cb72bd6a00f638515610d83fd4263c786a64c53305ed6b4458eaf2c71cd225555363465ba837e6d820de5d05cc51af4a1ac70615ba85d13ac5dd8500e652a87e6d31a4cd86e26925254a63468465daf4c3f6c57a6b40613364872780126ac9c53ebb29735e6ecde20bfc81946575c963e465128f926d7114161931f5dc276e51ec518e7d08e0f14534632f1992978cd95aa09259db962d0d388dc1fa7c44db0e1943f5ffdd233c99c3b139c497a10c01136c98c9152636526fdd2dfddddadc404dbceef7fa108cbcd2431471c1ce1caf27a2b4ab07347c70b1b6a53673ed165b942045bb5883f63d5115fba444a669179c1fa850d6d1f8947221588c042a786fb4d24128944a2c7ee0d11940016713e4e0c0be95a368c544c234526e893b3d90db321332694a3d16b5366fb919021121852832962d80d45f2e845d394270e8be4110e8b88706875cc0f3263b4ec3d62df3f6446968e8e0e8f9829c6f1fcc5175f642143ea4b9c09766a6221352b5f582345ea900d63cb86203f80d07003a805ac9a79b958b51d2c0d338686fc39310a0bdbdae4806039973d6773de724e1be71c5d21673bc5324de44d45a3ad1ddb26d7b433dab5734e33c7a8538eb39cd63393dcfcadbb6763c7664fdaee73ce894d6c3ac5de139373cec9f59c73ce196bcf66674c15551c328da07f46330ca3ae65ed3346d20cc48822e81f9d8a2c4045d5c6b3760446318ccec0a6e4089482259cb03547a01410c1450a3ae0852de508c444145315d694231013ae570b66060503c4448b89010040e3929dc61242466082032657408109508084ec0f323183ecffd51c0ee34e5d88d215a250032b308187043b1810147777b735877b3507e532f6114a0e40c65e5bc2878c7d577360d6b11c6ca0042a95ab3930093ef6a819234190310ccb820f1943650cc3300cdb6a0e8cd29f2822f7a42508398c4f5850489082127420fb5b54097ac8fe5f8227d91f842922fbcf2c91fd71287185eccf6388ecffc3045b64ff203c64073a410fb2bfa8e670af08552491448d24a6c8fe1a129cb32287a655100258e520fb674f00410c8c09f227875c76530c171a3646d005101240d4b3c21184600416d9818eb8c1088cc8de041f021152c85e8422a6c8fed60825b23baa066c9182096040850d92980213352e45f62851c89ecaeeeeeeeeb9e4c3c4ed0739a4310414300ca14511449c0066094cca4a3bf5f440eaf9312981c45784ab9403226c0fa98a247a34a020b280447744cb1e51441d22c86b620427c450f764045b10ab0a240414921811b1c30161842808d7b6022334136a14797c80443683112b11104f138cc06658f2a3ed70023a0311aeac8911ccd033d48312cc20440f508f138a98a1889d1d245c1b8249bf446d8825829881a7498c1cb06009ae172c810adf137ce08155c9510aa1254a2210aa014948680a1311845c53080d49c18410bcaad0d1b23fbd37f3cefe98fd8ec6e1a48fe50cc58a5348c18bec2698d47cd75e5ec186363f08bee6380517565cc186a8dc42f1580c6bacbbfb318f31a91ed8d3a738e61845f7c70d62041b462755b041f08f09562eac24c2e54843c4eecc04a58cb586886598a07ca98019034e503e8d1eacfc68e7ff8c4945ec59735531b79850e4cd1bbddc36f95b0e5dd366b2864393267a0c6322ec699c4de48d8f58ccd86f91373947d786126c187ff2e441c38c315868238f64c8a3d168f4a39f339037641efdac394650e4589dbee7d16fdb633735fa98318ec70f2d32232c511fbb4b70dcced77078747958c15a6d82950b7b337a77f9dbef90bf7d4e1e92472fda760cc9e1e8b5215984ca199e339030f86337f4dc4f33293cd0b3459770fa44187146e923818001f221e8913822091bf0a0822b86bac07cd045169cbbbb5309848cf9902463184a72808b216858cc188661188d34c570a171030428c8fe280e64ff0743d082ec1f7b7802cc0e6dc26a5042405bd907a06082211f9e1f865030844d1598e000882636566001f52084216c865ea81a54413f5a820b2ccd510a2dac909b48a145143a4fbc78820b0b7242e17d8f630a6d31c16e62c618a7478abb893987bc8802806273d7840da713aa84747950f782d8f1d2d5a54ca693551333864231632a17b3bdfcfe6929668ce8255dcd6eb5ccfc892f437a263f4159fe860561589053e12eba439738141fa23ab40717f228593ee6930906c2861e4485bb668cf7f21dcaf7f261646127abc92a95bde7ec4b37957dac29bdc328c2b6ce1c9aa0d074e2dd94bf4cf9462ff56629cb6f26329b4f2791dd93653791652fc9f23b8f521df33d3c5f1394f37112369c4338334f8c53d8c71ad2c79711a7b0f78c7d8cc5a9f832c747402a620f722ae60b4fcc82b0f23fd38f38ec1d0eab3922d668134eba276ce8411e3463a28f99ea111fa77ad09b7efa3edecfeb79d60b2d42582f4f4a5be04cab65680bb75a7485698bae5a7445e94a47cdb4158495ff0f8233082b7fb644a20edb640d3db9f9ad9353ab5320477f86fd98e0122b7ff32bca5ace329637da77667a65a8332ecc64dd79dba8b3ba556dc33f5e82ac6c1970fbafda8f58438e46598cc924d270185ddba6b5ccf6117b94e5b84a811cd9b7671f6b46388c41221c46bfdd5c6717c80483ea94d1bd73acfd8834b1e3dceb8fd8fa1169a2686e5d6cd125d4e46ca759cc32393b6ba758268a22919c2dc2324d34e222c7c9d9dc881b6d5ced6cb456ceb6d2b31fa9748aa7939c7d8a27ee942251f4b542a150572aa65272768aba9458f52c51a9b02758ebc7098b9544071bc2a2602fecf5840f1325562b29650625736541d993ac85450c93b3b1ac25676bc69c66b73ba558e659d65366ad438c114570bef427530645c54301b7c852ce7891258e2c9f2694f5435b412c168bc562a9542a954a455dda1393ba5c2e970a0acd899502f5244f2a1c5b148a3ac5324da552a95422eaa22eeaa22eeadab85abbcef3ece7dfd7537ed74475e88eaa07a44c5a412ea1178bc562b158aa7e51a9542a5513d64f8b0671b5f3582c168b65552a954af5a492c9743aa5a4a894bce5412ea1d7d06ab55aad56d88bd52f2c168bc5a23b54457be88a32299956abd56a754ac158180b63612c8c85b1f2fc8b5a810123958a1183a5c55b5a7aca16525e694cac0faa09e83faf219dd56ab55aadb097b7b0d72bc895e70bbd865660ac56abd52a85bd5ed80b7b6196161932feefc5337cc68c9e72c6f7f21defc95a596be52b10fc47a19c89fb781367f94f8b8cd56ab55aadf2fc1917cf98e1e2d2420b2ebce02fbcd053bec0ea20974ed67a81e03f0a65add64356c766d4ceabf2f4d1d1741e672d2d6b65590bcb5a346b7908ba6c2db8e0c2923cff05d50b9cbc1470b0ee903b79fe4bdd21b3962402c00100809e12009ace04e77bb9559a4ebfb0c0ef9f0ecab35d51a76534cd8a7088426538fcf7d174f2fc16341f9fdb651c2dd39a662dca85aaa3e66ef1a1455dd7ccd2aa56b54ae50a7510638cd17964ff7e183598992bea82125f0dd298b9414accbeaca0cb7be085d83830cfc810696417a394b2a38dec3264883494668961620d6d003e32f6f145fb7ab51f19dba4282e21fabed8345f1c0bdb8db9093aebad5017590882e0fae2871d747cd82104408882083f580f4480b558902487200d212674a0092198780089a01a2a601886f90083128228428b2754403802f603ed89122770028d6c61aa0be34094d2e5c7dea40f92b0c5c4a6df5e8c7d044a6218cdc10d44c0012c8a19e0805601045368a14541870718e10a203cf04512be18029b02c807095b8e403e4fe437a635172347a01598404a391b680537a094522c5b41502a47a015088d389148241a6d3d260e7cedaefd357eed6a778c78023602f938819123900f93ac32442bc2081727251e582d47a0152c891e821de4073800b4020fe4ce5c7204520116402a70bd400ac92679f3bca5bd7c42dea0af790e71325186290e1b4b1850f9848481bcd164c6b4765d1e035b5e56c6097a8481c90e13250c9ae4bed67740b3df60b46cc04ed1f7f35131de72cd35a73d372dc78d6493fe518ecdfe0eed6d906cb9db3794a1fcdabd9793e58dd91b7de56ee892478fa3b7d148f4ddcfc67c59f962c86a3fdf84f3bd4f99d82989616a6cd40fd890e3b09e27624bcc124d1126ce25306c63be440bb34245cc718a278020c792c89a28d8a2cb125d73b32ed1bd00b2b09f9b9b55474e761ce6dc24dd7fa2efeef0354ec4efbac781672688d339f64f93b540b17eac23292b8ed2860d15b47234428e404e44c9404e40c9a2f72158ecbb2c2ca6058e96f95e7e101b84cc188ef4b2fa40a2811c5ea0636b5dfc5863c631637c19565c1647c53355902aeed2f083f4ddae39e2cb1a24d6901fa5ec9120385fc61616a17cd88fd339a7c37249cdd15886f1c765e657758e90c082126e3ac0ce8b04a7035c4185236a8e9c1ec9b44211a200c4e4a1428b13d0d0a2e688094720f0a0e688e991481f9c8024015cd4943ea7267e8b8e65f9c2ca23999e6e0ac849eb17ff139023f6736a60a0c2a6503946f655652776bff260e763c1ce230850810e4f6a901ca087269050537ad33d728a410d1213133525ec446f224c44aab0c7308d097a868b0c913e3eda95401596f4a50fa310672dc7711298a0c354614d7ffa212d235599f5fe8909fa0f892ffede7f38d4349cb9cd307b4329c6b289cb27a40fa5da67f471f47c7a3b7f1c93fa907ef137dd30522c77628dcc3fba8488de47df5d9c39dab2107d0684fb915d9c2297c804fd4777c8f522004d58a992467cf60830e114fd9e1993e1147dcf144b95e9ca2554d8cfaa54c6b00d1e8348ac2e075b74991f310b156c4b1b13941fdd7718c99afc6e68247fbf611f6db00f8d3ce971b4bfcd7cb7e9fbd1d7fcdd66621bcc9de23007c72469da26658c9f12dfbeb46f7aadd690f21f4bdf7dc4a1ca6b3e22b622edbbff143fc5e6fb88f9e89f3848c7704a077dec6d2816b1ff6e486928f190b9f47d63d041147b785f837fc4a9efe37f8fa53e9cd241faef6d481fb6c168487f84f50de77bd8c71761f8a6f41fbe393d4ee789713a97b09f3efb534ad86157c1389dedcf141c4a7b43247d9b768ed58763efdf16871ac8f1b99aa37ef4d139e2748eb37e042d47bffe50acefddd048f6bea3db2ffd56baa1567a1bff12d6f1e130277fa4c6a47e1bffb6b11f27488ada7ce9ef940effef6ddcc6e2fa19866fea539b4a6b1065acde2983ea10879c1b9ccea50f4d9fbd3faaff86996ffa63d69ed29fedbd5fd20d8be48ff4a88f5306f5fe3deac6497a1ceddd9b1bb21c643e7dccad43e612ce4166ccabcdcc18be993db4a7f4eba7bc4ac9ded048f657b936d8a75c9beffbebd5f1e5b4f75eb297844313c6e9d29d39a48f434e9639457aef4bdff77b2c9446f2fcd2f5d1f8bdebe3fb7ed2d5f1e178a9fd1ae2b4bd2192948efef9368da30d86dbc321924cc13093f4d67ab58f43c2225a96a2288a53478bf80623196766ed238c8f0d3570e51b012b180d1b4ba01ebb21f6f3b795be9e576eac89b5510f030496ba086cb471b9f9225137d6cc19980fc000d9c8596d0a1b5f65aa2ce18dc94c349710fdb6d139e79cb2b3996533d61e37f3a5fc82a66cfab1dfd1d8d532e9fe2b5158f0a9d0eda37750ec3ac8658719810b73ceff17390e8be4c737a2cfb2cd0517eefbdffbf7defb3f995c29fe0ab3aef2bcf5e31c3d7ed964822c8742b2e47f9ce0a581fdbf1feb0eccdd1bfa8e51aa480efd715c3691424231655e68123999bba167aede50e6fad90d8364aeab37274f0848186864d1d55e74fdb19889ae6cecebefc0bee29c3cbaa18747f20c83e4d1b66108481868e44cc331fa7c79129342fbb7c8c0cea7617c22334a9edbfb861f9565192ac60a4bcba7607cfdeeebabfc56b7cf483ebaafd8077dfffa2a246c43b1e3fad9ab546b5352eaa59f4243cb932856a1f459b25f51b9998cef5ed61a543ec329ffeced7737e59ff25d949c726c93f2f6b7edbf3f9d4e26933be7decdc95e229948cf7da954a29444a2a45b499596ee09c709fe73f74bc1a1067ac8dd5bec13fc7fda12facbc031e860b1efeaa3e529be697912be6179fadba51f5b54300b658991c20e03e37446bdafe05066d4874872467ad275ca51096453e63de9292a7bef6a8ed1671909bb09879ed1ecdd3dcbb26fd26f9bac36231ffe248c63b6c8c0caccd13b7a8df61065ee23776f46efe3fbbe52e9db9ae3eef6ddb6fde8bdea387d09873297fefbde64fa28d971c8dd27f9bd71be5092bfe9fb6cba61ca6b7fba39c8cc7ddf8c7eb319fdf636f4fb52e94fa7938a4aa974baf64b4e38ce3dcff338ee8ebaff2e7d1c9db23de9c9670a737269fa73bfc39fc339d9bba167efe93599bcdf2667ba27d3cdc9270c0109c3f7d9ff6e7d8fef040b261c27bb87f7a6dfe1bde97332e9869e49ef373c92dd74c32039d92fcecc250c010903f7a41bd2c8dce3e8ef5a1c6794ee8648f2e613a4388ef008033d1ae881b2827a9851720e92b2823e90a5fc6e9930b0f01fef11652f1265da6b38c47118c0407b427415800384918107f704ebb2dd9452ea43a95319f88ff7685ac470285518d644a3997bcf4fa6578699214238846c1883e61c22e453fb91b225594da28c8d30e8c98d4d41286f1efde8b28d5eba46a38e4f2441dec8300ccb24883e932e7923fa2401ebc9a3ebf25a67e004bb250916a6a37c82d224d0c8f2a1f59efd0d0d07c31cfb9684816b82b51286a8b5953060d3c94bd334acb3189dc88043539eb4bbbb656b77c6860c4eb0ac1fceb9ab602e812277b20c0a0f3c603b3cc81b31c625398ba1f58bbb6461c308822e7b883276bef0e07d5b3de89feec99b977d74c11efb8dfb56715cd4b450a469b87fd85e345dad923762d6defb879e19a3fd64eedab8a31b51fde2a2eb13c466b6b0fe4d1f07f5ec2794ecb3b72de3fa41de9039ebc958cb7f6c6c30988d4c29b5a15d14762d9d58d8f82f2701e40d39613a8a542bb5a23e42a91e79034bad72fc1925cf0f89c0d8024b1191a57a6008cd182252adacddf964822d0c8a4c01b73f8298605d30a66bbae28d41073b9bc0d82245040ca11913532beac392dfb3dbbddb1bc4dc79d0f61f1b5078d09fc6a19d99914b6815349dac95378f453fcc5a389db3f72c9b42c2807df63363e2679f49216fc49cb5a64ea6573ee14264932b6c286513a48cd692099126fbf99207a2b9813cad9027145057d822e4f9314a20e4b964f6c4e8c20a4584fd414a1e505580e9226a4cc8f1e506a48cd62f3a4420cac0f4cbfc20d94f36452bc74a5b46b25a7228592d92c59a3071de0bc41a1303f265fe7440ecb1e207140f7986720357684c5085a8204df0f4996067032bc23edc0463648dac70b2246f5e7f74b994d225995b273284363286b9e63e97b8934c2f0c0d307acb1fd902c546c73f12956764c011a558228718a23f79f3644bd2eca9b7b7f74b663db39030f4d3ba237bece3744d28f246c7dc4f7f8632bd34f0903b303fb8b052933b52d5435ad3a86bb375a6504d7c942b0b7943fee4585db63016f759867d183feb8fd1e550debcd8b13b76c7d838667c6d46ac175b86bebfed97020c20ccc9618c2186f0480ee34b1c16117d67b1af07546a9420714a29a594fe0e3335f4951ce917614dd3344dd3348a95b8bbbbfbef3053e358498c2fd5e6cb12c73880f85366f917490c628205254fa008210b4ed41c71427e9448b2b91d812588b9c990116964c8686989342d2d2c2c91868525d6983162a452912695820123d2c080b1b21269565650a8488342a9a8441a15959494489392723a459ad3c9648a342653a914694a251229d29048b1c6a4f93e6b238db5b1c6a4f1bcae8b345d572bc7451a8e8b3526cdb68d469166341289228d48a4699146d3b22cd2641986451a0ca334d2501a6b4cf7ee48d33de79db146a491b1868c3572a4c4926cb2013644651c844089dc35d3022187a60c3393984768e1587801b5c0a549e16a893aca90625c402fca7c1f55b28c0c441ce2cb8773228e2f951b45220cfd52c62fd23216cf9e38412ebef6578a56a18b9410319931dc733ffa30befc85727737fdf94d8909b688896835c19ee9eef872d5ec61ee789209eab4173d34c1fed15ab98372531c3eeeeec99d021b8a56a295c8a74ecd082c438831c6e6dcdd29cadda7f8e6c18325083f208e1999a6114579c41af387879c9d82144f0a082bdf3553ce6274ac9b0631c618bbbd5b7a9bba7b668c5aaac7bac7a875db25b8da9dd22c882ccbb22ccbb2ba43d609c1cb3e6b8a655ab71034d10b5c7e1c6d94528db32622a66d1361de1c47bf526abda986ddce3277e642c05eebdea1b5c518e36a934d5d084a29a594fab643fd0855b6534c243a029689462391e43cd2c8ec5d5ae3ac8988c9e4894622ea2da234bb31e7706e4a99b351cd91592c8bb267b5a1b7f3ccdb5731fce3e508d1378ed3382ab99ecd299669224da4b1e66ab69c62999669994ab25e7345b10c0b61a24baa582f8cce09135d52c572ea214c185d52e5ed6ae9b1f2e79c302d3d46f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f04001084c27cf97f2c7893f6939cb7fdc09ab9b62be72efc9f3355f227291a8a714b90ac35c1a942879bee825b44366d768e4c33de124cfafacae898fc7792dfbebd9a4ee2e799bfae4a7534f796a1d9f76a95842d8d05f292aed982683fbd699abc964facc26f3e7c5fa6905b9da4767b6747474747466904be8e5e3e3e3e3e3a3a3a3a3a3a3b35992c9f41ad2993b53c5fa6905b9582c168bc552a9542a95aa5d3e73e5e3e3e3e3c3c467366161f4f57abd5e2c168bc552a9542a958f8f8f8f8fcf674a419558f247b664904baa582c168ba552a9542a55bb5eaea1a93377a66ab2582c168ba552a9542a95cbd52e57bb5254b895d425693d2bc944c8ebb0582c168ba5ea97a952a9542af9235b412e6f168bc562b1542a954aa582c1c2fdfd8444a1f88a43fe62491508fea350d6ca1eb962d278fa4bb2582c168bc5c268339685fa01c17f14cada9726645fa838d4a29a662df517f597bf02e191fcfd2c79f6491328c913af1bb5e9b0e77ddf1dde32ae7a6fbdcfde1049a6587a1dc5b3727e84fa9f20487b90567737088220182448101004c120dddd0db648adee6e10043d779325994c2a29a3d37f1030081804044170c727481452675bd3502821b971387acf4731964a38d446d6dd296d11572aa9d4c0f529961ec71c7dff361a8ddec4b9b7fd0887377cce0146c621277b5fbb76f5b0f7e8ad37dabe9ff6f7db8f6e28248fbadb6eaa8bd186a83c4a759847f4defbfa42f2e6617d6cb4dd70fb0fc7a083dd300ea5237c437a0fdf987ef438e668c338948efc4729a712572a6d5f1a71236ffb51a9866df4d51b89b6d18bb6f75048eebe35126aab3a606c7dcb6d28cf7e8c8d4b3844e5fa1f0e51f55137c697da1bbe11fd48348aa24d341abd77fd9612d8fafd38da3ef673c337fea3d196536f2864fbbe75e67ac30e67a30f478f836fdf3714527168bd38346f384492471f3fc4a1991bc92c123d0e2136912524938036d20d7964d217f298c95908c2c0f0c8dfc7094eafa3f4b3e748d997655ed7759d97e1194d9ad1b8d1a83b954a9a3591345bb2ddbbf75d7dd89fc9bebbfede0d6772963d8c03898159f0f74bf62894bd4a626096ecb91b7634909e7b99d12c7bd3e937cad9db3dbdde63eb3ebb210fea655ee6655ee665dee5bebbf6fd726ea4eb2ecbc5dc0361c1dc7f5e101be3595e09875970c823dbcfc9dd6bd9bbdd489fe11bd273f8c6f4d97797c80c35652657c19e722a7925afb3dd7b25afb359c66519b7753cf2f7526b99fbf4f1c3d8689941bd0692f7dff2d8fb183827bb8d1b613c6b5b6c7654f61a60262865c878fc40f08c182fe381b4fcc790d1f29847bf389521e381b07c0c252c38069ee9179f71ff8239ebbaeede70bf7de7e1306726e491297dd4a7fce84b3daa65ee372a6554c29fcae9c10cc2c0dc3be3751b0ee5bdff326ecbb35cfc31ee8c1e94e5f12b89f133be6b8a012c8f1f488c9f21e3ef0391f157c6fdbf0fa4e565fc7d252d3266601ccddd99258f5a83109e81f1b33c9019b4857751d20276c1f4ca887167b0844466f2f6a28e1bcd2c6bf79bac3d602648bff86779252d1fe3654d31e09fe5813c06d2823fc6b7b4cc78dc82523203db7ef1c7d8f60b4d31e0fe3f1019dff217c77e512203b76022333919c764b931ee13994112c42373e1c10f279871e1c10f26c8b2b5325bb02248de3c2fb9c94fee20983b6b290ebb1f4e9d3a757a32953c9008584f7ee248a693fd5a494ebda79608e8370471caebbe74c36afacd5a1bc4b337e5fd576b4d7941ba6ab21613014d26121110cc26128943a99c524e277bb25dd79dac7b75e7ac77c320d90b5d323d511a82a56fe9b01f8c3663593fdf8f09fa7f5cfda71ae964729f2f2590080882415a06353a95503f3ae1520ae964f2489fd9d377d7f4a76b632a0529913011300811900898b9caedf868b600434821041660082996b0c5b6ddd086bc9d2ce9a3c1a953a76f43e666d8ba926934c2b649f8a394528f888ba771294e4db51d5bfba8771caaf4f6de6f1da5b4faf07ecb4146c6212773bf7de79d77de7178a3efdd7ff6b9efaef41ec529d2d314e9bff73e6e76db708a84b9fffe7bee3b1bb24737af846fec73f8e6f49d4bee9eba648bfd54fd3b1c46ec2a299476b4e328ed28a51c0e9d76de945a8d5e4b3fd432473dafb58d06f76864b79131a97bd22dfd772baa65564829b1512a38e58432e110f5d9ee576eac33e4e4ee1d872eb9c32111978df36cb0b6b3b6b3b63375ddfbaf986cd7ad907ee5bafdecbf9b83cc24cfb32197bee47ddc0d6df070f77eb927dd6f66ee86161371e1b6581710b3f77e431bb2bfd775dd739d0b1712710989b8d09c25394d6cc8f1353bd385127e5c66ba50421429f3b484add6ae7e5bddbacd643a9548a65a6bfd5357bfdfbebefdb6fafeb6be57aad61ac9d56edfd36efb6fabe1564b58ada5533d5592c964aaa6fa5553ddbeae437de9b7d2f7599f31f8972aa5dfd7fde96facc561fdadbb371dedbef4f686362c92bd0f55327dccdd09e72073fdeec6ec75f4f59e7e746d4ef5bd5bbaa111efc32109e3b47767bd9fcd8763bddb7bb5ab9cecd3b4497ae348f7c3a1e7ee4998f3bebb5be9fbcee29bfecf07673b27976e7824379db3fb4ffda62d4ed3a33ed61a4edf3d7bfa7b977aded7fe537fdf4fab0f7f1ff66feadb70dc3e5af7b8a74d7a7faf716afbfeeefda6366c637ffb2fd5c37e383c921df34838061dacc537f5bdbea163c7154714c6e97c7a7f0f872a610ac6e96c7a3f61537b5c73b6db7bced4efdd1ede6c733d5bc3e1ec9f3d7d64bffbad2bbd0ffbdd7fd7c3dd95550709bb91fc751de626e8f5936ee5bebba5effec3da043defbb1bba8f0ffb16e2b8c77c6fa79df1bd9bef63f60f8748f2f79d96bd95b7deafdc385fbcc7d11de963fe4cafbd75ff8d9b6e8f64af7bef7e4fbab6bbf5bf1be24cebeedfb5f19e7bf7ce439cd971b3431c23dc118d366df71b8e641cd9b60b2379ebbcee43237682b6dab7f61dd7ca71ddd3efba8eeb7c6c5f5fcbd666abb6ab5b67ad114b6feafbed8d64fab5bb61ed3ad26ff8c6f41d4ee70ea73bfa1d67bbbab9577777df6c465e69dbac7b6fb9d2f527e138419cb6f4763734923b124d09875ec3c3a11bc1e9dae17487e304b98f951b52c44853e9f20b8f89adb9df36fbfebddb580f31f0face6e9ce738a4f4a39652ccc51be660b544dd4b2ad544fa9453e9e9db6baadefbe783bef5d1fdf6f6b90fdb749bbf7d7fcebbe14603e9bb6f8bb9c7b8eb947bc7a9ee3dd5fdf6f4bf54876db6df30f0cafedf467a4a3111aab2f886f41dbe31bd254255d992b09b4c2a35e5846bc9b92f71d66dc7b96fcfbdc525ce9ddb3a7777f711f7f48efefbbee9ce8c213d2d3dc5de7b2fab0e8b6987bbc7d1248f49367d77bfb7b73ee7e190aab2dda13df82061fb3dd9e2d1c7d1477bd0a71f0e73baeeededde5a4a4dcfddd29bae4d2927bff7756fb570e36ec8ddeeed0d31f0eab88dc31d56e5fa31cc197d05822a808197dbb40f53ec5055fb304507b27c7a92f6035b74d9606cdbf7f37e4f6f276374c2d59a452e8eea8d13ec7048c4fe37730c3a58ef9543cfb1ce2ccb7e07f6190ea54ba884ccd8e98256aaa20100000103160020200c0a078462b1581685a920741f14800d79a23e68529acc634916c4308c53c818640c10101110012019492300405760c1a75e65406bab5607f8b8903e4b9754f369aa78cd6a6526e41aee82ee4e6269bdea235dbe7b9207134899c4e47f8bf0928dd96704bf9a9000260d98d345a495d0d4661fdc45cb75f6b8a3d54c00791ed896121b8c13bd4b943f14158a805f7c4f7975cd69acfedad2aa54bd7e52e36b42852747bf240b29d80b0596a7a3a3e4fa59c1d641d55ca6e43da90b04e084d2f4165ab72e8b15554706e5944332075ce5feee98863afdfb0c136f3ee10dfb1fbd73d3705ccf9f47239dff3d07de5974bbdd74a9227d1216f2ce54d01bf6687c8c1247d2965d98e17ef5373cd7d7cb7c75ffcdbbcac8b57f82531e9e359f40dab190b8b080d3da6d211fa1e78b1cd173e0c2d51157e20d7cff036de747a76150c5aed31d69e215f2fe09a76ca3be96b2dd751e3c97c71137cf81b4c9fc658e6bac342ea79b0356261c3f82334710b24d406e391962bb4c29d96b77234cffd8eb9b3f41b24d07e4985cfe199720b5435c00c917179b49321a9fa3930d67c3c55576caf6f7da19664169361260bccf49d79f0070806b9934da22d36ada9efb2ae824bb428eaa379c45d1ced85d37743859f743026d13d18fcde50fe65752cbb437850cd0b263d3472e9f2680bb1bf3ee17b51b9eb9e598962f7483732d4570d906b8d94e89b8cb8cc29951893463d2bc46fc5d86538b708df1f2125a97fbdcf8cb3f57560388c006fb2e45f0d0c097fd54ea2cf1027763beae953b69a3862e05854ab478dbbe18b5b80d3376e0d83d7e015eb38c62cf9adbf83feeb178f719498909dce41bbc1361ec2a104b47cbfd82884e6ed3cb340906ca8e4a35098d2965466d73d25fbfadfa5130ebb606c20ef254c80a1a638346b572372478ee57fd9b56cd7bb74a43c98a9a92eb9c3072cac6733927dc8651b1174c32838a50dd8fef9a24ab20721a26d95a0c3e12a5f2979aaed63bd5020a37719ecf304ccc060ab302ad1e6700600ac5919c3a52ff0479db42637b9ddd3168b7b4ae31799938cce18f593a0bfdfbb3993edb7cd1d993048c889d2cee39a30d0ce816a7a68953688ee519299e917f99103181b68b93bdd0c0bb5a5c9f48c9eab9a963dbb93c8910c151712fb0cb3c848df91528e14c2f31d5ebed3d52c114efc386f3653cc35f0b6fceeb1bbfde27c15e2823119d0b63f012b5a3450ca67c7329950fb9cf971efec45e2274c691d5de24e9c8c18a9d53efd60709be4d61d757d657b080f1b06644083c21b6c51fa4966ae4d787e488dea4d4dfe7d85dd1c3d1158942e4751d11834f4e16f7d4f0a94b67c723f18808dee5d8479711a57404c450ff002a38a2c0246c50056b490c5dc0221f0294d06d60766ac9f353843d3e2e4cbef2f19292b850c9e3d929a18bfa78aba7682ec2c553faabb18d099643f7f4fc03e0365bf4b6e42f0e98c1abd52318bdc274c2f8bb59e8a26a1af2cb793da68cc884c9ebcdd63dd4f44da0933450ea370a79198d6f3cf8990af1a742c2bc12d1203f904edcf9ca52d39475f337f98fa7441c1d63af6253876c1bc697bc0f03c30c4a89996b98070a02e349a16471f7f228a1696bb4cb7c41dbdd226920c9c3e9ef5e8dc5294b5e99b1c3d35bea4d2c8159151aef21b6efd50cd4c6a2ea949bb7a26314b34a9063c7102ec18bfd65cbc3d04e8ca51c51f4fc00b68abd3457f02c7ebd4e10eac99555c6b72783fc6be383169cd1d5ff6c8beb1370055573c1e5ef3463da04d17f75172df1393b7372708193f9192f62dc76dcbf9207552cd0b2caba0f853c8fecc37048795dabd81d01e27a8f91bcb4ec121d0823222d0f31373464f079ec83852ace8b9187ef234feb6b144d7b4729d42846bce9e6d90823ae763f847a772090e12480ecf0821b927a249705aabc64df3c729fbfab6e7b36d06c42797dd342f5924222ea9055a80fe52dbfca67b5d1ddb99a546f651584ed1e445c00135c97b7e1ab456b8a67ddaf2947022d995f92efbe243a7aa0e2b10a5188d2bd84a0e4d5df06f46ef2a87fc0c7eff1b52e7cebfd77bda55c751dce63481cf93809fd8110e1dcb4888d1b9ca47ea11791463d384b99ac6b43ce5fc0ea8138dc17988da215eed6c0382e203e5524aa2ac6223684d0266656e8d059d0528e544781755d63f76f563c0502de9c154cb4b97a8010725e92546cfbc4c6d2b7abd8015998f72c569e2ba45faec919d4027eed4de6c7c8d4433557edfaaf21741732dfc5ca3346ebc04838dc290bb924e65f095241504b21929a86024dfff090ce9da43796c47aa922aa62b2501def9e2b1c01a01c09493d9cc828a4b5bb8879905fde74c67152b624b8d0c27206caf3924d82946d94db8fae1649279280bb7d186dbf8538f0d5bbc1b636116d10e2908db5ec0ec2533876fc13894c269c1d572280ed49097dbb04595c03e87c598cb34ac81d3be358456825c966cdb1604fd0f0b28cf738646de8da5e9eb7447757a563a2beeb38900b9a3d746a7da3733d19479169d912d2243030e764cf7e8470c1be71493d005377e6c320857010a84571808f9842bb338c30788a25fd139c8be6cb81a0b83e62b4a2cd513e7641025a02601bb01531bf42077769166ceab6bd4f6034764d875e344a904a2e3b56df6b601729a0c7e26510ada085a1682a828b9426c34a9b39bb90008ef19021151f1fa481db56737824cea9c9a85209fbb968a37f5030eb19c0f1dd187d620d93a5e57c416c903c1477f9288870841024f08475783a92f6304c80479047a89d62c5c1593ba173cf429f5ca2603a18c26f0556e1714ace4d18ab9923936e68ade840a246b9fe27d6959778bbfa890abc5e301ed1bb22b41175a8cceb304f18f5461c3f5e531a2993e6b72e0af74ba4066175d59891d82d4149190df1da7a9547ddd8aa0f5979652a2a0f5cd131f73fa63b135f7623ab5dfcdf77f1203e62d8a392d4b0824032c2e03cf1f265b0c36e1abdc604ae4bd621cf8a265bf820c5948f36c2488565f15eee144926af7a6f0d14f1c7e4183cdd0b8293c28905f5a5274ec298f198ecb2ab3f627480f2aad12fbb680fa30b8991ec3d28f642f2d08f0f0173fc0d3fe8ebc49b4a55386291be267333f87b6fbcfc2ba1ed8279a12f279b6d552c8788da077b37688f3fff3a772d1523ec76b870414c341c7603d527018bc2676f6e70a146eee2daff12194798b022f793cc5200cb6922a1c424a074b0086bed5ff092756b633809260341c83c75fc2f196f40675dcbf8f4501730ef381d213508386777d358121709eb826fd9a69fd48ba508f1d0677098d999a4addbf431d05eba19b29495765864c04c2d5e1d0c6f046e38f46d7d41bbb2978408f4fa1fe3186330833f49044fd16fda4049c60411370ccc0b9f981e2c634b51d8a72a1d7d8e1d97a6646e3b66985701855154c5402628dfa72a9527ef3218ff32adf584d934d643a8552ba6dc5a08b83aaeb39554b00a5179a159dd3d66bc48932e2a5b60c2aa7e179b7c1499e595fc77b429ff4de3c9fb879806973f8477d79eba9cc20245a404e25631836268e438d0519b3c5f4b542ec14ba5276c10e58a3e56c254191ad9b58b4e3827d07309f53462085a88fd78d1b6d25a9861b3440b15d63cc91611e07fcaa8c645c4cf7d6ed7bd2ea799578d050344ad9feba4cba28a457c8145be1e8a2d440469815ffb7f84457564ef8041fcdb8d308dca759a18216cdd8558c14ea988e68fe785531102e42a01959133b722ba4469376642772eac1652c749f88bfb15d1c912644853aee90470c44958e24137d6427c1369e9904a8d2ae7830c9171c15be022044a661a773f09b14ce16f72e43e5e78996cef13e2731b48c2ce52414c3fe03df40d7db0c50307912466e7c431b39985b0e5852887fac2b317096d3893917b585dca0e2e44bd8eb9d423626ab1a0e4633d89f928aca9395b59b522a7053504d05f48774561028650fe3e0d6d1b6c5f6b5748d52a16956ee4740fced6dc58284d31a8e9b64decfe27a938172fc7c5a19191115248ec2569b11643de148515364194c2bf03d21d63212b7687d0941011c33d0b85219e671b5e3232d591296587e3509a626380f74811b8c7f0a41e08db44b884507a0ac0fddab5ba34e2031426a463960a6db504cb1ca5e8ee19efad621b62a2914a0415100fbc7ec13a0fec97e13d85cd805c6e8c2a607153c9cfcd050aacea4cfbcb7bbceda8934e52017a0e3733e4da238892718578d9f0c4b9d6fe6e8c70695181dba7bb292a38bee99fd5a5d2de270281480aa597ff5b426cc14fae509225dd6d7f3986f8178bf788079ca1aa822b31967b6d78185ee7edcec36a9101380221faaffc7561783da6ddf4d3055092216fd3ea5811c0127bdb449498e2c64ea4f01e324d5a4a0dc2f92fd92e1705e8de6fa1d53aaf387cdfc83bd35b04188211b5cef012ac42415ff39c4bab5a40cab006dc026007f93441156e5a040c01007b8877ea0dd07c14303aa42a9bb10ef5cb21470aef8eb73000b87da01b1c84d1e541fef7470841c21a59152a47801cc7cdf6a79fa39932ac07fe9c2c74e7891e42f8467255eaef57c25745fbb201ea4c75fb85ffb6f76448a040dd348d6ecbd0bb01b27f4bda2b25142704079a9c92e78c1382486fe2af0d82394efdb40a41e7537fac87b161bf5113906d93ff75e87f881524798f1ea72bdbb091009710c41ae51f1682e7ba6b8ec91602da40b6742e4b46c08ff989459b1eba5654acd7fa937cfb9a3b2233f7187a73c86743ce417b8b7955c6ce8468ce79dc9cf661da474c778d2a12c049cc6c1b80a9dc911bb8c5c610ca0ceb979c850c4c17bb86011fd79bb3ba9e2328cc73721e1fe826ec2420c1fa23d3d925b8f87e352df303106f08b5903753434cd7f90a44baa5a3306a73128f6b8d5f61601b6041abb2e7139000565fcf431ae608780b02ffd841c9cd43e6461200722493d96153b2d0bda15b7a23b8bb820ce6e63a2ec98d8c458160ec730b253dabdc6538354245240adcde762b40baf396ab1651da451208151dade01f236a650afe14560e265d0542c85f3449d08b6dc8a2148e9a94e2826104cb4cc83ee2cb607a6135ff4e280706d4ef1c04dba2e2f4a811e1f2630d31b739e91d57de529558508be1253f93799fb9b2b3aaf46fb816eaf0ccbf59d70d31e64a9504c8408c7b23cb11550f43daeac96854975a96e6fd268df29398414c53714e7324201689dc5a25e0c33a43804d8ad9938bbc803037cb91b16c2d9ffe43e713f3df782e17e98a6b616879e4a5afef025441893dac9c95fd14f871cec5e4a57f5d814d8285a052889e23eb8bf0f818ed177a29cbe70d699a4f0628c86dd5b3b4ac82c3ce314080e333a2d1269816a077f9a8a129679a26e94e0b88836a8c6b8dcf5f92fc9d013717dabe2308f42776004139711782afc112106e2b1ae39724266d05d851e1c77a75febe6ef489d7a9006d858f7e3488f40dce4a9032673d61c941f39738427fe8039a873ac0622b2c53564d7e3b075602c00b200b96ddac1f2bb280d59c9bc11c3b300194e091c0cf93c31a8a094faf5e456624f5504c90d8612187fb2a693e6f28463172e759585fb7381493a785b78f872c6acf2c135f75641a92aaff1513d99e388015f30ccc2b4e8ef06c4c51e58e0cabac58448b58f72fd72fee8dd3eba5dead0a74786f572e26e281fb3de162bef2fe60c19093802da7984ec00fbd36b698479a0a768681208be00e9c5adba51b05c78b826e7645bbb17977b7f8749aeec266c832f59ac4be77146698b8f740e782055e6337be26186a37d8aed69c96339399fe4de90fe0fb5daab9bcdb62e516132eb8e305c3f5d1f4c7748e67a8282ff2646c4e4cc33cd9626118b81989164ff64f9a4cd1d62b295d99d75ec661c38e5fec26daa78345cff6b55f4266b6348fae940411d3a67f053064a631fcebf1c9ecca6ddaf4e302b372a4596ffa78ec92bbd53d10f70bfc465c8ce34f87ec1f85776b7ce5c77f7eb9c80a134a08629f511021da839546ac90d3ce6722fda4f4e837c0297bc76ae98856ab95b1c8be542634da04ae355707c6a9b41fd26790c4e26ffa97b9d2e4f927a99fc18ed81f2cb14fde921614980c00332c6972056cf223bd46e0b58bc89090b6610798d312e2fbf79c5a10a19378c15c0fb4804ebf9596c80a10aef8ee24b7055ccfc781049f6ecae385ae42c5771d10f4d003709dfe4e4f0a43d59511e0f86901347b270915e09dd51223c221eee8ba01d0be23d5033a4ec7c0cf733c6fd724b059774be2a7301e0f2276ca5c604767d804c14f0828e2045090d8ba38362ab218b9cb80ef50c508104e3b5e723c38c4dae08cbe603207f894fa63123fad713fc8b002d7332d442e4951c3c01924322f2c80d9ab5872e326f341d7012f781b7963eb1cc67a3b4260a30d38d2aee9b65b7475b801469cf951c5edf79408f61ba03a097c909a0cd06e029bbb26e433387da9e0b155bdeea55b7c6b2a29b46adc84ca2eaeb3ad91c9bd4108404d44de4202f34a5251c529b5c45a5b464e5eb96e4aacfae9b9f80555a3d93adaede0568a905f0455d1df6e04bfb9b0fca7d3c36cb504bd311a8dab59ca9ea1ff54c1c623da21dc0742945de7e9b6c59f835eec50d59986580b710a3a5b6246b7b5c858aac419e551b787ff07965315d08d0d142c19dd3688b5b487e6fb664c84a8c4d46fa05e9aa79ebe893df2672fed1ee53e7231e0cb4ee5a91f685d1960715c0544527342967e06a1439a4f2c7930a1e1667673183d1cb3486236480340c6423cad0680afb38c848c4ec7c0e99db76c2d67c7da9ad38f492424c88341e45ce13024d9bcce6c03fff8ed10d2835f298b13c870f82069f7630513b0804210c63e7ff5d18942a85bafb7339ca3037a3013a71e98ec72d3edfa7c97428ca5a8f34348e307abe50d09d55c679e3607dbfafc8bd601ebde2c1056c461779f8806cba172220f331807712d00f133f024abcccd06477701508c1ca2fca9f980b6823c3815075f520b6e58f1e050edca798bec4a13c5182a4b0bfb66b435b72ff608e238cc25ab0e07c007e811443fe5499789992883cd4ba7cf838f04f863ea66ac8f07042dcc081f205df4841c31c2cb3f9b47d617d3615cc06072e00eccbcb055577527e7d36f25403096e60d7673450f924a71a6bdab4eaa222a18dd7c0dbef054c082f9d9c2c039a43d449da1ea70f9046b3f12e0892abb6dbe77ad239660b88c95ecd6779db4172abbc5c9440fcc863c54889aa80d0ea75e9e533f40b8faf71cf1cc265cb8e5d25de54e3fd47b9178a77fd949ce7b665c02a7617221acb41b903dff88f1f26a9114a4f54dc66cc3651f36859e8aa971409e49041c2d16f3ce76379b09b709754ffe69dbe5653f35bf515b0feed52e46f5f34e8f27e70268ebffbffadb55f46645e7b7d407685cac116c146a80a8079a73e592b672740a1e0542f1279586ad149efb3c74b9bea20a8affab6ee260b4a9df824cf63e5c53f41eb2079691b12ecac1e17a00a8bfb34c64751f456d94b4b977e7fce578cb55519a6a28aabc366d8e4dd5cc9ad0ce3e4c3218080b2a1201887d12ddb1b0d1a572f2246e2f52d2a674d4c783e1f5f059c41a9f57f69f83467bd86f46af2d6704fc31c2baa6e75a93042e49750e88470088ab2d1b38a48ad18cf598ac09895a8b8ccebc9ec89a107e6670471b60b681adce6f31a2c868b1f78ca181ddee20357a4e31802b75dd01aeb72e239a8dd6ee6b9dd20ab2594ae211984e6a00fbf4d3e88c6f6e1f94997e9df69a115335646a01a5597f9edbb220d3731bc48cb55a4994482494decd5161c3ab562dea4791454d96feeabc8b92911e7a8609cc6f8dd2ae8546a424626d5d78ed561e32282300f9f9e337c166446a6ffd6ff5d8690d2eaafc07928a2c28aa3fbdd394be4e7e560a66307ea8baa56060e2e0971c583a97c083049e2856143d0a3a9afcd7bfad75a12a3486281b4693f778a187acaa2df992ba5e456f54d42f91b4ae699208f521a2015bde0cc562e236e071f70d06c89ad39c06a094e449ee799a6e6d8f7d9f5fbd4c40975781292ab2cf8f398d70176a99782acadb10dd40401dc10097b5c9595e17e91daf667e463461f220df470fb18f143b7f658813329dffb20db68dc2b52e30e13d9428403b2acaa60d58dca0b1b41361ba62ee669a23a1708d411e3673018da2e2d638e925c9f45b5838713c385428bd73fc3569ccb821fb047c942d6347df7e7c916b6b20ba8708acfe3baf5b6d27e6dcfc1f62cc462b4db53ce5c5f0a495f89a5f9338773dad09dc4eb292d305bd3d636bf4d6b53e81c220fecd301496537556ed3e191931f4b705721d73909fe674bded724bff659d8708daf2a671ac4b8abce6002f5ce8066b49f8d61d9c5b9174ae0608933f9c0c5cd890820e70498aafe81702c4e1b9a57991c82eb47789a797cd9d43d19ff873f9bd67e31ad35178f1d22a25e3725eddaea6739f25082892d0bfbc35c28857ec1a96b71e2a40828044e81fdd7b91282e2c1f16d264ac300b87c3893b923b3ac56cf01fb36d5f68987f2e3505a24a3fa929c0499d5cace0278ddd807c51b855e3f26c29de58cbcee9ee8b00a21fe255fa023e578e5c2699cd94b7dbef28af4b1c8946312a661e1650025a7efcde04f7eb0b5a543c14aeb7aca9a9cad633553b05081425304e1a931cc14b5e34763b809d4a52df1cc6eee1598354791e894cafc42e76dcf279c91d4931d6a9814f7094a5e6bd5080442c24deb76fb50acf1b568b4c69ab1b959a8b42ecaf5cc8a7cc74c37762ba71096c0793e1a4201c91c66120083c45a8b5cd5b8175bcdc2e932d1828c9a70ab899f6cb38d6a958955e22d0be1e34edbda9b08ef5400a49bde54743938a688d7c26175faaa4f84995c95e063a4300b076faf618b6e6bf07838fd305e83a7f02add93fabf6005079b090944e05e14310ab2cedd8d2af726e3dcf408b5c25b6c80c658498fcf0b9db82c4325e4181c2179acc4e85c75726cb093b4c4514a693b606848b5ec20f07db1fdae1ba818b1b0a6d70d1fe6f1165a5849a79154243ecf22eb4890a65c10fdad6570b9614a5e033225154337771808aabfa368de88c95b4574c9e8128fa3a82297c9d5989c62be37aa45ad08b5c2b84cc24358f56fe3313a5e539f49d3377be96ad92eb298db52cd84e7ec6c8e70ace8392fc96f3bcc127adc39145b12c27bdc3b996d90f87e6fd24619ca6bdbf0f2a8feba940bb963733763b6ce220bc7484d295f51ad772090566f40c923177574eb565d24a61bc19b02975b3824bc76ab9e25eb56c7280bf1adea882ce1d8142497831470cac04dbc4ee3b1c2d08a52c4240dc078b407b2578b01242ebe06c5de14cf803110c6b84170b42b865f0130829a647c211fb4f3f7d661eb2b1d5379c9fb638a3d63ec6fe2b67155ea1d6b4b080787dddbe172fea9b2935847cfbc1dea83ba84aa1e07eb36184e7a96047e74846293e00920c492c43aa0dc0312e6569df70c12880ba7ae38da4a285d6d31fef2e0be3c0965ba90a8a63877c78e2695dd83390bfa4ed9d9f9ead293102eb5f74efaf962fef1e4169c845473266a6dfbffa4d7586ab3130d5d8c39d2cd521b7497d574c7d3da15e52013af435c1d838e094ac4ecd30aab9699ec7a9b5124db87aa0504d1a28f389c24f56490c7e71dec9ed345a25355efdf2ee53e9c8e89ac379571c124a06d903a1df626dabccb2e61da7888842ddca8360c7440275d253a8ee67f2ed3fec42e3659efd3a37b45a7687aa91eb62c542cc0683c4d30ffc1d7d6a9661c8ec5a6951f8fb623fd00ed8b9ee30b9cd5f1c3422e6625b35f092566ffbea3fc942c5535baa4c0aca0645f7035f18d89017562faaeca24bb1039ffff6ba960f17084fae298692ad72ec3a1e955d2613efc62e70de4eb6029b360daf6206145e987572a86f07f21d6a661ee15c2a2df234f3fecbc74c7b475c977ad0eb389c24b588d6273df71aefc3e1a8f01b2907f0ff94c3fb5e24c8fb0203240ace2fe64e5e15ce020473f8a6680524f7ff5b0de36f4644870d03eae14daa13d3645e87c7ede5020f6b118ef78221b09e09ee079bdc04c8ad03a867955053254e1bea64ed732abb146ce9c902e547bcd26d0447bad2ef4a0bbe2aa50f5a10a3f67553f0ba29b09face8756d09048097301a8e06212ec462629ca0450011a584d5eb2ea9875da0de8194b4c0014a635af516e596dbb5c816c7f46e21f601baa1f852c96ee263e88529f616909db310d559a240dd565d962a225d652dca9f83f48473107749c41a376899ddc66e257940477db488d15e9330bfbd7ccb3ac088790d7d4ece94de090652e0f31974ee46c436e476580092ea34bb63443715aae71b89202b35aaea372d7846613eec5b304fa03e4a8d4de2b7f90222298383172833bdb7540893b788f4857b695f5527157277627610734f75b7d1b31ce90074c20140e7499467438120a6dd653404d988f3b0b702ae448089b655be805cbe8f5e47e0a254de51374126bf991178394d8896149de08bee05746463ec98038cd4462fc0a7e8390fd8979951f82e53a35d3c65d684156ec364dbebc356cad299c3d89fb537f05b7dc2398844ce6763178814d84d48c3f6b88da9f3daa8dbb31d9bdafad77549a0a5968ec5894727caa6f194cd26fa2a8d61d8e9ac5274000d5650512215b1a4861bf740b4d92c646901ec7642ecfacc0220a82ffece4b4abb218d66597daedc72905c7aa3c12b6b6fc7cfa37c22b1ff31e8729e6b618cdf1a83f1060b26b238f16c6e84a1e60e266abfcbc3e122103b7040b9270214a5a7c29e3b586a28f1d7443f3b5c91664556c982ee982066182f98f2e43ebf99a9dde64180569087a2023317fe2ab06d5e3f631100ee344e158e2788673c978033f4172f956dc716072b7405fc2facb0f6428d75fe588cacf418298bf159b0fb803a1a254d19a8885e52bede96f29d78a816dbdf1be24c4d9b2288bcf6ababd0d9df86486b83de6032e52a944e64c493a3ba3c653f8a9539764b1bb0f787b0ee31bbfc3130f7f977e2867073779ea14490297ef7237e24a2c25f1bd006b12a04cbafd8769c71dbb64d501a6e5bc4806c5d07150da4fe61d68034730dd8a05c47bcb8860238942827d9d54ef083a2b406740f59be5648e229d37baae03b7c2456a9cbb5f1d4ede703e12537cc6f51e5bbb478b1a2c10426d6af225d8e2356eefac526b1f760416fadba7f92aefb8018defaddea8eae968254d9927baf087d3db9eb6b911359ea4ed4a701218e3f476a235468e8883253b05cceb4a923cbdce35606316798314083d1215e060fb7cbfc4e88f815e7f39e61ef476a469e7047ee6b39de100b83bd69a7ea98c8757b029b34a0632181304fa50cade7990b62864a18382517bb11a9af4c4bba0120c4700bae15051400004281debbb4d09c8bcf39795ff8b41376a7cf06e89da3a262a3c3d84304ab7866c679d10c5cac97a86c2c232e064df89ff00e846c28b077d775483569e050ea7a8b48182bc73769fbf52bb7af6615f20b2fde2e74e61cbc33ead473eb47ca0a0d7398ce6c54bd10c1d822c94280a0bbe58f1aa3c2996ec09ccecd9bfebd16be130243e7598d5288cc8d7b5b77348dc94138433c6c36b289001470548bed68f8fa0dc7a4f03206ae64d1b1ce24423a6a6a4dd73da94c8a3eb0de493464c9746ef8133a1078179535666cb7a52f3c347c880ed55539bff157debdf1d152a902e6a43c60d0bc9a23603d5b43e050db5616aaaa2ff24c88ba16100a4ed7efad9aaca3fe385bdd31776e66e97e255f047a6051e243cf15a2ee81e46aad9637aa014caad61e4aa26b7cab66f5d7f8346a5e90b7b982ca94144d96b1e7153b040e53ef6abc71a30e749a2c338af64e4c4089389640bb1b6a5f795e0322f86f17d96255c1ee671f67453875b4f650fa922265eb16cef72a947a20d325c34d0d19670a3c801b63b8f06f49298da17f666fe833c285173f294f2d547a5fec8a856d0d5339c2c4ce7c01e6fdb11c33f2abf7d617840a871e9320df81682fd27c41cb463d823ceb0088f949e25568fa0498527596e01075a9c294195d695fbae2755046ee5bd91f1c200b04159a3ece2efa73dd79567de02b3cb281ef91f159ef934f5187162c3a10e19a394e62d1ae3e46974c76dfcaa880b017360a1bcb9faf6c08f73342a0700c2d6ad6ad071ac808146e622877e73a2e1a225c83516584489b012331395dcdd3206478e59aa553d1b3508e56cbc66c5a5f46166bbbce1315ce1d4fb32afbf9363dd0ac9b5d413abd5b127bc4d325e38398a189045efe53708ff35a703f60f08f73927f802762e2c1ac512db1f5acf9d68a27757b585f87f50c98b7f237cedbbf9d03b1eed7d5f914a016a981d33d7204c9af2b2d8d306420dc4ca04f96e5be9c2636f5f9a85703fe6f4adb42921744cc93e60cf1a1753e538342b020c49d85bd3afe4c2909163948f382b37d36feec7110f15aa26076a893041eb22dcb31656f638941d400a48670c4a16b49c89cfb76f402214c978e73a6fb2d5f03cc6c937d19545825bba0b7204ea730833557993ec87aaeda443ff6a14b61d6f7f773c6a1371134ebc1925aa9a208c7867a5bfb9081b4234366899019acafe1647f05e18165f1654acacf3425838ccfc9faee4ce3bb97d2b8acbc9a604084ee14baf7808364d4427524df9c2057e6dcf2ecddcc1848cf5c71e39a6841c9a675ff8f32feae7b09ed342df2f46ebb18678e4d742f2d63ed48bafebf92fecd20238793bf6caad08bfaee7e66ac5c208267729f1201f76ed3cfbbfb3f106c82feeaeb9c905f960827499f851d83a17824c500d1c256d0740a4dc47ba41b525f4a340d991a11558c0160077a725f9d532eec5b50fd5ef3cb5c768586e9f42aba2d83309bbb91760bc0d89da8575af011cdf262d02a0d7b4e26d916ee2cd105a81a20d1cb8f5827ef4e2936ef45e5963a5bf86bb5f333ba7c5f003040ed3809319b52293415111c3949c8244d7063f986d1c399cbd9790ede1567a7cf27503c677d15266322cad1a6ceb61d1dcac33d7da90c36f81a5debc4540c61801b34bfdb1af505148c37cac65ebf7b92080f1b8698efcea1d9b8e215c39968d713743a88a1dc9f616c2574352895d50ab52f8972eab35d21fca110e3f52c43bb1d024db288dd75a8931bd688320bed130b8d31198368f0e8d2d2145b45c4980bb6f2eae2df5eec6e01276f6269e7c94978c3e71537c586b90abdbca281a9ba4053faec7819f89b40ac880cb010d72b317e22c735cc88bd24e001d150d451eb77ffa5d4fd91322d4cbe7426971baa2cf417100455fc88f95e5939ab71cbdf26adb1d13d385ee8a9746ce8d7e1762d1a920b78b330c117fb8a6efa327423d327c5a0fabc715d262d855158d92306dba45348496721c868efe9cc6f32e064ed12d401f751acd863085d70443365ebfa8b9fc7634da656857de1401411a20d9c6b5f99b6ef3b882c1ef45609e492261eb0ae54947c2619e2774fac151eadc1af845c0c6a22154a092d018fa325e845384c6614f8ea66420be00aab30d626c81fe85d916720ab8b1823531a6cbec5d0b5b5d1c29d87b0d1cee85a0eba8d98f0924c8b5666cd962dce234817d4418b8ee7525107bb78119624e4ff84d02e4b9547544c81f9ededa75996e366562abccb5670f313966acc48d551fec41d093e0039b479004b76176206a2c921e61e01612d7fa12e6aa48f84b8f4464d8ad42577b17d3a43dcd273fa4d1da242e6028d90ddf79b6822f9aba501264e6e4132076f5f9e3245b7934e303daf40f16e649791045a23a2dea75e188bfe62223cbed9f6f4485bc0cadeebe913513fcee98ad27dd994097a1539d19181f39be402e8df3ce5b81130440b297177bf0180250b164e4a872d576754c37f8f340b9b5a2d8c21fade3daffeb045845732e867d4636403405452c6f81bf20a2c4b79cdcbccb47aad4cff4d3c0666705bea4f1511f6f1c47eb03f51cd6dce3abdd63d68ff4bb4306df9e35779f344075a8f2936c88d193d58bd619f6f8a15e4c68c1e54cf796c971b7e935fd58d993e68bd536c961b952adf1c2be81b139d8adebfd3af7a734e07452f4dfa6eb180de5eefa5a267a06fe83b86977eb35f71cd6f7a13bab0f21a0aeffc01bb3583ba03003a771ec9b368e28c4f03386563af0b5536ea2ed0c7de39c9b63b93d2f5f750f538e2b5dd26d293bc276825e19e74144b01c6b308c5510e575782f140c12e0c1ca86cd719d9af0806d373fba7dc049cc6548aa7123814e7361eaab930609ea69ceb070cc0c7126112c7165753f7ac61f54ba150da9d0789d6540d04a55581d4c352c0ef0eac4b07bcb46c6fc170653ca9633130fd6f411564d59e06cb3050fb483eae3dc27caa7203707beb1c7122d694fd9bd5798c90201ffbf9ea543abeeb2e30b70e41a018f374d692f663d218c97bdab3baa7aa76a4f2affae830170f18bf802ec6800f13a41c0d13dc3d368a5a5f20541a8707cee62c169886bc73f8a0124e488440b00f7f0ee03c4ff335e228b6c06b665b63a419f3a72240b14eb5318228923beeb6618424eb20d81bf6c77428a95b63e38e527593b96ecbc238d57352e5d07f4063c30fcc85bb65f60580a73e01fefd85e8c00bf33b8f8c6cbf984766ecca3c493ce1cfd2efe7ebd6c3f2588a08522c48d6037c021713aa880d7ffd302bd8e6261fee75e6004a4361007b55eeb3ba8d6678dee2fc9dd0c61b3547cc33dadbcecba6f78c4d972ba9be8cb7cea3cd6878939b0d9f70cb58b855f27a32d5649fdbf0279bf48f7cb5659b0bfae822a4485c693fa2887b25d42563dd2a831d6a01f22bc858156630be840a2cf792e15086982aa86adcdc0f8c4ae0c181c3224b7b50fd6f87888f4a5e326d0433386773536d8bfce21c87149d91896b4975fe2357415498c353ca263fed9ad6fc1b8a43cb89ea10092767b7e57fd303a3f1c7f6a944f564529fccb83502c01846dd8aa8c6a60da62728d9a0faa3e365bd82d2bd9c812cdc910c8a0a51748d0427b36de5c261bbf1316bb4a981c0413b1ea5b281a2b4737da003091004f1ed1239e2ebac17445e40584078043b6a0781665637b707a47a6c6610e55501260182f374110aede099dd12b340d79fad918009f5001ccbaf1c7b6aa4dd05f3c290c8247337dbbfa92109778d851ba5a82763dd2acffd7d2b6f972f823c66d63c7da6af0fedca244d9777ce2f550e9c22ae3e496ae2a42991980e8c07091aeea791df2f9d72dedf3c568f0d156d0bc9a8a7f92c638160b1aa0fac018eaf9fd15a534eadc4e6681e960eb3356848568fe5d787e4c4748c83adbf1ed69b723bbfbe3f19a91fa11c68305a8350307627b1e23d66e7a4e79f4a575f265d8f4c354958fea6da0348048a815d8a1bddedc8504b56ea234c4d633721f0b3cf72bada25532eed18f3e1379c2814b906a55c68e23213db54ef8154ff5a3a7c6c94a56249cfe03040893f39e5491e5bd1bfea20e1ad2eaffb73e0b3091120e9913338618f93e6ef2182e9f42e451d43a085683853c25917cc539d52776a5a224cd873782d794f921ad20c64ee2b9496cd443a05809b2c0fa6e24a983e74219cf1b3773f48e72835ae1acc00de47da586e2738c7e1882aec4e415e77560668d9d1e619a8291123effe0bee6b5aff05a0ed7cf61dcf5fdfa9c8341387bad5ab88714ef3140aaa416646084e3ecc4cd5a8a02dde9aa6adcf23f0da912a6e56f8996cef7e9bf3152c142835987aef421458f2ee6fbfadccf0861903db5c7c372a52c86c964721e2ea1899e91c83fe27f0f7586210f78ef0037dee70f72a1c9e604e712e34ebbb4aa504e3ba5141f1036f77ec4a6b7c136bf28af0d7cdd947207c7d563285f573523cc0511f9d6b096bb602957589f9dc8ed8264e68ca20340652fee6fa3487c98f5b44a7c5d1991c06203b6c90fab30bad414dcaaa6ecdb35a5af1d4cabe400834692240b45a0c85eeec8ffe0e7071c5dbe13424cd4bc8ba0777ccadbfaf4b26e5414ddc21b82f7cf3da2eb2fe5ca384e152800785f0dba4dbce0824b06f43c109730a20456c8db01ffaaae2091af81b1ec8ddd95af26747791c8fefe0ed2a0fd1d4defd29a98fc8324f65699c9b1bb51acd05543bc55134519ddd66ceaf77f43644a4fa0a5bbe64070735b658dad268d0ba5efc6927f9e098db68bcea94f1f456b4f087a5087e0b6302062ed980ce00a602194da1b5c830280dcddd9726c9c56aa6682b87444682dadef4bc5f4f9f8608c6e3f8e7ca44c2b921443eea6704b6e88165becdb150947c219646e76c6c167d2a1a190210b741d790bf426ee30b7e8b919f91e33b2ebd9418bc9136418649f7b41afd02a01e0179bb8a53e79d84a6c81f9a98e5672ab63f015a04b60315f7732f0ab60c6b01a35090418bb0ad13ff701fe4c41e3b3b3543364250b30c83ff9655f9c25a083dca079ce5486c9073a8b235faa96f4682d3ecbabe9025ffd82aadad1c1caed5c0df094d70a7b511652ced3f729b3d3944d3c341fa8d29b7be476b84a3d1ac66a92ee79cce469464baaf1889d58fb765ac54e020f8fb34682b92e9034b43fb85160a8cdf522c2a7170da3e7059f03265c269d980c74d593d548d042ec904bc53a3abc71d2caa9e089ff0b517d7ce11f6927c044ce8f7369802d1cae9137886147e0842599648d7ec8bfc461f6783dbae5dfe8fc2cb250d68fd9fce081813c79a37b1414895c459b28e8a1e057234a7b7615434e82e6c1e71f1d43ce350dd91e9d1b9c2d6546e7db11848ea941b44859e9fc7f4256ff708bd80d6f481c498df58794b228eb2c2dc939419692ef4ca92788b7e936129e4b011bfc6a3ba35a3ae7b57460addd408a432827fc4b576f5a818a7f720ee587b324f59e78052287aaa0a432afc72010494670301876589700728a5860237ffac34a4411c4b31828a0a83b409227e01b504fd50a41e680d689aac73e4c914339a07dcef2101fb4f2eb6b7f7ff86e1262c3982371ec6780f4167ccc62f6685b3be66ab87253de0484c90183d769c60344d452c510d653bd1831b9d4d9234fea688ee262e6c37ec5d1f6146e8c3becee841f54c1ddb80d3ee0c6a1e6e341c05dc185b8bfe28f5f53de0582f6b601da68a7a0da1a1c1834f0757b9d4bec8e4df3d9a87e5b57e03ed5985b01c583f6f3193b3a5ed43078a5750613fb3410936983b387644f7512936987738cd9a390d62580b0958d442f47520a49bbb1c6282094c68deedc8ed07375e8b5cd64176b5978b057ad3dc74834bd60763a9ecd9a579c34f74d10b90b459011a4ad623b33a93d8348ccc9eeaba1a92abab53ef7b7f4a0277379b579a656ff994a221df34dac4a0ecd367433d8c7286202a97fbcd23642215f50ac50de9ca2b637e2eb571e09f090e94dc75a8c750f7386c1293d5047798b4fb5a8a0b8c5358bcf14b2a1f657d07ae1125a92f63d747d5d68790c324fe71052eb3dda03d952514f135aa0feba7a4071d2b6615ac7bacc1232e9fb1bb21f045747a973296486f44635f083fe612af13edd514ef08d75abf80e2e3f31104c906f06c440f1f74fb096c2ea9d1fe186f415fc885b084c18b476d5ba70fdef549bb9f20f769839522b04a02c16b895090ed08f8ce977ed008d271bb52696e134141f62e9b1afc440ade304e19e26b2cc9945a92c5bef0229f5043d65053f491a1afbc5a86a4f5ea4611429c9c57a96572a3ba5f0a5912494e112d4ba95889be5e567d7a8b8c0e135adfc23b5409ea22aff04973a702b46c86ea733a824752838792ea0e7f9eeda375e439a8fdbb33de8935da62e40951d42b3e3c13c49837025c9d2adc3b9b4ee717c6ed67b44695529f8ceb6519f56adbfd484e25de812dc494a3b70f3def9043a50b02f3f857b57e2f8e8bdf1e4d93f707de17c109e09936d144f4b6159d2191e4a52fc9f050afeef0b8e42aff3e95a7b19879c6b7c27eb51fc1cf6f4bc2890f84209266d21fa76f1403495d3d108acd29cb4f56f78c851b8b3975cc896ca518ca7b908086a99b4738f047f1ee01bba97220aabc3648c4c5aae46905900892bf8a33ef61d010f5cd87c2a527aa40b71263eba965dbfe2bb985bbdc4bcfad42739619031a5da332c568478cebe30c62d3140603da2c4d22926ad7d448201618ca676e21c4f60bd9013609e515f7bff885e030f1a4b2ca346754729c62e8d3998aba6f638a350c62aebbafb30fabf5821e6a3c8a690ee0a7a0f8791256c4d5f431a684a1a59f292cb01cdc95b7c2c07f888f0efa3806b498bbb64c170a5057e00d38d3695b7080dc405c5eaf8f68ae2666447af30a6cd482664d3c182f885d8f18619737439c5e96593f269e50e2157cbec8b459e861b72bd4b63deb2b7b8fce0eb6f5c1c2c192acd58c166588c28d27c2d2a72c5c8e7cd87e4319022fd5cb1dabf868bec6640b1fb00659e227845d6d743cd511d93a336681b5013ff4f14451bb3986c2e422da8767bc4319c0a2427f22b6b1bfdb6e16931ffd012474a9ccc7112b76305d856180044a213e7840fa1eb4824ad7c3f68e17df9ab3bd8c33dba08b9689fbbecfeee38ac0b54e8bb7ab035ae14178d3061ff49b0fb24d0214fdb582342c08042db0e20a563353f618094005a4fcdf4a4e49fb1e3b217a4764becd2f7a2d8392907298cd33938318d35529577801725f660c0a88beb06dfd2adf471c9349d4df1fa9c16ce51595038794fa7511d1378662aa95df405173fd4c6c32d2e397fdb8bab2d7d2aacc6f4750ef786b6a8f1b41ae10671c464516b8167d1ce3992ac8a7828546a38517c01014c98e0db637a9c581675f4ed835d766e5fa12542c6c6fe7da3d2db436cd7d53c9d3735c7be313957e2924760b4a29feca253582e1e78705a3d7098e56aac78b47b896fd9083c64bb736b6716fa6b4906e650551b531b6247be7b50d152a59a79a56bdb7eea876123978f26d8e18870e665c8af5eeae32293193d2dae6b4765a7383210cee84576e6f6965827bd445e8768d96d8dc72fd9e48ee23c3bfe4c856258ca6356ef1ee8ed0102aaf7191001b13a4de583c7c84d110364afe28aa4993d0fee8c5545adf288771e4ca263b9aaa24eafac76eeb501c6c95c73e9383dae7cf6ba86b51989fbc3ec0192083c7ceb5927b59076a3d62ce661fb1fb3f0b1ae6150a15fada5ac2759c2b8a09d3a08385b236c7d188f87c6aab59d46ee4243f15c422bd740ef08e45f6b32fbbcb62ff6a410d5bf2dd6ce47f73da6794c38c73394890cdf5feb58b3d18f255336477f870ab659096c35ecb7d4d576682cc1937c2b20f8eac0309d497d4f7af519294ec01c40e5095658c3d72ad69f69f3cc5d551e4b97ef8d707d8b411dca793a1cf8ec80da172f5ea4b70c4eacf3c4c9d5e6997a29c776a04a0a20929290a3aa0da98eb69a5f6e939b32c557fe1422c8142d93e0d2707ec4d0647e17b5b9f41c944c9c34de5c35133eb3e734d2359558c633c819bca284091cf5cc1783687d6e9aa5a148d0200b7dca5308e4b0fa4abba9f35984060d65db23061ae9061ddf52ed16d97af437d2811a323905085db0d6fc949a7163a2dd03a5c076fbe616d86847edfe4fa3b8cf39d61875daa1b794f9b88f9a03afbfe8391907566fd2e20c79bd03a9ec956dbdddc263a893d9ea39f65a79a803c4a14084ed281e49941cfb215fe9743b4a13b92f2d4b701cee43b770a8710ab6883c0b3166c736291c050bbf70d0280188e0c0dc6189aaff09987b3be3ce8181c0de2ee164b3751496c83b04c3cf654b008a2f66dc4351975d607395a0c98ed6ac9c2d6a5105f4d490bd711ebe4202e84249130f88512d673e049ec025390ceeb443a160d90a12e548ab693df150813dbbb60c3888857af233d687a35692c4d7aea0a4b78352f5b98c3865abf28268d89bd62ddcea7786aeeca31edd6688df26373a140980ff817088f4e05443747a40a9bebafb99c9e7dd2dedda2a89148f95ad376d9ed3f1ec6e329680f7881574ed71d3a20944a4b2fb1b95b443d2402afa1eb4e7f0f85c6c371a4856be53c77331df4c84571949a5296d77bb106b4eee9ef8c4abcf5961f8f67a396bba5f0d98931c76596aefe379e69f61a706ba4132ec2c89370cc7f12366c2ecd5d62f1b6af16de01333c02d0b84738cbdd26c9393de5106acbc02330ad033619d8a5fe060412d648c8e9312fc25b9338272943ac1a524e8ce697a9a61a2b1a4c2aff66c693652f3d81c70783999784c9a2bac2bd0c3017e4e3e74c41cc019e36fd18ae750b92177e70a62fe239d46712c5f384a281902cc18a7c06c5aca3fc144ecf893651f7d8eeb9dc6214982b5170f1fc26f21363baeaed873490bda3b031e59599e03374e33a3dcb14f29d6fd9253fff92280d6a886f954ae501e6f5f6827831c96e2d04dd8fda042c1b8a47e145e8627c79bdc04e567d32b64725345179e52796eb00dd79981e583922a109e005e20b1f9580afff01a9862c4ce5c9b8e776cd22ad4fa659e58a0038a61648f35512383a2cf0a1f07babb039d832e1ac25efcdfeb10ceb602efff594c74bc63fa6652edbe0944186e32164e003e65e171bfcb4f3244b0c61d43fd8030a781ea77e91715e52ad46bb7b12de2be5cc38f114df0eac9a88dfe18cacd3aa968141ab7c648380703545ca9bb0e3400c9820f4e0f7b57b00487fd0447f18435a9a1327a8cae06e43d527679dccaa1264c88b5500ba32b81c5fc4b5d0fa10832ecf596c7d10559db5154a327317688e33b47c05ed8b49828080598a7734e3116474fc9b98b438486fb6a307eff5429c211ff33729d3db5d9498daea99233e984e0b6391b90101ceeba3b60af265338bcb66357972adfdb6a14bfa5000a19ea1e4378b900d75ed8f584c58b19efc7ffd35a408556b2b798e7f50dd13c207a20e468a58ae90155a18cbd10ef365dd154e562506b742b4926a3fd01611971ea0080458e8939b65db0c0c2115c2f86fc5ddcaf4e5322fb80ed613c99901dcd00c28e73a0770721103cc0e601c6482af5dce0e3c18a0fc1ca1392673c94570d0f3906b92b6a523eaa546702fc3aed4b1d6ba5da998e56d9ee9d02a1c4166e1f2ba67f841b16604d3710db944227c1954fc26f38063176b4cc7a681bcc3a33aa81ae7107a6148206cb37bfb0e30feb436c5ec8dfbdff738438d0f7fac0141cda486450121411c5199119c707a794744706f1f798b73792d446ec8cd14151cc9cb4564007e074512c3a7994486542fe0c4cfb7a91ed32f460d60200f131b8cd2344f86d1ee83279b893f7a2b379bbf74b45caa18b9afacc07cb28e62f4d1f9b0baab8d416756c23185a35e2fbe73000576677ac2b73fa48654c83d4b3c5691d1eedd8b121b143dd84bed6a59b3204a41524e6b2a3aed69e960051dc0813060d849a07308e9d4f16e166991d1aa83c85fb9533be10d6e3ad26e1abff2d57dda724a1039cc19b83fdb0db15d058a5b8cda4f6fa48e64468938fa0a338d6b67cfa4946e5c8a16568600a6a33ebe2251be49d4b0f3dea3fcfd5f20b02165b2784bf630bb0c10aec157a36e96c9a5bb5418888c123b4af00e77fbb1e0da196c3e29323a6ccb8a25507224ef9d22dd78647fce0521a8034674e201110f127928573f2666ef0e12d6ab545477b0135167d387a6168d89370e14849285647ee42cb30ba8ed3de798304ca0d8ddc3fc71168c40d29707a8c6dc306c595e75f733a87579e2541fdb74732daa80921393d0db753bd1527acec21b889906230ef681505ea1a24a88016b1ed7a0694c578041e021653a4f2110a371ca53055c288d4d208549abfa093bd5cd0a5a846ac1e4ce53a93565d2026ea5bab6bebc759831715203e417b00e684fb4f3861ac40e7bd30d5bea172377961b603846c0e5dc66ddd0c789ec3399f1c06f0480cceaceababfdc7679705ad5b4f3addae42834171697f2d9cc1705bd66148ed5463c69a195d478f749e00c274fd64fd20ac14eb7d9f3f2d69537a7f0e719421f91290dfb83b4b600c7064312b80b1b1d1dadb8ec6dc8ddfef4adce74cd467d862a4b81fe3dfec469358ce7824092b4f1110ec84124d3f83c2a44c9e938bd6643ea3f90c8a1407e28038532067059425dca205e014e1ed14a6a4dfc0a29382987b5687baeff3885c314ddc515ccdc4e0c0141adf28dcb9acd0aa5cdd18c7aad6587e483dfad0cf21702fed9e6125a3ce982197dd3332471fee64901ae93c59cc9ee1f55355f1c0e540593a21cf2b663a1b35a27c25568d05f73e51da21af59ec3c938a3c842daa0ed3176ea0682c8ae2e26ba96bcae4ec23cf217d0e294a72b091d39937cfc80cbdbfa71f5184f65b3c308c81d5f48117b23413b983f7cad29b1e2956ec16247235967fff838363f4ca7a563c744ddd170d5c232fc96b8e98d798293b2a81014cbbd836a9c651d2ae6d487cec298007e3292c74641271c92e4ac5c6dad1174cfc52da4bf2d107178b050b5231e1c3bf4f25017e3622bb922dee5aed86ce426c3989cc7807510f53b614726ba4c50493c1ba91d28b9bbf8aab9db433877ac3281be12949f6e3e473a50a851dea4f2589c80798c75c6788201387bc0f0d54815d8766ea2494c19dd2e280d6aa36d9583065a06faf386cca4348770594dd6d2a81eac2190f5a85f5c8b6906a1545f10d617bbe30631cde2a1df3ce5036d8d2e60a916498eb4d31267c564a087adb6942391c373d1d9b371614349cd7a1e19e35e79aca64284ee87e25b89de8fec17180a6bcb76c3ad5aaa1a8cff5420cd83acc4a53940db41ed2d8d2d85a98a963a6333456d01b9a018a7a57638596598baa0dc695ccce18754e45ab37768b95390c975ec166b6eac70d72bfc0c0544762775e763d8da2daf2e0357db6005d66dfc03be771022bcf7bb804c088a753c0ed23b3981686c6f8892cd759338f351e51bbaed4389e044bd1cda2b8586a989e6c5af6ac4cccbfac8109ebf5bc3b2c5be181e3b0bab9795c481a609007efa17d2810c96ec629321cbd11d19e016ffd1c67ce6b741c78898f017506e6ac6508db53f20071b3ec4c1703b3bb9ade0d4ae753bc0a8b69c2329db8b739c6257b059e160f12f881f0913ccecf18d9c88656a80fcba824c8c6a6666b22ab1516ffacc2414f73fecb20496d9a3272326f631ecdc657a59591dd35d8f2f1c0cbdc391a60b8a607262d0102e1900eea884c3c0c294cd5c47205fe2735a2517c6360ca4b37da2ffb7cd81d37f1844dd822a2cf03afe842941a6dcc49833164a051f0c5c058744dc9e8df3ac9f65b828f169f256f630a47eb8c578ffeb61a91d750eee4be03a3f4c7c6e837d1583d18e9d3f2e9ed5a2d80003c02744a21899296fa940a518094b168e0379db323fca36e637ce1290be68d5e6fdf80d5f8b5e9d6652a37223e42d4f1f24ee954392dd6345baed2b6de110586519a7e501c0075dab882898d30fd2d105b54cb4816d5e572c0f568b0986e133b988a3944fe320c0d97f8cbc524286a7ca5abc8894220f35aa272193ca0cd2dadd01f2795b110c4dc5f83a5bdcf6ab7a35bde1a3aa92314977884240685aaf1e0201790de5dcd735de1fc68dbad7cdb6e0fd34090ab3ae4cb8bf2c65218cf520d8096cc01d1114be822fa39bdbd1dcc1a209d709d9582e27c8eae9d3fa0c9332f3d20a30ae2afea4007607fa8fdca46c786d82a126f62c8f326480110d4ac20fa24b57e36bbe661090337052931f57bd73d8a70a119c4d555b1269dac5a58c8a9b26e6e6d00b2c19a01a9b508b9a32f3541381073051dd09413304f61894c3046dcb919f440fcd20b9c2873796904c6d20087edcc097dc960c8c0b16ac292a1530f014d8b35d86bcbb333d87380ae6350c6d99f424beee3a17a9502a617d9905d099c2ffb80811461fafed82c56d05b4cd9cdccacba916c07817f4a2575d4ab6a75722817966baac508dade1e6b8639a8f8d8eaa382956bac929b64d8390f549aabf971a4bda2ae39ec8ee842eb35995115862c36eb9e26c2803f7505c84318cdad2ba4c8e605e87b5bda35b565876315b006b1e7cb9776846c79e067043b56fb324ed41496c95f328882e75e3f2e96cae3f416f604123b9a65ddffe799ed4992377707d3a158dd76cb0622f98947d61dd5ee99ceee096a90d3fb61c73e93e5389590ba57df48da2380428ccc45632488707eb69a35a8536f8768f3452b2952b31a6423ec61be4590cb169e6904f9c5ce258ba63220da0c0fb4c80687731d2706b2778ac14733911ebadb0a68f6f02adbb22e1f383238f4fb793bcd08a71a921a5c1047dd3f67504238a60d77b5e747c62360554a24aa45a83df26dab07d097b0fdfb48539ce5dc470f4ee63fa1ad98c16470eb44069162b1141202e10b22d89067401cd7c54bac942f38e81a8bed5910c57721d0019f9c715be670b2c0a496af1ef9489ab0cd2263a3c5e99437735cc43213eae77e72d11afdd2c751dc42f1f952bfe64609adc998fda890ba769c5ab4c49759d540ed5ea901be116bdfe11888ba1f65e6004673cd5429e2834b840608c9f43d03deec8356e8d5ec24c855b5243af3c2ac10ee3c32a7329b99171fe49c44286141dd975ea480227143c817a9a67f1e6137acf0ce807a1a71855a300a447f996ea92bf0deac15d4cdaaafdf19f63cbb8883cbe92cda2e16fd4fec42cef6c0ab176fb6e35d3a1b1904107c1a1ce4116ba4aedd1a25a7a3dd0d6d631a250ace02b4840a23c6ac73cee6fa8c8613d46a61ee15e5194b42efd83fc30162574378cbcbb67e20b39a51a23ccbb86114926aeefb835098c1b9b2fccc51ba267885c50ac5c26dd980a540e0868937882172a9066ce102856ea47d305fa2bd37429ec0cdaba3652ddce47fede58814d112f1f6e4a0ff70f6e3fcbd2be8f1f2ddaca6facc6232fa5688a9ee5c5a1483c0677e368ee3407ceb34957a5dba428b11123f64465215e6a3f4ca33341491fae1ec00651ccce2d575d2e09157aa060d29d924307a4de2bcd35921ee88d8e14e5a78f8aa8177a896fe247b00fb68f30f5f3b92fb7240d4615a1f54c80f9f4d25451df61ed60721ec677f417aa7b49d9bfdcf7acb9fbca2a54940ff768c0401f81f1508a4cd6d3b2ed0b6aabe874f0293f5b60b9e685bbb82a3efdab4ba7ce3f407b41d5a04f0eb04a11e1afb8a6138c1e8e46f4f2b1a7556c43124d1eb3f3dd816dea9109cbd4308d0adbcd945fd83920b2fa53969f84ac677b40fd6a3b31cad2cf5cc9a0b2a87b4e69fe16e312df6985f0aaef9ba84b9aec1ba0de6c599999202564c22cdbd898b27d6dd38d3bd0f1892ef61c7e00e42eb9716c45575256678a164a62550fbb023d6be8e6dec00f711ed76a277e0512844b74befb77f0317644c7c0e6a6fff6af57eeb640bf6fdeddf5438df9d755b6d609657a485e2a07d52093843be3619ccef602b765a04a4f10c7c8a0e5bfd00d7463dea4d3d5d7adbf2944a146784c059be71a274422b6f3c2301b0148c081e4899338874c9692f72b052d96c4d93e1390b9cc7456fb1c4daa2788875865adf460031b50b0fcc11c22989cc192b23b2e3709c83c0422a5d9e7202c346bf01d95656eb30c274fe258b579a4bb9dadc631f4516e660973d6cc1d8161409f81f1887d5770c461e3495a0a46a55e120bac1ccfc21aad0c4ace2fdbd399410fc7af3871ff1a97a76da23781e2e6fe9ea2a02440bf70ede82569e8c7a1ab04735bffe7042ec839a59e59d530d3e97a8f04c7a44cc461c41aed6960147a599192cac7c96fc33324a30c5b909b894eba4486ffeb143b7ba548b8b0497f68c1bf13f6bdde8d96234c409f84d4288f3b57b930571dad601e204f7b34668e9e26362de50f6b2c5c9fafde1ce5c5914071ed4d613f30c5148ea4bfff27490c9bfe07bbeade50d115264464628583e105f785f0a776c7b803fce92741b321ae96f0f07bf41a505f8207d7d0d7cffe042d0504bdd786e3bbb15a1d6ad65202895c93da9c5aa3f1ba3a2a50252c2eaa7513601e9c9c10dc06d34a156642e922545218c4089073573275ad39c28c5b9e6512b43906c9950d528f1a06de3569e565b0e101fb22a4e37b8c3a577e1ec129f22495a81d26cf49a3b0f64c08983dabca5a01acfab05bfa9e24b22d41991c3dca043cf26578db1e504f77a6a6b80468cb63620b0dab2b598b16c2fbb84470cbcb4db236b8085416361ce07d52b80e0000ba655aad5bf850502d156d6f97ad800af44f01284a17cb2c6277fe93700714b1576ff02231c2d614665bf99ed5b2c1a29c2250d31ea1e52ca51a3d593217e130d37632abdfbd40cffe452ad561f7920940f2ed6dcaeda64cd857d3567f35d1f45d9e0b49758061be39a93877e15aa39c79ada1fea5104bc3ff3a23482beba3f59bfdb5edeb01a50336971fd518fdc88b59868b97c2e6c06fbf9d07d351bfeb5b3c18055281db5df404c1c17995888fbd4dce09822c40ffd5f0e588a598d070c834a673263c7bce922e6227c266e5761fb205909168bd09f436ada1a3a1dd171e828ae2fb2afd22e15805f9fd5e072edde6208afce066a642a0b4e9620a7b548d8ac70711ba5da86e341ae9a30d56443527f420990198ce87daa0ae674489c753f1eae7526a0e008ab7ffbc062424ace4cc24990518835b73aed071bcc48d1068c80d8dc04b66f1746402e0ee46eab251a1fb82df96908cc2d9a171368d851e5c2d9a5eaf829e3960c787b42573c0ebb20523ac51b982ea832f7d2a46a9eef3dd2330d56ae49062d49616cadc8f26b0005191e5459623caf1d57b04224cfb8134333c898f06a1fe992ed9c937c1202399838ff80d148a534fb4e381b4cef44c9d8ecf7a08ba5f4f7b798fb6acf6474d8ed1393d3acb1feb0441f11d8daf7c14150886634468ee6d619281d036c11eb437282be10ec3fe35a15f270a84ccdb305830b3afea3ef8194389469f5e18032c5fbfeed844b2f844a5411aae8c91663c6156e3aa7eebbc5d69cb7c415465f0f9d32f05fc936a35d0d7e4b82e7e3b18aaee0f1e60651936c78a9d2310f691d41b86e1855bba726d55e9960f141f859386853affd07072d3e7eb740bda16322df09badcd992e8617ff69a896fc4de9e83cc9f099d99b9bd5ea8957f1eb8c9bea08b42464e2da27e2bc6d2c282b80c5d08e195e921f89fee2429afb18415fd8286c499f3b2e0ede440772c80273d6b8ec998c76331367054a1114d933ba96db6994c51c19cb06433dbda51d741bd13aa92f24f20b38e892c8d15c62b4e2791436e403f082c81a6b8c0d558b57aa53793492ce77aec5a118dde8607cab614fa9547ddfd28ce7949672bc808a0b743a0b5b8a0ef1eec90bd7110ec87506e75f332dd29cec5c7f6b2da8f2986da8f2b1d936acc0c39d4aec269250ab4e4b424d456c91762c2ce720a8ba1c70d5b4815729d8d59e4834828742ef8c4edb20d5793ee0a41f6918f9b96f8012115af661a833cf1c4ae954e56c95b191228bc61d9f7a18cf41b3bc1710886c13ce731c4e9cafe0cbe3597fc6dad2293b4fbfe1fdeee34e60aa99864f02e3c5470ae3caa1374fcd01f06de438327054258dc2c6c2f59f9665f060621dc9dc20449a5dde95435aad88a7447d01fd136271ffdce167adc86ca67945dfba5aeadff05f2b9234e52b989afccad703da8f7e64666de178cc27c0a57bb5e2ee6e3e88371304c00d97f952fb251a99b192aa58052fecd569d8d9510a2c247e8c634a99410ff77ba090e53a8f0d1d712a1be38230d7471788d0a6f749c56b4bb8170bcad854c99475045adffa396893019318b13f5b3811248465b77e3faf51d4cbe2742b71561c7a7688a72be6fb48be0b41f37a92634e00fb091216bdc10f63f6292b0f6a4bca8e268909372f3592a8978e1d624c0f89a8e9bb4625aec131bfd9f2c644c6f88f081981c68af5d1279469c1802f110a2e1bdf760722f6c9b696621916cf13d50ee85b051753fe194278578c8f2a141ca4fcf843cf2e1c20921a8ab10cac21712a6c8072575ea7627b7f67f397b91c17ea4f08f6eb6982aa64eb3246b86eb1543ceec1dec2cca229688e49e74fb560f04e7524bb617e87e6569c7753d83dac080062bd46eec830bbe3ecef358a507907b8c3fe0278ffd28c4586491239a827728bc35932af688a4761bd1c18d0cc7d9d6b1a060e0116d41ff24d745fa629575a0fde62801cae27d48e34d2e3e924c7f0227ba3f7e7d25fdeb312e7bbca6183789101b4ae7c40bf18d695b1073247d571a8110b10f43435ad4b64a58eb3d8d8c41dadd459f131dcd611c9e7ce4949a49427f8ba60837735b852c3f5fd6a9230b4ce7d3132c6ffc16fcc92ed3b331e3b84cc14aa2460b77cd75393f7452a12b8b8ecacce5adf11856713d17cb7305f50c0308d8c6ce5d4d44c028641d8b15a8a3a2c7528a98fb56b9baa0066b7a095afb6e48bb8ae504ecb96d582001f05aaa6f53c0ca687a8285b91df987cb4f1aea87e47d57773cbc13dce3a31acf94e998cd8392e0cb23fbe2d8aecbd89d0a6353adc7d63fa1227a16d78d2e77335e51c4555229aa8272562a77257962a10b52205515ff2ca0b54ca1fa429f8d8fb8b9d5c95c114c22709d0443f3e10f963012812f9fad93b9b2f79f28a05116c21981ff0c56dfdcb35849e1cd624bd81c35c687db1cd6eaf5e1364018f48ee309b93cdfec9f23da5b84fc8b8524b44551a271a0f69fe838c6511bd0fbe0047be91118f45688f88112b52c4b6abf71a79565a5f46b29ada1d3cffc745be5a810b2e4e27410770a09029c91d65a43cf8dd35ee55499a3d56f858a46c554d3a2c18adbd3b6aa54fbac15d39a929fab5c4b9fd969ab7aaacfd0e2b7928aa62aa962c302cb4873228052193f3bf98c511a329f9c6f8c101fd7ba3bc54cce3e704734374d6a6e4e772ba43aed6b15622914a73e4e100d957e55b9e8848a7db2ab5a19fd407e9b7ee824162bb46d55e85c994dddb23ae1ac53ece249125f7ac98e03bd3ee32985c63f427998d39e58a57dabb7ed6bf3c293f9efd0a4a3ece2979c04dc7aa7c7d89dfd8c647855c21a6837b1d6c2b3aacfbf37bd672c4e35333766180c50ae4210b2b069c764a0019e0174eb7ebdbdada91880bd8ef613516d41267219b7c67e0be9fdcc69e8600a590ba3580e6eb65ebb38605e5f7aecf0dfe679e80f0d578b95fba35658134253d93e337e8cb30432c4b1410817805aa3906c6fab7bda302ef00ed66a79ada4c3568092d50c0f9c79654074846fb4e363d4bbf8f2085ac4dffd80ae0d98a4f8a3f5da67b9c1c5fba4ecbf13e0c6ba08e871346cb981d078a34e55175c844d78ebc486daec0190c45a43922c75122686de5928f8d3fef60e941e16dee6ebbc055a97a0b8fe159726c916c198628b714f5afcc492c0380d57508eb7e5cf8b9531808ac9ed053ee176a00d4d6b678f8e0c052116e4639c8ee7eaeb347200edb1d431e46fa85b438bddf756659f49ad25297bc567dd7ac8c48f35e5c613a2d4765794a9daf8daf40e4b4e8669b66294b62e6cae63e97dfcc25143c18fd02edfbd833e44f45c35816a21694ccaaca9277dd2655c8670c89ecfa61043fee73a6cdc411123de06968233c7468269ce29f937a0bc56dc7dd72945e2e873577c3d091e25a1d7de3406b2506b2466a1ad01e6826700608545ff16d99aaf8f9761a2c8c0c3f8a267f862e420fd64561761f1562fd9eea78c8a08876ab761f9d9d2b89abbf22c20b7f74f114ab4f6f01a92965cbbd65971063be820238b2c6dd137403b8db6061afee943331eb7c91afe354bd0a2aafcf2e1b0e4c7827cc9cba67afb20831dac63c34d9fd5fb209fe6c628f3c45b654f62773bcd2c56b5fd29a48a6d60b682f00692c3ad47c719462afa6f0879b667e6be3fdc71adcb240c1fede0c2a1a633c439c971f37c045ea70b21a8742c5234d2d7af1f6b4a7afbc139899190bef1eeccf50a82cf425376a6c9ad91d8915a41badb33fb04e42322a4b6ef815ee716672a51f6f2212130114fb5c1bf5c33d39cf90a6a9a0d3cb7e1a244ec3904d6ccbc16b4d8836c5bfaa42a81c3711e739bb2c4a1f78edc00aef5c86a25c20f1e0f31a106d75e6300f3237a3733f8282819f5a832359ffb1ee90b25321ae41a6c37f5ce3542121f8367d4dadfa7550aeee9bc416d3e4c1a745fd7ef5e31d0b220f515e4decad23c63a0af2dc397513d7fdd49cf75bc38280d456ff4f44a9a50e43f67de8f1e98f8215deae4e1ff0991cc18aa9e8624e916e593ffc9dc50d76758f44e1545fd4a39f5a0512827d2ca4b9dcb604481c264c5949352f94b79d2578d15d7f79352b1b950d90b438f5eeb5798fe30630d8fcdd2e9eb543c2f457308123d6acb82ca9508a77010e650576bdfbaa16a15fede3494777ecd5c7309c120441ff9e095c8e71121401f8aa2855f35a34476e0f8c6494eb058aa32319d292958422799792a100d13aac30436437773646036d79ac61d5c21c3c60afd37b5eeb7107a9eea44712a242132d3550fde04ca6eb9864485f4ae8dd85afd8eef28b2e823b18aa7dbeb6f8d8f593b28676495f3e76cc5449837ff2e7cc38623118b4731795a5c3e80a4d924b05cfba59f7deb85b98cdcaa2f7592f6e0b0a803a10a23dc2f400a53828a0cbd1a1555416982fd27bf59998465ad42770d8578ae2bfc0de6fabc754dfa69a8dfd747861338e3d821f12936a73ecb4c7b3a435086c34be6b0354f69db26c184090ee31c96c78ac682e3f9d542e572eb3e7801359889071f1080bebd162cf36a075fb52e2d56b1abf62ed4250dea898ad8667659ba21e51c754968208986dd05ae1924fc752aef93c2f19e3394a59fa6be4f90bae1a3393e5b92129118b87c4e9a85d91ad68aa2a6d8e1af13f25192c1b6c6405369e54002d0d404bd7775f3981f889ded5ac71229169d288a34d60574bbc0e02d3fd8661862b324a92f88ad74ee3c2516156f64178c9eb704def822f35296a36505c204c1d6f8d7ec1671e385106fa62cb47101d65884be5d1f8472ca073ce6b742d98e2086ff7cf38bd0abfb1982fa82d78075f94b9b7b049818b86cef0accf1e9c00277bea42e3e13febefd08b21967aafdd9b12edec9bac11fe158931850a756df515b381f7ca488ea11e7f5192aba45261374a21ef44bd42867d48b4d343404caf9d68b56609ad1a4c57bc2664fd0a02322619f61778d150cd3eed563dc441c07ae42728e62836b3657a09b43e73b9bd886bf69a39b499af07048f6d033c78319f59f49934f616c1357f4fd2d69ad91464468652fadeceeeede24b708a9088c08d1895aafd2e084736a3ead5869a873d29b74483a966e1f8c76177dbd3dcf9f8b58cf8d40c8455e456e449535f69e88f5d0a821edde88daf3e89c1363bdd9e1d9f8369e8d3fc46efb4afa130683c6d8343e65430e54062c5a9eb8c15a17d6fe8dd05ba3429f4d0634f23f0d706185bc86172397e343aff8eaab4500822008822091d37bc3e24944cf7bc35622d56d65ade66a0eafa91810f2cc057ac4f8cf82eee2355ac8ba220d807ef506fe3ef38bb581a77e89a228da404de9eb877d79465f3fecfb485f3fec48de1b78b391e2bd81355823b15b63f3ea309ebe991e187295761181f746c8a1ee843ca33bd6e1230b86e867597751837e2f348bdb237f77a12fb64746a04c06a23010f5cce108f4976f8d7a08cae887d1d783e083a00a21089a1795a1b0c8dcca3f797be8816e586f7f704d922ed3659a2a8becebad3850626bfe66cbcefc704309c97e133561a653c7500f83b1a68bdad3a537dd06ccb365c331d3062ae4f22297ba536f0fdfe5a1deeadb70683debfa614faf36c4cbab86f786a63aab2f57691b692345bfdcaaef27eb5dc48257885eb921de08e8dbf06ac3dbbda1a9acb1fbc706eba171ab66f786463f5d99d5d643a3b234567dec447dfa10d7db985356c6ca14a6655ff409831163b2f61569abeb197de287fdfc99052842902bdc50802204b1c26635342d023c337eb395644c0673a16abcbc171931b7054676055af695206bb1dde447df647d2fbe40970c44d509334dd374c46b32a66c1d8ac65d19632c1db125cde911fba2affcfbe91aab882de92b5f4cb13877048447384744a65a0682ebebcc141545105f26298aa21d614ce13c73443daa56fc6aa50aa5c8da48b78f26bc9174bdbd7d3d5ed9cf566ad7bfaa3bf912b5236a67aa8a34190cc691c516576bad5dd9d3a5b105cce9185f2e49d4e5a22f8a5aa16cb4b231cc1386d4ca86908aa295186988a2285aa1e6be56282a01fbf9ca7e3ef3c3851ca040092bd4b600e3420e5070856b0144d18b14e51aef5d1bd121d42f65dfc887b736f0e1a1beeeeb144dd4bdf4decbbdf74a39ca51144539cad34a0a5483de63a46521ed36004355eeb97aa35f2bfabb518f0fefbdef46118efe0e5957049d3dfcdeab5674685d2bfa7bf72a6dce9cabeac715d75f0b7aac286b8e754613e5e30ce96b877d1fe96b875d1f5dbe2ae9a73bd321dda156522e8c5fc883ef5e08ba40b7468fd61d76e654d31f8522026f4c928ef4c539bc4858b202ea771567e3d9aad157c5e164f4954b301fd9c0539688f59ac98d94766d5cfe5edbe5d3fbc4a00dfcc9b2ad04fe62e556026985d7dd8b2fc5d830068773c30d25252424231b56138522f9324953f4e7d2459e1df64a4ab62cc076fd730f1d7aa742edf27031be3c5c0cdbaed85e0bb76c23b2554376a3b17c6d447fda06fcabfae9abaf95941c51d841d36c9802e746172a07b1c3830f3ef82009492b21b1765a12129220820882848484a4b29a8c7de67ce69b1466ca2455483be42963320909094926d9308a6014e1eaa236c4402a8a22385d4882200982848424088e092e04e5b792cadadc4af2e5424d988b84796825d125ebba798af2df26ffde4259f3104553343d4f14e672b95ca2e812e53c5d951e456f25b97a522bc914f5dee572c138e72c49b922025fa877b9e561824548cf738d2278b8137aadc487476a4204ddc8da5b0b41d646519499264c86925e69a6c343b73193a1fbe8d0adbdfa797088b48da45f09b2361f1e826c741f1c8e80a40c49d26f04492c0ec2409566ca4cd31445534445fe81d308b9e6d53753f891241262d5203f38dcbf0701387abe77faf3589ca3287de5d8d4318fac6c5d4520403ace607db16cfc7bc97a1e1c6e11f455a5af233492be706cacafe9f9d2a9f53c3848fa62991e61a43f9bbab03823581af27d849f63c8deb0475c7b0821f809ed07879523d80e1f7695dee561de7b1c4e890f30537963658cd117096208018e33e1709e879998903f73a29e8989a2094a87d1c61e029bbb38045e7ce4514949553d496fac76a18178a1477474f867a55a7dc8d5dae44baf39cfb9b98b03b101e271263eec7aab2f1f36b4f822ad70a6290483c16030cf9ccf7c3e7c5fd57671fc8c39bb469e59e76ff487b09b67acc91b4baaa64cbdcd21e0423f6ea31f67028111cde1707cd8186b449e1d8a8b3b4d48863097902a6b32199a3226bb7879431df38b643194e5801117bf5840bc58e16c32a7c6c6bab046beb15e5c3e00317217b7960346746401f1426f32276f1ce0e22307c2c5880662a357381c1f5cd52a2a6422767d55ce595f44705eced54a553d192a1f1fbe622b3ee7e9d74a85af54ef27ebaad2c5990898731411b1f3afbe88d86faaec73b533d62bd5be54396b321486a2288a882bef693a94908a83ff4a3859644289aaaaaa88d838e45316b11e6712cd103c41b89a88a888a8aa8a0a1f88789c30032740b1526d0b3027ccc00934c834e905d5bdf6d67cb3c615254939acd000bf2a5babaf226007babd7d641f67f234a4af96675feb6279d0f334744e7f347f7a662af567da4387af90b5cf68eca10884bc3ab421befa87904ac54191b52e28c2223614fd1ddf5a9be8f736437a13e91faed610b6d6652f59cdf236c6ba73c9dcbe6dff1eccf090d18fe6f0b6521a0c74fb6a3190b5d02d86421a5f4b2d8df2eb0f5509d4f711a87edffde39e0854fa71265757db4294de8b1f59d3a14c96773fbdc3f2f6f44bfa73fcca9ace04df5fab7381a08bbe720cb1f31f4789d8d111fb0e9129625fcd331a7d15b1a3634ef535c4be47ec485fd2f331af2c22f6d59774fc9c7af665d21660791bfa83204892ac1f42aa2236b6c756f46cdde717b29515726b5d45fc10a22be8fe50696bf276fd43353db5d8df058122f6f36732e6304d1a038e5d715cefb806bc37a4771cedab52ce2ced62e47b15e6c9488755b2e225e91643e9bd012fdde34bbff7d2ef7bd2b3bc0cb8fc5e5ea468da68c211d57ae990875c2ffd1e9fde09b17499da962ebfba632f5da5499195ec8397de838f922829ba38a2b0141ddfc84ed685634fd29fe45e7d56bad3b1f56ee4c7d2ee851046d0eae03fbbbe2c495092aee2327c1046af6ff5f0127389a82f0664b718af95ebed76b91d5f7c9ec91c1301c7a13c5003643c8be7a129312f8a9beeb4cf5ffadb167f996c945edf8a7d53bb04dd1ab435f7b6b35a9b8d29912d7acac25cc5b1789b7e1e5bc26acca6eeb4dfb9049b0167d3f05ab1f4ac3dc6467d36a637f2741eae582b52e9e3e5b8bfabedf39854cd9638a57fb7068dd603b7b49eb85b7b6b5b46cd966030606b972654aac978767d6ab51042f820445b8baf51dd81ee8dabfdc563acaf18fb9d46fdc96e8deaf4d582c33d1cc7e8736c675d0f87cd604fd957742821dd69f7047297c22bd22caddd31add2e6cf0ecfd12ec6bef0dfaf187bda90779a3aace5c45a5ec65b63d606378dcd35cb3906e35515c7e0ab3d61331e7bbb561bad67b36b9b734e32bd79f315d2177327f4734ee8a273ceb9f85ccb65378be688809f921b1eb69f95524ebb9a1aa2b99a934cbbadd254ce89cd1ea22f9676216c510b8ec66997e3d15a6b1c36a3b5abb41336433ae7c48ebf6750b3b8e79e7bb45def7958b3f2d380ab4f1a0bdcdc3b14953cb5d63857c8a3bc734d9f3018b2fa45fa49fb15321232d2a08dbea68eb1e32bddf455017d72ef3de1b43b8d053d6349e7eee2c56d0dd735e817da857e11b593d65aab3d8901f51b848cc11873e65cabae2731de4ff0af07e13b848b7172e2c44948d3fc6077af6f24ef3ed61f6c2ba594b234ab134d4a6bd59cd1c92e964f7db51d1d6a3dd801a22bd5b552d58bde50ab6cc1c15c8467ee6a5574a873d25db61eacd23b8c9cbfa233dd4462a0816997999a055e914b59a918659b36bd834379a009d99d86d6a5d17a13550e85233907d91b158c52cde3afd6832db1ee30f9093af6104d65af0df70ebb61b7fb0e2f64eaca696af4ec5a6bd4a8b0e160c6bac758f22aceb1f6e0cd4734c799bc53fd38112dbd5cedf98bb36bd5d74abddeae3bf8c349818f2b1567d7da5ea52c6d53bd99978fe738949caba0d5aebf56aa1d1d5fbfde96f59d08aadfc1559b3ebeb72b246b48d676f0f8c8348c0fa4fdf36a2d92fd3894475e1aa590b22d1ad552321f4da2b6658b1b838c697a41d8b130b22dda9b2c0ae6836531dbd9ebafc982ec78e8979a55f96b6acbf25a8334fb21311fad5d120b82af0dd97e259592a69204a9add0ad01556bad6dcebafc59d3a515551c932c0ac62e96c7de8123ec4714e98be559db6458f683c1609fecf1ecc7150fb1b48b49b1218ce9c5a4d8f1d1ea8b41f683be6af68345d1643c35204929a5ec478ed6729b0402176cbba567b173d8cf153d9062f7c0895d37fbf33c43208826234ee8d27c749193ce492b9cbcac420e2925fc95bfd0592d95c066b8c3ce7d6557dbf592bedabe97c46243c6e09d639a39ddb9af87fb829ab2082f59dcb20afb72cfb1a1d5e2293b954d9037ae66236569fb4a7d492c76fc331f04e4cb4b2da56459d8bf1e1ff14ed2065478a92f06f5a69e3e36801dea8b077678a30a69054bdbf092586c9aaf0de95047f70685129558d25f292548edad01d50be3aed6b32f7c65d7821bde311897b462cbc76b837d3e5af062ad4adfac4956d14ae2f520be9e24ecf7c862534a2defa51224162231d090af950e650d90bf64677a335f1f9f7c5c59b155e7c0817cc543ef5c7d31796fd8cde8beef1e5bf3c1e16aabaf270a9bc5492554e0d06515f1d680a8f5c03dad67c73d31b0e1a5810d0f377c7bf250a36e3c70bb78a156c6d82184ce08b79f531f44b10fb09f2b7a60861ff0903e08b205b09f2b7cd0434a8b6a25cf74cc297dd9b7ab7eca4f9694bfb85356b5a53cc6cace44e247a7ac9c237cf5b3fa8251eb0e14a966697be62aaa200822d803d8cf153d64ccce15ef9ee3c557df6b94d7dbf86d5ea5b9264e5d546b542bbdd581a228a2b7d12d3d4bdbb6630f45573ea3b0b1cb19c330911a9d9e1d932f4cd3c8306b92bea05f19b76df5a4ae0906836aa9af9d9b648175b1f758f02cedf56a1affb1aedcb6657bb647f8c836b63a46e0d82aad19e17694aff562fa7a9b5d79b7e0c8b1d9b18ef8dac066f1ce059df76733eb723bd61acfaccb47fce554b0af7696b68d709bb9b7f159dae5746cf8cbe9e881077850851f00d927fbb9e207516c287c40865dddc507820a76c97eaef841bc35f0d9b51615030d99056ec9d2f03d7c74fc8ba5bd6ba313f5753b49d4638b97acb9a54f9fbf58d48c348687e227eb71f16a16b86d03b2f594bac31e0fdfd963dd4f5fecaee5802db84d1454f78784caccc7bc942fb090af9496e2aaaab61757c89d77b82fb62fd7048321ab91476af878a0a32485074cec761d74d084dd1ec4153bd8e2ba26180cf876a57e188ccb31f1341f3dbcfdb46b82c160577167a88f436102095a40c3a1d4c00a1e8481c6f3c7aa8f43693934341a6304a820904f2247122dcf92521ac08727a594524a29a58cfa790c20e57befbdf75e7cefbdf7de8b524a29a594504a29a594f24929a594523e8774524a29a5942f3713c69a4d41653c986c7b111854c8cfa53f7fc04303de1afa9a6dfae403467700731c2d3430aff12ac01d7f13fdeacd7ddc44ba87921d3dd2efa164db5bfd120675c708faeb61feec791f309827d14203a393a03e696d043dbc845eb428498300f3247224d1323bc0d240a306cf3d576c768c85c1606ae01e2b089083ec5040a0a191040ecc344dd3344d13354dd3344dd33f904263d2496800093f38010f7e74610551d0a07e79123992b840902040f1851518010662a0313d074627216130180c0683f9075268607412d35b3b65e5c7847d32610db6444dbac18811f1f6f62a8d06c4aebc7db8ba03f7385a68dcd7f65ee32f790bb81d830cdccfbf7b9f440b8dab93886ee5e7c397961131c01a04f749586badb5d6dadf7bff81141af739aeb6d6da4f67f20eedbdf7de6badb58f36c9c301f5712835a000102c0d349ee6616e074465a72a68d58343bb880e9e984e5c618311582e04f57165789812d94a40091d3ad0b19f2b70f0c4cefbb902073a2a00b6c85749c00e4dc653ff62bca83d623deccdd74a6685e8d34a281d99d6f55a762b919ba82d7c2e08d15d4692a43f4daadeacea92f55a6badfd10019187bc86483967c8a4555629a7a5b9b46293b90d8f0d8f7b777bdaa868f37ad2d94b248b77c7dc8e1d9b1d328afdd8d082191c4383f1bdf7627c30be182ec66e31468b6a25f1b3de5a2fbe93939393ca5e1020942c0894b24909250be258107dc5c8e29c9914d75a9149d171ef312976fbc17ee416352885375d7c76b2d93102c7332334e7d9b5542b11f9ac7ee5adb97cbdee45b48a33697a565a37898a9eb98a6e8f157b1ed31e22ec791da4cf5bfda479acf1237d553970204b1de33eda79fa3147dd4fd88ca93becf9e91306c3619ac422c8d3a4a394d24e34297511931263d3c449b19363196ac1c1880031881d623d905482a474ba2325c4a49b2e526fa2438f18929747790b2fcea66c959c0b1445517b8c9205c906630666a68820ce3a6caa297d8ab516a210f422cda6801e0f3128d21ccbe4d42e3d5ad54193801d18d064d0c738bbe35c28875b70341f0d086c02760f1eb7632ba0c48d0b6c467478db94f713c32a361c41614c38f767eac8b1161c2d36d8844ed4646bcd873b83563419c12a5a6badbde98bb55766736b21228f2dba9387bf26f7cc999dc3bf77bfa61eae6e5c70d625350bdc38087b3c47e5d2a34909ab6887d1bdc1a6cc45c3cbc17d71afac075f1b0d724c384ab3200dd6a015638c23ddb8e0a08b589a8758156b3eb2435169d9e5ccf1a1c9b0d087782ab2aa1c94d1af79f9cb5e628c71548f7f59cd85399db3f8f571ce9cabaa52cd02370c8231c658be05d9928917455571815e046b7ac8caad04bfd254e8b2617ddd33f15a901d1b17d88c0ac5d3a706658d0b97e6420b21259b51e82fa82673ad145248d1a9d59c4d0a1b6765a7a8187e0d790bc14f476bba62ddc1af6f315a70c478d6b1cfb163c3734a5b491c999610426831c3f8e921de36ebb629a6ded4cfcb181fe35914f12c6e1be53ca5218fba136f4366c83117295e7e362632676350244443d87a768c3b622bc66a65a4972ffe2c1767967c6541545e938117c24f081d840fc26b5e77e2b1958f7b12eb4efcc4f336626dadb511cf472af6d41b7a7b090f21bcbcb572e00004fd85f538937a17d6e3743c1c021f628835b4faaa1014638c31d288040f1cc96e1817b9e9dc2664081d88f0504d4604e39c73ce39e79c0b71cd3df7dc8b21f25841041d4486b09048e4369aa5bdc839b769cdb9fc13e74264e336ce422ee6759b288a44acfb900635194c8ff738a6f3d0848fb16b8dde498c288aa24863039a12633f0e45537203079f59984a2aa9a492a5b51c9452f946df2e97f5d2ab34395188460d7a24cf9e759597519df274cad359e59cf2866da7fa0b9fce5bca208d29a59432a919bde82af5067afd94f252bed13b267493d467c597d64ae90ba594ca8bad15ba356cbd1024a5945242f8d392ceaafe2ccda19579b430a7d6fc64d9ab384671f3d14a66130caad71d7908dfe09a6033a624fd7128313b147849cb4b5fa56ba25a40d867fd3cb32cab3456c6cacee446f3d135716fdf7cd8bf5bc33a1d1246292d69bd0dd1295f0b6e130ad87cc2f160b136685547efbd17df4b2ffd84f78c7eb6cb13b8b1df7a35fdd65a6badb516c1065f83afc1d7a6141d093a0827149149d2e17c225e389fd81182208869e79873cc39e61c738e39c79c6357771ac4601458648c31c61893e06d3c703567ce55f53bce6e53477d1d846f8fdd3b9fb8f3092641f9c6c3dd550f8a4bd957e3d178341e9a8bc6da35b6b95cec9070e190d88868f7215cd6b8c33653b08e524eda5a9b928e0c467cb065f8f4787ab81adf8d2e342fa594524a29a43bf075361b9fd67ceca6ab889b96947038292917c60dff92120e278525561d198c3bf76d3c7c38d6b468415252389c9292bfaa328ffd1e2184556cc91277fdc512e70361153bdecf997355fdb7474a4c89913911a66b50a926c3be98d98cfae7f8514d869d10c28e7c1a84f36f9240c9e65fd31d76f887bdf7debbe13512285e88cd807eb130c6589ceff88ee2c8604c078f1dacb1ea88457cf39055232a32c6586433186b2ccec8d8646133c647cbae16f2eff5f90ead472342ab027a43e2d55369c54785cbce5a9120f61b1d760385642d74d4a195a4c3cb9974619031c618638c3128b95f1b375058271264a363e30267ddb4d9016fcfd236b41b1e3b3e449833e7aafa4b52645c2012cee45d5afa66252b9205122e2585c32929f9ab2a67b6d9416996b61dd49b7bfaf8a81009b2d121adbd461e7f6d786c9a232a3243e66c727eb3e36c44531d26a594253f7b5962b4a46475ce39ab373b248c3e59b75ab43524f6fb15b71009d28810b74062072c2288c47e2f6ec1e6a59472b363a5511145b2909708174402e551e14cdeb5a822888f942870b028a9625fafc77e32070ee4954645a302ba10936c8b2dcda04285434949e1704a4a7ef7a8707abee3ab2f912c76b49b1d2241a894526e763018f185b01abe30f29e9287e4fae73aa834abfe09a9208d6bc1c1a4e8dce9dc64536cdcc81cd157db8c0978dd1309a99c4a0b0e169f0b1c93e2a9c03d1db23d1d3b9ea56977d9cd1e4a9d66021209a9d814d0a26288179f6a3885c930299e0ef95a4746e419132e6c0f5b837ea1617b3132d90b179a83a591cbd91453b329aa73ceb129a4a89bf60a2347445f7387e82b431acd615394945c241c5d2cef416c0a1910427db1bcf71e156c0a46c5100ee54ad8f0ebd6afac6dd0acc7f59c967edc7bfa711763e23dc97a5c46059b824d81c548db64c1ec2b01aafc8cc760bc0959ee8fcdc0e181f1fe8ec37ebfdaa69c85025b7ba178feb23ca28b852ec820841042811b752e6014e337c783feb23c76bc8b287596b633c626c614fe41c0fd84a0852e1f317ad25775511e193fa234465f34d5994b06535d44c368d1f465d2273524044108eb130623d6960fb9d94a529a0ca99ee3507ca8a8beaa063992be28a594626b4b76c55ab11863b794520a4f9816586881851658d8c8daf6d78a75c57834af0dfb0e96f64568a1c58ab5e9af16b6461e1b6ab930912148af8e6414cc3db3aa9b734a684ee7e284626b9074403873c5b6895b6bc1d11e617b645aec0bcee7d88d3d4ab8165df02221d6f398549a1b643fa7a6870cfbd19988afc707fbd3f18f9e1782cda8ba033d4a794877d8744250c79eb1180d4623e4b3d2588f73191b92fa903953c239e7524a7674cec9471da295c29c73ce092184eb214414b1e5d22e5b8041b019d6ea1b0f4a286b406b454c08f6f8604108f1219a0cf827a17b0f5f88a763201d5b3190a88e16dbd9f2400d64cb01be46106ef1b47059b4206c3f0d83d043b41278d1c740c3d3d7e3435ef1ede1b8a721485f9c16ffb6d31d88deea2684106cc6a33bf6f48c6a21180cd689510bc160a8b4272b449fcdae8d79f80a2d895d05cc98a145afe22cd54c741482c18012c2e7ac0a9a0f1d3798155d134c321d55b826628cb001714d5016a37199a47c7db826d8a18746c458b399c46bacaa99c467d82b2b5f634dcf5ca8338a1323458756d2242bbb2668cb6dd5033822c394520a5d46b642b1da48b7203af67369c52763902643aab8776be043169641a87cf12d089494c669e311cec4c57ba3eea88f8034d552b3fd768abbbada353f0860db49f3929533d6b32b041c4ae62e67493f4fccfa796246b3f380667864705b342de283ba05692551fe6a41e02fd9824c52fb110e25332532ec2a13615ee133d6f39840cad291a49fc704ebc3f66c93ba63b57cd3b107a9238301e908a9858043799ee789c2f344e1791ef634878d6069d8f2d19a56a20073b105893c6e9b967082a345f7de1af70f8e9131ce5674316e325e8cf85e1786dc8170c6d873ba19e7e5a15646d9b4d86632da687b4acbd25986fad40707902522204b30c6d8c49f40b0be31adf82c01c409bd049b0cc8648c2d719790d82d2a9f1dcac489168464785b382d5a166c47cd80b412c880c44778a13f57fef47bf3ef2f93cef70fd2d78a95f44f3e9a7f1bd21d0afdd123b8e1d51d7c08929dc980445be7e12b5ca1f67c8c314e80da9d78faea6c965ef545ed782df9fc80cba5cb64d96612e5ebb317ebcec48441871ead8bda7643d0dcf135f2f3a66d258cb5b3035902c81240965882735b4642764e504ae9bd114f9c134d06cba756668c9d3827d88915f5614840aeea2be31ab996e82c7ce2b9c0f1682a607b72229ef8535fce0905b0fa36e79c1b3d33733ec4821a8c9da7d6753fbfb14442a8beb20e1b5e74b372e0c0253dd94607fe69ac599e1d7587bef6d0e076f364b2fd7ccf6f3e43ae6acfca7a9a4926d9ce461b7488e983bd29d1aa9649767c55e5ac8402e82b4ac54d6bce997355b5b9dbd4272c8a0b149e27b216b7650d7916c58cf48d69451f3f823419f351c5b95b033fb230842cc8b41ec84395f6712893743129d88f1a103e2ae09e5d5aecd18274b6188debdc5f5f802177e6e176a76287ece78a20a860d766739371a799a51c94872cfa6c834e6b5aec2a803e3a1c832c258d7ccbdd470bbad63c49c90dec2e424b4bf683c18087134232c85555fd55538a5c55f92f4f28aaf9448cd2ab9cab5c5dc555f792d5c1555549d5a52b495596aa1b27ea124d32579f1ef52e6fba8f5e0c799a0c86ba4c5148d657052afd342ff3308f7a97373de93ed231c6c860401fd0071c13d1a96062d2ab939dec64273be569724c4439a2f2af8b0e18197b76289ab3a9d2643091c4281cb14b39724db8265c134f6af207b6d64ed05a8ca79d62ecf72b468d3e59576ec1f1709e1e519ea2e951c639eb2bc6b6f9e1931329638bc4e2115a706c74c88d0b2468312d3844826c7460f2e51e7339e662aed272267afe453dba179198e8517425912ca20c864539badfecd8ecd8ecd8ec683da412248557472d38ee2f9773ee2f3c3a39f68d978bbcd6e574cc0b853250dafb9b7fb1aebc5b70449728bacf17e972c997e76b8493f2f12f18e91775a3e685541a195dae7921af7a1a7dc5d8f7534663f649ca913e6133a01306e3da0ae92b9ecef9326c32d1038bcd4ab09f2b80d003e87aacc693fd344dd334d94f9d1cdb426661ac555c94d3cc2f567beba6099ff9b84e599dfb678a5ea7c99e9a4e594c5da545f77ec25c97639bd11ceb48fd627938c32272618c66699b62f99215614bd90f06c3da1eeaa6fa829f9b76a9d98fa9c91e77ceb90b39f9724e3827e86dc161cf713c2ece37ff5e7cf12fe931a8278389a87c2a83a1f415634397c7267b6f84efc9c9c989bcc239e72628a185b779b363b3637ace50863294a1cd64756abed63e67e853fe045d667f7f49b7d045e2f4cb5a0d45f632d55f52b6d7eacd8ecd8ecd8ecd8e9689c91090bcce25ff9e6333eb82f05eccbc480e1c880c06740883811806621888612086b98ac340c756a7622c06031d638e217cef3157699833e8416111e6193f826e65e66114bbb089492cb3885996b9ea9f465df4c45cd691be38186aeed58a0c0644d9e9105a6b4d6af7ecc70fa905078b82f9906c442f4597ec542bbd48ab9f0e857ad5178b88149dbe9efa45dfbb899efd603fd80ff6436af10cb0b5d63827272d381eced323e23089d5fc5e959865d9c9c949d339b3e801179d73ce59f9502925a5929e4379484872d80ce974cc7925b489b1dd4624e45265268c464a8d7c06237f99e474f9262965e89e9c5439d3a8102ebcf7b228ee998fcbb68835c676efbdf7be77edbbb3fe41d1e373c41a3ecee45688eaf8a3cab7c927b887bad347e543e97b2d389e674a4129a58e4e289ee7a18726fbff6bfd9f688d32d2bd6032bef47dca44174cbeb4a272959172fea45fc344652a91911201040000243668cbd2afe1858b918d4848a5c960a8cb24e5085fab211a6395dfae14ae8bce39e9de9a6c8f31e6d8068a0efcb3d9d39a48908d0ef72494325eda4711f9daf4c5e875f5deb7d786df6ef41ce176ada690aeb2dca2b6d6ece4d0161c94439f1e10a52da6504a29a5a716b255b3b4194f4eaa1c73501f92e7714f8e51c6bf28677ca27c424a8919425fd1a13f525ff7b447f3c190a0b2be4ab0be38565f291a2ba18f2d8fd1178b028c02c4624bea972a6cb8fef3a09000e09a3f0f4a001ef2e741114089c96bb06e623d8f4989f53c2602b09ec7240016002c12cb06eb792243426b4b5f4f8f2d7f7da5bcd01787b3a588fe969a8ca42e52923247158a5bd65921c8c51823b35a0d22a3d8f26d78d8ac84a8442dda67357468040000009315000030100a858342f1784498e4dadd01140012829a4a6e549608d4280a620a21638c010246440000000064340100212dd91f2871efb40445a5a50e7d40ab9f9c3a9621a5dc0d18b915a418a7e35b30409a17004c36b880ff37a612f9fe95aa9704470a5afcad7dadb9ce4dd7f3bf93a43027689bdaf0f94de5947e8d2f1e4c60753e77c507cdd9cba66ea0f7829a1fe588123ea597b40d352b01a01378725f0998321c34f41b384743675e39f4b62d7715e0fbe6e1826275ee4d913de234690522234e712b86b570f85c9838ae4dad6ed9e22ca49538ed87954428ea875a2b7947fd01e5cf12f7b81b847ddaa8698ae29490faae8cab026ac75bc48ad34132b8c1cabb562f71ea4d896f2a8862f28d6b4a03b3162c88c65e9e89a9bfffd2bd5d7d8290198cc7ce6d64798e6cdca03aba779365897245d8578b8ab4e717e96e7c324d4c163865a069e4afa8e357c1af98eed5a4590cdbc41bd24514d203ea2cc0fbaa0d41179fe02174e929d75b2f22e03962d0e994b7b7904136a01574530a191cc00fe611638d0bd9a96fe1d3ec2f929b36135cbc61a3bcc77c3ed0f96cc5f9c380c77c8edfd6ab81c037cf37272ec68d90d2974c7711ec5ad3c5e8c9f0a98b2a496484008c967b9df52eb08555fc62503fc4282ed66544f03a02a2d90f5b064335d2386e026820e8340925e5ebe4d48e0a34f936ea09bc7579ae61635ae3d2274ac8274a61a62814747cccf8e094fb0d90e06c821820b0a7acc4c811a603339ae5a52b420a2e1fb917822a94919c0cbc13041c5e60db6fb24fb4ab5a339ec84ae8eb8c913595eb8b3cd1d4e206fe2e0d04f93ff0b95308bcd9137cf29ad736e8d1db7390b323d0d2134d08d181f1a2ed7023c74c71f07ebf9cac6a07fdcb88430dd1775698ec940a6c75671a7819c85d6d47417bb5fe7421d737da1dab09d8d809015849ea87273fa475e599f38b01be9323b1b888b836df938c9eeab0a0cface8b3ae546951f4fbadbd3ab6c53feac931d3b70c6f495a4f1248a6e881e2b399c5bfd83e7d3a3d73d1ff95b0735c61d1bc4f8cd675cb2e929ddcb711127f9d50587635fae128e83a9ab780c4d566b3562dd461229d4e3aabeb54544ee0189472f02c985d59a7768801c7ea3ac99b8e9b71075c3e19b9916f15f6060897b94214cde3c7a154548a708a7a12e9f3c157a127b0e6c9585459ae3c842141400e33cb455c3c9a90501afbe1a614e554573534a85f5eb4831c10bfce6a63e89cd3e9d604cf3419fb0c4fc704b481237ad24af5529fccb369143f8a7f2e5853bac1f26fe71e686888e2d4e8222eedd0b93c17d4bc77782e709fced9d6aa0a4ff7ee7e5230ed6e6a4b95d838ccc354d62ad334289522c6d776931d3954604f9d429597a2d13d75bd8abfbba39f5774525bf9d606bf3119b416a8662055917bb20b4032895f0685c1e7c09455871d852b9a7b79504f0864024d5beb3608eeb90b7b27c28b5307e9974c7e04430b837bdb56b8c915d87b255d0cebcde5b8560f1e7af8f722099f337b27b74160beafa7f32bb352c2063bd19bb9698b779439c4dea63510f8986528bf14f12665b6d63d88832600b117615fd132a806e59ea6bf55b40537478871b7f82cc62c84bb25601a901adf6e537b3efa2a1ae96c74036a915600d6ba8d4267ec0c00d17db048b01c913d0b557d37ff8685489924c059d05f7e3345814ca8f447e1d1f60028d653dd6e45f3a70c200b68bc06776e9641ce3ef7a0a158d90b167784427313781e956bd5c35feee82222c3ef49dc566b34853f16912aef380ae406fcf4d1cc41e412934c3dd426ac958ccff108c7b3d328301fc1118d1975bce4aa7f710a6e013cb32e02e2061d9b656d992408c1f93760f33149c89c60743e641300344e7eceb86a8b7c4c061a7192c7efcec644c0cea969209492db322d8c27f4d5da07cb95f72b0e5701719fc7039ca462c19d8bd5f41cd3294ba70abef429e992b0cb8558ac92ec7203adb7dd9eb11d54c0637c6bfddeec9430a4406847f63b8e4fcdd2e49ee9a89624c7b822a5d6890da80fb3635262738b83a7be594a70dd484ac12a809960bcb130f354145c62db955f57111c8d2082794db9e8e87df3165b89e77b0b9258ca30f7eb6f6ace7140b554dabcd8edca58bb47e49b361f222df628cdb25f9a0b35ad103a6d4ca384805a7c1f726155b9a5950eabb451c4cedf512b7db6c8f7a3865446170eee47571f45b4efeadda281774bc54da540734cb24c46f7efcce25926c36b76b35ad214a8dac3d9a1437453aac881580711ba0f70ae93681cd83c03254725c26061d30dff3783657f4e7abe4275c8f97335fe615ff236d0ea892430ec22646af2925e3962a55116fa922ead79e6576a77e14dc86c7fcf7af8d4aebb8eebd68121e095a1b3eb40ba96ffe02829238458111bd593f13a5eda5b8e80d1ddc1ce6ab654bb64db26a23c4049059b3142a066d15ffaf1ec96fb2674e42fdad3a7debf820100a18627138dd52328dc85e16773e32cdfaf306194e55c8f6b4008a70e3d94365fe4fd10d83be46c6b12843424d50b9c0e6eff241f1f32b8429d70b30a6361df388f88177e4fe4b97217f3fde1fb8a4209b59ddd8878242ac63dd2c99a6cad44ff2fdcbf4b7dda6e272e409e79678eb99d317023a487eda09b43f358d871b1da41448380e76a88e07fae1069c6de95e70e000336880df405399f1a7c10beb75e7103901772d4052c551a7ee9326fb541fe2206e9605546671e627727add9c7dd7cfd87ed7d00a1afc8397afa9aaa2594fc787ad3a503e48d44d0074a0b277068184645aa544a105be7866c9a23f73b33f949704b106b2266012252bc197fba74c61bdab140203d5c85207d3355e71971f941453aff6cbfc4ce1c0b288277709eac6b7a6e7c605c3445d7ec0ae7673cd1a5989848a023191bf4d5686754a1c1d7b2ec68d08cc97516d13aa00db84845bfc48e8b05e2da1bae4a0c8b12f69347c64b21477782b3a03191a5563647509612998dea7f8bf98c0904c464111a0a6258c93a5f2ad53128023136fff830695bbaeb6454047687fe834c68ce43a7e14ae93443b3082f9888c182797439079c468fab565da236d4589e18531798a778f031734a208a19271d4dd3599c951fb7e8ea88cea31c8713d861d4f69ab69a28683ff057b9297cb9fbe0225dfa59184960c7366bd74ba1eef1ca027c9f63087588fe308598d203f80178fbf2415720368299ffed94899871b4618eb2c181cb69bd8e0ac5657a7bb8251d1db420b1d28cbe08c7ba1c9fdfed9f856e737e5d038433154ba94a2fd91df0d36f74be6bfa9df8ed1d1c1060172f7712218cb1616e27e678e7dcd92b55717e670457c1b9c143b866885fe1091051e3bd6c391360b655111023f8ad154a395ee4de906a7a88a7623730b475ba8bf9a6701aa56738a24e625c16c5355e98ee076c100e1555444586d62318659367e4200eb8684bb064510161206b4a300ea53709390c4a90b2c9318f72b87257a8c7254474de6033f646ecb3367441929ced57bf4789cd5053db486388d2d0c4e9fdcd3eca2941c4369d4a5012e706940b3db66fd6d51b88fe671d03063e14d895aa7959731cdd9ceddafbb58a3dd32d16e1f9026aa02dd23b53fc7d2eaf57bb4a7817d203c66a69473a2a69fd27062eae68ac4040920756fe68baae622eb038cdbe93ca5c48346fac22c43980088103f9ef45a976520e2a8e4c66ddbf3dd08e172c016ac5650d5db05ca7554a6b5ce92287a0d702b971b9bc6bcdbbd679d70764233161a7d026afed9ad4b79992f550be69b0d8e2e46f7333f3dd28924f373f81e8420de1ac27e330e62000300a55c6b7b5c8bbec682bbcdcb8dc12a0c8e7b9431f6908c64b6b2377bd311ae274ce54ee22f596c33c34f3c49db8bec07fdb090993d8338496fbef7f843c0538c5ed724ff6c4b227e83a7e5611121d80380a4bbbb469f654149d384d5174a9b53940bb1a2572737f6b87f900d33c70f149790a0846633d4d9a704cd0c497b26ca935394a79ef87f46ebba7c7e208432e82449bd60f1e4589a814353ed046571ba774fd041369ad0eef92c138a02d36c56de78d8d923e524c0133e9ffed9757f4a25606cd51ce54e67c0b0b41a26612898e38af9c9c517fa4bcc5a2eb4c892b623ab3a05b6dbdd4aca5f40a89ea408f8ceb45322bd1b126255b68fc8c9a3f5252f239f205730839a3e904f1fc92e21a3f82d21cc852e307a3f66b169bad118a59d752b85ffc85b7cb3d4973d9da1c84cb31771409bba0e42c7710a9849ab010ad37e23bce2c9726d02547e1f69b205f729e0886494142159f8148e3dcc2a3f4fb082a6d04172f77a7cb9ac9653a08acae43acbef92d0cff5b1e10d38c54bb589b7ba37529515a7c2aeb32507e19fd3ac67fdf9eaff0420d83d51bae150bee0eeac2bdfe06bd586060212cc3bf6b23e505f399380fc2f1181e9783f2f6ec1c030dbfdb6a878752b9b68816e99c88119f1aaa3b36fbb4092facaf9c7d5985a64700b00c9d067fc937d967465e3b5c47430dfd1cfca8bc3e12be1330c8974902f64b498bcdc688f2622a3e36f6cbe291cec393b0afa4802b8af920e491010e9a0126fc42b45e199be63c70b627dcc60c3dfa9f3c144a9748fc4630121aaa0bce818a178f2f6d8d2538681aaf382f8b9bb31589f25a88b5f4c54d3938f57bfe196ea99a631613abdb784aa24d1a996603f148078c4c2a62f12aca529518687a074a342a20161e86e89fcf2a84a3c4df302d6e492453016c77c765828dac36a7a26df925c1f48770a7249bb1568b3da1d8c40a2603f253f2f17b8f7aab515cd9b58448bed3dcf6d670b28e5a2eaee2688fe840ae59e6a4a96d61402dc63825c63741242d870654c45d450dfe1887751713602f002f9761116582248c9fff057e3f6cad673d92ad038090593a00657b344565acdbe7491d4eb9400090bbe42da30936209c3035339987e3e5163cd7c748886f6a1a27af116a6bd58b55154200a296ea7b670b25edf3cb7de32580eb9db5511b1ffaf72b2478e3f58f4dd1985a6b63d2fc4bd22cd24e886ff6846bc724803cd876f3767bab01f155e7d85e3d78e77de3c6a4aa5f85eea76824b6aba27276c38e73633df2280cc497087a85a686ca8bc29b7e8d8bfb4bd6f0af20790f21df5746e2fa6a1fc5001be6e1e8cdbf2f7c6800512c3772ebf025be77ab8bbb13569eda77f17e0fdc11d81dedc03a819ace8aad3bfbd03f2dbf5b0dc0824e9f8b752e7a7acf48a14b5a25abb94fcc783da012e76c19711dcfbd106f0ead63aed77e6a1ff0813314cca4c616f4fa7e0fd2e0b98cf6ff1ee8861223bc4f739d0366198887f4c600b099f4ade1fd9cfad18ce23d921ea8077459f70211b314cca3782698ac6bbb8fd658a4fcc6653359a86e16381d1336f1bd95bffa65499073de28671588bef6ee4caa62f58c25cae42ee25d89559edbbe18538646cd17f5adc201d8a2390b942ca53ca75c533b52205457208d1de14ca81fc4f8cfcd618c110c07749c42412d3ab96c742d04958f7b0abad2d622c92d8dd3a1953d78c1d2216aea0adde641cadc097399c771e6be7c904d1c0723b2724522c2e1f2919d4c1620573a397d8e128d2bfcec57e7dbc4ae3d0e1c782b7fafae0f2ac123dfeb8b75fd086535dd1ede96ecdae77462e89f81cb631a3a2f758fed4ccab444813f65c7e93071d7739ae615668683e5435ae258db20a6923007b4465875ba6766e0f8d918dcb55d8024160c529bfe63b854e0b6e05069346877240cc070a0fb4ad47168138628b7dd5dc480b460e460220f94db4c29ef649675929a5a118dd85a40b9c08936ad8674f111e1ac86a9dcfa5d2c89e340baea475c22a3468b3223f527bfaea7a5066b656b4a7a00e3b0900708516bd0f0737ed857e9901e57c66eb04e1e92e2da5127a8183ce33335547e70a4945e97549ccdf25f09afa3f9a7ff6e61d7e0d7bbc13837d58a1d8fd17e728062fb843963df924c2656338923997c1dc1c97664a2d5a1efd19426b27e8ad0dfbdc49cb91a2d653e6dcc57ffb856ff66ef4349da5530fc3ad8701e4fd45f6829fdb29ad2ee7fab72cb877afe72f519f8fdff0c3e4819e5fdc0c69b4ef55ffb3ec643c891cd052bd1e7227d6b5d8afba31598e1e1002b7f46fa1e6090190b67238556c6a00365690c836b52c4225f4692c2afc1ef9a9a79a5729df72adb9a8d8d360a1173a238f0b81f0cee0bed55bde85acec05b42740c687dcf3a509f4c78be689aaa6118a7f5f83d5ccd81237e3f7fa64cc4ed9110e6be5db605f10916cdd36921d1e8581ea16fafa33e376dcfbe0f0b2dfe36dda2fd92338367f18fe27b5ca708752897a1fb0d3d65e6fb8b0e7472639b704815430767aae22bee4e065365801b3ef3c6bf5b46e0538abd0f4da1c30e4208b9a940ebbe4352a2065084b4d517aa329674aca15749bf4be902369db5571c1b27576c129ced957ac235257d129bde471f98ccaa6f85e41944d9be3e710ba573d776e79c10a87f14eceb619ebce7ecd8a04099a742e1f79f7b9abc1922a83e67b75c5e6cb4c49ace1133e7dfdf35f6f40d6212aa35687d0dcae93f67f85a30fbc650090bb6c700707c7c61db6d2b5df67776e2c863bcb1b20b1c2055bf1af0713503109f1d90986fc6d218d11a14abca1267bc3abd68afb831accb63fc456a9c3339567bcc8903f9fcab8063fc700c890df20f16938f666e330ba33c47cc3490dbf611f2911b3f53e92df41f6cf85bf09e8bf9efc3c496410c7118d910f44bb6e485473701247cae0c77e18c11872e0dc2f014cdf08892288ac7f866c82ee37e045e1b6953ebab4bd6af615325050c5d85aaaf131257385141f609bf8a1704f5027319ebe044bbd1288043dc381fe876d09fa36f42f8e6d9e30e11e0e5512f725f936bd99a2846c935843040207299c80c94d1994b662d329de56033e6886e5bebccad7c23d862082b91a9e98213911d0b4c29aed3c959564af0f8e8f45cc613cd01451729a3979d067a2c5476ca22d9bb81c41a3a00ca2b97016ed0f052f28ff085a62c42e3ca325a8648d603374c6f826a9e9da88794780d43ab1ca467e74883e099d27d752a38550be2c002809dd38c6c0c71e063024d7ae904e13683577ff082e356a61f6fa4eb97e86442fe03c0192feb63300b79c08132145e85e165f60b9a4e288c7868ff64d0befd82ac7cc4e10444d279bd508ea38fcd19ec7708bfe5c6620cf2f39d430c21b31d5de761f3165fe74173231d282bde9f06f7375510cf682e4435258580a9c7c0010f46efea29e09a4a7e9a0570e3424bd4fccb738032cbc3c6e6e2afd92e6346c472636c9c81a162a68a4b896f1858759eefa9194ef62d9f697de5c39cd028b800a8cbc0200fb3c693f7dde5d4e8c56c08661f62f8e0cc6be6304a670a7a79c34435bc70d14268d0bb9659782853d7e67a911661aec89bfdc67d9fecbac7c1b56d2ddbd5f54025785cbb7eb1b47fd9d5a089b73ab4a97d57e4b9283062cd6e8c1c8c70266bdacf665db6fda79645fea384fe74e754548b12bbaef3f6614d9dcf9849c15a0dc3408420c502934378221a8a8ca196def96b211710519636c5ae3cdd4afefefbdca456afd590fdc0af6cd7fe1774e0c97ba72556b7fe103b8065187becd103cb862f9db90d969b09836d61b9e67fc70fcb1b333872987b919b83caa1543a6063f93921356f8e65eb63d91d0d6360b27d4196756d6f1125d348fd4a0473b6047318b74ee1959e1d7dd24b5ed52898491edaa99ca369a53e4529984d48301383b95a69307bee60869a9a132cb558207b1f9f6818aec4a7634a42da8133b00d5a929c4d544ac81ba05992adea2e42443e05f691dbd5c1e9eb81af1734e8d3566650e96767d190e162266c50600aee7899ec4fe0139d69624e39fe0e3ecca637a597211fae73d806dd79928df4ac53448c59ee2d6a425dccee9cba7b413316deb5c74f84d9860aacb05803ec58b98fc64fb3aad7ab10fd27733032892452a97766674b234862e6e16425048df6832918928335bdefc783a55f1a2f82426ad39b44f77be47d68a0c9ca0a5e4adc589b686cd31ca8c56425b6d18fb3158b280da591766cd56b32978d8366cd0532b1cf70e84c5e5c5fce0b417af7e1a6e3a8ff7106c237755499923806c56a53586835ded30df2773fd682bac9d21416c5fd0898df5b2cbde8042bdc4d0f1b047a5a7c0c2f75099003d7146ef37ba9501acece34c376541f31cf15eac8bcc0c02bac434be1f1f436198c453d3171e569a2d6fe04fc95f377c81700e321c3df76cbf5aa7da108cb97a37238d844ffa635ab865a05fe94cedbd59546bc6bb816dc3ca1609059cce555739090b19532c118d34f9477df572d02dda2c405832cc710c823cf7eb33cd90cb226fb19fcef0b0d8a320c2f48b06e473e40b6c49665de8f7480d9f51e2c796fad1c1191227f7c672e92bc7743b80ac71d4fb91e029d71bf50e931402feda29868a072c3049ef6ce5e154ba312059389c5e578d0ce0e53fa47ca1f71b93ea10798e99eb423be3e92ada7af73cc72ab506cfa6d949d999b30ace5ee9d38f2ac0e7c8b0500132cb99d3d6d5ccc556e5328d8b815e4b25a37d95e5876c0e9fb422a8552afbc501ea3eea2286049d6ee49ef1b4e4a1e7a3b4fecda930bb5091aa546228160c3d045b3a9bc6b6c800ae5b197249c9429428ae56708349422301825bb3140fd3426603346bd3d25ad423ed083a04d201697d581f6184787f03b1d138c66e6b688d3069f7a6395ca060c9aef326b6a2669f5261e33893f29c355fefe110a3888fe3b2ed16e2a0017b7454e106edc624d2d9c6e4b6f596485e36c35aa77c084c523ba1fcd25d6f870f8c6a093f6ba476fcedb0d30ee797f326f8d43f80e08d973f11357ebcda3a6799b742f3405673f1fd752706f6d180b66a2ea9c6304381fad4cba629bff9d982dc609850a190f51253dc0b19812d051191e20a9ec76220e76c90b4c4ffcf17036e0895aea405c9860d03778e2f9cbf7c68a53360477103728d0536944566e7ad65ba2e86f3cdec444da6c89395af660ba6018a553da67afd294f21ad851624521cc6476ad346b709e76acd78abd95c4ad14a59e793e29e73c860fa8bad28663c6be6ca03e713aaf9410580b811a5abaef2723759bc2c0804b4b9109be703af3333006af20c16f3ca88727bcce1ae0f206912b896e70e0f3f95a9cd2562e2923e09dc6a322ea2c4e0e9fe01edd26c07483e8c67e2dbdbadefeef089b9ae1a69b370a703002508025817fe30e0ef332820436335e4ed0853f2472b9d9cb025cb135a03c7ea384da3c34611e396411909e9d50fe0959ffd03ef1d5fca6173c1fc2159ac1a97c80b1f1cb36cf53f297e8b9afc505531be4e2088f8f9c8385b78d850d492c642eebf309f53b9030e5bbe710b95ba483ed81ad543e57cb5ac92863e56f895bd6623373de04a3b97ac046d444dc8bda03791b4f4be707d9f8142a49d1f9e28f08710ed6881ac86d2d22becec5f6e5e82ff49b4ca0603d699d62e85d690dddfa286dc7626cf57694e5595d83724935789877dcce0cf2b4445601c941970c2212377a66c62f4df1525763c30aca30b266cb160e3b6a117f10ae52598e7b697b1f843508534fa775e8c940963de5cf35292dd9c131e08d3dc560dd058cca898db6807266b76f6651a86d0f075547b4c2937871602691bcefcb8313e47b2471c92d43c6715d525d8c5bb16cfdfa4cbd0be58fa5dbd7c7c0ae557e093313a04ec706f685107ed044fd44a9fed4a76078f2e0316ae020af8cdfc76f31406e1ec5029ba4b4070a34056757aa14a32501e80b8b9e067f0981ecd1ffbd07cf0119d17f85604f512a06c702f9842783990fde268636f7dcbcf4fd592f9b8375e89fb5c13cbc6cc808c0ed738b46d78c40b8355111bf6c260c511a590a5959aac9e9c1a91211653f8fa0959023c80cbd670faae3c668074ae3c1d0420de651476a11c8dfc9e34b258c87ffdf8805bd9b3b1a31427a82e3375314565d02a6330625cd98fad738d5b98cf158695b2a950a65ff7214e331cee8a6aac692d23cb1c791474ae5944e0391dbaf02ca6a37257d98804b4cb7d624cd3565e1e01d05ee2c6d2ded6dbfc42de3c13d8b8dc99864aa9da03105119b208cdf676b6f2d3a50dc3a4b1e43669571495960fe1352eced63ea66ef32c63176787b0d457c1737f7828a78b46cf8f9a3823a37f7786be1d59c8786edbd18ff28c9d454e1a8607b6cb1f0fa3680b5b9971e6c85753f9bbc81c53f013c4c2cc2835a195e2e40173b46c4581c66748480a340451dd70226472349d5c10946d58213c94b4d6617f66ca2497fb29cc1b4564458796aeee4694570f433d7f8d1aa52158937516e0ef9fb02d4a8238a81dbb513d9249cd00681908c656275070cec5c1b0a42c67feb05d9bfb364c643cdf96709567a3bd2bcacbb2b8300b2a45d9403c120e8724d24e4a786390fb8277de4ec58af842050ed85d07308dee097320ea1f009c0a659464333bdc1d270772dd57d821838acf23b869d5458f3a016eb28c4e98b0bbad7dfcff576ff5051f84f6f01c6aceb03e90b4ff4439d699fad4a15bed7551e5174f1c04cf9f49e4f2af4f3a7d79c99da122953812e9000992a99e867c176818f7b8ea4fe23bbbdacac3060b4cc41173adaaa29fec1d3c970c40270f0d5229c09dcd0a09d63f7128798baaa38145347e252e9cbc784ae9130e80d25ad7dab38529bc96bbe200f5123d5993d28e33543bb4562ab37718a1f28b512af02a1b5dafb3ecd1405323f9adce14cfef055421a27e1ad4a23b779184bd6193fbe2c4da78434e30c0fecaedf65e76648fb10cfb02b4d3f1fe69f02c082113bce24a6ae996289903cc20eac2aae2f506888d11950abb77ebde98d50bf349c183f93a6e9137fb5cf99964e1dec6c2a92b7f4695aeb92767b6510bd7612950653136805d9719db6a9248afb79830f83c0d9f5d4bc9c12a669fac18df63d801bf42d36c8875a839592d3b4635285673004bee48d0c8356a02f3c276c9e3e3b0b69b8b4734db490cf5057b43e593f96bbc563df5c5426267836533b13a0c7f73caddc6f051b1efac8f1bede171a53399599d0322d741590279ac4bd15d2ce7ca8ea65cfa4f0494c3b80ec82666fe6e799e369011799647e7e9efc52b3e4a9aed04e0e40d2d6037a18af319ebfd322264b6a9b88d81dc72bfd69cdfc66cb4edb12599cf48b78bc9e9ef8ce1e0ff44836833ff093c6af3f1981442e1a7a6fa62b43d25588ede8aa1a0ad3906fa0aff43e5a4561fc83f7989c06bb8cc14672e36cf7394ad9766514460c1b09f3962bc8698e3fb734c1057dab5b6a209df6825eef645992767c025595353eb481de1e51a36da05c8a1acff36a744fcd21de716fdbf09f2f1167bebecfa9bd27a0925d20b865c6f4e7cb9f48e0d85d9b89a3ffa70fd09d3838be4d90238c0f8f7f027474a2d7483b9a3c4fdadf52333b75fc9cc48d3dcb15809ae97c910f259d7c3420128c7c45f4ee999a963bf59d8b532544135f71860c41e32b76028448bf7d54352d5624a309a2f03ef02db5676d5ea76ab63eed65c07df53da33d9a7c604dd15e2cb43ff1f880480261b87c79c55c5689ac762048230bf11f9e2450ccf01b87e5e52498446cdf5b22b5c57ce676e86b64618325636bd283ad5b553d827d9d19e980b0d6bef752050106f46663fb6b76cb6b79a9ca5cd5785988d08ecef6a8139956f16c29bda7443a1719918d3f5be2e23c80256f5a24f540cae906ebdba5aba843007b7d11c5e053bdfa118a8daebfb336e14d91ead334139cfcfafef94e466952bc0b59ca4e396f57e6b9bab00786d2e0ee9df1ae96e113de2f6c7d0effca529da6b0997b0f340897f316d943047373040240e1caa70d3406d4be0d8719a2cfdc76c5e928ff6c845d33d16b1d1d05408721d5ec58449a9c1da28f06dd74ba0d863e24ba6aa0fe0b6730ecdd3ed590301351fe0150f160125f803efd6aba3a9553dbdcfe40ad6ad247bd0c42632e9937d5e7ab233ef7b6ee8555c894aff251f304cc8b3c9670f5a9ca79526f45f6184eb8ce30a03a9b78477903600a9382a172c9dcd70d48ba54dc3f13fbc035cc4b5d86f15aa5753b29ab18575b0a35b1a3b8ca3107ac5e9ad2b15b0d5234a5aeaacc1a954cbbd09c629f34bd5db1a01587712b40ecc619ea4ac600a3ec173980344349087c32f1dae7a0e202c042815c3ce3572a1c76de70ef3269b9879411893cd5e90fd53d94a61d85325729117c9278982e7a09edc621ce97d0d16bd29ba2e887959a414521de0b5e3309cf106b250983598da50729523effc2b8cd01ec8dd846f03212b8a5613896a5821791ca6614c6a5346f467053f88c509a0a1e77457185e9555bc5a8b3360cb6d96868ed4916e45a23e5f42334801ee484820200f1ba89d1b6d9f6c71b6ebf15346591a882744074ee88c7f9b46886bc8585956595332bbeb9eef8b0775cae08e0f90a9e93de3fab7c06d6e22a89ede2edac8331ff4470aed3d80a2b3747260ec7e8c9b4d7c75e46d72e8e14bbcef8ae7a4d2022526ecaad30973cd221ce87632972939ef3f0cf17e547f66548e6dbe80dae37e42ad4f08660d5c62d0d11b3a636620158aa73c8782a6b2aeb36a5a67aff75625d33b26d6947b61d53b7df289124ec6ccdcc9ce40648e218b3d89ba42583d52bd67f512b4a84a95330ec0abfbb5f7cb75c1e4075e0f7cf155c1549e2b298b1364f14370a7d7dd6ea5bc8ea68c5388d12f2d4fd24c2a12497bd3316011964bc6431c89c00e6821c0ea121fcb0208d0bcb18df21223eaa051f0027a297ceccef0b3970edcf4b934a622bdaf7b6514be115a47f5a70b36010808cf92933c635ffb1015c5a2a48d568459f637408498cc8c77a289d413cbbda45143db64b044c21e5f7c292fc4ea2cbad45cc94aa9079831da807e3b50c9744eec26106162602839885c961c0b4b95c4788bfb64ef9d93b6ea1c4edff05f9b49e09cdadd6c8217e2932adc31d83f9ba8dec5cc9795b5f5ac495c09fb459d16b6610483f402b08f028fc1420760190785b4200c4d81f2ad6dd2b7108042f9640acf5c4082d6e265eb34747d2db2e43289c51394d4a5591d4fa773767a0603af2af7134ff540cbd8e454ca0d6949292324900dd54292f25f3717a4937e93a5d427d7a2b459f7be5758e90d8f3a581fc9b5f93a5fea089d2393de1b43653ed19ecbf3c799d691a9ba118d8b4c51910e55c79da601a8b3b3d272e69002d54ce16ec5c99f2e6ebd082f31aa3ce9a0690fc21d71f83ce8088de89b7c5092623bd6ca3f5a1c68addccdbbf3840331bf3b5f64e66274dbfebee1701d6c91f8559d73b266729f643335d18b845968c0b0db0a5cbda4c2fc36ac826da38f7f66adfde65a6f7667aa2e2a83da3b050117c9c44054821378fb2c2a962995ea85d4eb0f5d8ff4ecaf2913671bd94e9ecfc096e9761219211c00e31f390353887c1424033fd3c4cca2052e7249c8a10ec35e358f0ce7197197962c469927c2033fe7d7459e7599ab0686721c8f7fd0e842ad3c606acd3253203d2190cc83553b19cbe91740f399365afd1563465d6a5a9345f961518fcaeb2bcb3941958009990111ac7494359005b1225535979db57a7a9b4bfc3b0481cceb5a0e8f83bacf9407f0492a4df064a206af93da215bbcf5645a939533177e82d0fa7eabd37f2814ca8d9a48e8bccd06d6fc2f4c72d68a3f671b23dc31d2b60bb58da65302c6d84a635f5da68ce9c7487c7d25a09baa5b4ebb8f2feb4c9526188a194215ce31f0c453a9d2e591b25ac98aa819b954ee4e00a5be9afae51f049d3b8896bcb40344c2590211e6d729fb4db0fcb71f968248d58962ba5ab7f940d84864ada1146fa85e325cfacb6a2dda44c9acf785ccf42d3465a21926ed23fb7a21cb5d158c83819f2e6c8282bd13b61c6f1166997db26d2eddc7b3fd5576b9934d8393710265b843a9fa4a2705f3ddafff9a5817ed6890a34ad0ad57372157d32cc54746e3de4c99fd10c05ac834e38452a3aeb9ec4452bcda2c7345a1f15736021bc09461b760a4218746673342651baf26739831b4a1c7651f7b9dc49bd8bc2a48f2d3a683f119d2fff47dda4a27e14339d68776c757af0f5e16309eebf4b81bee1b7a6883eeabc8b3a51e24ff43f87c668aea5cff7f820f90eef546569d1b458942c2a99cd687f7a9c9a6603ada53811d153512c1feb55bca9417d6e6421ea0a12bdf920ee4509fe34312f6fa81c0a5346eb4c2f72e2a7a99f8314409a717ecae4efa766522513a94339af067b216d3291891bd34f63938b8b130250dbf3164309b37fb70e0ee848973b9e01953dc3722da482d2a498c0eda179bedac053b9fe1ca6bce5bb928bc741d75246012c91132767b14579d1cfdf14a04cc24ddaf44e086f45013df4e04380b84b9247d1b3ab9b7235facb9372072fcc243b711e2c4851a7f67cc11ce3ea6595007e2b3b616465e78f3b96bed3646795f44d96e9cf3352e5266f3d9dd372a0418b9d514f29aa383b863fc517e95056820f84e2616691219d7a01509e930de717ba3049c16105f0810403c6e21e99e1011f53c3384158d1a4eafc0916effbe23261d4f2c9e021726b142edac2bbbeb891f86dd88bef569d91bd378c45031dd28da415df61e660cada264e85f5c30758309b8041b93b87c85795e8acbe9cbe32254578fec35baed2c735be00278a183c691f4a1c56afeedc7aff9581794dc4eb999600887d43bbdb7cbb1b87d777d12ee2aa8c5b7bff066629757fde2a531912dfe6f7c9f8b765212b239889202ced7c2db3f91e3f955764a0d8d18f64b414d7541f22622ba971af4fad271280ea5b6b42095a17583b959d13e7def7957fc1846424f2f9ac90decd13c978c2962f3668b090c70e7460980340d376a8835cd0e75e214ceb99a15fe35b4ac74c3bc2b2c9ecb079cdf4ef35c860dec5469883c1a44cef62261bd9550bd81d738551de2b07c1a0b9567051bccaf32ca6f11aeccde2618cb65a6b43c2f9fa454f84dea40d6d296a25483d907bdea5bb2342c4f5ec8a8e28f3815580654f4337e3fe82e5a2519702528f60c62a10f230c2075418aa0e5ed5a4074146b00ac1559a74b073024c3342517f1f3aa6e9346c7f8cf9fbd13705ecf8f68ad86ed2c5f7533ec806d74353ce652aee1c35913fb864b7ce3377cbdce88a8855787393eb02758420c1fe332c4ad70496c8e3a02f259092cf54e8a77ec8e259479b6def35c77ae60b9b334e6e642cc4809a05bd6b0a0db4cd615a3b992c039a94c8a43c46136d7f0e955ed9c009bae9c2dd39d9349a448768fbe813f4844e24ca7cab47f8373b6160cf230ffb6aba179f111e8e14b8cf0160b2bde2c0310af550a147e24e51bce86ddfda9c849941a671e2fb27d7373316816bba800fad607f79f54ac0464246d28c8032cc1ab4cc137422ba85ab316993f1fbac610e4c8cb00abe726bd44cbc738503799b5699070b88d5152495f0c2a114c55ce9795009554d107484aeb6db15cc51f7f08bef78d99a327cd01339c6c61dbe0794e6c59b903949735aebfc09808d1acb99b9fbee63baf699a881b81bf32e889a37af3edbe624c40c09ad71b4778fc1ae2e8030d6b9145170d98a079ea3ebbd183830ca9fffd7ec4bf1b31c63370dd0f8cd690a0a6aed98466faeb55bf9d7fd0340315f6947ba7d3288e6172551308e226aee472018d2dbb53ef9e2f440185d0f0481a46db819087c611c66be67ed1948e59b752867444886700d0664f06fde271133f35976294fafcd275e92e6e3e664bed08da5221f0b9adea8d5fb68a056fcd30b0f6dab4ad8655334d2641f84c71790116b0077c979f33083c316dd8c5aa2402467ad179ef00da599934b8235fb1989bed229cc55754d47bbd3e7ffa820a0fa5455de9f1e3206aa45b1fdbaacdd59bf23ef47c46c4b68904116799f5f6890bba07e4203214c4dfe80073870b3c16ab217a15c524e29e5c70b03f7888e4b3b4fa8118ee49765e507a47ac71033a2e6ba5589be25edfafbd0f0a7678eed8c99819a90cd0554365821b0b23070c66979a777c8f80d502011c689307364344e58b828184da20ef2f92f64467d742ad2b5badbe66ea52def35ee3222e69d2335259e57926dc5138e02e83bdf29c722801389c44b4613244f68e0aa055cf04245bccb4111394377869cb744c2844e3b41414d62a8d6f1be3b79209f13ae41f9d43bdb38287d8269f62059089e2c3a9817b3246f67492c4de39d476acb926070d56589b550c09a381c4995f388cf45817bb1b4db1124c10516d089939b2b81fc9ce4f802649070d377affd2fbe8bb4634b67bdc4947fad4a48a8253d0d34dac7e58ba492fc630605347ca86b7f2a8586cd84400075a80d5613fa222bb9df5234e6b2cbe524c9512a5409b9049ef5cb06a326389cecd3e7e7b70e7664374d2ec86c36b10c85a8f455b9c15fd194e836cdf4149c4cf5945c7dab22b7160b7546133ece7574c4c2b9d9ba852b161ad1def6ef53a8e3beb091e184dde87ac7c43e24c6cdedb7f5276cf33ced77335bf78dd842c1cfdd5a917044458945dd47551b56d0a0ba1d0af8c4ba81be5c805aaffbb1275289c9b45e2ef41fead48ff596a31de3995611cc9446d8acfb26b47a56dff71c78f05be27e98b6b0ac283410be5077ed7bc8d2707cda9cf0fc64cbb0fbdfb7a70b2677d2b8eacfb270d9f704eef33ad4822b1d31485922b9800d138e7c26ad18775a648055f48cd37da7960bbbcebecb3383364f4c4d7f34b0eaf26d10ec3c8a27489aff3e5bcb9e7a57524dd634f43b811fbf4ffaceb1a87d43ff90d2556a5adf8cd1b53df29e655ff4fe366d91210fab0b45dbf5211b978c000d039a4c62c60c096afe8ce4ae95a1ee06520ada6289286c3ad31a60ffe5d7caa8cb807214d8396b60d1082460066370fe0df489285b81abfe3d5cbeba391f88166e9148a3a4d710f2fe49f70795445a843e39edb842865eef9a4a82f0df7fe0284df565bcaecceb977985d7ea594aaa871dec1320d540f799d1fa22c1ce12f008c18f7384146aa8a57206afbb3375e37d1d24205542051f754c10f120ca23d633339b7a9e46e06809c6cdd3dace0c7bda10dddb190a48240904f3aee8dbbf40669152a8066fb175518133823f44f7d90fb5c0eb829a67fd459af8800fece42a4a75a6c736597e15070effc244e56402bea1f977340d53e15f610913165a12db993bfefaed605dfe29cbbab10edcce171a07d6ea5f456ff69c1485be64a446cc082bfd3ca55000cc47c307876bf02ddd59bc4886d5f5899b8fddbee557994a05f3e287bb8e966217adb517037d5ef3f22a2e26c0ac8fcc7e63e9a14d020d1eeca25ef6be705e0aeca04ba3291097bdb04428a6d05971ec972270a4e7e46ef795d920c0f0696f8a128048103bb64817fc2bd420265cd11d4fff2e50ca162c0476e068592a946828bf4faa86f2384bffc9d59ac2dbc4ed121a8a0de51bef578455ff9f4d699514a6202cdd82404935ab65fd636aac12430b9bbd94d0035db4aa08a34f6ba68ba986d850b1e0cbc4b79b55bd087eabb1df647d676f4a549c8a54fe7be1f0510109b4f6220bd8e46944220b863d3a894fae17899c2ccd56b8d89aaaa7d66c88afb90370294ed24048e28df1bae82cdc02bb717ff9d304bcc07138cdc8abecb346df8392313f86aec7e1375ee15b0c5e32c9d2866334cb032073ae5951017d6319fff072e558335a200894a9a50d099d914c541fc9d6a0a618c392f618f63b04835434eedd00bc6c4222d8b5e5c27c863b89e5f28395542608daad51c1a2f157ef8178c91498aeedefd98a6cb57c5c053f05af72c3468b56f749eb00686a1668592ca53c7751dbac176475ed978c2925b66a232e2a0b995752b53538b1ac2efc3ec6803d20dc37e66b0690880e7c0898cd9f3681feaa05e3efbe3028416b2fd80a68d69e2208245ff0a5bdfa7b6b3352ccc7bc7e992778b524ac2ad9a39ef327e903288c82b9212a73ae4c952096c71a9795266051c463e985606adc8d5307bd3adf13287fc4a46bef2d5659ec9c09c83add7a00d56d405e2f1cc07d87e9f0f1f10a81b9278ba107dce20270c51d50a0865c1c5ffeef887062c56396788c863d33139673233d17e58f4a9dab32198e9aedee39e9a6c36ed007f0c3d65cf389037b0c98a02c24738dacb4295f2315cad40435c408d57b1bf87895463c8014c4698b99a1331b3132f44b7055fe532126c3ce670ec7ef4812028340bdc4327a88531b154f0ebf78c94ff8ae1097fe9cdd146058925766d940b59c3d5b991e9a321aecf86a0f52eb5b173d6be2e8b931dbd6d68616ac65ea52e69ccdc2fd6185a73b11d6c9d995029194595fc390d3899b1cd51c7a9352ffe63de24d76a012a96bf9f37c9ea978d2c770592bec379f5a7bf33f597ff5fc54d1eec322249b3fccda8439e1d2529c02104233f984d04c44d1eda324debcdaaca742c2f5158ca3d9741d691e1c012a2e5ffdcf054065bc572650db16631d1d6c42005f6fca545c96c343687b8800fa21504dd9b84549ed3563af6a3dc71199fb7615475b77ac762fc613f28c6d56157ad1c3741feb84d0ab5081db807a19db564b5ab3549145c040df2bc45a0b3705c5c427fe72daa67490847dc304556aaee5734693b0335e3f039703a2892233c02d366524f80d9ebaa65c514df97da4aafbea4e180ed3b56f7a4196dc8f4a59a09d56e03bb00a04635afd31c5265a3bd0a964a58b9f554d029a76dfc50b7d6c2065407d5ee194302954d82d9a5e53e87db713681ff1b97db315f4541f09171dec898a103e55df286fa9ea947685af54bb3f755dc1893a378af6e013a07ed850bbd2d3e444ead867a569f8a493f34d1de5e4dd48ce967310ca6ff5e1c331073015d7decd8f22c809a10ae97a53320562a9dcff173300a32ca3b328a573f74a87bad76cc91d65c0f59de7445f568b75d53f4677f5e92ff9ead426630726a87d5df9274431e14ac429fddeabfd0ae7360a74aa39c1bed63095584754d9f67a279664c0ee78f31e98982a0502c86a9cefdaf475377a26a4c01d6b581080b5664d162f6e83668ef73a6453b624d762c6927ed730cc07d35522f9121fe4bb45b7f974ad26ae52933659eb1ace4c8c55e227ccb61f9d564152e61b08bf463ab0f866a49151357d16561b2979571cc97ff7ec911a9131f2f4b35eae2e6eec34a338f55e6727d8e912c8a959d4e54eead6d3e5e0ac6f48f5ae8e8140fad1f5daab839186d9e48aea2887e01c32e48681734b51bc3856d547468456f03f947f0dcb424d3943c55da3465b07347f5456c61ce13b0f6e04a975ea07acbc0a687e60132ec6755bcb2d9eb8a9504097485b7ff214b67f0a52543007aa2988e8ffd0866323030e95026e2c2585ad9fab2481496a0350d9c8e8c5eac3806af044a21c543342bf28491beaf5f63cc2366741611c4cf13a415cee019e39223b067cb70ca6b5c950f549c844b6e67a8338e0dbacf931dc2f30f0fded2d27765fb5380be10b55094d713bb29059f3a126ec4bce59ff7926dc3388069beb1231ac79c862bff82988983fe846f9bf8e33ace9e03a50841396cf996f7a0559045f08fb0ca1ee66545ccd234e8712120343d232eb62e5d53c3eddf424be17c4c4ed7e7e9c5cf309f62a4bab41c1dad99c0be19878a1946405c38a409af8c40ec95bd9f4923ce3db4637b2703e5874f90d9a11f2d76987bbf54d57452d0f344056a861e459e58de599e636ccb60707debfbf082a48fe400d37fdb69c7e44e4d0a8e6ecff9e4f0ce44009c3681b43cccc7eb824e7d507a241ce02c98279526b264e82ae3eec19643f9dc98baa2cb1d4b0d4a906a229a85c9a80751d3965a059e348d1a14b8e233ea98504318295e75f1a5b1a6ada4b5b0ced697c2685e8c1cb0c4dc367c50ee36e2ffd8e25f5e39a93aff9c779d2adb53a9799abbdb23d9f48ffef37e7582d1de8bd2b198b7f8cd111b450836f402f377c93a33b10f97fac0d5fd869cfeb8d793ac2b85cb302528dc31571cc488271fd91857d176a52290b8509eb7c8df25678d03f71ab6291b2b34d2249682c54dde54e0189a86b2f7b0f0f175144f5876644dba0c25f1f16fe88959cfa2bad3d4487fb669cab18cfbe5e969120551c40a1c4950b0ebc1e1179326ea37cdfc70c001a075fcf9b0cf4f11d49c05a58c1d39768dbe19f8c70937c2fb17445f8073ce2551c44e0fc36be79bf528d29614d00af094afc2b10ec628ab71bc312884740c75e6e322dc3f69e6a23c8ab8a3ef7b8858f4173305443521816feae31528024dc955dafa74b5d35dd8c678fdb3ef9592f5eac3d10b08799104833e1bc7c57976e46673bc28872a8ff08f3691a6944e607f6ff498bdc6454a86a568df7a0ed15b7c494a117c2eead4173ca7f4430166dfd7892f294c18d19bf9f6a16ff7c6e41fc143d50534689c243ba973e33cd1410e071a759b05aed2aac6b04e52b708d33075dc86877c204f2bdc02350013ea54d1fbf392c501b52465db4e845a812d8b7b0d0d16b153df66a02c1434294308c2c33b9ec679a92e0ccbcfd85d0de0172f881fbbad0147716890534ae48e0a90be8a871fb26513bec76970ebd11bdb6f5273a21209f8e9df9b19b68a1015f4d360a43befdd312dbea12ca6a76733568a5d582bc184da966e4f59408d8e06721ac6e9ae2bb146a1533453b011355729d0981d2c883c52dfc31863c65db896b7857c2c681bbbde75d8739f06622866b36fa0703bfb466c102b0a688834de3b85a2859d2b190ff1a452d8f924d869b4561c0c718e4d5b6b35fb39d664886bca19aeee8e1cf5d2c59608d253de0133ec8a549b8f24173e1c69f399104ee8a6927f8eb1b22edd444de5fc17b76238cf0b21b308cf185f753ccf5b23e70722ed70654a768dd72585b81d4482f918fd394b08d91d4d376823be6cdb109133123afb81eb76a4af9f35f811b3d1034d60f46e1b0f996d3d7786a4bebb7f246485688620afb19e087a428769301098e02d36a6b93271822125032589506a22c0c1f32f71da06f82ab32ebdc5e2c087970c647dceab51ba6b0a4ac47fa97126a37cdb3b5bbd3eab8343f6d798c39681abcc312a32d2c0f42bda613f1159da7b8c8f2969e0b3721cc0eddce774fe43a11f1f876f1127b23af4493f619fef24b43085acf7f04260fd53029d6ea7c71a295ad31e6be64595f645f7c76ef229066e0430659fe64320660aa8a5e9c1cf1a3daa16cbbef177196a918e239959f2447be8425019290f48a031ae479327919833b1c2e5c502806a53e9bd7051653e4705f1a683012c05f8a5fa09df38e21ab8229be06506eca1dffc0104b7362884ef1a6dc106f221408f32871765d21998ce67362961f3ca7c00f070f9112741eab69714343cf122920d65b9f1cf5ff1c4ca4e0756f8051b1711a0361e6db83e045c3cd9cefac5369bb6850389ccf04acf57ea47631b5b19e454c63f5f0ea34510265b5b4afa00565875268a1f386c5b4272dc8c122d5ff11b9b8263a2ca31bb8e776533b26633407583e6406e6723cef5e9cb9da31a8b7e3f0087f52af1c88e7d92606f6d860ffaa507857b24775eaa7e1d90bb0804078bf0091a2126bf2fd0ea9655a7a3ca4f46273483d1eef4bf8af1bbbe1ebabca1043504f57ece37fccc59bee3e4fd4e3e9fb08e785c8451c9e2de77dc265a388e47436f849b431481e14db432e72233270aaf8d528c8adcdfce7746bad1073044a79787d8130492b4cc7244ac5590947b98355ca703eac5a699b9bbd4bb771dd06f02567498f70bbf222d35cde3183bb1f3b8e0dfc9c44ae03089ee58fc38e8e78248fba3cf94da55c683073a411b7e90b969b50ed1b3c785678c637f438299b6f6e098f897f53196461c4bb6afb52099c9dba4a5d29f45ee4cf1569549c33c133586e4d84ba4342c9bb1d2c642371630816f2ccc0e70b76285016f2c59010c90db83a318df2748b2ebf630a4a3d6263d3c1c3e94c9c21a20e006a52d8735401f4a8a381900d7945c8169480a9ab167f08ca473a6ed127a869459bc9af178ad808a8b4665634f9bc6127f0241627ece8ff6161cece7e5c4c887ba0194d151e3e77b3ab3ee5c8f1848494f5b0cae58e4c49116c0c7edabe114075c8e336af6eb648ae6aa718e2baeb12c98eb6bbecd4c69ae3c99f75c9595319648df0c3d3b91f97c8b87b7050516509597ecb73c99a1d94b9eab36bc8332f6d39eb1c75c5c5b4748d5a923a55f55d9a93d1664a403e656307431ac7e37451ce07089ab3b14c111e53e703265113caec363c5850a3e577b070d3ca00c81d15784042f1cba18ab4ec32552698664716eba09aae43040796a065759194541facb9d1d5cc593ff3d2701da857d338dcabfbac799d3ea6b0c6c6e595befd3a81521700b9e305cacb342266d4e5b232fb3c211644498728ad1028b0a9393e341c62749501103723ff25f27378fc932af1c809285ed4918b1fb5ecd8366876ef4c3b6d9ed006f57336e7f5a6cf5c988f06b34d69d413133af4025e3e2f394835d36dddaea233fab61440df58e5c8422f050bddf80b7d5435c2b1a80967ac088e92a87558a966dd94cd56ff82275bfbc2eb7214132eaf68b3742534518b685912649b020395e3127a3c1709d7ad9a89888dfd41814273c8ebf0c51eb76fd96ca3185d478c0b7216236bd0a394ac869bd9b403b6f91abf29db087668b3a9b5aae6bad6976b048eae7b264029c8a4832f2f5ec98ba5a4bf95045a5de0aa5cd5531e5d872a0f397bbbe88710c90096304ace22dda8b08074e5a1723a87caaaa38236be99c7f5f4d0296f81fc441cd9b22c9aea39da6aac2a5dbc066e1116dfa691d814e89385d248e0215653faafe3f47e994365e0acabf532202ff058493497de9ef3c6519e770af7f09cc928a65241b749c1e0a55eee50164cba724f795c3a2fdedf7426ea003699f8293855153cb7062ad74ee9f114a7dece7b4ca724c80cdaaa4e46b549321c2aba86228e0582ccbeb65f2bc3eacc03aebdffd997cad3b6b3981f9bd63099d84759b734ee22697ceb0e03e1d96e585b3c7065a4b15ef6f29e6774d35291d413640747261fcf9c0d7bcae1c15f78c2e23242d7856f6beeaa9d9915e6174dd9630288bd7325a6e89aa5f9e2476a1ae7306d119869ba823c2327d6be40f70ba0032d6ebdfc1649013217a79004a24de2a9042b749280f00c4885b6a051eba0656675b4a3d96f2dc57775ba6fa4320e2a9f18e1135e49fecf703c085daf8c91341c2f8fe2ddda0cb0c37133cbf21857dd18727fa33d8ab250e38d76af85658ee81e9e16487985419b77515f2b36275eb19f611de820175ac93584f5eb9890cb5374318bd19a68aea7c70136f22a0e10439933e565b8b32a5d6287efac688dfa958cb66e745a1ea35363c25b6ea7fec95370db100550693a62aebf8214ed7f6e49dc8896209e6fabe34b70d3c41f4212aaa628a40317af227d4369272aec452322e226235ec73398c349c6ea7f108a8d030a1ca02f1564a158e6e1392dc0a8f1d0838321e94986eb2af8ed0ed3e11907db528bd98c99bc18704f194a214c857f1ca3e2163008600a10d7f014b5166f6b39054ea0acd88e7b385132f79d7d1fe3ddce707cc247b42ba787f44a2361da04898210e3e7effa7585f5c9390d230c49cae5452e2ada75f17b56e559781ed92ba438562076b5f90f8521f93550a43aa6c82af3ff3210f0d83759058fdf3ebbcb29adc28e55c5181ec1d6cce41409f743df30b7a80285256da24dfd8c6eb29087a7a3baeb017dfb2177a0e5266f475703c236be20848d8624c59452187f19d81855a33b31ed6062a928ccdbef93b159a9dfcf89912b9a679cde60eec35073f699981b6c508eb9730b71f79174ded861a1ab37c7ce91af51afad09a669e9863e261d9e0999708a6b4d9c336aae553c12a17f6eef5e17f5a20a8ea164ac2d5ed55d7e1b28002d9d557e8eb847692487198fef522f763eff00861578d14e87d170385d0f37cba2af0b181b61d7cf52c209e39155d551143cfc93a08201e0aec096c44ff6552e425a27063732e95f12508d01f63ef340a67a7b9b41473fcd730b5bb1a879003ff7db2547b66232868113eb2e3e4d05d21fc50dffa49ce4361d03a429aed57bdcbf0a71e398d028b68fe4b0895f0474fc19aef9bce1dc373251a97ed1ce9a914639031a64c153dd883ad9be982c1ba818c0319990893381786ea47c45217cd41b0197e9cb205821085fde01a677e74d7c184a76b49418453be0edc6e3e4ed7421c0c9104e5de9bd153c22666434e13c754d2c523c8c873cbffb6a2d75b518c40cc72f3dd36514bba36a32ac561bc4faf1a5ec024ab91b2dc1ae63d32a72d35c690d5a5ba24e9c8a92ba781e0961ec806f815f83a25602735d99fb42c8b4759202dcaa6627804bee3713d8c3744f15cfc8fd65d3f66e505e578b9f19a960841285792732cf49a41cdce682fe087323966b5348bde8934586d3119b946af9261ce248094e60408b28a3c3d4b11fc297456ceea3bf1918127da86eb527faeff59f86a093a68cb6737ed6c15822f20bef01b1c6b2a80d5614f3640e8554ab23df1e0ad9994331ad3c89abade2749458d62be912f98fa9bfeb8d7c5525d29b34b3c89c5f4d401567c36962e391d763192839f21af7c5b8ecde6a2b0b4f383ed2f9c1d6042cb5eb00c40ac9855a2a2259e7dca7d97f9dec0d222caf78e316554a05bca0d9a4d2fd48e83d29df7e50e80651d3b593409aa2c31d3a0c4b5d42ecb299997893b29018f7b7b0abf815d6667dc7caecd9f60fa300e03544d151352b4a2afb00d6f95196a4a614f18a031ae7bbdef11ff2a6123ac0f833a1bfc559995a582658dcdfc2ebf0ec351c852dc0453e2cfb384d277a2a01a45847c32806180542e0fd932b9329c59a4824e36e55250c43382b57586ce688ddc381fdc62c3f3ade1201e04292ca0a8544df456759dd2b6a2606f8bb0625fff9065df2e4c683584e1aa98edca9ad6889869672253dc72c2a0a99e006bf1c8cd1039a5cd1b2ef3a55a5f93723c474463455fa66f5ee44123118c08e0105857a8e03d3c8ce2b68a236fbd1d139489108e904c8a9da687d88c8bcae557e427318028bd1c000c1728a92d923a274101304fdb4a8b5cab5f5c01a95c6a788331744b2cd4816d491b0a839672f47d727104bb8b951ddbda5e13b2e6235fdf79ee7bb4a6e0e4d3ab11e085bbe5f6590a5193992e91e46326be6944737edca20c8f05f7d3926319c6a3b39275863dbdef6262bbcfb92e9f7c28f2ed730efa65fa549e821f7ec06a9b909bbf47af7c5f3acf31ec96a859d1d887771fdda4648abd5a636cc47a97e0cd5ffdd324e109f8b5930fc328ed84de88db398c151626115364a633e5fac8cf0c03d5e9cf13ddb6986a359b4c07b257aa6fd238d166b655ba4c7770c18abf5a6de9050535c4de63be976bf8ccb11bf1f5897c834d5b95e4f79068ada74651cfe0b97dcb1ebf928e9460166ff71b8656726ac2eb7e319e5d3358cdc0d3af046005aaf11239fc820c435e70f398efc103b31fb11cc359d5613ef11a8233c0331b58fe3425479dca0ccf1e1938968b3ace920273aaa4fdcd295c6b30d12dc62216a11506dee5d42dc781427d98340e96c9b0886855634481dcda6a9a69d33ae51742e244e76c4e73aa2ba595a2c5bf0b04df5eb6be7e0cb1d1c27e8358f2e237dbb077fd166e5431e1fb9122746af47b41ff630618d525bcf93fe68e148d2efe7ee8fadf62240fe828c40aeace8b196a0ed80c59cfd0aa8e082978a8a08bf061d7ae3d89fa324e1dda0785602595947e28329707b063426e880bb158a1100198ddc3ce0bb8ee81262b3c37645ec2650bcff1537ba516126b5919be596e85885011a4afc60b18530da5db27aa7b52863f84c35f90611da51698fc2dd2dc8b4d060ce807943887868796c0d47d7b8d8fa0a5a5b22031470d1dec1ca9a575d05f5c2042fbe0ae93adf12f9317e48406f69a7add64e16562bf48a201cf12011697e314ed914da30c16e68e37a98fb73b8baa9d7c7dc24284c1cc283faf4ea271ed2fee77750eb1267388648dc6489352bd61e941a07513c56b0bf48cfe181765d4dc5c749c600f4d10b396a69c9375496563c2d62011f0044d6182a91a0aa648c26f7191ddf159a878f53754c2d988004d11903be7dc82cff833790520cf7861289fb625e89a5b44448caf31420eaaa50dc592e080b1f5c496f2002b1c373110c3b38b91917a6354d0fe93386648c15242421062596ca40501b151e9fa4d0dd06d2f572c4f5fddbea3a00c9f9b0672f746473772e05be012328ee1e8060709f108f1877052c071738c3f03b6e553c7caecbe182ae1270ae4c14abb547e0fde87383bd6e68d2112adf473e365fca59ee51a1a442f1abc99b46995f6cc1289062e1297630b54a60eece1d1b246582d26c8d75628068ce041540175d17389ee1553aeaeb9d70e785c97aeaf2dcc2f2edc04bfdb8f3c384ae0097a47c106d0b5636f27a90268860b3b6ee8c4b593123c69b13b9e472cb91b21035d780469dee7418500385322a4f9a80e82eed844b133375b93546ed4fc19044af7a0fdd0b9351edd396f0ac2238d5b2a997620c4dc635faf5e258a24c3d99b11e25811b0f7b8b2dcba685bb59cc600b3c2f440984288f692a2d035a84e011c40c1c791425b4c592b1be5e94b968a81c42085181aa3a429b2b742b20b4a2a310b627e84d5040ad9ea4c5815a6c80f9d8021603150e369221725a0f7ecd5c0b89d456d60ab640c09727d35eba899b8dfd14a8480d00ebc46fac63ec5fe50dc14bd483d3a60dc78d1a12330900b66a650efe7e9f853b9f3a53088009ed49a84fbca3e3ca66597a06853e8af562107da9eec4208e74e7fd085be54c92d1cdab154decc2d350df2220465df743e3821d1aa85ff7471a6187e4aab6c5a5a6c06960006e846db4d5705260771601c48872c5220532d4a03b7a5fd4587b793e50affb2cebacf3d51251eb8c69f931d6e159c2723a4591f2addc9769beee50fcc1921f40d2260a108c8be3c522eaef6c38a9a0cab2eecca7596f5f245d18a9a2d0c2704445b22f2a86ef86b056ae1ddfa197aecce4b772e55e7c8d871e66964c2b17c6aab56312b7ae1a5474a57c245c545263757ac8f8c79afea74d7f52790ac7daa002b15eb94b11aeb56b587eb9ef17249b4b4843845e2ede491bdff5985e405e791d02659de1928e941c204ac0b8b3add73f73a98eaf732c738982d5d7a90bfae1c8630e4ce3435960c9b1343ebd67f6a1d1779ffe6ee1d08554f70ad6f8546fa8850ba3e28437846fa857dedf26b25b8f09f995c9bd0b2e4a52090d82acc18e1889e2cbfe9bae2e52e2259f58c9e2dfeda8f1f5627cf4dd08a0bda1cc1e9de9c29257266d5d0344b89f58dac00fce230df2d585c5b07fe8d08993bd611a8888e0f46aeee4c0ae494f39f82469be7713ac8cd8a9c3d73cae11de62364e08987345b29402a1f474fe104b4bf7f16914f84392635d1a477a3d5a36727e6a362dfaeef7f3fd7a467b2a7569a94739a6e69cdb31bcfeba8853a4de2dac712fd7d7b235b28a850d0199f158a33ef05428a93c580fc64b92e0c691fa1bd3ec514f91d24349987215a51dab0b98ddfb4f05e30b304b58d080299b92e31a7bb76c842523229e157589a7bb9eb266f39228797126f2949158e672649064086d3c26828c080bf21684d5e59fade81afd64da3656c4db47793976edccf2573e1eebe34cd8eae97144e284edcd5e6e41f73288ed8ee233adf51cbdc17e6df5895cbcb7c71b05e58e048c42d3594b5d1ab10d6a3569536ea346a46dfc0db27a8d117ed793c8f54b407011b34787a75ac278868600baf803e60761f7b4241ed88f2779206db70bc3b7d3305f0f2aafb3bdea9be36cfa70aaf26f6b12be12b80bac67635e47eec8ecf5b5dd3cf75f823e968009fc6c8c446478ecebc30d25045fb9c7208e171c451c97df97cecd2e9600b8abed86da240da11df30fb8af183bec6f76de7e624305d2215ee319654c601a2f1e3b23db7b1dd34108dc6f2887383512a8e468c8d621a159560cabcae47f577ea9c1b53680cdecca2f4b64ebd4e00b35230b34a254574657f41b95a4485702cfdb21dff3c6e9ef4d99ba75590b9aacfbe8cec03e0676769f50256c471d576425e184d3e3e66a38170447855253a0eda6695a40a4f11894fc43b6966e6caad4e8df6374c511aec2d9b1605503821a3d6078a66a1d9507012f35354ef8c853422ef953ff8b702a4e0f2f2a5d2723840dfed5aec990d61f8521b48341d21a5e847e54699d8361f95ee2bb99015f424461acbe286dc1673f508b20e09428acaad6af093b4586717d896190424571fdb70aca8e263e6b7d26fc3cf9b61ab360aa090ca1a4538da3ea32594c556132cae0c295433897f54cd0313a321956cbafeb1970f0c42c1a542d549fd2468efda835f579c8c07efe2d599c832b6a7cdb3db767c90005567a231f5c81a66a0255efde002e56c39eedba79e656ab6a27f5bf10df4127c01df57d048acc03763ab97fad006f00065cc48e3fc3161929ddbb379283ab11c52493fb43f6cfd07b8146f6df970e8e171516a4659604d3727c4922478c8cf97bda65de91bd4c08511df0919efcf5a9eec0bb380d77091bb0770d7ee772290130d4a74ec3149c26cbf2194429343ee98ea69a27fc946bd13c572d9b2b47b4d37fe3b314cd7e34ffbd858f968cd74d49b5aa83239fc709203b2dac6e0aa71fb28b91bb30f2e2e46ea0c9933b60bd70474985213fa35289ed83a434046b123c156595d717f49f010b6c95902a970e55421467767f5d05d35a13f26de7eaebd39113bdaff98f953e4aa7a24221c3fc4140887ed54d574d0450a030e1bf0cf5ec4ca70ffa18e0fb664797e1922c2e4343873497a933a055ba0f0b5764577400c6bbb61c8df34666ba577d84e67138df98e4115915d52adf68aeda71231bedbb54eca82a5920f264fddaa0bddb223af28d4a29407427b2bb7eb39e568d86a4098feecbe5041e1866991378288bc107fdca7c0311ce457ac299f9138a4423d8ce14aa06f04e11ce1e015e6b0760bb75c2e91faa0839c564be1bbdc16085defbe4ac43f04be70941e8ed10f02e426880d4f32fb4700e96c4a2229fa5f7091d07ef744377e3e2f4d6ea8a2d3658d41786cf737e3d2838fdc1731060d60a8c168d9352866839f1b966d2f49e5192f4fdd40cd952f942aab7de3d72cde299c7460b533d922702b44d3f83075bd67eea47de0e5f9b33fe2c6c523d9fda2b5e11aaf533eb8c21518a286d67b4f1ae0b192dfee6c8e08f776adce38efc9d00a1b68285252eb91b4861b10a70e73dd7658013b08e4621bbaa6ee24400bc45aa000eab79458af2ba0bd408d21bfae34d7b607c004a06a3a98097e7d72774701d2abf921cc06e060058d7837cfd15bc351a073c3980a1adfcdc9ab391909e5df1522c3ff391d4461469c2a4f3bea65af0d69fca969d1efec2ed05e0fa73a1f76010929920ea8e8d8a6f85df983e3ae476c32d80a3a756a062dd4ef3c503407f77b5ffe50209ee6e1c68d964da5a9dc2ad34ce3db4c485ca6dacdc5a490ba9f3b88dd9bb681c71919714c0e4f260d14302fc50d8f204985313935c4efd36f33dd5b196c79f764f19ff40fb24ac019b83e109924b21691eef25c2a56f7535b81e7f80f3ad3706f0b3364e2a0e4c474836d72ebe770403822f35381af9969adb35ae06df776b63ff2e602f5a238c127c3635d44156d708635cb8a70cff74caf8874f192340a21e18e6365b0041b89a2c6abe846b71e27ed472506c8b834a198f162d43b9aed0882b25cf27bc3d7b01bc2b07f93b8419d3136d96331ee1862a78f15933e4ee7cb8b81ee36fb26bf823351daa31d803dd31971b8fc628218e8f142658a60aa9cede7cc44c1951ef98eea66e0f74c7c0663e127080368954e25a4499e685ef98f4d35303e05c05da245e240ca4748489a2c78a261b7a1890088e35decb0d1cc92ec798ad632db85e16cd956a5e922a75c03314300ca516ee3bb83956b0dc38738e3572ef3a792ee66ae38b05c419ed3a311413ce0898228ece19d45f71725a33807f6d3b47c4a69f3981ada0838a5ae79d4f6e9e1f07fbc431e007ba2810ef5c2d0f20820f911e085158482d651de746bca6c6878f07e43cbfbde52d85e50f5954379fe5488e4b81739c45123cd9487f93ae2c8e4ae59ebf3e3ab5489aa7a5831b55ba5c01273d68223bb88a7f86cbad335c7aca48ac961494a00f915118382f1a17ef34fe0803aeb778333f68a25f50d2bd290e1d9987262c8d6abe8db255c4f0b0acb2f9c43a0b878dcbbef742f182503fab150197e35b51fb18e551d14175f86e6219f67b4d20162b8bf9b4ad6d5258854037f063fd61c5fdc8a25e913115f499af968aa4014098ee0804a13c55e85c7b01628eb5614fd935ed0af1392ae6ceb289efd715473e5b423ec56d1afe85acd2e76065a4a6d35cbf7359b76c6962866ddc3dfff33aeb5345ad859c5ec47f451f5663b0a26d73060cf781015335f7c4ac76aa3a1fb68d8b8d39cdf28e96f5131cf45b18700ac5736b57a551099683edc385808728b0d9617ddfe141652c00918f69d274883045e90c963ac0bb428bfcc3b8d7b29ef2cdc199cf199ac32e30cd85f8299773d00476210e020f49da73d8348c62e92292e61c7cac2d0f9fba5366605d8d2533f313077f70f325805561cc73b653a7acc7998ed37f6602e0b4a50eedcdd5da00b45972fb133031a63e385f7fd28ae5d1039cae9556a2970e18aee8c17a21dcd36b7fa904115b56974e60d78684904778c6fcbd4ffa43c8bfaee8f9ec8db29a60427e266fe24fe0af9c9836f495849f4710f21e27c80c483869925103a6252dba89b725a1d6659bad1935b0cb6bcff1a0e059c06f9e8a6017272ea36c1d2cb52a4b3c4b5ecd5464e747768549dfc7d91634d1895a4dae36668b4450184b825fd4a9ca61d03add78dd57b47907ca2e1e23b0648c225fbce9b0ac49067fc8fb27d2a6028e5d715d41f7c2b76676ce9d475931d4b80080434b4e582ce0e207e281edbec8811e793c8a786c151bf21023ad85bd92a3a5e3d5639ef53f01597ff30fa31d73a71d1faeb42c9f245b18151a884de5e6fffcb30fd42622d7b1707d6981128cd6560a15bbaf795e14fbfebb9145ebd9a2172db8402fb3b2a73f6b66e94414e0fe1a6ed3b6ea8d345b396cfeee0f4f58766df749727096c1754cec27f28ca0fafa4a170dcf1a7e0b6a3ce6764026d81592a9bd73a0bd35161487f310f0003e47a5cfa2b919c1a24d129bc43f9ea12dcf7a4d0a85b98a448c368382872c0d646890a1811c0de4b03a665406a0a3cb19def25d8f927daa51e37b947af342a507ef28a5c7179782084830390273a0d5c5a2f08adc01b50782ca00c44322b4548ecc790c6fca705e7dba1de07612cba9787a857633bf5733ae5263e29e33434b60c0856664b1d7ffef5383d164c99933d1f65fdd5f0b66322b135fc8453e7b2f49867f9af013c996e23798af8a1fd5b052a42cbf912e135afdea31beffe1da2f0489159c1c41f061efa387dc9efa66028ade130528a927b1aa4d21a7c942d35c08625d3741f65381a66e6105750d2c6d3b6d76b5a3a335b6682b072eb23d6ab114ec85c5fcd5f79c392588b3ade826a2be4de62b4b944ea35434ec09ef2c33e7d6bebd17b10603d153514aeff1706a302db44a4af1fd4e3da0531fde58d1cfa350210903f7e77d6dced6081d09fa10809fdfa6302c6b47e218551c04e6644124e8a4beb43115845ca1613af476cac1becd36667807f7cd693fa12f61eab7276690fac0b8a9b835f7694ce2e7ebf9042d4d69f9af3aa10d14ea91b91d360d99f9d245c2e8c53df59d4caca1c8d28c869cbfefd7c30fb6af9029981d44a150d744f4f9b9c5647c428f66d6422af461d113efcccea1ad9d7e4ac96e687b9e79e597ee3bc652a72b1a4ca5671e95d9875cbc8343b71c1e2e20497a25dca6f6b443d79a68cf3f000cfbb718a2ff05315868afd459015b3ea5357bd02e7abff469ea6ec8a0a23625d0a6cb8b9122fad5147eafd7e306845f289a25c181801a05d16f5f74cb81f4aff6cb9e0a54972cdaec467fcd39ee8c122fa1ff6e7cdeb69e86d2d9286fb65c0ca2109c98ba7f988c81d8c938755be02b3e4a56d12df9e29e998e781531f2049a4eaf7a635f056994a004641263b7d5f988e7799a0181afa3e5c5f8a9c5a4fbac6b84bbc40bd27cb3ce57bb0d412ee39a7c003cb9e0e8753986fc38430d1dfb19ba1c36379162a72b01e0ef933699eb1c6d6090e4fa6f3d605f01437be65c6794944cd48227063c61e435f28b60716b8139a6b702251082ed6d2639c7375fb70276ae1560779cae025d7c96892fd46509648c881c8a4e6c88c0ee22178f76809dbac3af51dcdb601aeaee910972c94557044d76b19c0d68493d062626f58a8b3b1b20034c7c9c982689f54c8c253241fbf41b312cb6e19271c2d43a3729d9e92bf1a0832ea44d5cff20c91c775ac4a4ddb18d5e628c9772418c46c928e6a4ab10fbd398e26b95d328a5637937ac86d3229eca5e5ec90830628a9f84f80bb3769dcf36cef1d2007d9c8115de09238dbfcc7c6376e57e76cbea3d9a6a0261a27edf4f20e9ad1cd1ca081ea4d6e77d6461e54b6547a2d8a1e8dc5f8aa010a3172cb048b993958a9d8e0462fc7a1006c5a87df697cf73e393289ee30fcf624251021f9cc185a4c8d9d08f7588096504d0067b1b0a378ada94337af5f0fac87663722aa764e0554c7b3811853e957edaf4d7a0ce9d75dc3086b02e23a89881af2dc22052d9d31f672a72a7ed5a8ce4ff8849a9d8e9c1b80b552aec7abbac928ab15c08af0b30778520d5341693b3def3e273a92b0515d3e373bd51c610f0994ac8077832698d55fabeb19a7382facacd92bbae4d116bb582c501eec2a43ff10c3c861270715de32c77687884fccb633af262ffb76931ee1aad21de625803da455d2cf30c8adb5f9fa50785fc4217252f3568bef3bdccaa1acebaab7457e6f048ebd0d57e0c87ae11717510c994c5cf27d275b76091cc77c51b15b0870bb6353f329ec69dfc2f48e70c638d3d4488e50aa3b41f73085e49b3ad4bda3572dee29c9d6a6a7357aac6c948303ad93f1d60b3dbbf779d07bf9c8df15dfc05e842860144a8622e491b770a95696f386d0f9c5eca6f3fb3f08a5cf81c9cc64ae45d38cf2cc5c8390c0d6606a1d99c97e3c58f84bf01ff77c9e6128697d889b6843ce2ee6f4b17b8e35f92965007f9231b842f1574ad77a44b15f313ceddf483f093e125aaebbb8267692ff0f0c1d41164e734d5d5e53f801865a9c3d15ff963c9601d3023b222272bcccd74633eb7c8c6a044d92cd801823351b3788c13822832a097e9782c763ec2a5d57b00d008e65d8977029a47efabd81603934868981b2f2708864d3f6e3711518eec2c0ce9757ee829b5138ef68586df391e338be81c45dc0688d392412abd8c00a61ed81650abd334ba6c312c9bc217c0d2e70cd5bb8dab1c060fa2c901cdc73f77214b16e1aac5f2a06883d253d9053fa8befa6dafcffffa5f36948ee037bf904af8d7efd1e1badf4ab8a887dbdf3f9a4138fe2d4447525e3fda2a400c268a4481b5f5f1dc769bc7e0f87489c9f695109d6ed5fcbaca22ba65984d87c20f1a753cf2e226e33528227ab308a592113490202498f363cb4e8ad9504b666b27e4f26647c900e23d02aef39939fba3f44fcad0cfbb3153f734027d06134725d46518a1832e0a6dbab071f2ece65fbcaefad2fc68bf9f1165e1079e7dfb3b97119369a6301d81ce4e10752a08fc031c88aa218f81d24933a40d9918423cf582bfc2310129cc787d492a415023c44c85f517b2a898eb14d90d34a63cd0fdad2d06a5839ecac7c76abc63f0a8be765808682cb36c5a528522fc4ba4de7afeed472f766f317c5240a5d9bf1c3b4c4181a6e9d2fac526676bdb9f6b53e5c60985c49e746483cb7e71ae61cb5721f247c20dc115e7a0d6c83699dff836c8e6cbab924617768119ceab0691f13d3756d40a7f83742957ec306e3bbc23218469e9a9f81266c16e93ea528380f54c1cf9fff002c6bb18f8f2d532aa019f7f5045d6e742e7d7fee747546ea1577369c16e0e41ef242d017ac43dcecd465404482dbdf9432ae11da22291c6be4346cb1cc4a7c50ac39ccd354b8f1b38dba7f62a4edd9037065c0ef4b0c4505a8b6f2c294d0996d28c5154f5c6a374da83f83f698cca182f5521ecc2941ec61cfea73226e98d8b181313d76782683cba7c312e2729e86085f9f4001743469e935afb92dff309176bc1219dd44ab74616b5580737a98e58455d31443bd3f449e184af11c88c7bc5d031bb5b27566c218d2870bd01b042a0e8868329e6d45f84d9e1ba2a72ddd82d42d3156c1cb946fd5144bb382a1eeb1c2122a849cac6ebab9389b58e14543ab9d4e8f26b41cd3d03a74dec64563bbc2bc974ee418feeb5271a73c92e616e27e670d8db3abaf38a3297533866772a14a40f7f5e0986f820a4125466e99d25e744211d7b26937f57e3bbf0e581ebad763a0206e02083559fd8bc7b273bfc419eb7ecbfcf791dabc804f146773c2c61fe8e58552e6616a00b9eeec1a81384dc73a09811bd5f504407e44cf8241849054555653aa1c84745551418543539445075c0f7781a61ab31cadc949d2830da1831d3b9e83782e1b837bbdc13e811f42b97bd2ac40beb3bdb58f45832b5b26f2c9a71e84278684a1aefe16983105f479ab70f9d527fe1d751e9acf33c35a8dcba9ecedea27c97e8c63f1d946080796bf3697e123c77813c4719d50ba35c7985029811f7dbf301033e5200eb3b4fbbe875f1a6e3197b5fbb3b89c417175415e86d8a00ad768a077ef2026742104b2901ba22406776c832fd42f5f7b01b01e2e3fea8fea94d9685dc45924ac5c141e68cda6467f6596721ce32e5aaf2ff104b56077edd8dbf8ae3ba7c4c0c5ce2eb667d781ed95bd41966638bcf7cbc8de2fbc4478250ea4ef459f61df26a1711f15918d5ebdd942e17ac8faa6913a04a8f0bc0c00680f5b9409ec3c1edfdfeed46e7776c0ead78fe07ce91c5af42ad6595dd636dd25a2cb0890ef47009d01c83ed54d3d837b9f5e01dca3b6f7817895488bc8714116ed8e8f80d250cf95f544768b766ce1f5a659d11083f516207e394479feaf29efa6df2bdc07545de1190baf49c18cbcdadd401d8dddbbc4ebc1e6ed9d76c61b3cff961b91075d200964dd5a2eaf1923d28d2ac0c1962d17d81b46991fca6a53c99143e8b6e205a7cdfbd1df5fea86cbdd55b7b4a469f0ae66f810940073b45e839da504f25ec7b8158acc5f0306a2a76920bfdd7441250be90cb5cb24fdbf4073adab293d61a7dfab3b464e4291b3ea6ecc6e7742d1f1699a397a81f59eda805829f18b5197a5c49af0ffdaa2b31ffd01e6a21d766ce8f77db34a3979affea4cddbcd82e1e35a3d9041b759432f23454e363b5a34ac1b4a00328eac0d45b0819c12f9f1b03b122fd6662b8b6747a344655e8fb639239e092a930e861988669d050c1bba6fa807a81eeb370e5a4142f7e54361d66c469a84e8ef2438bdc60f48a5d279ed1b2ec69393320053a7b03cb0f35c9a575839b58676566d4a48c79bfad7474e431f3a2c7794e690815c9776923853f5c347e303b3e94067f6a41976c0f63640128fbb9c4535192375d59df22e588d3b4344b06ae8377982f9de295d1cb246b012b1cb7445619d44e036a17366d5b3090ba4a07593a4d1e9f8a47dc4be0d8e28c1aa5ad9b1855ab88745859792efad76484fb5c489374c01b0e69d4578165078ef55ce12a949c9acef73a851e55b3167a03ca3e63899c5c37499b4465434412b2b7dc5b6e29a54c4906be067f073b06d12396d8d1db3bc618638ced8e1ddba0d027f87f8dd22f65aa91e94b0c090ff8973692c57470d4469a98803a265b9043111d6b39860e34e8ae0d831a9bdc690b0d4ce47eba446904b99b92a090228adc0f97ec152840824bae143d80a4075722c2a1328a42048e232abc3072d4742913f6ce277870c3912838e7e649172d394cadda9a4e9db4430cd3a9294aeac8145b44698a1dafb85c2b2f563b3342f065891d229f99a5176421f26172bf7293d3821c8ab8616ad1a0d2859316549250094886490b742002628a49be84131d62521347c45dd9605a4c7124f757a197d2a09da4133154da49c629a81f9e8831a589885533118b498a542c0a18a4b03264763ee1270d86c839445ef1871fbbd341e4f57a15c910432847e06f2f24824e2c6d8b114d1041279987748ef89b6cd7e528eabe80ed3ac698713543ffeefabd935846ff31a48f53f7e971cd23be1a5b64fd2aa5cf8fd467ccda61350b189312f7a0c157ef3c31852dc295dec24a124a69adb516e5cccbdee5c3a23cb3e859f2acbba23c9f7e9017e5e8c13c3f28ca28ed1691acfdf91b7d8ecee9311f2017e7d92d87a01272a053da256ed5da819f13936449322a29d532f92a440ca368738b544ac8719df83872f4f9ee0e0e0f57851584220fd7fffbcf69654d589b7536b36c9bf3db49c772cf2d72be7a9acbe50324292cca20b8196c5251391f6f653e3e3d2ed85357ac9e1e9f1e1f1dae3f94f567d03eb4e0da64b3dc43b8444eb11b854b5465b90e757fc66ac6f3a48430202017aa7b165552ca949a7adeb445a058fe9418c6226d8274fd3f9859e082f21a266bf729364b7c22d59035b6c8fc556b8354c3c946498b2091cca369e4ade853925cb75192e47a9c4ad337969494661e37f91acbbe7ccebb514766aec5da16c15f7af188baeca6b5da050c674bf942a65fd1dce13a9c753aa5c17e50cb05637f3bbab48cfbb1883cb19e584faca73b94fddc88f84fbd305324dbfdf8ee7e3759e9ed27bcf504d64f26cc7fb4867900a6ce8aa90c577bf8e4e936fc9193562432ed274f5837ad2788b8c0a22d02ad784a3a979096e2572f2bc6a50f28d3fe7e1231ed2712519d7d7ad280f2079f644449b1dc6dede4dc2edd7363ee0704146fb0e004049d808072708732271688363084880b5c1bb5512399dbcc361e42d6fa288223a804135250c147171c41259890820a45808070049560420a2ae4e076fe80725682ff682fb588f4c125201c26f0608108083ab1523d116793f3c192d09932c1e5e65949ed945d31b8b9e9808c3b946573db88b86deba4971856da171929a8b082100f1faddea2b7901255656fe1c47ab130e7cb349f92a1cc96ab5f380977e0b1a813bfde3077c8fee4625afc677ec996b2a58c29e3b61a4e16db28a9e1e42d89bd8a922fb992ff5459ea15ebe6735abef4c54a8b61b8f4e15db16e3ea715e6765eb16e3ea765a3c486892b29ddd5dbb896eb311b25359c7e7862fe406339da28a9b12566b3589843abd66d8d57f7ba97ee39aefbc6d30331f783534c88375a2dc9c50ee631f2d7ca5c6bc7357f3816a01351c661d822401464de70cfbc613debe63b05155610e2e1e3bdf45b6963b8f44d484185158478f818e3b60929a8b082100f1f3d402c96092908ddb4763e38a5d5e3ead99e3c613a4195c85556b4311c911ae7d8707b70e5f637cef9d6e7b4fc87b6952d0f62fd56dac63172e9c3a6db397efcfed8f381c4316380e2c3f8f2c6c711ffe3e7c468b17c281b30339038664cb612e5712c4ab5205968e14da5505d0ad5f5e8133498e59c734e39256a7ea89fb2bfc26d6e0f7fbe6fa977fcb9f20845eebd78c853af75b8e21ef27ba452b8478b4031ea35199b19a7699efc6a9edbfc20b7a8ce9b1eadb09b34d623d623d623d623d6638919fcb990677e7aac5a6766eacc8c8ac6ab54f3695495a752d5aafa5a594fc363cda854aa1955ad5555e1cc4ccfad3275557dac64eef7d4998a7b3ede089a41f3af2e1f8cf56c800522492e4034c4074df577b723fb0cb52da35ef75c576fe57cc4a4ebd08fb1187def74745c0a453b8ec5da66743783ae83f5c07a603db09e0bebe9baaea3f4530de002cbd49bb427e55d4a611d75f504d119a59424a0e6cb9eaef64f6fc3b5db55c5a5e4a2e29ae29a38887b7a1d96699fca3c087924feeca185d627e571592a7b992b1e8258cb7efbaffe76f110ff1ca77a7a2a1eca7a64578f663bb4ac8168eea8ef855c7f6a9007d603eb81f5c07a603d45a8e69c74423cfab51e5c605c847808f58075af799d500f9810a52f04e322d4033e650ab9640a63419e37c3eb21d4a3c6eb57d4d1b8a4a22ac42356c9f4857808f18854327d211e714aa62fc4234aa62fe43f144798b7e806cb54fb524f69b066b4116b35a306cf80319a19d57c1b0f0613b647191dc3c9828739373c20f7eb976708fbc094dcb98b6488bf7e75f2a1d7828742aa88844fbecd091b7c3883ecca1f1c93bff8de395c1f017e77403ec717105720414c8585101e605e1d3217b2f61fb57267f601940b33f919fcac89b81cf1941dadacd5aa79304a29e5e3104d19e3cc44ad0c6f11b24605d73db674e9f187a4bb7728a4725de518b190f33f29a55c4d6ece395b06f6930f3b06c43c045f42af422ebeec2d291b2eec2ccbb2ee96347a4321a394f1e1c76c764f59dd4b438ae18c734e192b919a8fe828a9a474526eca0aa108527a9379e385fb7abd8af4d77e6f8422777ed6b13bebc6d2e6a0924ecc79cb5f7627a03f5c79283ec755d7de56af7af199bd36a085ce8d25fee1cab59d066b650823b83047a20d86b3c5fdb1c132c54313d7a146e2f261076a57552aec4678764210804e003c0c801c0f70dee6c6c60c168d1a3534ab1919d50078764210804e00720080bdc7f1c0c68d4d0d1aac193534ab1919952b0de07a43fdf27b870afb174ed590c771007cb8fdf73ffabafef2d643103e5072256fbdfc490457e6dd8da490ee9c93356302a02cecfa5aff8183e0ad17ce7b37ac771a5b8dcd667b6d336c8da5b14ba28e733219ec25d2fd9cc984f523f92a0fcc7fc8fd3bb12db93f04ecfd02c0fd3aaf16fe2a08f8bb1fe08f1500faeacfa1af2ab91f00f873e58fbe98723fa62f0f27c1c12ff7007f2c1bf8bb373446a54aeeaf91bdfa69bce0929c0cc682658739c5ca9e247faccf9970fef4e544d1628489162339c779da46092b1f4a081f627782104209258452ca78bb2184b13f5696527e5cc4cea4bb48b68c755fb2f69fc5ad9b7f91b6fa972c7e16f723f22f31d65a3ffe8e5a63ec62abff507cfb9af656dbb4cf5f7ee60dc5f7f78fdde590de6a4dda2c8bbf23239ab033f49fed354d6e5e670fa2a0fdf6d11bf2686d7668df42675f94e96b4d61d1d6f3c8c29c123cc61863b5495ce787eccfa5d67b59ac7f586bc6d918331a792009de5ce28d5e12bf02693fbb57d56015685678067f2be3a4144e8a37765ca7b9f8d05b5bd9d6b67500b9ff27f74704e4fe09a5e4fe8d8707f248ff10aeb85f432ad9493fe9770e92e0b95b4cee079554780ebf6d2e4e6323770072ffe402b3c41ce449c18f50336824b8820b17b12f4ec09298600a30be88410a1c98ba643a0869a2a20c1f7670c28909b8c8721012657081254a64612204114c1491e1700497314950c044f1c28823213aa6e062c911182b7a8022bb21c8045070440a131643ac6088cc06aa2444033c7851c5952852c00394d89481269abc1c1501430737d410441a0589824b145a74b9f28351093224428a928011b3041035c0f00512599420b208d3d4440d98b20f0851e22206263f0cc1d445936c86299cdc2005521457f8942058204182234a629e9e803012928015901103199ed4c0c49523416820020642281991850a80c83c10c1100242192490e289873161bc640802c58a952426f04288271e3223428a7e10b32504626021a2091d64317cc04a0e947c50f2e203530683101e2a478e5001156378e08bacc803aea0400a2f5e638ef840f69a4004897569428a284370798d1122450b7a300289d3135d8469268448303d2801032563ae98610733a06011e3022a62208687322a500328548e8cac60410a74102448154a65c8b083172b53c40491f1218c327008c3c3ab8c1c0419120302cc17d891172041060452011aaca060891094a08a12f42948982527b61c016182299c082384014ac038c9628b2e4e98c608228b1072440507225022850b198e5030420060c4e94a1024703a32a585083283912f8878419519a4f8b2839019d0508694232d596c58e245080c579ea822cc0ec0204911420c136e6025071e9488a99243972008a862cb6bcc92151469226d90250c2d4250e408930e5ca6911bc298000c16287ae861a99708a192444597264937d030b21e94cc0086a631ae608283a2d7eb0a1a8c481241920d2818454bc610a206482c512287a42df48b294d9260624b1450a26c7103211640f2822dc0e0220c185ca80d82c42c940c299696805902860ceaa405089a7859818509ba40d2346419420c097658c28b2bbcf002032988e28a0c61d45082221a8820900a580b580003244fe010a50b0ae4a00b2f72a8d2258a105c4030aa68e294c5abe989305a8670a2871c40a9e2431522b22c608715188591021b94a8c116418a44602446162550810e3cf8a045103244e902851196d7103121d6c88820b86802050a2c289620b20b27312f38cac2440916599ae0181bbc404a11466c5802832b454a926220e60a11351809c104b7dc8083123d7079ca418a95a21a3ca0850d488800065dd8500328e5cb13276cf0a50b2d49d040480198280204465f54318514ba04b11062c41364852a8e8001858b112cb1830ca2c21a2612b761fd5c52014228d257a5ff7ceb5a9e1ec59fcc40f42f0310f2c4eaf56d7b190062c4866bd4a9ab0cc430b1638c317a2c364c628931c6189bc3e28d05684a8d4dedde349b628c31c6f62b31c618637b538c31c6fe18db9ba2a4f2f57abd86b45c39d313164e38538c31c618db63d2616c8bc5628c31c68e58628c31c6d834c618636c6faab16927c618636c77dc3e31c6183dd6ee4d31c6181d4b8c31c618db9b628c31c6d8d34a8c31065f23b5d1079ff20fbdc47ecb3c8c0ee4e3adf942f1068e9a69acee041df3c182a7aeea6b79296d032e6b3d1f9deb056ace55bd7398cb867d3afe8bc29e33755c208956b6fff1060abb90c46704ea23d0382841152f0574e005fc423d0abf70bf0341b1c3aea8d9a31f0186d477ff41d8c549f4a7f008af0b1500952ccaeb3c9696f399140b23ad9fccdc4de6be9ccced648ec35ffdecad77b3e2eacbe9ffc3270fc9fac22cb2ee402881aa5db5b5e664edb9306e670dd5d9ae9e567326b9f7976e37ed7277c3f8d3a46b16995a361f66d97c4dd3bca9699a96414de62abffe50f654010db90f4270f335c242a0ebb98db24d55fb5766d50650e17c2b0e6ee7fb30cbf0d73fed0fb894bfa22679a8fb21ab83fe7deb55fa5f873fb8a4699aca43691b57513242ae7130763007c5eb75de388b53efcf35ec54ea61a78c32cb1f8805c4cab2efbaaeeb5c447049c77cfa032ee5a159bdeeadb5288f7b2020200e8350c6d5991fe44273d3aa0b66d72d15b01c51f8eb8ca2f11f50eeefb7f16a78349e2533b3fa7b83db1935935633322a9bfaba69b0e358ef9bd618ead88a53fed2efbd25f84fe7b8b09ca4fdb75adc67d787d570adae00d8b89774d2ab933ac96b9e183be67cc6b9a5549665339b2964fa538a52e67ef39c6559f6506e8f4ab9dc362ffb5a2ddc887cf27ce80dcd9773ce29b506cccd9b1e8b4654e5f9b3429ee96e2bb7756e4a3deb80de8ad95076429665866b1d6361ce107df9b66e77abadfadb50cab3cfb6cafd0e2dd3be7bce5ff33cb56d9bb6695ced8af2fac8e557efdbc9157e287843f5b9b795db7e4756bffb8da39d07398e7bcb59bcd5cf6a26e914f2efa350ff595cd4a3aef70d15d9984251879afcecbd6671d2b7d49b165ae97d19b723be26b7bfbf63fb9bf23a9437c4ad7deb65ae39aa270e52ce1f921f5fc628fd395fd8262a6a9223a543fedd1977f47d1a893a31562c44454f64f971fbfedeb40c12e1ee840d0f416fc51cce5badb56be1dbb7b6efeeeeeeee2eda71221ecb1b5c3292d44c4679dbbe17b20624f3bea2fc6ddeb7f3acabf2946797fe8351705cfc290cbdabb2ed6414deb687b2027921dbef85ec01e91752545b23dac9b5e264b9da90fcfe21f9ac2a71b55e61b05ddc5d0ef5c703dcf8fdf11b322d0e5307e1b2f0a65ad0e54a74534a2985f31b52a1177929a55d67cda66c30aa84d9779c55ca38247e6123a636a3d977cd3268210a53bafb20a594b2e99c61fe9c90ce49a76c17f49c5576cb870ca831ce8fb2bb7b4a28a07c6be1436f4d29e94b2f6440e77c2c373efca0285b05012e9e80324bd84a923ec4524a292184524ae9020b21fc0cc287199dff01941ab38c62222e4f1ca5d8dbfd4fb7b18ddca494524a29a594669ca49452ca715c4ccba59452fa284e72f25e95aa29fd996512627f9a84b39e4922056fc05997936661a2ac358ca66d36725d8c31462e072a8cdb9fea74b420bcb1ca7033ddddddeddd3eb35ad1d034cdcc8aa686a6a6a66635235353a3ba353535a91a545753538382dc0ce0fbcbdad4ed76093fc6087f29c3ff31ee1791be989256b924fb31ee776be56a13d68f716b95dfdc17f726f539a96ec7cd55c940293f76cfd9dddd447146664675676666666668a06690b02f24b55218ad24810fe3421add61e23f5dc748b47be22f26b9538afdbfb834f8932956afa2dce436a594514a2923b5d65a2ba594524a29abd69aa6d5aa4929a394524a29a394d55fc6eaba816372aebb94ee70db2cd77528940f90ebaeb87ba94aa59a2a95b55beeed562e3d02c1815b3a4529ee72583756142a956aede4b82d958acb215353934a7de62c9c91a87b210f0b8845e33432332b9a9999d58a86a6a666068bf55e6ad4b059ad68686a6ae08c1929b8b3c3730e0d1ab7c6eabb2192bbe849eea2278a9ce4c60eb7103de48151b27b3794524a2965ff00792411b8b4820f904b72dd10df74396e6c441b366cd8a014d7a803ff8b972ff7e3decbaa8675938366b5aaa1c9e13424abcbc1ea723496dea2717363c3bd3de6b87278b033717070240e4ec4c1c1c1c1711c9cc6c1c1c18135356ad8dcdcd8f0c027070e84ff9e777360586d6e6cb40d285f0c1b37371e743a6c7439a68d9b07e3e234740f705c7a6cc8827b442b0f032027009da1bea2dce57e97b3a9e772aff75fdc7e10e416bdb59ff1aca751c3a6e6ffffbd4140411d472c7417fad546ecf4397dfa9c1e03df4db6393b3b3785ea38d7e63ec3ecee293b72dc5fff9179ffee96f9eb759723c8bbcefad4e70f714fffb397d511b0faac5ab99d65feab3b647a676766932effd9deb929e3755679df7b76a772a9bfabbf7767a73f76ac9b958e0ef773656f1407e76fb0f338cf7a4164fe5cb766da0eaff591d7524a2041997e7fc9fd02026119e22f2853fc05598b9be3fe8358be1da1c8851f04a142f49f0e8726a18f1d12f309d4891ea84891e76f1a26824a993ad10315589e953ad7d3881f1bac7e30c4f5b795660f2109ae8abf6f83dda4e0d6aff1d1bc72c52314b98e03eb31f87677ffc531c4316350e83163f21011fed198a8df883163babb718bf0e488266238a289185c3ed37b2dd618238b89751304cbe1036a9f6f20a534081683c99923e58f9c3c1471ed2e0275fc67a83864ffee819575bc204f11176e48aad70f97b5c1f2806ee68424ec40123a432823646fbc368a3a6ea4b299d15ffc67e52d9b195887e719f67bc97f62f66f261d64a779f257bc612426e170c388b5820548e41b38b8fc402ba268c95101fe3352b69160fe6353b98c87ecdf51fc27db2103415641208b91a51c16733fdcef8280e57ebd94594ef1462beaf8bbac5c1b7ff31f5cb2331e136f2179cb3a4743f3b2cb51f31676333ee57dae9c7a9ab71e912bdbf75e8a3afe4b4ffc07cf97d0604782e17e8dc4a2c19df9af9174ac7ee68322f4166e231b42f186cdcdbb8c0a5ffcb5eca73c20ef5cd962fa7dd45a64a7aa578ddb2346308225ea95999935c571dc0fd5d457cb719cd534ab751b0862542fd36d322a0ee66eab28eff79f0e853ae2388ee338c975208849b985d25a42832691416388fb51188541125c1bcc065d6283e119492c2cee672396fdbf1949d946cc95290cf23885cd486a2d59d2ea4ce49b36b89d65d5b2b72cc418ab53e7e9b972f4be4e5282cb45f1ac7ec30449976413e20d162a0bfe4efe4112f7c65b2148a5b46c4ba552196abb5acd34140a85aa194dea6b261daad3bada659dedb8ee76a9aeebbeeb6a0b7fae5433711d7fd4c6ca70cd6ab693d36add14aadbb1ee33448165688594a79ca1155262b905b75936c4730b9e87b6af70abb16e36c397fecd727cc9dbb6e1fa1a867562b9757402f3c18143c6f316e8e6dec453d5221c92a39fb296afe1f37461940c210bee47dfbec32b6f7f2e99276e21e6f9d6bbab86b15b9879e221892be6fcb654413c722347e23f44bc355532332b9a9aacc6a3f1bc19194f055b4eff217253a88eb35b24d22feb5de6f278c3919090be46ea23f7ea48d1880b1bf89c969da432ca967c12e492d2156ff41497abf5f13a2ceaf803b95c2c893fd675d8945c595dc5f195c4fddc916abd978524002897be53d506a04e7361059146b2bf5455553c4af94d02a3ccfce78323730fdb0038b2c3285991756f412693fedd310e6fd5c870b8dcce997b0d0a04e43fdec499e07026aea8f3465e3948e7f7bed83b6f5283fbc9238f78e52dff20f78aa30e6cf59f16661e923f1fbe852dff4a03fed4bf2c4882f673beddb6ef7edb220bf26c18d2802dea3900429d8b6fd4a1af23660db3200f5de5c65cbb3774dd8a97c3a0bbbce5ff320dabccac5c33bc1a8fc65b79339dc672ba1de79c71f6fcfef98d7dbce553c78db75c32aa9bea38bbcd1db3979a09ccb04f33f19fda4ac91b514a1fbf1124e52b07d55607611cacf5ac1b6347c8a5b54304445b3b40d403d2637e00e521f9dd9262114a703f376a02e94eadf7b2589f3bc9feb39b70b8df751ca6f28b452b272785eab8d6e60d26b693fb06f5df8ddc3fc28d99effe194fc6defe0a26a5a558d9bfd64eeebaefbaf78124d4ef7ee5d77fb4ef62ce7d4ecb1575baf65fda9d8e1c8ae405bf1cfbd5fb727245fdd7e52ce3fef32cf310fea8fe23f273ff73216732f8e340ceb0ed9850a97e2e4e2a39f5b0cb91a29fa5fe2b7a9287ea0fcda72a9584adafe614fe50ded7e5efd610fc25ec66ea7f7ee346f8b37df71984d0003b2dc29d6e23fbb733e7fc8c665996d1f923a7b5adeb4bd89aadf95aae18429e1c556b4096b39d45d994bd2aebe393adeaa6501d07b4b90381e3dbf10102721d1cb4c8baef24598bb5b7da16b7f31639ef0c76969aa62a124293fb8326cdfa41d2fd362548c24d769ab4830051e4fa9faa88f7b972631b18e479ddef031b58fee0a5e3266f4a902728561bd84e9297a4a42c27b7bc596adcbc1f309679dfb4f883b1a9ba1bdeeab6cdbc84526d3f7b98bb4fa13a1aabdc0de73758cf714999dd1a89ebf49cd67e7b5996b93c22b8043463326665d758cb8c8ebca31ba0b4a834ee3884da319a1200280043150000200c0a078542a168308d3351531f14000c7ca2466e509a08644190a330088220438c3104004000200000631063640402046bfe893123e32a9c2fb4a74f2eb2769b351aa1f27b2a01abf765c94066222f6aa0922a7a2a11c524af0c4b4a9da9c27156cb3410324db4ee3b9b52e09a667a929b92eaf886fe950c550d36f82aa2ffc5034e304070ec7798ea24ce3796bc7d404efff58be84bcbd040a6ac5b6deec077d63f882a869507249930435fb6068af8e3d8cb821b07180be490dff80845b1c6028916897623a883124ec6c871b562484fccf57717aeb317c0521df88d7b29cd3ffd8a19b6a8228a13eaa05668a8bbb42e680b0d0c6958e18408d9c40cd015746d3c423dfacfe2881431b800c722933027efabfbc03ea32c8e30e032ae21bd8b218d6459da154d66d0895558092e4beb036cde196848b9afef59c43e4630b190601b431cde1a1838abe21d731a2ed8e3f1babcb0ec9b91bcbacc6a555f420acd1f97bc752d7b8461a8817168d04b3e62657f70b3fe73a58acfc981669c22155fb323e8c5c22f7f1ebc42a622456940ed726c629650a6ee919112a449df2fdd1a9bff24fa3f80992917937a9f1ddc45279a7d6284cf190a208380943bab64001b4d9f4e0180aa189ecbe5c8b706f729116bf543f55c00d6d3479f635917b2487d3c66b4e419395ab17785cf9b049c481ca77259ca1758b9e30339ac39d65241050668b8a3778c2ab5d327d9a7ce313a9e2cf062e42c55c8162a6767a98e0aa5c5c79657a4733c7a4b6aa008917856dc9c5c28ff30f708d5c115378fe908e2a96e12935a57cca5218d0415de0af56456fdb8dafc5fe6f612a6f27c2a4dc106348a04fcd2bb03a03c99006562b7de007e322430f17a7efe0956052a41a8a60c3a20cc876f642bf6db6fbdc87ee8225ee2f00fcbc1a2b317a3911490e2853c9b5d1c7bde11204aabe77792eb42ea737de51b6009ff30fd4e269a309b9290bf6312e13fff2e99246f1c463e3a1475da3683001e0314f68c654d62a3443dbc312a01de80b0380e781d8f1578a6057be8f99b11572bfdc02b70eba3c0556fa01b1f1fb80d54091a8c91d005428a9ea8796a26b157eca38d3191f7686236d1db0075e83b3c93310177f58a02e5b8d8ca8aaa0c140ab4f5798710856b3644c42959c62a1a8b34cb806cd868da76dcf047c24106bcfbbae385ae640b15e69e012020b244fe8f07393adeb431a9c013d90c4954775effc45be8c86d8a8cb700c83bf7ef7229af3244fe41b9b16151df0638af39661c339c3ca448cd30969a752b043150ba58d07235413c8efc9df3dc1b045c8837a599c31098fe1a92b8fa8f014c95243ba9aa352f67a0b54f0f8907ec0e986db4b6d2891be7313154c8e5c96bf1ac2d8e84b2a835d5f594113b00b706b282ef94adc4a5c57feb41fed5e96919edb9483a0494fd94e4b2ac14ef9e738eae402202c174329b18614962f9f112d55b22237a7afa97771757afe41e7a7091edcc1ca04ecd1b7aebb623aff462d6e4f850fc595423761268aa21e16201e1d1a2a0ee4dcf67f2f93c5672e23ada29338c5dca9ffc7fdb82e72865d7e699636a6aa57aea55e918b1bf79462297829d4df1ff0f7cc787493286ae0bce7bc70af020f0f52d616e5eb5ac3ba0228e413a12778dc210098c9d5bae0446b58e30c0dca470c7333ebcdd4157d97f1665e0b0ee0ab5ad016c3e1491b9421a098388265292071008fed2280884a963a8319cdfdfa3f13819722a927a4390b3114eefacd000b4095b39233ba0c8f3f65f133607c84e40302a3e47b785a1c8e3fbcec9ba0c60ea52245ca07b01accd312501e75fb8aef166492089f40b0330383d2e287a12de2237f85984a8611ab03d4281e386e5c16ca3b13cb11c6a5a2df5f77ec531a5e0e9d3641965a8c96ee1dbc24d73e77435a4411022af1d02faf6f18184926f35dbca2c044759ecd4f3d74ed531c107500403081d11e185c5e0323ab5f7914919e2071ed32ba1e0072e2ddf8def33df6854ea38cc88ee01b3c4efca3b20fdece5792552e4a96103f3e0f8c53ad18ae23435a0107c7efd10cefb3a016ff4d65ae3df82f68f4bc4cc7ccff54133f86c900df6771fb4a7e91a04062b8fc1c6c892be0d56804f7d08e2687f4fb22fc072c1fdf5ee75a2595c005ecf6f0d3b2a554c92513d44c50bdedb2c2279dba1c0f65e8fed0d670bb7bf1acc9f4f57b4462823af917d8a2ae736a761989e01c5b20e29a5ff8880e7f55c538a78e8f5b001f359b5e52e3fc6054796e4c59e3cd8d95697bd9ab2953dcd8a44865d191c2b32e9f88ba889654f8bdd824e297fc42e6025d22adb6ff105d17e54b8caacded6610e989ee0ba8fc19f224ad9c39f3788a0594bb6a713194cd2762494f8e15903ebde6a63150aabe63954105a667d5bd97bde6eac1ee4a783952d2d457174041c9a85d2e95e0ea5b8c9479afe0203a0371bf4735820cbe09d0ac7120df94c2ea32287f2dd31fef596bd6b407517cacc1cca8f2c2c2aeb0dc66e71e650e21ea2a69f8409930d69a23c5880a44b278984c0a027770e0ce1289da1f552064211e6fe2f019d32610f08c40361917ff00eaf447cde5891083ae6f143578f6ae6616f43489c6e41c47e82b0259a774ca76e156b573a04e77ba3d8cd41f31aba06d38cc95a667c025cf4811905b68b5193ef3b534d6eba667f93d7f17393045b256a4e6297bc166f2807c426a528a3a73d13ff3ec449361d3bb6a4ecd7115df505cea1a93901aa08a34e90d19c1db5c56f1c2b6698a5921d3655aa10f17594310c456a19f8c93cf4addd02bf6ceee61028a7482164085685467da6d1e1ec2ad53ca5f30c1cef07f797f5bb2a855912b16926883837b448523d203f32a525a38c257ed4590495198706155bb09ec28a8c33d934820bad116009a0349a91928244b6f245c9b49ceb0bbed528a5731ac1bca2cdebc05788690cb4cfad6f1a39844c40fabdd0f1fa8c7485e770a606e91eec8162b5f63e69d2e8bf619e42dc0ee5f687e74e234d499d4a7ab52958ca90d7bf8518d2a947c6ce40bd5353debf8c28d1815e2f3ea3ef083f72e3bef62022d805c27e22dc1816bab144e337399fc8ed6b92627708f0749054facd2007516da4fd44a44d330c3ed710e35f8686922ffe600d5242df7581a5cd491ae981b990672c47e052ef1367aa867a74c6a699083271a108b90dd79730007492e6e92a9e4f97b3a1f03d66a49e8b5de36f43b7fd087ac50ae2525bb1a1868543dfca54eca25986a8177aa69dee721d5266d1ea3e204774adbf6cb6060bfa572151df852844999991aa8ae4bfb8ae9c19c1714177d82dbdc40bdb90766f9194f6fefda468b8e45175a53b3fc573acb341c374d02c6d71207d350a71a06402a6c2936261a192927d9e7f61c15989641997f68548b8a9a5cf86c5646567ee16a96144b5be763294ead165a3b029cdc35abc23798c44d78a30d9d3d74eaf4d7bb6ee0e24653780d3c42feb10e1ee6b4925ed543d74c6afac73f5811be0c1880897ba44bc46bfcf03cabdafd5c031f858e3048ad3d392ef41b371ff5e218a5d9e4e20756b7286735e00488169e067836d56e233a2986bb0c985e363cfce76e8ee653f6d962c514aba1edc0fbc46926834c2896f12fb4d03328e950ca553a5148c0c05c44cbaed167a740d2be76c86db04fb3a0c81273d401bebde71086257230111e7f3b3d05e4ed0cace565c11aef8d173822c863f863f47f4e8e9132eeea174bbe57486e73c699d84be524d152c10c8289c2368d6218dc9d68df6593ee185bf65d32b11d07a715f24c7cb201d2df32d49ebddb81908bfe0d2b139d24c7806bd48b79b6aaa5fd5b3cab46d262c2be82e041e123b49f7d593cc14a55086724aa164587e38ee373fe028b6773cc02f5ba24f13f89d2481067b4416d0ae2f2df61443de44f13e06ae85f07722cb2353db1dea23110e67440144927acd030462a5b5abbe1ee01c3700aac9606177a2fc0832a28683ef2de9386d89d208ac8b608bf3093b09f332fb2749bf6e5bf1173f4ea4bf898a60e7d5cd12708eb480fdbb4391b79127e26b08ceaaf29e055eaaae84b5444f9a51f22da6ba784d0064bced24d903cd2b29a47025febd76acd82cd484c3c8955780b7a3456de15a465319751a9e7311e95d1f6acbd568b0610b44d4e68a45ca434a2abd80f41ebd992a6df2a77d05e5d891b245c7a32012c039fc1c93e7680083296ee32c5cc0887e9c4fa2670d007ea98f24c3835e487172636ae4eaef6b1270176dc2c2378137eecaf8fdfcac72ddac615b815ab6115779e579bf69e7f53665abdaa75385dc53cf734399e53ea1d8a50bb8dd756d2874b2b117b84d1360cf3e1468b229c333607347a50a1907d82032398218448b7c657e6146aca52a4b6e3f10db8e432c2dc7e8f95e288174a2ba2ed69a96238c1675cb14745323f021a1211f68df31da7d6fe69f4d498f6149a355713e0abdf438c4a7ac47b28cbb85366be8c41960f5292c148566ee5b22eeabc53d04db4cc019da80e4de588e2b1b94135bc0a4479a97cacc8f00051a395966e27a205446d91f913e678791bc1ab9b9ba2b5186bd52214a0006f719575aedd6da89f2e17d21706b84f0c0f4fc1d69c574b3887a68855440a048613a28760fb17d558dfb21a76ff066dc4baa5b4a196953368faf73374017aaff1f8ad9a5611050b9ddf8d5a5dfabb4cfe70eb3aec89b0c9f505c3d3d97a421243d3a86a618b9791616d2ab2c58303e017b6e5e0902ecf010e795093a02b1e4bcc2b2531c3c56ead68575e705ba5a6b38e66753e46c8af4677036fafa514e88cfedd8060ae28cfb8d347046d6bbf929f91251d416cdfd6b64d9aa7add94179c5f914c3113136b6495a53cac780e59f0efb9eaad0b7d93214327f93620150a6085c474fb6e648223dfe3644446522fcdf08037e9bdbf5406ac4f7ffca8089d6a605c3d863f3ab99e993b828b881941201c170fca60da2c7064fe57bd3666cad6f49a11718b6c3ebfe4dd6253787bc696d33b448a93c69bf7d132e138ec9b62e0673e718230d54a29cd2f6decfc8417add8b88153938eb872a10340cb4ba415876de1d82e678633e8306bd745d767fca63cbd6a07ef39714bf96203295056c868260773f571d9eea7bf2070ef226f846fc5acffd464e7656921dac84432b5c3b353104b0fd733971fbf982e26e365af8e650921492036504aa18cd29401ca2fe3b9c8eaba7d9d6560998a2c89427f89c13ef30f1321d8176c3fbf65f087dd4c3d2af92a4107e64e2805c8df8ef55a101d174334662329eac55e8a8dacc4aa3442ba63ba008e87000db0000b8f7f117825d6ade3a3080dfe052994010859d4df6bd3cbd3be646fadc72837be8d04fc6a29104f70b7ba633c74fa00a660350d1271d8bae84f4ca55dc50637c71caed4b6c4d7854402da3260e9aec5927b45c3dcac30e9466d05a441bdbd26de337c50abd7b0bde221deeb30bd306d9ec5e51e7689b95fc5c6b0ae5cc29579deaffbf6496245752a827eceebe4a2a9faf3e68507e8d8a51a90ac6dc5966ad0c75d5d8317255f4874531486c791bcbd601953e53d7f5d0bfa44902e1d45e6acaf76ffa77a4b5dc863e807f17f120680913acd0a1471bf3a29c45134a4094b3bbfa1f9f1d5cd4908135e5b1f93a671ec23e7b84597f4dd381e5c5f1f6f220e79b140fe127c552bc9f1514f6f46113e9be7bc161650c41fac794caa609c0f1c3030d12b0cdc4043b9008a1ca27a8f60834f604485219eccd973d069ba368c77f5b6641667176912175b055664b4de527ad732ddfa191e8893772088e61c0492113b23fab1a92afff811a0554b86c984cf09fbbdf820cd1b5072b68433d4703e15fb33256c63aeb16ae88745fd034667cd6d53041a2c205deb1355ccbb0b6497f4936bb11d1ae7c8fff38a1eb995607c02c30a20ac8daf8ec999a387f7177e4a005d28bc7474ada3b4b4762d4cebd2c6572eb816a7bd01b329b56991d97de01cc4987a7c1aa683d11af35dccfccac1740442210d701792ddbe7dc6aa949d714183324d0d9084cc2cfca74afe412cc486161bdb0e873746ccdffd634b352ed45edf5d9efbd6d008467e0c20778bbf0d484dacab2ae406a57b80657ed30d63c3c381d8bac1f0c87848405d0382606451d9254594a1ed7c546adc82117b766b0f2f40b151d943f22246d78a43cbc824631964360155bf42b2d114d763c8ba5d5186bd288bc82a7e9fd6963444e2a1b8474579ab463e560803079d8d20f88f09184cb9110708ebdea25d824d5cfe45d1a1d227e7ec272d582b6af28ecd4654ee09eba8bcaedba99ccbf2b659044a235e7b754254f09f51b92504e050aa03236c4db0f97f9860764096487deefc7900598e67799df3d3d49fff78e3069c789d8eb7b9016cab8936a341c7c32ae7aab6e78734dc0a814fd4823cca0dba96c564db1827e30709941dd3022c8b8aa7915a612e9f0528533d6c9e3ec0a6ee94fb6035c7ac672916e29da853d66a9480694702563498beb5cb622eb681b67b272083761528b6ac2276681f9476d10b049c924f069a356e77dde68ddf86a7a45571085305e6aad607bfcc6db6a0fe732f9fe8012e3c01555bdafbaba247a6aeb78118c284370ceae08f10826cb16d7f55d8ae450597438eb70fbea50200f70baa6b7b8f100494da10ced4572f8fad69cfd8686fa2f7e47e617d3d13c6e03d81d985fef0625f77872db1a921004059e2886a3bd50d63c05a6d9c60feb50eb9fffa9b48db80cd0767c238150e8274f3f35d2d875073a7fbef524219868d29a8c70797a16773716962832c8127f061d233bc4cda01b356e0b4d40073605100c9abcfb403823bce6ce8f0f0bee5eca727da268d44609534872872a33fdb7d0a0400b1c88e5b05201ac8fb4e32a25db0291626eae68716a52fcd396c84b8e6197a27d5236a85e9751ee59156461bdfbf4ac41a264f643b7f2b036f361ae983996cd6759c4efb11d193d56b7965e09a030b01d04e1f72652811bc7e3f9421a2437488948141c398cbbd067fa65212cdb43235f19279c6cd6c399624b9faf45205722e4cdaa61f289a79f3dfef286232d004e752a54e07ebb573cab46507615f54396a3837d0a84a2169037e4a2e83691fd8f5d63396dca30e85dde67b02f39d1335b7fc1f6666c95f73c3f1867c55cf34ec8334189cdfd8e947e5bcc56124f9b4c28fe78c3c05ef1dcc6547889d4947e4721d6041ee6e81d3191ee8f169fe4be04306c564521d1d8b692e2d77cb6b45f0bc1df676cbb620f5c465a59530e5adac77e5d569e36855e0a00101cdafdc69fd78cac42b682756cc2df7eaf35404d50572c5eeee7e0330f0758f8d6e6cb17ef30217187732c694e31e3f58ae66732ce30d621cb117067230c5db923484a192647533add8e1d962d08b36bf5a4128d05aff8e57abf05ea7ec3b611d023604b19aa65cfdab53bab284de18cb184d90945952e2b6f56d9e81e5ea46526458e12352d52a12208405b27afb5c7b38ceab1de8bd86bac46b9cb23c8636b2b04c640954a41a7866cbf4e748c48bb4761c952c9e5d474388f21698042798b3bbf0198d51866087b5e6f895b0e17465f9ce7618049e4a6011a47f792cee7a3d6ca8f2cff800fbab4fb91d536b8c8c0c35f7e3ce99885cb8745b574d00b1dca3e4b856d847c9f830e89764181387fbc76f90bb529ecbc5f4cc851109d28b137b95b3a6b18dabb209488dc3b71dad9da463b7d9d6b7d2086cac17ff68e074b313b42ce09a966bd0fe745fc5c186be092d229f8e4278242c999c9a4282bb5e74939dd384f926db13761e2daf4bb32cd13cdc5d962d1db86f091bf393e8911a9d21e845dda99a00bb287785280dbbdf90cf32a5c3031bfbeb7f8941ac14740d188ce988348644ad2a43c0b557e54935ac7a3f73ee0547db9aa73edeeb1524678b45eaba3c29a56d441514dfa959b310b5f124dc4880bd16965f041874df1e60a39e2172d642d3ec63d3bb45df557821af7939df57f6399528dea98a84a6aac5b715ca57473130374ad8f5b0727939116253ec570a6684963fade08608714e67c373124f84e9de01ce2bd3604c44821db1bde4d62a70a33b36bebe1bbafcf7d73540f95d0a2d34ac39dd647969d63061f04bc777c663571971b3ab6f51c88e83fc5219f3628a073bc39d87a51b4874d616a41e93054671c01d9c372f91e8a4db40333f9f645c0529ba975e094b04ae448f3aeb90f093304537a84393ed1915dda0558def96badb283af479464ebb8ae549a5280bca38e652c711356b131830ed15ed67cd763f77cf13bd43ec855e4e0ce1577db6fd4a426bcd23963ecec90984e49e21be4f2f58e48215804f6dd92529c7c68d4769b19bc93fd462927f1753ca8f6a65f2872908899d4f131d060baab92a16cf311b0fbb8dba224c9ca9049f9d6506b8bae59338c87808f6ce88a9904f0458507ea6a33437274b075e508782f300a9270f08340adc1a3e91cf15f66758accdee91cee5822441d7f98eba72dd53a80cd616e44fd64e47a3972660e31432f35365b6919297d9715193d25c3b301f39cf979257a1209ddf4de2802d3b129da1142d228afa5cda021c36e49e3e560efc42071f0c9039bbc4f81aa4144e97f7cb6fbd219f1f6ec50824d4d870535330064f9810e060a13c07b54d82c0302351155c8458b67848a9c1b91a00e51a3e7790ec102f1b7adf7cb923398761947ac629154fe3db45768d60e90c0cfc6725489591fedfb0c8b12c36eb64b2de909e3115f9f2d1199469bd4b237ebfe0aa984533a4800e78154fb067dfdd4f6eb451f07905d64db88c8e9912316930491345b2e8f301f791d37e95699b4fd3f47f4ef448e5021369348054a4ad0045c788fadb39b3d919880466a0d482c56b98faabefcc90409cd58a7b86115d22d3d0361199897fc501d36c1f82c388a38382e612774e44b3c4bc166d6164f4ba3cb74f70e05742cee89141ca2177dc824d8d5e2224e7cdd73ce06f463c0b01a435fe8de18f3374f0fae1425282eec6f03776481a6102e904d4ce2a4a79a6e2f67a81d1305f4710fe389c4ea4e78edaee6be679eeefd8897787de295d1b150ebfa91227afc8811d2c67b8ee40f8c70be09f6862a9f966bbd517e8416c00ec22f660d6860fd8a68cc01a4ea9805c121b55354617022a8658fb85e651509c1c6dd9cf400e997738666c13640ae21fc661506c58045561f30cf091cdbd0d41b5b7cb2cfc4ed8c810928d0405a71ba4112d510120e058f717c386e40482829dc4ee2473b79094177765b7230113001d43567da9c83a860795b41eb7ec45c3a639405a70c47a52e56df074b40f35b4c0c357de9343eb67ce5e5d4a836d981fb780661b9a2e0ff1ac33d6edb4642336f32c6567206402fcca2f33f454e2465fc25e86522d7f7eb6861f942c57b397b7d5832ad296e9ed82e3fbee22c37832fc3730e16521684f655e440f19bfbca313f5c44159208bac5937a5bceba2658f9761ea811d02e8036d0241a64c6a24b6f8661213ba6edd0cd329bb8a58f267a912b337e6d9d7c7d2ce61821c05faf0bb30016e05532bf62bd2cadaf4ebccac78e3f96e9675ae99d47d91496267fb78c23fc0da7a05401133efb7c4e8ddeb7b4de4908c135e1c42acd502da1ea951cdad5844841d3298fd054a3808852874da6416aa0ca493b3368cb25d15a4d2b9975d40d7db40c94048deb9ee544ce810cb622eba0d36dc5abd164312ce5a19e17cf1a27d428b7036b1414acf0254943792bd2c6204c77cf20121abca192a33d2009575bca3d327ec1e310610a6d4a01871779c31a05a9cba8d41f35f217b1d0bf7e825b95c571be03023b340476ce101b5a00aff7c24bb372bcd308fabeef56a0b511defc0dd9d7408f2e947eb65556d8a0ecaa045a341f0c64e7bb70ade975766b7706b8e5e7c0098abb367c3d16d21fc6739dbe5924ad2d6f2928749c7099c61703ab1512c4ee732e522dbc413170988b2353e3c76e809ec60c428330cd2b49257b811e940979f77c72a316c890678c2f6f220268b75be4bea46a86fd9bfb956a1076056fec12f72b770fe315ec3e0a6d00c9f08d1ab519d0f8799d11050cdc4d4fc091a422537504d452e01678e81e29eb84e90e813eadc56aa4b2b52d2860f71bb6523b6f21b62355d7564cbe0ac8ec11370543cf55361b79f7ef7810f57064424dd4662315ce1460cea3dcd1faa323a09ac14432bcf23a9588230c04dc436d4687bf18b3ca80e6fd31c9a5f151f99b6799c55a6c0bf5c6218d531bd94e138d083098d1f4d0b3e9c56c4c52c97dae1c1b07b2a0b1cd917a34127b006712550cc62631aed5406bd607f7bc8e62d6d47285faa5cb4fcc747d298fce9ebbc524e4647da5e8f048bfc40da5310173f9c1bed06349e881c2b51e82a0b4552a7eec189cbab6fca3338b4c92c185cbb379603d77a6d07c90cb0f661aeac456f234a449e31e9edd673d620bf1d4bb3319bcf4480b511aeb9840c14e79f21bc4b572201e3f61525c29c2da87d0e93d7fa2423af48080b7b89c3c90c07c88b2ec8c0a227e2d8ae1a3b50e20cfb5ef54fb6077bb46981c97fb0770d8520751edfd26b28e348751669e6a2c8e53b41ccb5ecbe8080efc882505a55d49f3c58a95893b2b49238e4fb38c15fab09a4300978d62ea6b1dfdedfcca4c92e6c552eaf1ef776e50f1cd264479e1e38d019da2b44deb34d869b872d6344698806cc8f71d324d8024494a74c5c269acb5531665c048bdabf525d1538f9299057b5fba04e4a8d32ab4a51b3d1532be439449343a35545aca8d3ccf637115c29d9818646f989212cf741ca4da6d1060aca7803381324d60d302fde0c43bb4afefaedafbca097e0dd498ce4c32ab4688895a6b0e8204fe4da7cc6d267cdbf25e2b0b311b25ad25280a0fa57dae5815deef70e9bad88c4377f3afc6ec07a12eca7c73204d351bf65bd67b52280f20606e320f8f4cc3709448832936e09ef7cb2124dcf2829e2c2418e0e004ccd3022888f574f21eb33e379d15c56f317df1f209116076c98d6d9b475a151eda87df73d5aa1db0304a405b50bf6a493c8a8183b28efc586371005d11b1eb6ea06d89f7a53e818207fc03051edf76f203771f27565346131ecf1cce5855d1d7ef89bec973e6e3ba1928f776bc01ee362872dd6d18b27a580bbf5b2e801818c1f91f8ef7a8e66ea151a4fe8df8f9026df95b956eba1c4ffc38d9bf0019a7c0105de53383cafd7d9fd370cba7df64a280df72c76e870eaac1fd7791b080d043a33f731081ffa25a4c10735ec993e0dc15e7f2dab136fc7e05cf220e76934755a4958089417b258f35d1d41124d0f7dda9b9628c8c31e8e9e4fbcb5a372439bb5a2893304b6c0a7118e36cb4fee0b94ede4703e0975fd2ad0956981fb92323f3fc439415b1e463acc00ccb2d189eb34e4b914844e2e46c3b3758a7546514a18b08d2a6571e295ed96caeb89b4607b0cc9b75ba418729e11257a0c3165a34cb31765bbf81d3ea5827f25473c0217ba396c53064d654b2c80958ebd563294768d30cc27690888725684f6dfb9f3910c6f91dd11a97472f05b2ad2bd3b10f3dee802b458bb3f5e3d29a924ddcb927d83add34fb63f4df39dd9b00007595fb7c33acc4b503d7ffa2ebeabc5f9c0b68f0901e79985c13e54b5349c9d0c6cc47ba90c5220af2a713e74e1adb097d8131c264fec3a7b209bb96f45bbe02e74c3fb3ff262ba852ca2dff943745a3ce2951795d2b0350419f070248b63cbf3fdd921144d9afe9644ff63c3aae3c73f6e3eff9e58720b8b07b74215531dc3f917e65a3c8fba48dade45dc3c92adc97ef326e3dccd91cd9c4824ff5baf4901ce12307c2b6f12a005869a8f522ade63d04432f99714ab03a883d6005d07b8144aafe4bfc5901d29d697134ae5007175d75466a6ce5df8217841e863007439fcda359df4fde36ffa412af7b32875cb2476fbcf42ab21faee57802bb59aab5ad5b9075e49e0325cb200090a422f185bfd1d532c2f79dde7f8ef5bc9bd1f0192e870c86d8ad266ff57e1366fedcaa80f01a243d48b5409a2085b1d6d6e40200c960e9a33ec7946182982b85a944319f978b590618b90287a9705da6e745488b04a7ba4a5f83b31cc2725bab763e832550a40d20006df0300001802009c5b3f9e746809ea9d5b08b95428fdbf89804ef085c1bf7196c1bc6d44b2b27afa5efecb102ef21579f90997e1ae262b60a7123de9f0c11baa64242e5d8ea5ecd83323eabb1a5e0e1ec60a7c39b0af6693732028c5a9fae25bf37fc351af7fedf096e9e035391eae1e0484c6a14e5db1e2b9f8da71bad6d8f8663a23163e0f31128ba15e7cd391e08b2ea37dbb520752f2951a56e81ccefb02801b40b80d99bb60439be15a4f7d468e4177d1b8a5d7b54cff24a12d39ff2b911196218b5cd29eb379d0b1592082f2037b62222e2f2cb400397a10c7c8f683e0def6bba3d5b4b43f5e1f6cb5c7e55f6a8774b8a208528b45e760ff8fd5e0103e2042261a2f92233234ada07c27111975bd9fd71d6c483d9eb5f270f0ff1c8362aeac0a4ee12d2e209a83a978afc9931f38a554a8faa7ee2e82df8fbd14a80d926c0fc4cf49fdc45a0467d652a4d547bf6381df3a761f77ada35e1ced07ef8b3c127863c26a0c5ef1e84970c11ed7a894e2ae2963e49148edf972f19e3fb5572417c8221edf3d57d10121c8dde94de26bd19e98c2d4c635a56ec6357fb3862f71856094863089c4d1234ed400fb7dd1aa12925b0e68bf847f804da8dc830571090577020e5a55e04ef7181547c530038043e1c01c37c3c407183caafbd69860f6b589ba6254db8177608f122e7bce1540d30e24e856b45aeaec50d769205b59214a814103cc5485e5b45c44509a4d91b4e39e9cf8a5e45081c94f36b517ce33f42792af9db968cb8438322e31caa0874f3731dfd34bd38ae9c14036c9d7aad342465c91b5c011a45c4e242c9348cb8e99704c8f3451f7d5cf4ae7ad07783e7ace6ffbb2a0cd7c29f163537f288a45c59e349b33d1358ab8d490b062cef81ef1119df73a3de3c97b55973a453f5b4e3b811f5a317aad4b38f5653938f4acf54b40fb34ce8f72aa22339915b0f5b3ca4f17650b1e80e8261304a062271612bb61a2fdb04cd40fbc736ba8e0ea674a3c13eee12a8c2c1d80138468a45b58833d815d4a6d6d5065daff69578923463859c571034b6e92ccafea6289611d2cf78dd16df58f828f0ace4b068d82a08cf717704257fb0bb63335738d9c11c04a1239e32af3bd25441b8038443d95fc3ebccc59fd8893a220d2b6570ce3498575e1a03478eddabf63e90e0bd18f4b3d0d4083735b91189b111faa936f1289840e8ec531c0b406a627cd8943e4fc0843713fb5ca183b323d8b5009875f06d35e3796d348c34258ad217c96018538d326d01764061b135fb2357f92cc08ca27210d33c89dfd62699c597a0d308b65f661a05081cc43aec6b4aac764049dcbc19801fe3a2b0253442200e81bd249c205d0cee81306c0f49a34eeabe798238af425116a5d47e6e0fabf8c2bc1f48a98d6f7c6db0e4d5944cdd0c683cc2d0c093a382c6fe5173d88729e299dc2f21a30c8fce6ea67f2958d223df78780f885a45f697489f214c4af5d4838c3ff2e16d2acb9f7a9ff27880f1f4f7ad8b2001c173954f6e2b09981a2e1934d842de62c947e185e1dca414912a41d1e90e1b091cf995f025427553528ed868a542072a8040fa76eaff12d6c2e349e3059525cb883c554f1a1594ae7d5d6bc712fdcc210c8fd8eba8aa9135ad2ec506dc955ca08b7687b606cfc5ac98f8df990b131916766ebb00a2e7694e90472591627407439912a2392d95c9ded261442a2a264c4a22db0d8bf423e3accde97ea751d0404a25f742e221cf3befa54c6c186c66a1addc05b2bfe045c169b4c63f3fbc80948abeb8e8f413b6df78d134131e710d3c41b00f6ac56f6e24b1e0451e0e1f4e7dc0ce837f200f543441fcd151f871c3b0ae92475af0cd5fdae2860ec096d6a2c6e0e6999a960db1873a3bda775c2f568ff6b9f27e43ae83aefa6bc9aff26dac3b3ec765beb1735b20053fb0004d8c94e058661f4dd58cc903c4858ad113b227e651fec20c5fe6b08ebbb3523ebe0cc91302ca929c05b6c84c13afe3dbb0a7a2d61657f9c26de8a0c6acf9bf1229503a49d305ef44788ecf4f18d9f94a5ee5ef7696287e76b16b9df1b42c95777159c1e112d1040692b00826885d56643a868ee59ee258e0a7a4b125b5cb79a82791d23bd71216d2fd2bd8444f49e5fb174d639e0f6386999f2c2fe878e3a204ca11f9d33eb9e89745077c585eb6f4b5ae110caa8954fb48c09ea30903ee4b35b8e5789bc8eacf8fcd8d83f8830aace73e78a1b2c3279d0beccc6506fb92e7c0ff40f4c50bc766401a575eb16b7dae80e26ee01d0782073c9679bb2a15ab7b4017c0cb0b3a261aeac73e8162f0ee82681da8fa2f068fdf529fed2987151a99342118301ac0065934095fc04217075ad3c1e1b2d03197623d0aa742e305907a21667c6aae99e66ee2a713481bb04aa5605ab5ea92d89ef7885c3f9361103bb4d1826e77e819e3db654da41d5ac908a9030cbc0e7005c0cff5105bd066c40e4b3953025561244f3ce1c87301c4fa437bdb62bb7732ea1d25c665691e7da44c5d3904194ddb9eaff4a66fb9e197d210a9ce3308c76a694d3312245458970e53e7006c8ea7b095c28155c88fec5c65793e02610ead4f3eb4d0f0aefdc5f8f545b760def6e9ce0015c7170275301a8e0c97002101e7f9455fa61e442d10db68a73ef87e0cca377a92f945ed0884d2ee02e18dc82cc59c9da1f718975c93812cfefa4c8af8c3250532021b75ffb666ea560d12a8d4fb7b15607938018d13e72f12f5cf1ff17ec72d40594cba5a0e204f414d4156661f9701f4b4286f63f84dd5321d0189cfca8e71789b6b816ae8ed4a1098c701e40644548857deabfa5b9a6e5396a0fb33ae18e9373400fe2557a89cbbb6ca6afb601be5e013ebeaec19e16b7d50c04fea88e51d3868c5ddda5dde33328f47596669f6d6acb80f7eaad67650fba1dc90a8850e6ce8e38b2b81f2f45fa24ae95972a425626fb76a5a25cc21eba375d085f3ef8cd1bb0b73cb3b6a538a20db77dcffc57282e9abd75cdb9db7d361e878cad92691b0d1f4e6733fc02e4f48408ba1038f449394af64e9674be81632cdc1d2327176b0ee1eebfc32b04404285e2c8fbe6ca50f931ec8d953d60d34eb50bdad7b3886dacb2ac19859b4d579c11b2c2ba8c727357e525ff428f70dbc76bb182e13961675061001a7634a02a90266cd162b0b3cdf9e854e0c20774316c503a284212e351deda620d6a24992527899f4a05c2c46f6ed9684bb65da1c52f4a899cac7fe913a32c0581160f4fd0bc3eb9a0079c1b99ae694bc637917f56c15e28c00d6dd8b590baf00512a6f2a4069043669afba91d518562a594bd8acea37c3d042680bb352823a67931533919124dce9c5b8c61b458f6919c22c438fd98d5c3bed55736be4bc44df32f6a12be668e0865692245b29d52ddae7803b0ace59148a95af7480ba0b953619e3504996ea4e10031c685583d7c68f7183648eaa6c8d8d0327849545102198fdcc11a0218fb7b57760b885fabd287855c87eecc8452cb6ecdcfc641e2b867360b68b766c679b76678ee17879cdd55a10d13970c12ceed50e01b0c5c334b4613aae69a2ef728352254dc69c434b81d43a8fc59cadb55f9a74fc66dd68dcb1ceb5888264c1b79469f3bab5a702a1b68c2d857d9bdc22d51f73a2d68c4581f462dc16a5c5a5aa41b64f489b196ad76daa41a6701cc3d4ea2410c2c9ce5ac731aaa6d904efc9b397d10283956ebf0efe95ebeb1ba306b18c1f9d513495107636583b8ac06cf2251295a1f444d3daba2dbf2c0c62988242a246c73b43608281e2f019907f10508821e8441ab33f60b83c0006a5640246b44a433d601135bc8e1f40e1e434cc95358e720b5bea8c641985692a297c483d12d809976b1816fc0cf4ff05a487bd868671b65d2e450b21e2c111481a60df339695f0d929e6a42c856e822c577d71a74f8086e7ba81f65a2e070962e627e850d04e11610a2ec9c5066387a97550488033e20467acd79061745d90496f2609546c52ceeeba7fcf97cb03609d381c6c74908f2d2f4c227d4afcf532d91bee1159a505777098fc8be5a83f2ce04a9df03542c2665c479aaaff4ae2a584c0da01f0027ce89006fea8efdf88144400095563ce1e451b9d8cd074196a55157456517b710a9cdaa6619a3e44cb13583ec9fc4425aad59b3a30510af4a23260b4c54808cf87a608d5146fc1d9c7c03885ce24e03986228a65b411a91c493bbf468eb1172968de63f2c54bb6236519cb0cebb072615d8ce098c18dbeb03c114fc2f3f959829d76fe33f355ce630bcfb9ee0bd0806e723d0356230a867772b16e64e26f8035fbd3c8c2b08937d4149326bf94c49ecef2e550de6987099e91c69cb95d76f5e7b54c896c5f46ad8819bb6558148e61618dc14ec7ab7a197cc6090a2cd16a4aa8ea3f3aaa9191871e9b01ce6fb67332c6f4e92430d663c6d07274446f7f6b6c51b0f0149bdc822a5e41598f142412cd7d41ca2bcd614aef8c760ed1453fb9216ac2e3bec6255540d4f7304969052d45ea6ffbf8b869a4cde7ab422af845a0f4eacca4bb6a587103b6cd3c63abe6544e9ddb4a6df143c746602bbe779a3f6734da2441d38842a552cb2872b25b186afbb15801003d72638f534d0e15584d220bea96e21c272a4d8467753927cd8feb72f93650c6eb0c1792a8457ad1f9fb84fd0af4154f1c8517be2c9b758e7d9765ac82e16a80544088285244f5bdd0535de1ec80421313fd183c33f0c2c5b5c183aa8745e53b5ed3b218ce1ae44c35a594f4f43fe8dde58206194ff519498967780d96bf82ede6705cb44bb980044f80e914315ffdda8927e1df68b309db627786fd7d2852453c7c26d790cfc8939bda982b097de9c373266b252541c5b2bf68077990fd1e59ada4ba431fc71c94dd202a91b7ebb81f68c107646f0499f518c3b1ddeb10f0934d4d8b2e0818d224cf19d262db2b2b74e00746425d32bb90afaf36d69752f8cb0f2ffe2a2c04b6d133e5c6ce7f2e98023aaf5959f0b10f253e37ac6fd6e11d6a7288207bdbf5c4ca0d0a74801b22f811427d0a245cae47d7cce9c137e346f7d3b3b8ba0503d0a71eb0a165f393279b914ef6a79b723cbf13b79e4a0cbe5d99644d748c0719fcef0efff594c6e074d45ad6e8cb422db4e0ade446539973f421117bb1eb7cb8b4e8897a46871923a0f39f1090a543b82533f17355390262e0fa4d179b879294d1ce32a83e215c6c0e70b7cc0b142de00429d272613427634ef88244ceae8adf5f4de8ee1739a91173a97c0ad8bf24242e7b14f08040dd7c80b3b4f7d205d49945e182951806471c5500c3f242c27a5b36a757c87c40590c138bcfadd3eec4ac3725afa4ced9d1d30a7cd5bef0bc08996c0022a00f5be25b7554bd35e4dc67ff1d26c328fa9e921b95988847a1b84d7f3be2921237f7a5ee51c6f0d4e61af3a316f206c062667a3a5fa28b0e8d332f622f36eba28cdd60704a308ca336390ff0bbfd06ec89d8b8f8695b8465519af1050834bc60b0f28089c6fcce51d7fae3ffcb1f145c58731928826b108b34306353b3306f3097666d46bc84aee8dd03aa581c0f2bf76c1e4132bbffdd75aa79df01509ce8c44544325cad4cd8c01fd0b08d5522fa32f7f01143e0014906556bf809ba509cc2e35a827360c924d7ee888ad66c6716fecf923a8a45433632bb8d68c48033c4aacaf4ebb73f0cc6000d67201e0d56522144687c10bf24440e9a738203f96c735ff97380cab1f399a13100d71184e3f72be4d4084d969142771249f587dda1127719e4f029fd6cd8f9ce9c7e5d00a6cd7ebfea5708684461c40f2ca14e1d92302e3761384d07e06647b2c3b331d8ee8283f3f8ca71f87f3f7d5473fa93337b224e5aee8d9bb1a10212316a187e181658c4f61e9da0468a4b3634e80902fdfd9ec6de2040f5c8f299a2f7ebe997f0e2f6606e61d20afd5b997883ee8fe6d28b745e45c6e2834303a877cfa4e7a8d03ece6a679950fa5e56b538f8cd758ae8b023d8afcfdcfbf2a195c2c89369232003338ba82f3e3f49d97a37066b4d8db8a5ba32fa36f3a88d461ebc4e49473c5605e729e70737e4188fd39779c949621b35bff92c316e083c8fd2f22320f0517e26588178fe16ce181fbb068e5bf8515e3e00437e820c84a336a2387ba900d9f7abf888e8e1197829707ff8068ab0060da2a2d4430d07cb100bcfc547b52d0b72925dd33e9ef402dfec2817fc3a53c260421babccaea051f1716c82486bceb98759bd37fd134bc30602238ae109038005b19af9043e5bc6218d63818d4527a3e22ab6b030271b630223e097e08de88203f8e16b25318cb3da2da10c938a70366d4b45517191f2e88ed254673dea67848bc2ff873b78f57ad296831caf6dfcdfd875bcab36e81214899875941b2f7bf9d6d6330b910501e687a834efb70c3715d332c1779fc0520cf20fd311d9656b7e27e24cacccc201613b759522be981e3bebb21fa310e2fa4a884351b7a580364702d958376d8ca837348171bba8fc2e13c9069d84da8d63831a8c6017b2e636b562bdcd4d293f6c271f8595fff0c79889e349f2e4180c9599c6574e1551ad4ff81079c5aac7055a92dfb03fb0e362f33502db2ec8b5767449034eb63ebf72652298e0987c232d38bcab0b95fcc06c481f7c6c1593841736b840dcfa599a2994e012404d313da97810d858225d152f51db76fb3704b4356c3b15c5af8154231edc04f31f7867d989cbff3dcb02d56722a53053b2c7b34955a845e6fd74d2c051b99c932d5610cc9bb5a22cc40fcbfe0a9e46295ed6fc7422b4a843fb4555b2274df6ea90f73f754617e0d91512a7a94dc0d7492e4ebb2eb9bbabcd321952db99154c41913643c57a86500f1e994244946b626468d99a3aaa517d5339e681d71c1dfed6104a3073a4c8006dff672f519ae6a0192656eff591b7e2a8ba0c10845c34a8d8974ba08ddbe43ab31fe86be60214778a53e8b69f929e278653987b65f544db5a08f5334eab7e5be619dcb6bcf6b7539a0858f6d21c22f0d92dd890c67488dada2d868ab1d172261f4dc704ee70960b2dbea56103d0827332ef1581ecbf216180b3eb387cec4532ded0e541256c7800b4e1761e6b84485cb64abefe3e5e05937e3f28d42a6f8db1eaecc265a02dd06945be38d0bbf3d28cb2d2f3b38048587c91d8b4317e4e09216c23ba93047ebcaad31008eb9245ba39a4f3446c1e57dfb9f373a435dccb8535cebaaac4cc86f394bcc649af5bc74407543df598bb88383eb398c960d81f8b0e7866931032e3feb81be9971090ef05bc65b53ee46e5f37287df797b2e3621b9cd193d1f68638b780c67813bd7e83efbb15c873984bf7557efc02103d7db929b1b035c92ad51cd71724d295b97dc4c89e5c45c1fb94bec4ea67623aebc99c4d3af1f8cbdd6727d90409c5cad272246000c47b8f6cd92eef6071093547e17e95b8edb983ae65d000d3db99948376b575f6d03bc4a64f309434821b090a2dfefc6cde3df6ae97a3fb7063ff9f542d5220db09c92c49b45b23970c839f35e04a88ddec42bc5750755b3dbc4780a28c31f7d8f86c94c238f091ee94d98f77105e00d0084dfebc7c54c48db53170023623c999a34eca65cf34c34c23b9338791610a853fe1f6ec1efe516fc3f6ec1cf1bdc82fff671656622b4d7aed45f9af465c4e0a13fc7987ebeb1e133d5ae471af8e0601eb7d7a17a5c42180b8ec1d03e2200fcb7e395752a458900d0288ff183f779e4109bb7cf1c44babca6d802cbceaeaf1451f3e4f5a715f6e9f79f6472ab7c0ac1d1b78dd833ae7cf04db1a0599d199096bfbf432c51e81c98ba8a7f54f3c78b6b1dc04e8cea31a92e89434184623b17fba507d2d3681c21bedb977772d8d99e2606cdbb9521d319f9af920f3575b568791b92ad81aece5912e89da4eae04c4e57d9e4a89774a5c4aafae53f923d0260a921524748e43a6598db89a5950b008f541efe55e81ce5f29497002b6c87628000729a47067c8f13f5c5b56ce34197a53ac90f4e4ddf07c752f8196bc29f06ea19ed3e62d531e09418c957ec34dc586f24cbc050c0599b2ed391e84e6ce3ee06e25c2f6b86e64830330787e2d7a3a4580fd30fa3a7be8b3175a899937f32f17b9307645445ea302db19729d62bdad0007fefe6ba18d90dd1d39513c596d31905b8b93da631f669f892262f1c949f382ee6aed52ddd59a919e7470659e0ee896fcc9c191ae81bc1426bb8202af1138b0bcfe30d26f731dd0ada8f94df8ce501a2354e5397e7781f15a2ac68b47dfd848eb27f77d05d383001010cf7d22302c268f4ec4bb3dd90048515d656e7a0ddfa68344f57e214671cb06103c030d8eaea3ce51b3ae0c4fa6e2836ac04b49922bd14b6554db6454df0383ab299ffd829eb4073a5ee6cab03843dc491e88db118538f7a07121820527b909423c21c2431ca8ec4bd2d0b37d56d9c0f8065d1ded90f3ab19043711f641898ed3f261b861b01e80b13c5f4cf9c39e2b1aa47d96598a0f7802c9da208e9547b21dd0d80a970a218fdd1e7465403e377feda0f777c03bdc49e40c5d87b5ce6038e6c1e63966e0afdcbb421d539e784439172d4e0c6ecdcd240de1017bb91c4b34918109b6f3c3033f334902f8487432057578da466da361c5923ff76384fcd066eef48aa699491bbecede64c5860612cb3a9f578cecc49f007b8edf3635d507147074d06684e5fb658c5146ad188cc20c73557627142113b9afbec446a1ff76380e71cbcd637aac71e6ec214ac84b8e72e51009ac850aaed727a34d3ee713a4df49e8d51134c4154fb81bb25bdd1e9039ca9b1e606f8ee804540e3c6c82ce192f98d683792c97932f190e7a3d15a1fb62ba90242ed2601e968912c5a8e7727a5f3d57b9a27d6385706053e9ed2d89b04da1f914f773449ef657c3fd46d53d650ad9b63f63b59167557c753186b850a7dca431b0f7a531c6b3966412f068f95d7d0ebe1b045141785af02028ef557888f1f53e11a869796e66e224fa8c00723228ad398718aad5c6e5ad543e774613b51cec77a2c0461f5dd5943dacdecb5d08d0fd4d8b76d4420e29d861e563c5198da34f406a45c33a8e786a1ed921299805e2fd14c87efe9df489cbdca020a4495687a79f693c14494e206aac142f370278b66601240d24aaf05cfbacb44a2cecd980babf1c164c4a978b89dc2d3cd847e22cfa194d6c74811c331be6478d1cf176d5ceafb56e282639cab315418bb6531a1a0a544b4aff5c21a583e7c371d0bda431eb00927be6999e8edee83691eca86aac97283bec07442cc48a4d96b0cbe4f03ef4308022221eeee877a2e6a7b90e8d5632c6494812a887b3ac77ab2a1a5016e6ae060da6bfb2593ef77aa146cb59c4878d86b5a4393f322b03b79483683a32724f13c99c214a98e8e9f308701db62800df7acba61999bbc78f100d7096bdc189844f2e13cd1be641730ab5685515890a6ddf6c5ec7da01ae1d07c73789b8e1e896bd726d31cbc3b7b6bee590249a79bf55f6b919a06799c4ee60a49328529c0ddd07fbdbdbd80793c6235fb07436334d11101cf67fe54c5b1af1a5dc17a00b29402d396286d8395bce34f4805dca5a49713deca5526255a520d438df16c90b78badf6a8d051f342c2de508dbe2aacf8773f4ff1c20a303e5b37afbf7940c43ffe3ad0371e097e993ec6e519949b92138c10de0e258048105323bb188a2a3ff948ac5da778f3e459e0ebe70c42be5d52f07e0a8322173e6e84d02b468deed2c3f2fcb2620238b90c258ee0875a0008c00435c9e4ea6d57799c3bac4dd163d78ff8515f7b9143adf5ab2843108606de97ccdf3cc68ffc46247b6f16d9ec91416ed00bf3a69fdcf9b15407bbaaada981eddf94f712a18d3686c06ada8d628041c7d4eb3e93466a7361d18d7a6fea21480aaa5b5c6f6f374b8e42c45eb605666479a0091c5ead8c1b3633dc65ae67e6c1b22567ef6bffbe7a50dde4c87d106db4d073d96f990939a72f77cfaa18dbed1a9a514d2f99f136d7d2f71e0253f6f102a32d92d61fa10d8b3cb6566b06354227c94087237dab0528f8abec1af90b4603e1415fd50e5e2e8ec2f69bd17df0606140ca9e3e06004c8c7efae791ec7e75732e1b55f7b7dbc2a9ee584dd19bc68d68820526014d937fbceef8d937817f133febbebe03d92ffbe32931cafdf37e0bda34aaec7cc961661301e42de9760d2174d17e1b07aab90f33ffd031564a1d081ee606df80da811a969b46f2eff3b63691f80a595e7c22a166173e40581f31f9291c9cda93d3b13c5b3c1d006a3ffabc5f4b40a5d00448a170a78727a8c584ab0856f8500a50c486eefb1f03017a8060785e7c3d0821d142b7a962a60d6fab0835cc7ebfd49f82cd156b9ff275d5f65d665e6a57287c0c87c5a1ff1e5ab11480694a5d6583f53d2269c6f27321f9389db4b1275795d15e475bf5c81bcec2e600c27d5009ea07a1cf62ab784ce1d8e697c4a816c76d14ede9b68404ef697ea3807f91f9f28f9629438b594a9b48e50cd81923d34762dc206d19dccd62224ec6179b5e9476db6f521c8b0d9818b12cbf6d808e8270976458e782a178ae822bf4b05125f38f7cdeec44c328f7497c58f97d49a598819bf1eedc86e71e34c92340088828b9d978ae721e71cf30faf6255058b12db72b0f3fb4c378800573271fce2777e1dc5739c7142653abe4ae0ee916ad887919fdbba0fca2859e52aaef6c4d35dc3d1a43888a34149c79f688e15cab819be2f18031381f2b159a821148e0cbbe28b4b11a4b522b98a8595f7c70ffa305810f7a382c601a084d10f4c67ca9ffa2a51490f421e18de27299907d144750c03fbec141667d4573e23098cb21766e90145ed9ba51b4e7634bf74203e53dfcdf9019f5bad21188768850dbd153e011a8d0a3348373ac9c652415b6b673586fc34ef7a06d2095748dde4043a6ec6cc30a8688a44adb5fa7d01d3af8deea7027f64a7f1e5185f549257dec5413a1a7af83e80a00105e2b35c630f170f111a2d10ff9c0ef1787b7e3c2ef37823b4a26b58f368000009d67e828038beb1dd9f54294455058b909e84206423d75474cf911db2a118d72c04ff520a5d860a00449088111aace0984246e6f483aca0734d37a906dd0d1c76c406121ac5c1ea2adc7ebb31043e8ddb291698e81f12c60d1a5e96de0bd2f30268456f35e1b89025abcf42c391f7735121bdd5042bee59193ba428ba524afdcbdde69455f11e6bd765fbca828b16955b800d8b8037e3ccbadc960cfb1f36ce9723cb068a056baf62dcd7898eacc4dee2d1efec537b79408e4bf5c42398656da4d725f3b4fe36e6b3ecae3858176574305b3bb1c0b4558ded2ba78b0353fc13924b4a126e498c82df3e50bb6d54c8b18686ad90e9bd785645afca4977697f356595748185752d1d631cb79594ecc013cbcc96b9687926008b1fec4e6690932d654408b1730a390c835aa9f64912a326d5764dafcf76b5edfdffc9b6ca91626049ad8e5861e5e92fd1cf5524e1c66ae243dc8233c59858de6a7091118f6cd9d17ba3dd2de5c9b707eeccf089b3e0e1eeb0865e15586e774d0b9ed30a430b68706a29fc80c70f234cb2190770a968a23ace08602088a1162c8e24348043fe8695511a15312e52d76c475d87ec127ec4f17154e878821588371e886656ef2a83cef50b66b4c4981c32a879e7a4b9d04a9761ce53f798af68f0f5cc54409dd7da936ff226adf5556844c8ff1f434f09a31e10a34a396199c670364ae30e719f29716c8b0adb91fa0bbf8272a405aca99460205f01e6c89b3ec5886f1fb403628ac2a711fc86f6fd9e748e8ba29c1b581bf26f022a4fe2c9246a01ff94ea4d56a2d4d7ca2308ece96192402b43d8ed3b7aa0fc974e49c6905e17ab6be776671146aba8aafc6dfb35ad067c7e7457c93c275d31e78dd87dd9990121615146296075d8b5bf55784d498780cf9c096757ef445d62fe2e15890a6a665f36fd1386c84918c6e417f52a07797a01008838b3aa2c9c48590d1987beccc4a669c1eb0ca0e272a1ac53c52fc2565092ccb38d377c6d7baad01e3b019c6f5b797ac88c1b976bdcd1ccbe21c6a16ab08bd6cb02c50b9c6b6433d3b8abce66bf9887f992cfc106fe0a176e4b221414855c71c718165e6a51f1113f9beb2a09dfda7f05feaae3bc3c979673f1de585649f3953dac28b865b00aef36b0ac155d6bf8b8c37ee86c464a75ab7551b8f83c867ec429151a8f8268adfc12755680018f0b990e0821d9bb1bfd08ecc2f465ce3aa8f0e356148fe736053f132ab114c37f13db79ae8d761bf69a0f0522cb1ca7ebf9c10e40422b6d12f3805de7c40554779ea1d62926f4c860a2d999ebadb36bc851f1806b180cb957cf3e4102718782a829fe920d382ba4e3f603b11680e4ba82e187eb393f2793bc7d18b8d998b162b63eef6567cf32e333e3df5e825a6188fdbd7ab1da82551f0dfda9f574d4fa4f32a25eb5324d4d5d42215e07e5044d553c91f185ea4a7276764fe3c4fd245d00ddda565cf749a20229389e65e003c54efbd55de81db224eb916406d066b9f82072747767e3248df91873d42911b4527174edfedcdf5b24a621a43ae2f06f3db316f7ba9d321d2324b45b8279671492499217319690a509accf2de828d25fb2d932bef9318f5344db826398ff58f1a248728f3eef2429e4003fdae4f9c691736e261242f3de77062b1872e0ce3f748c4eac8be874093085987f363846cd99e348aaa8b190e024cb05c258b4bebf839e772e2a83c3e113c3ab8484f44e66ac3762b4cb9274fb80d4a4aec69627df79b313a84b39493be8ab12058e9b3ac39fa33586494eda340e9869a919dc3c5fe02d9ea54c04742efa6dfd8e96dcec1d30d75ea69e7b7b37a7cc37c8f1e9d4766be8579a09a713b9d121bf7d29bab707e0c2806ea798483084b17fee6c3cd327f483ba24bc9cef2b516b2036f2eeb7d633c1b9ee16b2b915a2683160c426b1b17af515be1a195a67ddc4d5149b1542fd7edd62f02025348f091d409b3870fcf50626b198e92be7ce18755099ae3b429231631dad024ad0c32b4dd38006954ea3e817436c68daf344cf88692d9788810eccaf67ecc860a207d172f25b64245344f60aeb604ab65820e6c8c5f53190d221265b23b4221af0d942dab0b53609bd7cb44fa9baaa4e25a27710c0c41b6c5913f425319b1e7acba3c9fab9a45e4f5a5ea894c21d9130f5f8333a809c177a54972a1e81347fa26dc28ece2cf0059783cdde0addd2b617986341db6b9ca1c330db8a1eea85cc4408d9e6ac9099c686f0e26502fe4cac3afb330c816b8547808e6d6c726880e1effcddc1e24c961573ff3be3a0f072822172970b533c03714dad7885287b260314cb8a99319c2a595b8a6a831bd4fa18a94be76dbb375e3e7b4dcbc0f0324bb19d1578c1021901d28c52b5b6891dd01610c4d807d44f48fed640ce9f4487df09f810b45befdb660a3dde25ccf4dfd53774575051b4fab0dcb95f53161dfb1ed1336defe35af78b6240ccc44afd46bafe390ffb0fdcb1698cb4167433fb433b5210a4d4ead02f1a13b677c13dd69d438d3b2a69cf98d555f9ccf7654eceef736e0e625b3442aae859177a09a7aec800a503955e55b9d8922dbc91966a946c24a7c8ada938096e41839cc71669cec204267635cb9b5eb26885357e601a040b2ba04ef4268ebb31638a6bf101cb403442366a596c541372bc5970e1afe72a4692a41c9be434b5ea3505ed0c393cd3a3d40f846583b811b3ff732eb1d1fddba5ea777ebafd9773ca1945fbc4c95bde6e59bc999d8a7380fd1bb07f90e3dae2288b28890a638260c12a647b68e3ca28fe5e84bc25f13dd07918c74f7071179141fab1231e26bcd8e4b9b1bb6f2753b00b7d80cb196b3a4ca1a92025c08c076f40829f99b86d90b74c3c5952b6d12aaf12509ab2b3b86a252b2284644c8bd9035d36855d0c4dbc453be38f27cc38fca8b0a61abcccda586d654cd502573ea5d5a15c67dc0e13abd2ed37f6c40905186d0686e908bccd8ac21e4e6a42a7d72141261d526a99d12d761a693af5d47a5583655a92e3784d64b3cc436b0de3c6a9f8bf30df4665b4d5454ff2684866393208851249334fc11dce5263e006d770fc2e5c0a90fe3602063375fbbfb596141cbd40c0710334753b1139a6201e05a62c5f94d8568700852961a6956e105678c1160f65afab02f1c80f3b94b7b260878d9f26eb0bf39bc6e2083ac49f51fb5cabb49e7e111f667fe7a3629b2778b784932373f411b62ef4635dfcdc003e4706413eaef5f73faf02cd21ea3574db5c456d5cac6004e7abc0d06ffa89c3e32e712c02c5d3a26b9e3e07b84b99a936dc82c8c1089ba7996af052a0f214e73dd6df3ee7a49dd90a55910d1b7dbedbba64c2d80813d1e7b2730de4d46786bd2122b2aa7e1f4cdd008b9bf1fee4c46bd881da6d048799e2726ddcdc772a158f9acb701ef9fe5b9ee699a26a8555280833fb2a7b730519f559cd89e23e82bc9d4c4258ed9c70f0a22e73099162f89a96b6903358f5563139a31d77657bad8e328b3bf601540b6b8ccf8719c9d99e91077fa95d29ef284d037a15011ce309430888175d4765014df89c6518f32ad85f102f646df376d0234272a7e8bc9c5579e8cb95b9a1a9645ef2abb4099205d50d3dac0c235c06af174e2a01a561d66767d1851da4213c914fddf16e7a1bca26dae153aad8416540d2f19705de15ac54c40188d7bd733e915a507f560db112bb1ea9bad2cb8818f10a469122c7f0546919328506ae1be4afc173fbdf7d82db13d0372bc991cf08a81c03111df3421a88381c05a21a16b33d6106ce879bd03accc41e14fbb6e575140b4c7349498c06de5920f07d66ebdd5736aa77807d158cb22cc9acf5d61d161cfce9d07f46781089c37cb7c5de8d6c118b950a8029c3cc102ab143835afd13b4f6664ac458a3d99275dc4f5ddaed9e6012d73e114d5d84f8fae8ef50a7fc8d412c61139104533d4928a1acb7f8c4084e3d98d6dc91be39ab82ccbdd53d2343d322fa764bb5cd56b0050fc22f5287f19dbcbafdb05a543c46f5fcfbc342bffeb9fa0992b01ff8c30786f5f3124e64d414517a7198ecf1c92794bf5672308a090547723f7ebf62a9b6e62006916b6fe59aa2ff6c6b4ba973095afe5df0cc31c9ce1eaf0f172c819764f3e1e2149baa168c5d9794461414d5cb1ce2c2c0cf8079d04476ea26bafb6c0cf73aa4d8be622eb085a031f2ad86e45a85091f9de7cd9d623289e46b7392e6275c528be87036036e8e7d880ea1a04aac30df90266e56fed5cdafb67b993701799fa00ed18a8ae002ef4e8907b903bc44c74b913c70235b307fc035423de63bef6779b237ab22da57ad063d80610fd0f20ae69c0f1b4e8c619d85c01ed39afe388d859da1bf0160fe863d544dcdcb7ceac619200bd9836deb4b2a27a1e202b56720bfe881dcbec096ca1b5b2c85851134ccef51471c5fada903173c20990d9cc10ff5c8e83a0ce08ad0c9d0ba9f4dee063221152adb2941f2b63123c9edf1d860a09df4c80779fbff949ca1e8fc1032a7fca107ce59fd7b209c6dda334628e3cc535bafdbf3c1fc84ddaa5fafb8800aceae416ea3ebf080482f35a478666734cb07ca9e00681784f85affd07224674fa26d291fd8a1bfd41cc165636aae1770b9709458a0117b2f5b5b2ef2a19d801d46a8fbf5e35b3535c75885d54b68cdb8b023c8614ba08cf5e812c72b901c0fd230059b7e22e407d74b9f29b01a09d0cc09b06683bc0103c84c4a86a488750d487e673fd569868ec575a748ab0fdf7b93f3d9111d303ff34ecdd0dd48c962d56fc6dd94af21fc56a8ac4317f09ed4a14c0feccbbbf09f8bff312208d8f84d7bae1f44ea4cada8faeffa27befca17105e7ed30ac46dd9f8619cb4c22845f729ecff0f603bc70f2c65d32b4c98d9154d2a11a00bc01df0c7090748e2e4951ce03f58c661c5b92b95bcd13cedb69f5ffd84abd9f72338ac0da677be9b801cee8cbc3bfb05f0ef0a24294062a59aa90ffeca768af31b73a6cd3e4210689d714222acbf4566a01ae03f9501d4504423a46a4cc5b2fa3749d3d2757307d45e08c4d803fac9ea8e5820da59a503d58d23d2cc8d3208e47738e08f35e329cf511e6b5e8d43cf2332a9839f684e4821ea3f646e0d431c3e00703000439b243dbdb756b41770b0987a7688ffbed0914b210319201d1a5949c8de7b6fb9b794322519ec086808a908b3f4052b453bbb065383a23981d63b540de763ae7da191ae02c09cfd4612c9b02fab14a3184fc7667d6e328527887836288ec898b1ccf3119f5e29a794f2f29060cbc2115a1778b0708476a5e9e4a9c287c74e3aad1c25e141b219a16539820532e0f1c1114daa2001cfb6e4080d0c9e2488a00b7153128fb8e198639c33c61899a1e0628c31c628654f39e5945386a7e6c110f0e3053ec68e3176ec18638c5ec418638c912ff37031c61863646e792347e1b8cf021c23f9e81c77cd5d923c24bbccb7ac9991729774c419950dbc1e383cf3903746238cbb249d877c796be20cbb8e0679ce928a077d67243f45a0e3a5a88a2df884430bcf93266ce71cd6b5a739fd4e2e2f17ebe2625d3c7771f18eba8bcbf86c94a142350dd278b4ccf989e3ae8fee3b79f46e1a6c1dc2f88af1f1e7088971aacf79e5fce49c9f381932fc85c89225329ce3eee56547e98b6fb21f91929b6a57faa6c9adb34f97968f33994c764e51da9cb39fe92b62dd887c1c7b8964b6d4994c44c8df7cc60f6d249a150b20ede6cc64cce9651603d39887c3692e9d9b1e8ea473e254ac3d03a9a4b2194c41e94207938f742db9c05e3e1e73665925cdd78459e55753e3f2041527a8af1fd48a612048515516912d7b3266511fa911facd92dae8d9dd440c8ceb0b29c8d7344261fce4d8cbe58647d843c9f3d2e7c5438944122d1e4a2443c422607aca73d8516278287764b07c710df21bd66ae386d5aa56162656f94e7d2b4ff9fd904851840867791857d57019245bf255e383711a1ff60228c2c2f88baf56156605733f9a5a25912fa09f5f23d6104a1f154ccfec54f261563736a8fae2f7afbe2432891ca144e253fc35aeea2302829d87b94beee5b294abbe94d3a0f165ff7d3ecf9f3ab98c2fc6c95f1c85ba27c73c1e8efc09757ab991b55afbd23c8c55e44b3e9142323eec0b827df1cd4ffd23e37bb1e2f292f1b5708f8ccfc623199fe9bf20d89046c657fa2f08afaafad1fdc9eb8718f893f731764e97621e8e8bf419c62b8434613cea79b1951fa20fe5ab8bf2a194ccd2a994413ffa064c4e205f2d2f3872fee877c427223dd6260f273fe1d94bec1e7bc7ec9c009e7d63bf1165d85b6dba33ceb0df7456fac29aa7f92f447df5ad9f91a8748e485a95253898269e48d0801f16369c4f5b867a4f156cb5c6bbf9e6739bdb746a641a99dfdd4f30cd002427c7437620ce695d7b9e77936681aed36280ebd0006506d7b1a1a85901306a96cc51025c0788eb28c075a6ebcca28ee279f0b099bc4ebb3ac9ebe8b49229ddea22aef23a7ce57518cbeb741247f13acce475b8c9eb78ec27cfe96c3a3ace48ba255d2b1d5ee27558c9ebf804ba25533af1caeb442cafa3334347c775e8964cd2d1a1e331dd9a3e3a56c7a4a3a3e3b3a85d3508701c3f9c8703b80c3b780c3efc460f1e387e87b80ce0da8d6fefdc0eef72701b3ab803c03f1c1c86005ea300bebac153364e6300aee2229f59e3a8164b87005c6aa0f11c6eeac117d0c39d3f0df6d78303f916e0d15bc037952cc07b78356b88b773433efee96a16106f9f43beb8808f081b8f8123f57bf0fa3d5cd58f1e7c01de832fe01ae11e6ec83d7ef8c17df8e2e7fcc01e4ece5df28387fc413ce78b712688fbf08547de078f1e8e0f7ed34484e4e4f8f0c5ffe18b3b847cf183e47c4484788c33391ebd20395f904ffaf0c91f3e29e46b2c0dc6da12c4739c7d7a0faef385421c734ab9203348909c20df9ce2a337857c4370395f909caf484e4e8e0bc9b942b0d717a200bfe99c8f9d931e0e900be4e3204e84fc2037e44b247b05b8904b847c05d01e68901b1ef920ceec395f189d7cce0f1e0ea0011ed6e0000f651ee0e107010f5311f0b0e5a7879a043ce49f392ee312f838cab003f1c52813812f4619087c0ff8d81d20f3ec0df861003c721c88dfc8205f023c0747c639990f8873dcc9cc04387f464f81dc21a2934fc091108af80478f470124027026e589f871bda1fa803ec70c37d74510f1ed771ee10d9ea240078943ae4b0e3febd31c00d6ba4ab003e04f0c1e1b2eb866be31ac0953537acad1b5ad69d3e3302b8a1d57143d4db10e5a907e0a3de3eb2355db348b63849cab4d770696e8e0b8019c671c34ac3278022ec0cdc0c3ec3a52f439d63c087e4073fc277fc70a560d0b9ccc30125085222fb0981ba287519da1543bba8df7015c9164f89e9a2481d862e8ad4ef2da2feb5df48ddc60d51d43bea9c9174c916238932d4a9af5cd46bb8943c7518976c4d20ca24d9d221ca50c7f2d45545d469145589a8a419322952977193a68f4bb66694394d9f97e9e3e2d372a74b7ac9338f475692204d4df7e1a4dc475188e0e5a4410be8951598a6d620a09708b65324de431ad21e1d5fb09427822f914543044b1e0aa421b26264072a51ae4ec734ea73081ac42c9dd2200e829320d04c1f220c03bf226c514a49697c45a2ae0b1db0a0ca24474720f839fa16aa44cf45cf2e54d0333bf541f5d86a45e322149115da6a5031c6183967d97d32be8e8e303e7a49c5099e7819a91370cc68e28a0de473962c59b2c0a0c41cb7d0e8a86996e7ece273f20d4ddef98c4fc60b8e2d796ec22995e62c793e9f4acce32179644e6fe4a4ccb2a913cfe5ac61f7d64326c27a9fe6f593ef79c939cdc329d512b7e679e92b62ea36cfb3a68efb4d16e99c080876be8811fb1e8e7679e83e2dc619ae1e4e268738f1e9bb0b2c283e318d80085178011684056d80080bc282544cb0202c887f8880b020c98caa162599a5947d8511cc94254b1698182a4ef084e49e341f038a2540f13c27b30b58ba6c16c2e7f6fafe3e0c06c9d2c9587b38937fc42810e108b9bdbc4033af956bbbfc86e8be3d7a3c44f70dc5778c5eb7115a1b07e331180fdc4773c331c618976c38e8f0f968502ed99cd2d9517a6b4a60431be33b96dea03b5aa963dcbdd8b50d86da576fc49c5e1a3a92329f3ee263172322e5111ddf7fe002210fcc177410a822d44d7c01212187c6054843d0500184bf80e7c3ee9b8a21809639ea31b4c5a9caf3173fb2a3f3285b277f71294aee01955755de5505b1fee23e587fb91f90e2032f6edd07970f9c62dc233bba17b73ee474ad94e922424efe723d70baadca3199543ef08e16e71d2d1e77bc78683bf71c1577986a831d8f79f298065b3e6b4d9ff685309f61eef5c4018d07438634c8ce116d44af7d61c4c251c11d3d3b0d1f3ce4000d712226f9768a4464f18fec8b75d3d97788d97ee45bb543fb918f1e0f39bdb1ef3e4e8848898d699048163dfb10d9e28ea40c378ba322b2139183f2eccc2131bfbc9c900c13d8f6107bd59867c9845dfd6685c732f36dcecae6b3937d2e37a94954ada15a168b86c49976cc4310acd5ce595353c343e24c0c38b0d2e38e11e6ec39b1efb5782d8d246cb684e5399d9bb3622ebb99c0c5189ada20a845086638a48723c39a9b18d8e9e9c022b66d45429acd8861529a24365dca7989c88b83f4c1831004c0a1e646d64829312a65110cc3b08ca5ccb2eb9f3d350883a39ed8c3b9c1031bd6f4a959b2b31255efe9da0675342b8479a20a06e6959b5dacd5f9bb946b7f67d90dfb8a8c4cc298c7e66caf7443ec0a524a29598b4f9ecbb42cab9f47b0a22a26817020dd350de2fafa6c4ea22554f97845143c8ffa78450dac3c877549963f936e6f6457314cabdcf479d96fa47482469a2c867da59b851abf18f9350df45cd3bc4e4a89554a51b0c55002cb4bc4bf809431b2c33b4949373538344bfad15151111191942d5bb66c59eb0e93524a6f104334382f8d8e06d97f4c6069567d461be9f54e41d28f694691b0196da8532e5e52a4973dcc1d51ca481e54e2d144cdd9712e35f0f286754af74d2ecbfc374ed3ba6f729db4c26645ab7102483942b0287c7414ce33e6c9b62c337a7ae3450f1d543b78b65d8d47fb3424118b22ec278f45d12ccc5a14ca1d13ea568e949895cf91ad7aa5b1181969485aa331b120189f8d05f580e7467accd3ddeeacf55423300fefa878c8cfbac83b8ca432136e525df3e3260d528ed2511ae4cb4ca28836fd3d4bd853f5d01a19211d191d7190110caa5e157bc83d288e22ceb4d7f00e23c19cca4863ec248f4f3ef56963890d1d300166198f3eca127ba059e1118caf031a14126dfabb41182fac2c55c1867cf43e366260419c2147cde206bdb0f2aa3c2171a65de899ad743ee03cbb4cb043d07ce630adce9e6a9605cb79f6855ce4a657e764cd6ac66dded9e6bdd5e7b6af0234e1e615a099be7d15a0096b7d3ae5d6f2089cc9bd6e6a1a4776adb23705ebec9ce90b4dd154f24c1e3dcfe4cd909d8826a25f08ad8b1ed7d9130ac6634a3c8bd0b9f655544b7943ed0a534a9965db067ec76d58456d1703a78dcf805027ba7b8a860041c38718912e2fa7407ad83cf206314427e17100d11007c8d82c229ce5c3138f16409c698faf62150fbeb5c12457820d2b0d59448315b639dfbc9b56df343ff298d76ddb36c77cdb5cfae6db17b9f6a847e6b68dd3368d931d6b1eaaa4c7c79c1deb3a6e6e1e55ed20fdce29deacaee3648742a1b480d134f6a8ddd9a0d49c72d56e9839f3c5b6af61109e4b0fa72439f6887096dfbc7e4210e12c4f44fdea4114e99ddf2e917a39d6f1d508311367da8360be447080d5b9566d4e518b689ae69b7685c8bcfe569d08b95d22e4638e82e1381b6180d90c2c2a1144b2e70cc8e71b6a4e247b26423ef70ca7b363db86615fa43f030b8bddd9bdb22d7e6cfdaad7b9945e8378997131254f29354dca1b5fbfcd2cf32a65cbe45966fa4e51a67ae9ab0e8875d6fac3f4591de23167b0661806645e21c93c626a7144c334d89ce4081a8e87061b88065b4a9a1e38be80c0f80bab6cd5faaaaffaa2d1612bca3a0af4394f35082436ac60373737d71f8c05db96d92cbbb5634dd3b4b885f53594f4aed65a6b2a865f0d2f3b26862536f31b8cd2a22320983caf66cc7c3dcf6f9aab37ac2e1c766b8648992eb10744880ac857fd60cf3cbb324dd8ce659ab02123dd3881e52b48482f247e1935d85ca5c1b6820aa309d2b39fbc35a0d5ce7c489129e42b45f8b7ad7ddb980aaa8bb060fc17efc5b0c4462b5135bb606cb0cad6acb0eff14acd23df9079dd82e5df3e22ec17d1dc88fdc83942670da641f64dd3aec43e7a3ceecbe61b88b05f84dd88e51bd8b3cf39876caeddcac04a1e4cc0a6672ebf6cd6ce9e347031cdab67f2b30cfb2cfbcc43982ccbbe5e21b82fc440d5b4b05df7a97e749e771e1f094c64d779a7b9d61e4e473d1ceade37bdfb24b779f4b44de3e87daa1fec9a6b57084df54362af7dec51d7ac14b02fe37a1e9920a81fe83c603b70e2c0cb70032f3d84a1189093084fdee8770c61ca37c75ca59442ad7ccf2b563e7e1a9d442304de166c48abd02acd8ab7062c2c7badf3a5735256cc6586b94480eac7a456489039e6ee730616a17415f5234f6d8441937d34fb28cf9f5f7fb77bf51830cb7a5ed58f9f5735fdc8d38461f91621a28117c288acd90d896433b0b02c1b055c1c80edd8025b3947fbfc36ced12e23091bc6a250ba8f2a580d08f1e0c239daca48c2d66a2dc6629772eed0e01cedcd7ee4f986dc021bfa77a338477b12a8ee6e77fbdcec22174b5a4ceafc555ae2f764d7f1e07c5679a57420d14a1e70dc5d66e666173bdf261099dd70469f5be4da6298662b9739f6f1cdbe4e7ec1ab8732f222cc45f8ab11fa582ac61aa2ccc7990b22d13727bfd0c84f0f703e6f58ab58ee68c3da7537b2987ffd84903e437e99b536cf3cf3ae5b9c67dc17c3d78d2c76bfaec1da8de57ef5187e6c8db3f4d0b93aca0a28989b4d73e941b0732824edf687dcf3e3ea81618addd0fd86f2fc8d7ca1f886569f8065da5b509b6028be7d720ebe917a3847ff58e9b15f190b6abcb1b6c56587a9e401f1a61b6e6e830babaa7ee46b8feea633ccde48a1cf3ea42ccbb41d5af4b28a6518a6c2fcc807215be8b548437410b27583c4af20b5c42a5b3c4b8293387b685cf942c1f2336f5edd1bb32f7bd33c34775bde1d6905d14fe7da5508689f57955d8e986a76c106fdf49f9f1e1ef9a6e2f5f179cc2246aac7384344e6edd2434023203c72e38c8a1d731c155f22d95722d97740543fc06f965ae8ae6b87d5a9f10daa1d5c66c9f292db0bb604d3a1b653b558372b77e41c59f3c490e0d89c59d716d5d9aa6955b5c3129510af1d17980f01a259f231cf91647d441b97c79c871cd9c2d1329863e08a091bd63c56834367d9678e6a168ecf9ca6665a8936353efbacfa6810335981020a0a965d06e162ecfc748eb1182936f98b6a92fc740a466cd12248892d948d8c283c45f849a93c8d82508c51b628400a4a787adfd434d825c6a1f9e6d66a2d9d124a4ab169d4e09c39a2e09a9b1b0f6f1a9c5e5353235bbc23368bb358ead428dae0054b3dac5952fdd6cc5963438f0d6b242dea61bfc8fc9016fdfcaab0edf416d999dd65819b1e63c2d58b088f1a8f09bdb4616dee1211f93403ab37b27aea3f5d36bf986576c71ad11234b79f3ef587b9ddb146643d5de3e7ad81d413eb15dd89331d54e351477146daf0856d42ca155dfef0587dbc25a238237d7efcece1dc2c6c0f5fb9a2cd74e9ab2922cb04c432d560d5e46b20459bcc615cd1a67a4b50caf5692620530da613a9844679be06151cbc84996207c6156764ca15f432bb3590e28c741b7e6cd0cbecbe2add7e5e3a37397689fa4d67b7fe4c356950a65e2917162c7b9872ad5e1a17b86b64ab9c13667e21cc1751cd3d9d30b19c52ca29a594936edbb66d524a29a59432ab5c6bcdb22aa59c524a29a59c52ca29a594534aa9691bd7759e572a994c16b3d6526bb7ad6a5d27a594f1155a186c606383f3969a4b9dc9eb6cad3162944adeb9984ca89a1baf539f4c2dd6a5e5e5e4e2f2f2723ac5882163c68c548ac6cbcbe91423860c193366a0502924cb37b4919999999999999999999999f9aa54303012060606e6240305822026ba0213dd78c590524e29a59c56c6a965cc191e0e4aa582e9a2468d155dad5673b592bd5af16ab55aa55234542a18981a35562b1b36dcbf8f860a86615452ca29a59493e33e2ad8be3ff86b4829a794529e561e0e1796af0dff2e0c9c755c9052761d6f80080bd202c652daeef47961fbb2bd46b166580c2b19ab192b546ab55aad563462ac56abd5aa5986920e214cd63230270c001fbdf37030c99894fcd8bcddcc47ff9c605959d7f50c1ffdabc2865d8d3e93c81c33b0ecdd737277cb898532edf9d5bfbee608b2eccd51ce2c37a8dca07283ca0d2a37a8dca07283ca0d2a37a8dca07283ca0d2a351c7db55fdcd14636a441e03db29a735146ee59aa95b9a5accec59e9327c7f94df764f639e7dc9e0c0142e1275428dc84befa06e56b6f4f1ac318db5e0d5669b3da1ad6f5a7626f51f15d42ddba8b7398cb7779a44c75ecbb3d52a6c61d160a76fad5c252ebd52f916cb578f55b245bd4abdf23d9c27c7ef53b45b4e97c106d34a7dfe569015477e9f9ead8f5f9ea7d77a4d89daf3e47b0e18d9e0f980fd4edbdae6ef9ee4bca54ef8a6ca8f22955b14df4614a887a3cc04f09355861786c98f249f9a47ea24de7d5533188369a574f01a57c6ccaf5291f276c37e7d52f4f9794a9d795c30b7b85640b0649ca54975ffdbe6090eed15718a45bf4f50996739626291134199990f0a4988ae24c957e5dd7c4e315c5996b64c3ad88475503125fbd069e68331d47514b8de7da36417aa8f2994171464a0f4d47decdc2d29d94129175771aacac6b4404ab87a925be7a4a68aa7c4cbe7da5bbd338b8466c4cb428541c94b6a018a87cf53025f437a6f8eaa1cae7862bceb4c97dab415fddf4853448de97f7d56f9366957cc22035abe4d7755159b0e1f6e175511c3bd1a6c6d71a5f7ac286d7354d53a287735aa2c17af269b0faa9a7c17a9d60c353cfc9e7d4235bbca3fafc4c470d56e7cf84d4a0e9e84dd07c4317f1752bb2c257f7b2930c44f56661397a3d523d71a64a71f2d551af6863f22a69f0d5c3194a7cf5193dd1463a47bbe9f558fdbcd5390d86af2158f4d53757add66e2ed4ab7aaaa74e1c7c78eaf9ea3206a43853dd861f8bd4a3c69b6ed157975ff559fabcefab735a9723c9ca20e430485fbd08ff75459bfe0a7385af4478f06fd7e34c751e389ebb4baccf5a6f93d415f6beee7535585d730f0bb63dbcaeaf1e63ea15a286a315155c8c0963c850c2dcb46f4799a03fbd4ab3306a2562f9b9332952124cc94fc790882dba844825b6402983146db89f537c7ed22528f1132b020cfa45516ca1b80fe9127e9e1a9c3c3438316f55fd90bf43bd2c7673fafa68705699b64a0e27aa1f1b52a756ac5869109c2145a23b718679c7746ae5c443b3a6db00045b647e88922d1f2145fae93aaed8902251a46675156aa55949d5a7532ccdca7c3ab6d32c9e66519f588f9266613fd813cd6a9f8e45d12c55378b60b79dde7a8ad1539ba0a03091846699364cd3b4397976a7780a5db638cf328e6298e79dfbe05d1fb84b43edd47a6231a30d4bbf6949e34cbc553ebaf671a88aa31fc2c6a12a88fee350154ebca12aa4a8b28661bbf90b398cb3145b09b7013b63948f5af838e48413f4e468c21076708037ca1749f8ae82434456868852f01c77ddd4070f509e653cf3511c1a8ae24347d23f5b7cd63374e577b4d3299da659d1083cf8067e761ccd9246cfae03df205d070b3ec421c5c412d960caf73d31d1ed71088a0d629aa5ea1be36198b021aae70a17352ec65894736c551cf16b8880c6886597f27962d89c92cb32c7bef8cc4b66b21dfc11911ee3ccac12063952867d48b3721ae41ffc40a826f0fded3d8434c8b3e7c7e0850de311941c211c8317164a100d6494f444a90c54ea26f5a1288661ecd2624b5ec76d5a8d1813593299aa093485356f9a6e3259d962264d13462bb2b3d5a2dc411a1ada1c94aa42dad6924cb1d6a90516e7d72c8be09d532408aac08635341d73bc31f304bbb0431ac421e52bacf91a59e3f46f831fa5c49e5d05b66b4f7777ff708cf970b15926cee74be6d133dac4af75db9c9ddbeae4e6dc6657b5ba75edc9d5fb2362d6ce9e369e3f5c1c806547a5c006c1b0393195f4232f6fc80f848e1e78477c551d4f29a5d479363798028b84dd31813a08d5e040035eb18c43e8a3c7a3a6d60ae2f8f913aa6b90c08615359fb0cdca32ac72b37628cf47837d136b8742a150d8ad19977d45707ac4bfc1e14e1ce617382bcfde9ec33f413aa0c0531a21078ebe6f163664250d6a40c543c9100ff4a01bb36b21f613549d9d75c03f54f340834302b7bb572c0c914a8ce7cd82e52eba99941dcf8d1b9d754951bad1d1d9adacf370b848b6ba242dd361c7931dc9d0511a7bf4cbdbf13831c337e63482514457e18c5a5e7a799e42f1ede10c4fc1686f20d9ca80a44c7bb5a83003faf606ca8abac84e41a1302141c130f19488aebc82b06bbb1a9f01c956694a5015d795522664ca883a9eaca8d4f168dbc6b1a5876b1de8ce53e7e88a4ec38a2922c380b977d09d162ebc401af2e2c85ab1a88f435e0865f12207368b172f80822d7d1cf2a2c7a5898d11c60b126c6a8b5311eca90a172a76460f5ca4b0319a7092022e5081a7050b3be38b29ac6582152e46d8978f43549ab434c1da8f4354a278c2d68f43549e38a9c07a1f87a8acc06504544410830995225a9ab0dac721a424242b521869e7055c8c094f3fdf1e637c4286f4e78957422953ca93ba83289f4fdbdb81775cd4f6837d5ed8902ae901c377f6c8d6e6431bf5d3db8fe4d8a51004cf3dbd1e359e9aa09f6f8c3392d927da60de252abebd24146da89bbe60c1b75a7ea4e7136de46c115ae295c4e52109c2f2dd02d45d6921ea9d4fa88a0ba8c6b7fcc8564769997623a0294155aebc5ee8e7ed68533d7ac71b3496327336b2d431af470d9f92509ca1eefd90458cd4ef3883b926330601c7dd25e6cd87878da73e630a3473fac553ea3328da48a72d444fa977946e997e76a20d911096d7155715ea26a0a774f399e28b6f24be3d6c21e28933edd82774c505a4b9b21a6ffa91adcc688a09a88a29e88ac985c5f4aa484c423c311ebbd94a09cb1ed24f7ea059468e33537294a79f13144c68beecc0d28f435388de5fae98e26489293e9805b8c14b8c7b020b28b6ce5396b8420a46358f79a9c61552f8f13c1cacc62eb408810b98c8a2840a7e3226be4882ed64d8028c9fce61188669e00a12a88024084646c8206b411694f0188e0843146220250a5dd882093f7df370260d26dc38fa313ac282d15802141e7382d130828213383ce65914c2bc5842e9bb222da23c01cb4fd3132c62f1081fc23ce631083430b7458f3d61076f8a6243eb34782cc663180a75c5886ec76cd6351ac157263c2baaf55aa38c8c6699141688e0850aa218525e90c4c5083f272924688152f42285090aec932217294748624242508b142590a0f464c74a7942128f8a2493142a4a744525295d3001d7e365c193c47642550154455067b340010934a1242e0b1794a0e630e4dab280a204590e46d0b2e88109b01c8ee0aa5914410439100108093e395491b4c5097280b27385278722f00c65812589263924e1c990134690c4939f1c84802c6a0a495d64f9d0fa0a49f7d1823a62c066611e62d46aa10b242cac8d8f435d50d1a204eb1f87baa0811576f571a88b244e4dd8d4c7212eb0f0d2635b3e0e71110597cd3e0e7191454b09ec8c8f435c10c1250c6bfa38c4051330ecfc38c44510c8059094893f87a64879978f43435bfcfc383465094fd3022f4dc1863434a52d6c91f91d4ecb10c7c7cfb1b7432de2611a94492cff08a2c1269a828b31e125fae925e762d5762247980f3b6ef1687764abe5b600d5fec8f3b3e5e3a582ddb8ee44245ba61e7982d22ce9af183bae2453901514923d329d8a505344d6468208a25cd1266e2568f2339c41e5a7cf403ab22f34882dbb135f80a24d8c9f76043fdd88d8320925f1d3811a9c517c55d8d02454f2e92628cd32f9e4d126212933dd411abb63bf979fef45c98bcfcb122f3d2f495e788464cb85c8a5c8e5c805c98abb243df8b2f3d3d924f473ba40e969708906a794d2e463ea6970baa9c7d4235bb14353cfcf17a09f2f40cd9a3ffd258a66c52129497efa4b50b3bc9ffe52f432a55b1cd4f3c2a45971a8080bd04f0f5f9e08fd642548f9f085e8e78bd1cf170ad4acd02414d4acd0da1d9350b3a6b744d12c39859f411fb630f9393ddce6d1bf00fdf60ab7d777b4b1058df0e506370b1b879480f4357ee72f91d7768a182949604e948f606a666666250d36fd69b0a9e9b9ee981ad1a34b8b4edf4e8de8916cd1262e2a44a1a89eab945912a5cc08ac8db12351c0c5983075f4ed5c9c3489cf8ff634d8b29b1c6dca914e9a95a8e9b9597ac2869b4fea492a4aaaa841b05345cd8aa9574aa8c1a61ea65edf45fac3cd876f30c1b74f1ed9a29bcff69323c902bd7cfba664fb912dda14a8c14e117da78852518ab0a1f173f391410a1bbd99fda6a4c1ed67f339b2edf2c32d65032ec6843658f98c062cec26bb6b64db654bd9b17331521a9d8b71869d9f9d166d5aa3010b5b791a8c49d260e63c3d3024c180a5c1cc638c31d280859d3142315424815f7c46e5b39828842d1fc608c55cf199a7aca84db050404dc1aff0598a0b9ff98c19d8167771f94499ccabb57c5db215c3e3aecfc0a46853dd940473848d0d720c4f8ccfc71059e6e16956928c5af199c7564aa8e5a987db934d28dc889ed29e5412620b06189294f099570f879e4ea7d3c9c5c52485063d3b058aad139094c9fc449588ac938bba247d5121d93a39fbb66da6a28f36a6d872716a9db918dfe9b35d47031636a3010b8b752f1fefe06294507a637e3ac61743748a29ca8911923299a3aeb0618c4f8c8f928c8968f4bc4961df9af8cc431924de43199ee75006299e8732143d773f529e69043d0b4919a01fc4259e5d349478761baf68e3bd0da1670f6d40d13c8c2912a2e17aa6f17aa6d1f31cd2f0790e69fc8c200612cfbebda2cdf6ec3176a2cd4b0c9e672b3ef3d88a118a32314988b1c118257c0643d2b6f32921d98a118a71356b9514f383c85a8541533142b275025a2535ebe4ea55189175fa410433530fbde3e5da000418923e73976f552465b213906cad90a48cab59ab2be2e90727a0a72348d92f251467b237dd15519cc9dc8629ec2a29da9c3e731b3bd1667ee63000451b199fb90c51b4317906942483f4314c2489f16930f350e68a50a6e8b3166e95e4d348c43091510f6decb498e855fda04e839e3a024a4b5adc48fd252e3f9fc5a3efee4dbb7c3c750712294a9d6b1e4ecb9782d232996f1e0e5f2e25e4e1a4a0349839b1a10b3145cd12821243141325f3939368133f9ba5cda759262622cbd4c4f6c587ddcf67ce715f6732492999062cece66974e3ed6a1fe7b4477b451bce33df9488aceec7b79e68d379e74404330f2912da13ca226a4fb4d13c3c39f98cf67ce69cf66ddefd348b16113b27907c7b5644527872b281607ce6a129e93319a8f8ccc355d267ae7d3208c59922fc27273142dc0593e24c961212caf29a17e19450b461cfb83bb54ffb2cab91146732c7a1c466ec618cd0676e83159782c9ca63abce7b4e76d957faceb3cf3688991efc25618df5215b2d3da2c6a8f4ed9495450b5591000000005314002028140c884402915838249635517d14000c89a2486c589f4ac3208829658c318000000000080080c8cc0c012001573354200a96e330070e3e4e609e1debf746848aad11d53085029a9606485cf099bea0eba609fb91637a0ea70b5bef55d5c3c8f49a83e0172d3997e222e8b418470c393836269d31ad974f01315e3d5ebba8e2c22b13bfa914f062208a692ff142731a56b03f597cb42f1c8bb3438c58d335d7c18e073689b641c6bb309323f092b83b57a9f12b38465c65d8d6084e2cc910c9fbc3877459814afc0faf6264fd99867d23d5e4978d6562ab50266ed4929bfe872afcc1c3d42510ebf39f79f8bda03fdeeb476501e8cf18ac6b31a21c7478ea8cb76975533810551ea71f404d648afb0a565e409282ee5b0aada09fabd0a2e4bc44427faa9affa867470deec993a99ac1011caa5d71072ab0f7cbc6ceb74bb9995f65b13332fc13e0e5959671488300ce489ec1477eec56754098eb86d5e1ad08e0d2df9ab2ff07e00fc053807f2b23f02f74207e80a050dd7503e746ed6bc9dacbcedae4422afce1b89f41e82b595669afaeff25da15a93f8877362ffb4a81a7a8a27737b9e40f9ed7e38a14813ee38a3b6f5d03c8b32e637984b5673dc3508b3758bc259eaf4f5b8c26669060231ea18650581b673316e4147ccdd16c70165722077e0ac326c74b527c563614dc069ac15ae10abc2380d10bd9ee73f3d1d46b7651b1e4868738a343b6f47a8a559767af3216dbe308fbd7bf1542e00884c1a9258e6e1d7469c5ecc107725ca49e04abbf37f330a98e2ad01f77c0b414c5f1a651f4f4559b3330a678e2c20752da9cb95fedc3840c706a67c22d29faf136320054ff56090480e4b3c17abb7d0999b8a9df5b867c50df2af56ebba7f6bb1793aedf53ca71d372613b7d494f6626227595fcf3c9843a3fe19c10d6f1c249c788b51098862f33abc01d87597fd049d3d16bfc8fc69fb819f484bc7562524806e0939f72970b941900356fe74ccce5f39366db5d8b9a13f79a0474952cba7d2e4c57b1c315aae8368ba2e04f270d383b65174a2155b283793cc1aae0430aea83bf3321b2bf1855772593e76a1bbad4b25ce0c4b64146a64657c46fd8d27c7b25549c549c8910e065d078e7c6664cad75c8667b8a737f921d51aaa4baa834f7df5f48a52e4b1e149a5e091f1e77e7cf4ab13aaaa462f1cd3f5757ed6ccbc6ac20d05e224f537ce1b5f3ea38b1da442e983562e862926189d0cbcab2dde692388fd66b457bcd6d33ebde1bbde7fa7eaeef0fb4af7e63cd84a71e7d80426cf53485244d58aff448f7db44cb4b7b2d2d9f300a5625b1d5d22d54bfa0849d99a44fbb4c03bdf9c32f35e7de73275cf2eabbcdda14518cb4170e91d5ecabaa1199559a01de3919c5071c05f1ce142f54924a96fd9e9dd090c1974c77a24dc9ce3c4f07238dcc7037c91d77ed4c2500bc68e228d48c7e692f549c22194b1811bc106efc634fa9d44f9e9bb536daa2ef62f33d4d295cb6823b085bab2380ab50993b98aa91527ea20b4d2e4f676a8d78324f2a666d5ee390d9e0189cbf63c2eb373543a5b7531206409cc5359a9857525a6b589a57b24b8e28c34171761849c2c3ef80da60bf6d7ffc7611f6f9b72c776c00c9cb51f572e76a14a2317cd350ba6cd03f9daaf599f96fd895f4680d672912b265658bb406810bfe4b4363f4b76e704125854c245ab7af1b46c4c63f92e2bdc11539507ed97c50ca4879a67be56a525973ef4e4ade2299a948183180bed50d995b5846d23aacf9101a06f823050968c6990fef8628117bf31ba6eff5a07a08146edc78e8514c767272874790e86fe9c61df381e254b4753c2978b5b2bff1bff04db74cff17dcd5b0fad4d27666235814780f86e330aabee8e37d24e443525ab510992226069d9a8d79c777db8d1423e3e2206fe6617d74e2056df0d689cea2f6d1b0f317099be84455fe93b833542cdfe169a371bcf7777bdcec82094e38a037bb75f5d3ead0ce8db0a4634132a0b1bf925c9684677a7b53a95c8266546e1da48a96b1c22606a7b78453ac42d8c7cd3b8afc7412fa9d3720809664020e8092241e25ea55422f04cabc0ceac69bd7b17f0f9caebac4ccd96f9a5639e3074ad870c400186df64b5f071c0e6154a4feca8065281dbce4d2d93b1d7af522c178d35c816c5cc20409208e7c35444a31f320860d208cecc52741f34f2c6fd6cde3cd876c649706df046e11655f741f850aafcbed80fb8c95768dc1dfb1ec2395707e256f46c18eed810624cf081f8db9e82d8c04359fd20c35a3b04941fb9151076809a6790203f6fb4c9f42d33ad5342c292487ea7d26155e5b831add2c2c482047eb074b9bfa96c634aaa95892680e530ecb37752d8d6994538365890ae020fd6ec526854960ffce9f5949871b9e6c8fed9b9dde708997365aea130acdac0c8f06d2ac462a8a71e8f7563665ec01d63405ab48adce59a732c1da199323d8b04b1de4eb4bc6f712fcd8192e3ea157e59d973cb152d173e2e265f1d8f6ae2a3377c8b92178dacb34f670eab0e204252f486ee0532b21dfb58c2357808388322e422c109a08e624f1b6195647f25195e6671ffbdb93314147d5223a094873ad0f31ef29431e19e063534897d966eae87c5802980afa5619fc9fb76302b0342f18d599138641c646cc8937b9e5dbc89739c4aab23cea83e3b61d735fee205d26f7d031326f6fefcc1f1b9adbae7037f4f2f5f5b143b1db1b001dd0f253c66b4722b40837fc9afe750f1bc4e9bd4d223787ad5c04339008ab99089fed25783262ce8ac35058b0b2f9fe17b86f4214842d5230e837694e123dafddda4198f9f0cb6593ecbd42f3d6e73b4e074bd37c055fbd5d624334f542e738f2cbf7dff0af61a467034ea62a748643b121a73cb46296c580ed55d9419113d18345938692450a7e2ceb05bf7415d4d3c80e5ccf3e99ef16ed45eb431ca90b59cbae63bd354dc2eaf820cc62af95a338f9c72badf7f0da2c46c14ddd8ebedbe510328a12abf21d6a764becf6487a90670a12078dc3e620aef0648da42a80a9d95d04909f41102eca55217530214fc51a8eb61090c0804faaebcea6ea5023c1119e8ff619ac8b083cd7f57e49f5b7a2918145ef10102bdd36ed87981cd25463a29cc59028c1f19440814d739cecef6a24a83e8d6fc6406a01a5bea47771a38a13243c97b9ca21765884ab88e98eea66f251c4d39ba5dfae290b3762b4280f44c30f24b37b997a34d59b9d03ec063bbc55df6f6c981d5b0a667b52e44e064ce4cf8f30f1c74c2a146c2f74197d8937ef59d9da9dbbad932b3f34a68538f1adc96730e4f03d459c4f46242b247cb3322e6c2538db50390dc9e672ac0250b09f5a3de086d81290eeda6f769c440aa0bbcd00fa5e50703880e44eed5ac248a643d05cb624a870068053679b0c72e09ba6dec1cf20bc6e97acb261338ae66a88dfd1721dbe830ccad71878556c3d5c6c077c88d8c33544147c1c021fa21d09de1bc393896fa23a8380b587b9bcc39a32bcd155570737e140a815bf27ee24ad9e0603802c2cb65cc49b137c393140b5d9b900349dab62da9f797e78bf2ff39e6cde8f811d8ce768a35d5f2cec310a9ac24cf18efff8da4b2db35f822f92d4ca95c1884ab140338cb1d25cd446f20ec58dfd704c60d7b38cc44612f08d0630ee2d70943495a8a48690428081f9e05051323c2c016e64a02828f77e5558edf69a742bb9e7f612b645baf1ade07d212be13422d1d3789aac812a75dc1a0462073989ddb491d6351ae8206828fbfd154588ec7926290080bf4b2d89aebacab03f21eb39100f7b48334adbaa3d703041a61b304c224101b1169d2fed4e4b3c051bf5c93d3f25af7153dd08bda667e8385f8f04bc32a7c00701d442b97dd5c79ed58417b6ef5d1b33194874e27dbd8b86a9a65ae67ff5f4f4d6d7610ca18bea119d2c2876598869bf4e3bc27d3b443222ff2870cec71da0745fb700ff277f34c2553020eacb755f64b8df5bc0a1c5bd182d46ee4e78ba026e9975fa9976d185b3b0e7495243d62827405b402694e3cebc74f2cb41b139484f71c56858cb2a256b878aca18fb90b01b7d7bbadc13d464afae185a967ca7614a1a12f3bff6c0cb8d256446ca9c81b0d76b1630a6e20f948d2b4185a7437efe088a284426264238b125a559ce375c7aceb2cc6d06bcc51c0924835331258b131440cac2468c78399ccc086879fc84ca85559f1f0cb6213b8eb18c4ad824090084a8088be72c0832abbdb3c9576744ed633c62f90d3953cea592e0e5ca7978c920a07596ce2583cf6345abc57ecf4c8168eb97cafd96d84fb5f9125996072c64b98eaac754fe675a2a35da288caca743d4e942a9fadb3a3d34a959083942157261848f5790b03deb6919a6cd11de8134e345432fb2049b06126b05169152fa9e3462b1687997f4bd5c970d4683bfbb4d8014f9f0385da4b86315eb4ef9b396e00dbbac7bd268c9b5137b348ea8ce6fac06516d2018fa487d588aba6dc8026c482521e397a341b4db073c74175a2cee19f2368c58455d70d5ffecbfeb84e0bd31fac5dab052ecae70046695ac6850d6dcb0d854c336dc4256d440401e45ec4c63e51e7b5d53088dbfae8a032c58bc363b4ac65c06d2fd2224332dd188f73f7d15a71539fe2cd049c9aa4b5c2502c99be086079ade0590993ff90343ca45bb987e8557067ea886b1b8bec2165bc31769c025fcecf5765cc39f8ab164cac05cb8bdb4b68a58cef2199c17b7709f89fcb52e94dc9462cadeb7ec7639c2975c91b1d2f3fd488e8130373736d09515c621c31b5661030ab1a123ebdfb28024e95a62cdbd13ec2cd69ad445e9e8c012b546d63088e95cad1feeac41b671da4f1c80614aab20b0e7a2edac87d9bf1735bf09ba7ef32a54cf1a3577afbda5686662a168e31426020c371164726ae4593b639bedaa9c5ee92294fe2ed051eb3e3561ade66b35cdb2d363b17447772d67a8a092c1176bca7c5ce1d0a220e0651cd64d765c499e185a8d80c107b7b02434e36e2f394344172298dcdaa31a6336f82acee40a2fdb3376f661707704044d2707b46a1744c038b825e67e94d88bc49e0f103c8172a01e07d7cbb29d336d5b6c3166ce04c7038df965616393b17672a3de11376ff9934d28ce287fbd68508db8a958fde157065cba0e4f7d9bf4547f9c4a306858ad437d3f3638c5f7adc0fe92a7886aeae0882ba6f4917ad2c790bb58172c484b034339c2c9cb07b376b3be379064b5e28e98bc4b2b56da5746ab327c7513ecce4fc2f32814f5e7aae8d9f557b4f71c499b75b3afa2242435bd2872d52461972a35e3b039f536d49cf69e27b9ebf5ebd5b0ef26dfa948bbd3967495c0ac5cb186bd85c9acfb53a5dbca64ad998c42a3f4a364f64fa37b743629900186dfcdb91bef7226012dbdd8934666f8e4d88b980f7e51c5f69da86e8047a1bd9eaaf6852bf83b9f9e766998689ec215df1ceb62325c201c737bad8d1e8cbbf74441731a8a4104c1a4edd31c7abdd94cba07b824ae7c824e6c4988925136ad6599454ce73f81d84c72149ed1369717bccd9e65c9d43bba6fc84618e77a8bed7ea817a537bf63a767c801345245bbfb393288b058dfaa75882cd726f000587fad6546bf66b1139113ab1960d6fecc145862e938457208852ab22b723790a868961fcb07eea2208395f0c413640809d3885fdf382b01fbafe6b8ee0ec9d4a0de6eb3e609590166d09244db4f95b259ce10faa4356d41823d5fb47adb669a9f10777caccd3887da901ef405ba92262795176c0865e10c5c9a99a637d55a14ea807a671d551945d8c3ff53fb16838482ae8a597b96eedb631dda6f06bce448b11afb30c6b23cbc0f380868bbd34d0d5cca335f77cc77f74a68f92f21accacab12811376e75aebe82cba1ebe3df7c6e627837acf45887ce9d0a273835d28308a8c77bd59b5089130bd40ecf24265efba7c7edd95e5cf6ef45ccf51ea847e2ea75e46583e6d7ab3bbd13d16feb3096ebd988f90f7d37833ab1c431bd1ef7efd3e12555a2b339908b05ab83fcc8e0273c91cacd1e2ecf4f0c78fa8bddf228a9e7da823418f883538329ca37f5d8658f4d356fa38dcc19611c91226620403e9d4d805844c04eec14d52af77e80332cbe525fbbbe266acbd6d831660f0ce4d06e10c7030f4117fd8aea78fd4635658136958c7b1a10724aa95a513188708a4eb58da7a46150d4b5ed5924b807b0b3a654c6b4fef8b4c9c1ca5b123dfa6554aa6c29c7b55399558ac651b95d63540ca0ca551316019e872c5b7be4f84c52f4b42b0c954e196bb38d0933fab92b8c2ed03aeff63c7f171b128fa6d2c019c46a52a329bab741d80d2e9425175f6bb7c58332c6c6fc5e5ab5c2c9cfee811e29725ad4aded731404e66913a244f262e301e4206a8156f13600dad87ee058a94261dc6430a90212e4c8b829ec95eacd02ec185d807d1d2aa4c499d0f1e337e8bc0061608e0f6f42d28af8396f658b70eb0628221dca21b4e86c338598c9fdc1dd2255f7ebdc30e2d941eadbd7a6f278b04a33d98c02b842efa2637a23815f1251dc8dba11cf7e17798165bd724727be4bbb4b5d197e61cf0bcd3c5688e3eb140f94b734e3db223dfbb5829dfcc81a12b8bcc45857d76f6bb5599cc1a526972e9d1770157ec2cb0838a476a54171b1028b5af5b5b74e85e15931a1a42073f25edf8000f85983d9251e72558f2cb2b9f95f1198f1733dbb7d5dab7f10dbe7c00a80b522872b21af4799279518b4f5ae7212a50332ac9a05456aee085f101dd9ba384bdd7966a3ec0f8b6aff7fc901ae4c288d2b4b5a23461a042a73904fe9d56c227a3ff738d0e5471657c83a3fef16f16248ed7086e3c64f08aecddbb826e2f1b503264bae148f06154dd1f96a9c01c15259c1de5cc54bb8e0fa0c9051ea9cd3b34dc03a122232fc874c883d318011f7504f1542cb20605fa0e167edd705ca18dcfd472515a0e86c4edbdc408ddb24654d8e4b0ef1ac07263c063ef16d53ba74c47f4de83c73e6cc7a21500c2410083fe4118aa7df06c9a074e27c591fc80d1126afeec24571a90243f7972b1a7e6c5c31c84c1cd94d0a9157e2db5366ab960b30eb01b9d40bcc7d95905d3f8b70186df067c62e54294ea3dc571fb1ba7b59a5bc2ea8bfb51382e9f34d2b426613997ff15f969178192f4fc286ecb79c1147268bc7d3312db2240056ce8e0859b9e2cb8498964c42bc128c274316e576680fd6ba59fc1251cd6402080a029a6312eb60a328688701cacf528caf239afbcb2a46264faf7009028c2fc9c4a1e483a2d1855c79ea0a8cc9a9c3d78a4aa775119cd798595716419e0831dc9eb649f9bfb4aa45aeda0b2aed2809267cc29b0193821cb46df2346f7343366b572d38d13324466c23044de8aec3f5828c3f6b933ed01ee81769cca6f4ae8c4c426353c1d1a9efa74a2640c0855c3ca195561d4fdb9a642372b2199162797defa3e83c276f5154223d81367a72a6b929ee4de0b8beb2e5be9209de131331182315f30d57b6466ab2bae33c947ddabae4fe270f6881af04170237ed807e319d221272a90a4c7a86099a8492053892b24280880b111f4432f9c3a3d49b8e0ec8201bfae8901ce424c02358846602991a8a4c18eb9c4db82725b036df851aecfa0c5e5e58e7ba1f8fe2d78e224c46b9a9ad584f2c7a2ad511f1fcd3c4cc94e013369bb769331bb5a3969db799994d8da6f28d89f5049e1339d917c8419dcb2aca1541bdb7d6fcc8f67644a9221cf18f7222c25ff48d81a9237ec46654515aebbfe3b9d682c5a49c6a8ca726722ed2aec40901419146192b7441159427c3110951f0ff402ba557b2ab18172c6919932f68ae1c78f3677e9f1f1c50260829e4fb3a4598e3f87a86793339248ec1a431e7d9937c0a1210314a805b8e3057df4f67c38b2e7e36be5cef8d6fc0451fb9fbe0bfa6d8f90c9b3375e64b71682f6ae50bd84a10618912e44e65e00b008870f9d92ebe620506bb60f1828e20358502c7a92f9402691fb1c77fd46e6c62a610a607413cc6d4ef1e1fc98157a835f0ff997826704af0408fb8cc5f4910a20f0abc1f80f909e33a8a3915e1ac0939d476e5a199cd4c9b11708162a668e9191524408d4e572ded8c3ee386df86fe0379fce6dc52a62ea32ca78a41adaa7ba4d9380ed73bcfa80895c12002cfb09fdc0d6a3d4dcca8f11109a10209575312689a5803fd7b5106b4baa7fa4b165a8ab589374f8690c61078fe14f4f22e21fcf7351707b18f60b7131bce1a2efb71b192d752bc4d30d567f5238a24e675b84ccf44ad1c23edb9ae3e8f722e02f2dd5c54725750741eef02b46ee410a17ac32081d9bf1cc572d748731cb2412a966afc08d951cb5602080485b672a27de8f30090d684a06887c92bc2400072e9d6f470c50140c399d7f31bb757f78190353c08d4eee8bd56ca7fd1e2f1b079e6aabab0af4a180832777e8303829d8ba2cd14bc40af1bf97de08273a6b00522cbafdd18ffb5437b292cee41fcf60114bb1d40aa26dbf1e1d4bbab201d85d705ba171d7e139f65288d3cdebeae745d41ab0af345f33d406e0de5378b92b4a3fc75c9a4bc18fcae09a10aae06a47acb120b209ba5ecae162d4314ed0c82ab408e5e493f5d26c8070acda5fc9dd022609536ce2bf77cec9df5032e376c39730ad6beb2b979747dba3554db17cc5257db49c77da6021a0bb157860287b3857cbf39ff7980d2f60ae184d18f65534f0ba7a3272c9f681f62c5e236de6421cd1b583f0562cb95d6151631529895188969cebec2f7600a17db7bb2e108bfce5c5ae803c3b5cfa1fb7c29b39b5ab2d3250b823a732d6a3565607406696570a1919749a3a13f9c8e77d74d2f3f9c36fd5fb5b18ca39566cffa908942eb70174d8edd603ce3335664c5fa997625896a392579b4129be65ea75f056d465d1c6be34002dd02e4ca0b39b4b8cccbc5f6d6c7558b9a65fbb1adb5e6c9e1ec05952fcde1f886acf97027fde52dd8beb664ceb4bc0a707f5c8e729fa054ec96a5ec83154694183e989576f0b505735840e1bc1e01c461612ca92d9fe4535e6f86fb5d7ec20e6f6f0bbf99a49a7d07958e7f70e465d57826527b578233614d6359151b991fbfca9b6a126eb5c723d9b565a258ea5fe9e2c002dcaf8aa6609f0bab7bc4008773a3ea41d5caa86a794a959c253448ae669862712a816c66d353050599b3d14d8310166e1b774b29b6c49290e790e65afa5b52326ae2a8da7bc91318ac7d624d015f108912a2be1f9f8ecb881d9b535e16ac3ad0681725ab07759dbbbfc83497c8712443e455520a9209ed0a383b0354cfa0e0021307d26a2efdfea7cfc812b5f7d0a76d2c3f7dd0ecb2ea66a5214cb9913e835a9e5d706e77c9de31207bcb2c1913a16fa2740ec8c71b884b69dcfa65b878c212dc96f1abab2379b8380d12803717416a4750803015b670377a1a18c1b26a91a40d41adb2187a16d75ef5a9c9fd7b25b0e34afed06dc092719dd75e4459561788d475261a46b765c2c2ad7c12446c7b70e44f307f7987f09fc8982417a4b38aa441c23e1d3385f9c48995dfd88467001401ce4119dbcbfb745a98a14bed76865baa18a724e1b56b764c8b91115d800b00fddf1a01949be723d9f1eaf6c214fcc1483a59ac1b89732f46d343612309e32e294e00e8f57b5813145f409c31e980ca1c9e9a2457e945956a58b70e230e5db6dcbe9459226215cd205b5ce1021fc8ee97ae999842daff6538bf01183fdd58b20f1d02a3b0a950c8e1c4112b96136be8ed381f9c3c8b6ee16dbd781174f642f26945f92f2799016837f4c4ba011f8f82143397664ba310777d759c5f6e169747e7c5db557a171f48f7864906866f54111845e7e98c5008ae08cef0ec96e4958b123f905f4a8959fe3f0f65cf068e2997b9b115a030b268efa6be0e741066ee05c6aa44c015602ef038cd62a698fac3653bb1270705fca687fa3317b011b886588515fcb382d6820d819f5e0a29053277cbf2b98669187341988fb14a07a66eb94f051b746ce9735014b818b4a0b465a0ad82c25dce94596fac7250296d554a94fab54ca967497e490babba7d6ca48d7091e81007e508bf179a166c32149422bef70aecf31bf7279eb98d797b52bc09585c34c3b5bca296e3666f367e1eafc714c05110644e2f6c41a24ffc98683ad7797e714799ba7a54d28dafb2010cda1b5559bacfabab8a91358f33fc511642c7cd403a449a95a7bbe8e40ee80e4cebe39f1af111c2ee3f350a574844d3dd72e42478ebe261dfee1f8889e21888d92e5b99c55d69c02ae143270f05aa844860f7f52889fa0f306fc2580f545dfbba9e229fa0fbf9aad089292cccd29b974d85051bfaeac61e8e17f7c20549d61343b1109757ee996e4ad0951ac51241aed803cfdced22f28667faeead3c59469168233766f02b4b5aba3f9b642eca59d1b18dd9d9f1c3ed901e5d26c6bf044c379a0e7ad3eb23eda18aa38f30d078b3d5260cf49b9b6b14ff19b702d180ebb90ab73e32e12897c8d229acf47f266c9c7fff03389813eefd7b5feec0bdbda25e13ea72f38f87876054150796319f683616a364abb4fa2bb17c4fb507cbedf81784c13ec5310c1ef8ba8d3963eac7891efb906f857fc63ceb555eee457b855639c39f2df54377d44879eef0287c1e936670dba20a25f6063a38c6fa392d0b285e278799e4abdcd4b725452f12d71073778903d8f11f33c0ec5613a57dad1c4f4f9bfec3a4f5e700dd3325bd7a49fca389f3d74fe7867bf33a0369f767124af7bed6ee1061e12cdc7e82b9a74a5c8de3f65584c1c65137e68873c72790757331db91cd80681c043b62acfff3192f13cf9c0c9bfae65c8200089f7b8392c086a3cacd6d8ed2c22af82af33203eda35a08193604d10adc0f8abec788c61d8b7f5f9ec67e46a5c7e49dabca1b774314c799e3b5baeeddda2769229fb45fc3bcd5f51b3d246909cc10c1e0a67e2e8844d61c17333ae1b4094a228fef8d973186669d1597de0c5d58035109d2a9cb8042d1175884412912d63c1b9bc98626664cae7a85eaa154d48b4c96ea73dab87dea77d093d989a5a5841a564a2af06cf1728ed5df3ca6eaa902203193ff26326eaa959218527cdf15d7a0ffea920e7f21edc14063195de3538f4522e05d86c57c6460fb91adf2cf348e3408563fe53f0a53a6325be4e93530c867aa4dc79f435fa9f5c789562a958c96ba53c073487eb0283bc715a9638a263e633ee91f98e7141d5b4d08823ee535227855b0be0c99addbe8879f2892fa3e56e50bed0cc4f7a1c87c6fb00dcf93ff631e37104fd5c26f9927c9f7b799d8c21858463f1fc83b19a5f255c23ad6c407e2551236aa715e8883bf099d700bff9cc7de02d4d242efa5af92813c7ea38b2732378ebcbd2544e807eb52dceaa2d62430b68fe883708b0b4f36dcc2337995d89d7521110c036f8d7c20a0451da2687513168b80aa81c4152b6d688515cdb27b4b7c1f06661d1a408e5f77a5ade2d17f9a46cd7b5ad60d6239b7e548dea8cacbb26ee76ed289b067cfb22ef74a15ebd7aa0d1f3b05dc8e33c22d698c401048ed32edfe29828ea6ddba06dda113ba434fe8873e062579eebc22792df8703053c1821108d3fb20a4d057dc1419c6845402c27c9993777ad62d60ee0250403af1e77e6d2065a9858985694b70c3f1af00b09182bdba7c41ff0ba54bec584aa41d5cfa9c0bdf91bb6a7a5244bb31b05e68bb0582993637a2af5874f014959d29f1942b29fea84947ed01f5600dcb7b1b39cda9c616505240eac88abd2201ad4ad1507349b829ea969064d0aa09e8771729250c59b6c6a3b296628c7f0028a0937a23911e60c3c3373a37a5cdd8b157c1aa4c69c8b40996353f54283f8b86a8c8312007ea4b379daa0b7709ad25d30886e8150a934167dff3847e9824c82c03a8dbeef3e729ae8d9b500459070c8a79a6b33b9519a437ea7e15102caf5a3656c8e9563bc1bc4edce21c01010f28037ef9a62f9dca9dbcfbd8ba28ea644a59a8a56fa28dc2b7c1d5bfaa94de30e17c054ec1554785f093bd1e3587bb7cd0e20562a7a8fc3cdf291a2c50fbdc4ca35441d58e07cbb29ea2aff1e6c199f9594ab728895bdc73d4d0573d0104072bc06e071501c1f474003fbda0e395a6448b50ce9c9091b5ce863d1dec1a55cd793612e3902aaa5297d5e0ed06ccc6fd53889fd435068ab2e6bd336f3478275e3d0af10be3509a3a0d2a3d5380dd0e7a9c41308f064834165df1c1b8011995537fa7ce97644c3f5c5c310b26468dcb90bccfc2dcaeb7c0627458a8ce7dcdb1f7740e4c3e2f094067be444f114340ef346c0bc1f6c96afe5b4c34811c76d18794eb28b37c8885db80eb4379880029e6a80a73a42ca13359b434de86ff28e026d8fd81221bb851a192920c708387d412f5bb1569ee42f85a11ca7a7addd541d1b087d9380f57dce6638a0f7d1672fa2bec93a213f2d3488827b0cc24a1effe10e2622617cca83cfbcc5923a5fb12ef5b5cc446ecd05a47412471175fa546632247ecb1097b0c83575c61b7604ad18787f6ab771b65d9a0811628e05f7e03262e47f22c1b422ca00525250f9cf518dc85d94be56c33b430cb2dedf87246c33e28b60c993b84a9b47711b1895e8fbc16b40d331d9ba75bea327f6c35c39ac75fc47dabbd7ee14b7285a8637bfd47afd68a6c24426f35e02c835f79783882d130abc6b40ef6237b002a47c46867dfcbbf30ecd1e4309a236330f70d11e66af451a5d6312d3dcae9fb098e189222559c00a85e2876b6a77ab5a7df34ac5574d5b17ee693fcdb9523dd1d5208a4672ce419215d3bf2a2fc8fa38d64c7b0de239566c8fcd14235764eb696b4d50c45b394a01d052ebb4b9b6cf8b272a8cbf30632d436c32d0f1187171c7ea6ebf520644609f3b09e424a36afdf661ae818abc993d72be18afea381a1a468c1f470a8a344d697da9954a2bd543ffb9001b4c5bef1d307cb409c6719bfa4433dd2d362b3d3cbb21195d163ffbe0f4a324e3c9ea6f8c13e54bb78d0e774c22006b3e62d69e8eb387e7815acaf2015f010daa4c0146294e9029cd4202469ba88266220dd523bdf50f7f4a7d3919d9d8379a26591309672bad29cd21413a4d442f39d2b9d8018eb931837b2e43cc7adb53e480b5535c28a74341c0c2d918c49fb55873b673842c2298dde6f8b9575112925b0ccdb0f2b74bfb08ccd6656d299bb61e51d3c6eaa51781ebc2d0caf8669148133fd6a652862591e73e861476db04f802fe94394791572433f5d53828262819eb4966f7785cc12f8321b8c95988c17353c6bcc456dd9e77c4b499443bf05a905ace783810a60f5b30af2a150496f85ae26ba2f7da1e399c05780c1a8d0829dc3ef5bbaec472f5db5f6cbf205e35b19dc5b52c3b375b21444648a0aa38572f261e5c099b1166e0f4e232534babcb0588c0361b6a9e2ccc3a0b7e0440796b67df93549fdd4f99ea5a557de0de1d2c303e6dc524220a67265c18619a89cf229d8c176ab111cfb4affb555557c63ea39e0d5c15463dca3b73b374ee143c9857fb6c378673c2bf12e987e42ab396078f16841795475e7ff7ab84a4f1aad0d3c13cb463b4cf3c8529ef7a80dfc538398f9326a84bfca54fdc2880d527769c5f37c9b3317613f48939468f397456f5dd694726f221e544bd69e44f3ee2d1c58bac765af4cfb88bc235f31b48bac05ac981b62156e94d059df188654f264aec0a3fe654e08f046d08df523c0622142b871b08052e42180bfa837abf67078b9644fc955738c487e500a959e6f179a959a8c08b1c5eae3fe6800e616157695c04296da4f1f065279721e92d569fde2443f784b9c603553bd80cdb412d88c581113430336d369fea435b292737a4e04c9f0e10a7e44cae99b86f23f984102b5535c88900c66cfb557cc3d935e394394f78ab3f5a222be79e035e2f00cb09336288f27f0b349f73ccff526b0a299c1f49d0af9be3d97a9273330aa05bd943747bcf91501000d675ef0d7c340b471876a2e8e1422c89ea157128a7e4f6a439ddc9623973ef64471fd241dac44b5bf461089d1b08ebd028e73720e3a3455217ec4add21565a8b6607d09ac1786fad02e974b7075958792a9da266b6cc1e8dd568b997410a9eb79d2b508b8b6f9c1a70837eff04e900daadea3f78bd73768ec06628fad90a55b5d4c012fd5cba3b9e48be7f7db004478ed819bc76a79602a0d66202aa23d19188800a64a4c6d7a9a29b05d9d625eaa7e7d293e0e376ce3ba625e2a3d184b374c575a0d0342b2081f6fc44b699951695950844c324e065e2adc2a6bd481234eed94ef920ac5a4188c1251efab4a432e33913bcb4b096290ed8daabe617c7cea1ebeeb3d2f5598a6a351589f71b9bf7d8e4f5eaa74a07e2c97ff9ecca6cc8ce488cef9bbed1087f791d64cc16540211294e24c0019f2a34e7ba4a4891f347a6d216bcc67d5ae0f19407c3e52c13d8cd6282d3abf37d15a2ca2bfbde2a466b51ede19a17c6bd2e95797e2c93694b0f94b0cf015ed612512189cbebec76ed220b43bc3762bc2dad6b2d30b683c64078958a1f4c8ec881bc3f8a85e91834165310fe4c099e16e87682dfe80f3a67580fd6ca326b4aa23a519c63815ca5ab509923ec10eedd7736933e088b0d426d5d75bb4cb31ad6aa345f8f5bcf79c8537dd3cd6dd41199161e44a8f7e58bccb2f3f1407d8e8aaba16f8cb9ca788a85c8a2beb9b27ce534d859527f043b4f09bf77b0b2c45ba5aad4ed17e24a98c8c57f19f08bdadcdde950382faf08d448ef6e58873a5e03ce6c9613c946320c501bb4994c581d4a2bf231cccff2dca67d2241ba0471328d4edf966b72541fccc09c8c774b03e0e5553b907e257b3a19c56b8d2acaeae4eab2e502b774b9cc0de81b20d5f46cef5d07adb16ae3a1976c0d2c7521f57921cdf6182bbe84cacce045affb8927476afacb340e255b7a6ec0225dd62953ffd4c833b9ea12b0104b4999e54d17ae0fea37efba6386fd1b11338e2b1201508925434de3573a24c1771b846b10435266ae9c2704d81aaebc05fb84535a6e278699abc24e13922b955c52c3b49a252daaed2d2fca970ae5c0a2dff344f15588188d1a7104240dbf3e1911bcde618181f00a4fb3b57450452d3b5f4f6c087466f2db7e33f714887f014d6d6190a8fb6acb347104d132c971a9aa86e0a4130c8902b5ffe7b76529d4328c8b385c9ae54ae13530207eb30133b9fca05e748e1876ec7d8e332b8b048b6715751a8a75f885914573259ac90e6d1abbc4f4086b3139aef8a9008f5730c566f58a1f536072fad4a919598a8a48c12abcf5a3eac7eca751f2e4d84c458804be3364515a9e72bbbddbbc1fc59964d2c691876e61c2ae073c15472832927ab00f107e722f621db0f436b3b944f5b5ee940752b75c8debaefed89f2a8ff90fe2a647586b2b6475fdd66a8e7c997c58116c0c00651aa2ef301c23b6f845c93360382611594c38e4ed6feb79198c20d31c8fb58ab9b9a8a5885dd776647110e80ef01f8cc63e0af56e483831fbba468a92188e5f7bf988fa0385f29f0ee58c836b0409de16db6654e98971cc24c78229f8ece46e16e39003dcfc0f708b0bd7c0a818ecc837945d1e3d94951f764e84dded151154b8719d8c971f9106f487002d124a793356a96198eb6b32c03b4604a531f10bbedabbc6e3d7c9e71e89fd14315dc3b72c5b94e16bcd59349dbf12ab27cbe7208b4305554e2464918728082f9904ef8263e10092dfc2eee2071cd6766ad63dcd7b2052f81a9e75e9679828ac45b7622022f3108ab9f2e949f730701a4009a0ab1f1899bd78e880a94d238516f5419b3b037a0be592ae101c193228f61baea6996c9c967c6163137e052a7cf8fa9eadd04c912b33eff3df5b0fa089b8f02e6568d305c7a3ef2c7d9781228b63853cad4ccd52facbfa6137580b6801467491f20935a6c211229c1a2739eaceae49a6b342141f83465a6c9bc5c2a94ba3ec164fd69692a9c1eede9f54d872081c735bec8f6fa07034ef53568897eb04a590407163040022381d45b8d5e165afa573943a0baedcc4f1b0cec213142ed7195a20ff74abde5dba222238e79ca3e1cb87a85b286b8d43deb0bcc03506aacd0704b756f3ed3424e42d093de055dfba8fd07eda14073c10c16bdca6cd26d87cc24ba18d4a7ee3d46ba597a6999dce4f17467106b6a305801016ba60b6761b1edc22f9169ddd961a92864b979f4b26322c1953c34cf6d76d2ea6935df905875776207a462aa1d939b0b5e6c39547f7a3b210b54e96a9d42c13c7a49579211c7d622dc9d8d153983136e797c65f717d475471c560232c9d0d68f517bb941bd47c19a945a5212e071908bae6030c7d958234ed7db6bad3a910b6c5de5d28160faef55f6daa9e05346f19dd2733e0c2524c53b659156ca7e70f1979b44e10461649068d49de37acad24d09a5710ee9eb648a5adc8563f3594e40280857f1de1369d86de3883dd9bc60095c190aa6b0098d3aad8039792995cd58d9b6e001d2b85cb883c7b83ad14242cb1721a05cf5a8827f17fa8743179b1876b2e7e3b88a359e8180adeb05e45c6370915040993b0746bf78297a1ca045460440557382c27a05347c2828835c7c2dbc5d8e4092793109c2ba9b558bd163888f024dbbad9835ffaac0c4091e146424f99a0a1d2bac397464e55ab9d2ca8801704c5569b573888ed35bad27a29776989c558ce5699e4e0f39d50f53f5560e07fe4965aeef5334da1f6f695e586597b7f9a955b74e3307ab92561a8524737a1297d463a43fb8e0a1663b054c313900d40037d7789dfff0c40fe5854f3a927ba09d2060a27d5b4818e1c361d7fe69bc95d069889cb80289414b1565db155133fd7e871c1d8575622d46d79f7b09254778eee0f80a685e5af861afd4a3d0cc1881cec70e52e86e90734ba515d51d6123e72d8b5431ccf9a7d9c9503ed4fcd6613ee012ead7a78de0cbb83008b9536505d3b8032cf9bc2d6b86994dc70aebf3943b4365fcc0339ee6abf55eb081c150e18d58dd73a64c235fac9164dc5ffce8025439dd9c39259c80eb1d8ed67f89dc936b888fd64d3f089efedcbab7e663681b40dea308fd75e964e681b1ea7927317f1e2b5397de05632253583ce4e01f4c3c599740701c82eae080d668383ba74f777fc742ba0825a8c7dee01bc5c68e47230e0f42ecc4d1759cf1458f31de333be15177fd1f884143839fe1de30fbbf294f5963e1a1b021f2b2c76f4f50a27be138247cc2c7d47ab826fd7b3c269479ac64c84f0df345a88c73eae1d297b85574c0389a765c73b7da39c97cb39afed0c694009391e6c7f2b0b0894727a1b1d90d7b05ee5b8024a3a642d726a163a391fa02a25ed180659f79f4747bee448b4d08a42378eb2a7943a6ede66e148aba1b4130f9eeaf078aa318d95faf9a3d087f905c7aa3686b6371ad3c48a033d126b894fc5a60d0744afc553ae0c826e6fbb4172bab747bc56ca4d66fb55782b18a4e147f6fbccb71c21990d2d4997b46695bf948acf95bd6c831cf80c3e9ba774bbd89e8284ecec3e961739fc99c0ee2e112ebbf5dc43c023fdf9aab87a3aab3af85671db7870a4d72fdd081297673dccb0a1da99c97296874bfdb5b2abb073d4dceab39a3a7060e9a130aa9e3f70bd8c86fe6260fdc6be88b923553d94783475424b035135bb543fa330c610c21f0bbb747e8fcf3b346a622e7850f9719e0dfafeffffc6b9f1a12ff26d91b280cceaba8d28bb31cead50c83bc9589f13251cd87bdfa6a1db5cbe509ddf01d125835efcb65156220128a3caefe5292f1928ceba1c69aeddc846952adc62b940543a1113f281c24bb894e332b93a588e47a8d66b03c1a91d583e3da0393041c8a5038715f632894b34dec5f63b61a71997fd33bda08aa95b8d7672b52b930473f648d1576dd985a50f075c42d24cd39d9b0912532f4e01b885ee00e7a4e379754817e5a13d7643f29a2d58a0119797e3c7db39c474a8c2201e81d584fe7ea3702b4c64d1859ba1b0540abbd3dce58c020a2e419afdf8c7604ce27e5d6ace3786eee88ac3e834910cdcd23759f7fa57b98d20e1f1f961a1ac26a53e8035e25b7ee4466a186a415432ff46e244a1130bedd422c2a40d202631a5e81e33150e7d530bd1a6206019a746959aa444d51559aaae3a81cf566928d85ae22d1ee1942ae55cdd5a20105edff1f2c2935200b1c2e53952a992cd9d6c4133f9f7ee798d49029ca54c1f4c0546899189bcc4bcd0d74204d2c2639062efe142fcca34bfd2495f217a3ee2815dfcdaae17fa39d73166cd66dfc89be144fc2ab1c1ca9589011839d3cef2d744b43dadb493585ebb3ee160b7798197a30649388db0d2c6fc474024e52595297f72a4b76a9429e1964590634d442acff2683f5f54a9b83a0c81c8f861838e3088c7918e05ba027c61ec579e084b550c7a0c0f5de01268d7d7c2ebdeec9075370a1fe2629694fe7c0573a7c93b9b8fed6f34e2a20d8af1c22d378ecfd650aa2830bfe0f337bbc865e43632e4c43f4ae6c99b71053bcedf274bf900382818829b6ce86bbe1485a4b201b1adee2fb4d3e6f94960c385e3312b9cf4dc5e7f77e398d850ddabd76be2d0baa5c3baf608f165ec070ee6f29d438d3b359611f482eca35667771e420d8c104a3672b8f706b8e81a6a2822921ec9e16cabcd164fab451ce92bb5e6b0d6548b34697014ad8605dffc86dafb1d81bf392c1627f3714d8500ba6c7fea7e901a034be957fa061e31ddf76e4bc7c98e584bf0fb3e7d10be6f0c35efb938d0136085cbdbba8d6d25cc554b1a1c409983581c545006863620f3d7fe5fab54ba858613b067d14f307fddc89f12f86b7a71f4748063d151431e6d71782a8200121e4237c0de11b884855f943fa2a58673c874370ab5120156b63575796afccbc9204708bd5e3d1602bef771605ec788d24607872aa9826e281db13dd19a4e3616578c19e38cafd137f8ae45596d411010d238a610e616a322c2c00c5bbaa769be6b2a8fa00cfcce171117034480a413e851ef3aaf3d99b35ef6b7e7e28085d7d844f9ce016bb2a8c4baedc8d92007a6cc0821603a50fa7aa558300b8d6345e6e24f37c449da52ed8dcf9d78d0aee143f4b00ca959ae095f8ed8cc304704601377464125078893488f4c94bbcdeaac3b81ff77dc921653c05e7810ad05aa595934eda2affcd442733b4be76bcf5171883109ccc51d11a8d354b1c1384380aa88062595cbff8970b884de2cd8600fe18b33780b4a0c54f466ae441d97675326a47b8764dda6b0b66712a144118d5531bf6f89dec49f54145ecc428da6dcc0d0e295564fd3fabaf65dc8e3e6fd30174b240e84a28746ef070539e1344a66f9759a34b10d53d0f0a7780fe9f372bf0637966baeb6e0996353914e01dd3391ddff56a48ee2c323ff439c64c5482cc5eb367fa811f7373311a64ae5b3fe2085b8466aee195dbcbe1fd95e8d386e6537a1ddecc537b47ff4b65f274cce73aa67e24c5a0c4eae83ac06795ae3bfc6c393e7d1403330c0d48ddb6ba58157665375e37110f3160924e50f28e0520b078506456628ee1bc1c5417f88394312e53d165ff54c3555cc454f56b72b9e43657322ffeb7608b8832db5db0c8832523b1c67aade6cbb43c150e0bed5ca6b003c113bd723346572559c07fa5475dc2295d3dcf01a914588731f9c1dacffd8f5e267acd7758d6f6d59f51dfdb9635075ad64eef8e4369fff1c35362643e0fbcfd98a0e3d508598d6b9d2c1e23e958a2016993607c8db6e3a4098ce5aa50e5600eb7339630c370540de1255378a87903bb4d5287610db0725d822d3c64b495c8af369eb68b1b248a65a11d0dde54ef4bcdcc05b4a3f1a2a9cbf49bbdeba85e66bfbd93b5b4657065f9f6dd8cacae22b55de95220cc6d494e5db4f9422139ccc61d9639508764c0f1da40e84337cbe0174e19230a0f0a89ba0c2d739fae070c651db144bd7a3696a2db67244b7c70ccae73173664c8e6d32de60ddfca48d0b2e4dcaaaa00d2f58af2a98fa33b3c8a8f59e256af880d78a0e5302eec2756c29e2b7ef49e562ba3ff8a936475b5cf88138313be800b5038e8b957ea40d30f0c93d011fb3f51eb0f4128ac2c6f9e063407d69e159d462e1c26e814f8232add8ed28f2134cfde9826382638e41bb2bd15fb3413bd0e9f5e877d4dec0b38fafe2ccc0a878633cde468bb964869d32ff2e5e1444c6f195d123ce642273dd453685dcd38a928c10d38846853fa799639a40d8a3039da5899ad8cbb040d5d3e7a367e62c4873ad4eb015645a3298111403515f044a78c5236d70983d70efb0465c4e00d4d0368e926b2f61cce10a200fdc1e401b1509a103152312ef9a815afe21a931f9c8d3455c957a6ad6f629f152e49c68c7c923b66a692e7ed39f87887cc3a7a474c31f4c38af0346988d01fef73568848bcefa4267bdd3e2571e1a85ab47c7a28ccaf1840ef3cea8898331cd24538dd68ff604a427f540752a548c0fff5ea6647165d748e72388b78db6f91f4155c5551527cc700a0ea52269b2acec74a53c499318427dc898afbbaaeb21a4d972089536f6da650caed00cd2d93658c7a9e5e141c5243d88bd781e5afa982f3f0b6bfc0f68175f89c31875e7970d43511d3b034188d1461228609e23b50763b87f7bbca5079cc5e9bb60b44f5d85186a8909e02427dba609e296fb01d2934879cde4d27fea1d4141b1598d3388775a4643f8a50909b0026b875911e5311cb19eafdf141c05a7e6a6a302ddbfc7c2ab8a40c3d3818a519b2fa69f266d76b97e737480bcdc66ab73d5061197275e14c350b9c59e3cc0b62c0f323759b16443ac5f6f59002807e2df36e13eb21b985369670792a9c508d4d3fe4f7ecc5e1ee4cc4d301fa02b54911e99fa7f9f5a926af563940fee5034b3bb5f0c119aa3921b56be25c1d0ec83390a570150319ccc1b00623574723adefba7609ed0a2f01244fc7c489395c67033a447a4bf816378724df91d42f2a62346add80a2c263729f2f5b043275825eab4869e0925b778d71246b431e32b45ab40bcc0e3925b7bdba2abe339e146875298ea83d21d76feb034dbe2f63e880d5cad0581be4925b170dae1f732376466409ce88acf36e3b5113d609f2ad31be99ddfc23607a45b9eae65b69a7f806409fb4fc42228153c3bdaa351aadfc2c9e4f145858a05e61e2d3024e2062a1dc0181e9b7ae7e433202cef4276d75c4d798cf04b1a793678df3622b690b0bd45bcb60bda22caeae9614abf736b5b9a7455127e1e219a403eca741af523ee67a7a19285002035094693d06d5dfe3409b4588740675e084c13aff151fbc9ccebf455b97b0b52e65621d22523763c7fd8591c862412084c06930cff13b230742f683c69e7434ac856c58fa9f0f1645f74cce9e1760ef0202f69d89d39535cfec4b21bdda931f8ffe807f99d0fe2f4b23b3002f635af480f1ac664778c5e1d91f1b281fa74b871a5fe426cd401bf658e85e2ab4be24c8939c220a796170933116407fe5279c67edf4bf13edeba41ff64fb717afa51284ae716f69844afb75448e5bc586fd40b52aa75e8d351f60d87968c71089c2976903fc47d32301cfc416fd359ded58f64311e1b16764d876719146adc200228d8f36093bf4ce74d66e686ab712de9fa6584017981cf16480104bd4e50d5980f405f4bc47c946a282056e7795ce46c8a4909777845270d98865588dfc84855440d0a7c0c126c807a15c0c2c7155d27311d21f45d2496bd0f9bac785c90d3debb5388c804b37f3c54a0b426bec337163c5442124f9ad38fb408cd1e953b022be3052c5fae0f227c504a43c378d4fcbbe05210f94ba717981f61b7f547d7d0a342eec40476abdd66778238bebd1038af2c6d68e3c33cee4b1628962b5f13f9c6d1e1992c3c78887c56474dca7e241c787203f0b375d200edf9656e79e86d2e1eee20b6715bdd95f459c4922dd071d4f0d477c6a30a629a272c9b805db9170f3d5dcf94e1346d1ca6f1a449f0484dfb7009ff46836c622e67e5cc41d0ec619932141a977aabd5ea20745caa7dbb9a1e20a55f9bdfd0613b66e0873d9ac2b4984065f300bd7e27ce7a5371f50ca8f8acc9a0f307b4e576573a37e2936f6013eec4bc343502c125db29d142f25264879c88c0106d682c2e45a554fad3b12ab5bfa285221ea1c6f33a4e2825c3ae9707fdb309db964ae68067dafd468a81e9aaaf633fe0d8d06415bcb566674ac6d6dad59130b4900ed510089c95204a3480be51ddbbc286de7339e3be2e9e64d517324d8530639c5686b8c06c92946523885f6bdc732c56dd018971460c0da33708a501601484bc1b0acf6205b0e93d66b08fe207fe98379956bb0bde029b313302ac7f73190ff87363c0b793aac57357648a2d40d93b36117c47e2f8de5e9ee4a5d6c875ab2208c74eefe280308ca66f18acb2b38e29592a5ecd5308db1c58568063f27d19e0124f805880f6c558362b27b39072ae28d62d8ad7d7102964b0cbf2ab1c4de234b3c2a1358db6f6885eb9d8ca2cd8b13fe2ec989602adcd7f0d64010c2b067839be21b03ba57bfd754f88e675a7c641ef2b8a2fd6c97b9c43683ce82fac49e915a22c8c6befe91def503248f5301111aeaf64b9c33f14d58230060edf888113b48598a83ca51d0d1544787c5f41c63417868d1bde72e62aa0bbf6c12d3e13359bd89097a8d92b5c490804e7bfb226bbc5a0f907321ef430eca93fd56c0e1fb99e0cd72be82a639e90ce4fa02298bb99a811f759f5624e5a891988b14899a39a42f968e64dad025b42252dda81d51aae4665574c6ed30f33083f2d30738e486528d4610ccd583c614485f51c7cf3e264804329c87ab497d31fff8c08ad9976f501382f7a3b08fc8c6b7220e4fc84e8fb10eea11807f400d6226182cc516b873b20a91848f242b9b40e9dd7110f1f738876b0bd7ee85e751af82d13490562fe1746e35337b680f820d60c90873d5ecbec393dfd53477cc1e8ae04ff4c44bececfccdf94c823955acd3553bcb0dafb8c1ca8f23ee77f3f841c8e6e3d2b907efee4b70443ff912b317210bfd824b29ad4c447e1751d6e2a1892f010d3a14c939d68d90f7e270d3c3931a52702ef01a500ca9047d26383a69e1f29884540a90970846b15031217762f87150e4d983f7c5cdf9c95c32f8cc283799305596c2aade9bfe958835e79bca506a538c86ebb7436dd5d55dedf208ce456d0e000cdd3c9b06927607d1693eb14f1f8871e17f9bccf926588b920e64662a580d613454872c9a338ee7ad83bc5dae85fb8389f3a7ec982084fb7c4d7290705db20254c9e18924e2fe0e6cbf7c6eee183308021e0eea18f09109fd0e00f029eaada757ec03dd93a198a7c18f0c58a48926696ddee3dcd5af39911c9636529d87d179f9e1a0bfbd9539c22bd1628ff78d3d1745ee121e87fdb1e808d9983b4cd8aaf02358c5ca143ad7a6eed6c80e26f544025648ffb20d8426de3b89d0fd9d7e8e388ff912f1718e00809a0ded0b553b803ff091c1c4f26ae3b0f88f67fc3d46f18662ea38f4cff7b70d4a23b0ae198e3b64cd07a33ad55030ce82e97b9f13eab26d50bb79a646b0bcb950adc0856cf6ea8102c2036368608205a5b37ab42141778dad7bba2eff293ee7d04a9928d42a522cc1580367f60cbea560a8381a8c3893ff10c795850248b4c7ed417dc0f20c6b1a14f8de7fff197a66b161d1359b16b0824e973048a1512e5881fb8a5f80a7c41542895c603ebb23956ea652ffb1e468f4fd4ddad58744b9fde965f9d5b06ae96ab19f6167847e2e0e579bbe87fbd1dfd6c7b773871eb5e3da731eb6fc9f8f3b07e71d02e866799b7571ffa96d8231f659cb4d051fecf38d7c2b75a3c80e1094a5dd1a6b0698a793c0739af2407ce59bfdd0e648df58a696970c5c8ae98ea0000e274921fb58227d6f416085569bb91b16200fef07ebbe5bbf5408db8a70adf0f9e73cdaef46aad5af31c181fa448daa3eb0b67f0a2a725fa73422510037e41d5d4ef4444d6cb1622d4adaec65fb83521b88e743dac8283d31269dd4db018403b049ad50342be5aec080697598ab386b1f00394a590fd78fcc21ae922e6ac6c258cc8e1d8378d469599f8adb1a45e678ac2c36172ba531538d7da52013a539538bb5c596d2d44a1be556d12832ad746966897874296e7225566b48d904f26c58d56640291d7d4ed5a62d12d6292d4c920025b1a66e5196ba4304aeda7b30a0e57ace963375f978b64d589cf95facec0fc97c6dfb2285cd0c7c349ac96dbc3cba93c36ca00ef486458431a5d330c2c718ee42c4b415882af666ee938f10988d00f148d01fc688f01f4bfaceec6c020cdb8c87066e7f9112377d0cd48692277e24570be28a89a7a245875249838dd76c506ddcafc77032e7260ef8baa4cef392bb9f5e13dd3980099c1ab5e93aa007ddd7a80220b0febaa6691aef116f34b47f92f5f5ec7277f661e43a9c04d643d1a3caf59df355f25c6d8b865b9a29e10b842fcf858ff908bf8ca23f6cd2883b35ed8c73a2e1f321740a1699636c1d543bf793e0c7ebf0b249a5f488efe8a6dd82882187e7852ec142ff380c9ff9014b9cf560ca9f958a9fb5d39b9346b6cb3aa75076391c9fea249071d04cbca4e260bbbafc2ff79707dcb845a65e64478ec592912b48250ad179f072778ae318d82d4737a8e51e6fd132542909e92404b44e6772824e8f17f12181ceaf6d4ede4a608bfc2c231d10f661bb5cdf8a1d59cb3c0e4f10873c5490feeae709e8da47c0dc990c8698056c3b1690c70c403f7290ba7fdc619ade70ec0c67076f4ec26cac26ba69c8443d4cc6c72434662d5c415b1b0dfe4ab4d6eb29f6b3551cb4c7a9cb92f4eb9acd410cec0563cfa7ea30fd70d3f7e50772584a9b15c9fd78b4a7e53e473d62e903631607bfd8efd60026f5688965ea863b968359b6d4a34039276125673ccef6524103cbc850caa00e1fa3065cddcaf1726addf7e39c648facd483819dac8ea47e6daafaf462935c69d2033e6a13bff4ea8304f9c5b8c7a84a7d2d789fa0710b2296b3d07f386bd46cbd4e2decb5ea7717695cd48a55eb22f82769f96aa9a840655beffab5e5ad06dca26a613f40f3fb29fa0ea666bdc335cb7427cbfabc71bc8495c56256d21400665195fc56e63272259ae65cdcb11cb2ac26eca579b73f4b9b172f8fb2b3c8e0fed0f699e88d7b88d9bced31b5709c1de8fe6dfe4382106b894bc20d8a1f8ae9414026573e60c4c1c910ee291d611a2adb9be36d38185c0f7ab7f2eb95a92de70d2777d895df67fc02678b0f8ac58f616c7abed0c4afef46770d2d75978a8ccafca56c3d4671d92ac63a83ab845997b63b7c872026e9397e6f59398deb7e4ab816e9d6df9f0b6039ee3ba894320e8edce1b1496a731fa2ebcbcfd1afbcb1033212758a39f405e8af2651fa77b674ff992ba164ec5e7c2c702931fb5515abb0ac1b4259f99f7076dba01d25dc0a1441626c78e227aa4cc6790a0f0e464d6cbb2752d5029ccaac69885ccd2370a517ab4715af57de3abf0ee225698ba9064548c65b937ae798b6cdeb2ef4f49f0ae7cffb01c9d42fad9c46f9e288bf7c8c2b5a436d77537aac7783900f33b5eecefe6c512fd48c37ca977cb5c4286ebcc0b534f9f7c283eb305bf481df6f0f57856835067d9bbbe787332783b4d55be5e18599bafadd48ef6c362b0bfc34020918880bb21fe99572f06b3a293d608ea05fd736f1b6d07c874fc1ac18969459fca06ae2bbb5e462507f7f5c6c0c065d4a3e3a9630548e8cffcf8ac25c85d2d282324724290b74f9fc1ea4121235e611e3bcf60c4b320b5f8ac9d8a36bb24acaad56723acbd5bf00a42f800f24a73520bc5b3951d0be910bf11d18dc9ccf3c65fc955f99780e395f2f205c52a83de0f09bb892f327019d4d8589e5abccc6d0db8cd24fe1f0488bf18212b952f7211729bd131a87c610bd840a764ac05b1b3cfa464dda0e786bc7a5e588f74a9b86faddcd68bd8735b32b5a00cdbbf8112d355bd456696c1c85d6222b17ca58c7992760787be0af5de4f1233eeb6e5389c0803e7327156f60cda8d8ee2492b35bc5971a91b5b26b672d8039ed4406785972a8d5857981e537d40fe23226ba7206db13e426cc41cf4e35c8fc64cdae05607a6448009137f17a41a550f2e4dcff28375bb91182ff944b313fc0d7f29db53a0cf841c82109411d1b71474795d0ebd5ad7f28b8429b1efb137521e6159b489589a01dea988e2c895c7202f58fe307daa09a702e43350a1a9c01f9e318abde69f426e5272228495be7da83b9d25a45d9c195d5d0c86c1932155f2ff0036a3256059f2142e64087852fb3781ab69ba395203da12c5e3eb2a3cff697b1643f90d7f1050e138ee7f4dcc384fd4b1e1f42af514c428c3fd97b830e348d80dce0987ec4745a1bfcb1d53a791b80e416232c497e77c98a0494c2512a91afba6029d6d3cb5a901138115ba1c2cb1a5911f74c97368a47bf989f0f0501485fda589c76660cebe61c558aaa1a742a4539a47fcafbc2e3603d9bb68c8229c141d368d67b79358721bbc7ce011f0a094debabe3c7efeb6a9ea081c021784a4874823154d390876d78ded7c6368fe52e0b066fceb83da54e3993eda3df1757a17f23b43a0bb04ab533b0ad81f1a88acf7154dc8d6f7a56c7003b27cc1c32842008dd516b37841e6ef719feed7834c55fb513e2452af5320075fc35f064e7003144e1b3e0ae9d19c6716c2ed54ef770f3264c1cad1ba237daee33d27d077cc7cc28e91ee1143ea02b06b59ae29a884a6355d4de34ff239b0969c180937e7467dbc3767285064bc715a559acef04e5e6a70c59aa2acf3b6876b534fa2fb1e539207e797f2d8eb3fcfbbbf657da41ca11fdbf4dba3f00393fc3c184c76252ef0d3506432dfc70ae8539088ff98c9a5113d1633eba06a29fa3fbfd365120d450e49efc069ce8d4063f4293a20718a9ee4c9b49a3075fb01677d585e2e76e0dc62476a7e1bf965b6410d634f5cfa626f251f07051aadf472934b79e53452a6daff67b9b9fed9f1fc4f8216cafcec13070437c151ab7c37494c63f8308b96e8976256b3e98a046b8d2e1ad637f74135a29b377f2f202caef85c4ce0dc76717dd3a1ce727fbd29a54d3652dac10be44c3985df8efc2b18fdd17d30aab84642c28aece0799d24e2ff00377841753f45ac6f22934f7ae19893a0ad9d37aa80a45a8ee9d6f29b93f9a38974136bd9ca6dac2a3e9560c3445e953d8d404f4b8997ab953d3b53bd334b9214fe1c83b93f90dbf80e1ec9334220ba297c38931650aa9f87534e9001568aa379815f36ffffa31df45d3e500664a4e3421601bca9eb698296bd95d329dad918e181e68421eafd63b6b5dbbf4fb65554495c745e86628817d1514e0e949730249aec7b54dbc96a6182fa54ccab9969277aec66fe606cdc34b78cf1b3103976ca77c1ce57f98be9814c7645537df8098d2f5681936919c555ec0c767ab1482472e1e80e0a06ae8e600958625dcc028d5241375bfaa808a4eab16dcb212f3cf994a971fefa16b2a4780c6576906c64538711a710030b3740d85ce2f679cecc652cac4546db72d0c32d11bc3ade5f519ddbb01b1fd220c7f26ba7f53fdcbeb334aa3f601bc75ba7d565b670269629a0516fa1215761f5a0f2b9b11997b230a94489a911fff2332f8c5689a876e24a8bc0bc19a7d4d6daad04b1b74c3782d31c7fd155b5683ea37ee5abc007032190d626adfd1377c0135fa58b74d3268dea4340fb5790f8f04f6fc97762044b049c9a28f23622e482cb19650e1f7b5af645941623e0caa09c3451850af213fda1c4c0eaab0d9f5b55bba345f1dd16691c18ea564336c6165b316bdf0c0aee0f2a2bddb043cd8807640195fb91b4d6daa16363a04b3e9dfb3113b0344f52220b3816b1d6b28e545c8fa461bd5b3528f361a25e08d0ea1f945626cbc0dfe7c11688ac1a5cfa46613c3355ccda643c9f4c103b030344467b34600ef64ebfec870b16e61341664bc5a5dc567d19bc885c35df925c6092123be236de696f79268e1195511022501a21a6a12f022064d9358cd72a5e4262f0ed99988551ca2321184220d3a43f03720bbb8c73c8eab0acb9897ce0652cfdcc38337e1264ff8685a501ff4a9485abcad723af5a81a0fa0e95d6071d50d51d055daf433583df61513f9c2e399d53194d601e8866654c5561e6f109b69c94d72a0da6f090ee53ac00274bcde1edaa5b1428433db059265729115425bf9c56a4c223dbdd27e8467d9e3e35d02455ca51ee9f8e5391d3509a221fc814bce2f0bde2aa9020b491430b77fcef47a8fae8dae92f9885da01d496a3a6aeaa830210c3f61d9fcb2a343d851a57b7b95ec258d92bbbd4a9680c64a1244aba1ee561941cc3ec895ee8e326cc15d3c3d98a34fbe2374266f2091dde17db4f4f6ca2e7625441138c5b8373f3f5cda4bedb17410daf835d28419e9f0057eceae14ff4bc97cc42c58898c19c96e1cba836169ca114ee41fe41c4545cf83fee8dda225b04eb45fa7f68cbe21021c2b7debaf679c416a142df1d12471360db069d88a528c6691834602ef342cade1b054451e9652b9869afe26d0e83226f0378507a991b9a0a63e4b48da84933155b19c1239ee3413e68125b136ce12a01dcc626d5d4fc3d0c6aa1c423d8f2e7971f501d4eb189a7a25f370ea2eb9210fc5d097d7e88f83203a5e7b32a021dbb36cfbe3a1cd69154e6773c4c59cf7a163ce9b28a76d3124eae3d5a8d060da2d8a6b5692181fa76bd2a7704170ecf447e17e7b7064921de0a1327ce63e38c0b47d0a67d7f8f3a346669ef6900e79b8a0e05c3693915693fa4ad67741a96ab9185728840ccb447fcc0a2aa02588c419b85ef1087022efa2d3c9abfadc970e653b23531ba7baf2146d96a0c57c3f705d2987e2e015e2fde291e17b0e953c32e04ab84b85fa3be589e77d8b16e78d1215d8b927a51c2555caa5c4e0986052181b59715f14394ab996218c5620288b6339031f1568682cb2e68683092ce124e5a131c7bed06e99c9e84ef34ca87da5496a22c4b604b6448041cd5721f75b0301b6aa26bf912f60ebc2cc077b59fac0c187c611ad9eeebcc529340a21d6f70cc1656f16540e0068f7bd564991fa7291e5bb7085a2b12149c25367bc0e71867be1d3cea22e2651bd35501f10050dea81f7d490c6a42f45919b84a32fcb6eddbae834a70dea2b06896aecf812e0df163d505a327e73832d6e138eb70e0de88aaff542846f132668dc878ea27d3baa860908883af4435892da467f7812e8a690e7d11a094e96f8c84c8f66c8282bad18c17a610e2aa6e73bb39f26fb76024f56d58cba5c09342664825f653405a2de257c0a775cecd05cdfb6d270e12c50fc781aa76e0a8a83cef18bff34111aede5ff155e1a69298e935e15d43e4768dd5599ddd909d2de61a1be60040f00bea18dfe6b0f7984aa92aaefb55e90aee036d71d4a50b611b0041c55ac805e77dfcf3e10a028a4b58c5b280e62839dcdf1069aa81b7143a42cdccf66b46f0a013d4f393b5a744c4669f7b2c0e3054100b0bb0ee7b01381192c60194db1b3f594261fcbcebb485707726ec12e391754ae2b9da486b6da60afb5d9fb34e32af6a8e8e6d930fb8a47efc456537ff9fd163bc32ee0a4c64f7e4c3015287c1570b9ecf2d17631ae44e07a3d0172625cb2bd8f4e1135dbd6831a562f755fd251eb5260d49aa158b175fca07cc19cc671a8d58d9c40809638e7f662fbaf38832ffcae4f0ffee62988c76435b820daeff3f3a60714e95ea04ca5415c435fd5c86bad21027a46fb67a7e160bd3da10b24bb9def223b62577c57c037afdfe5c02e9a18bcd8fe9f8330d53dd58b962b615ebde5f359b3880856b82597ca311dd6364f947840eecbba9ec0333b3828c04510f89470678199207316eeb74c2d4c86e2cde91e4ec5ff1960b25f2b830f4efe81e743ba25c2169a8e66eae8ca8484e06a3c69ec67e6d836373048c4995868483f39ba2ea311f3bd3477115a7541b372390c8b59f1ded4e78feb5622bfb1eb4c44adbb40ca3a985830ebb001d5a00bb1647cd639464cdb228e73cab5bc61874b99d758ced895eda19d8bf9e95c15a9580ae57239cd4ce3cd4d694dc8bad6b7beab126b3f4123fe3465cba0be979f4ca55cccf5d7d7c9cecea2022264b00e5ddb613a16b7ef10eb87fa58da18eb152376ff3412f952136ace0f524160d05dab881c2ad4fb092c82828aec5a7ecbde5a150fd8468e7f9caa726b086cd339635aefb3a4e2bd71b949e7ffb04df477be201d1b2160eb606f1f17cbcdb4406f8bb37f0fdb3d109d54da98753f8202b113c51db7b77c66d527b397da4fade4926721e34bd609f4cfee6a09c8b2da2d1e6dcf9964179c9b1b415e2634a817ef59c55db439705a2dd2a0a203cd9c5490eb6c9b0b6c90563c681827d8937549ba374bb0db2d6ea030c96e8f38f65bbf702f0b2dd0a430722ed8f7e7f74e26b26a3c5b0a06866740ddc3c95c8e16555d0dff51037127e8d6eb7eec2a20ce709cf366f4e79aa7f897d4f97ea5013e51fb1f66c53b0295cb08a44f7e4ee115ef0df9b8a9fa3257e8617edcb270137a10ce5c34f3e2338733f65810cbc2e8c6f038b1b44ccd588f8e46a99c552c3c070d7696fb11497097e2c9460e937a727fe0ced8083296e332bd8ebd35203e083f765b24a759d35ce0f6943910859a770daf21ee2ab41a9bcea5fe8035a47fe9fb756c513d98d5fbc9b6d2bc32be4cb361cc5e82dd8f87ddaecc505ad831564c2de31d37bbc060cba80741cd3217945f596e4b38ca517656316ad5dd237f0dc3e42b00a24cc986ed0564b5a3101abf4eb00cad77aeae11a6e3d99ce43653cf8989f6a0f2714bec9bbcf4e67bbb33ee6259c76d14d409ce6414b0d3473e818a79b2cc5e8f7f815e73f7983d8725a9db633940b73fa99aa5aa3b30d75c4ae7d30d9a5a5c6601930713f7ad8ed88dd74947dc8afeb1c25e91068aec5f1e19ec78082def51c38f5e2c38c943c809fea071559266cce384581148993d909c66072cf3d033a7033e8a6e42d3f36b0cbf8648396d3ef9f2c4e8877b9dde88af45174a517da687a0f052fe092cb6dcba6fb774302908088fdc804fb16b05c54f285078bad653cdaf23fdc75bbad4ae283f3e8eae366dbd6b7183236aaf1f3347dad3ab262bfda3ead201989f7b57feb442fbb869efbec9a55a560046ae586f2a97444082671525d1ba066f735da5b9b63d0ea360fe7faf9316af87920fb5f70323dae78537f854f4ad1fcaa03081d0c4d1b44eedbee6e1652cb8053a2d4ca0f5635595502e2a46b136038429b8c5c2aa9977443e4870e03bad4d2d5d630a85ab55a3a88bb268d6b7432e11b146c7a5f96a7dd728cd5f89d14633dcbf07ec9204eaa8cbbe572056aa9a0ef1b3b2742fb0fa03f603a4aee5adca5f20e386fd68cd55b17abe11f18fa08560425bae68cebea85efdcfb3e52bb025fd04a9dadd9c29efe7bbdf78a5727b6c442a32534b1ddbd6d496ecb6b6f79632252903e80ba50ba40bac06cc473d50ab70109163a0f9281c34457194b68a59f56766cd40bffe0f88593fdeb380bef7a6e82920f43b658314bfffe8fda694d3597c70356cdb62b1dc4848a222963fee3f2a2c0e22d851de28117f71468e4c40f674c012993e1c43513df714903021d94a543d376330c85665fc90f188e08ff28cccfd77ba53fcee1cbab329035ff52324f47dea8550cff22ad52b1141107cfaa00a07b1e069f317f7a80f4a6125e287badfa5207d90fbeffb1ef59482283c8738ee9bb253873d3c655ee4adf1c907c24bcafdf376c0cb3e746d4c2196fbc743d9537382795f29a5b41be39882fd1e7c0e85598e59eef5533888c8ecfca12e4b7e2a892583240af5f2513888486220d4a7503888a280fc84f36590f98795dc7102a5bffc355940d8bb2c20d49f4ef886e6b174e5de59a66e6891c33afe92af4aeaae449f747db11c7a779ae2e95a9d2facccac1fa1fef92d796616fdfef414037941a8f75e892874fa0f4b17f21ee5ddd3a58f7a25220f8b62960da7f71ee8e4e120228a81bc4779d8fa8bfba06f829c689dd816b9837ebfa84c7225d068b4293722f44e0f7329177989e3d6c4b4a4947464f361c6e4d8f39bca2073ca7628fd0ab068ced90d468a0aa5b4d6a62b559aac745dd7791e1ab52a683099b70a1a4679d5e144238bbc9ab70a1a43c8ab9e5e0b9a867af04d010d9d1eb4e84329bf062177a40791e75ccaf648da5cc8f2795a00c19427258fd4cb39e79c9c0c603efd696c61657754c8f2bdda837baf07eae5f7791fee8cc2630b4086fd9cd7df63fae8e91f7247ffb8935b82c153b993d761bf3d64389f4b43f638ca25cbc791db479d11969b4a39f13893472bdddd5d762c6350f6e126fbcb00e6eaf2345a1d323b82b9ed977d986d54ec4310994c81d498dc31bd096708b98009317c6085c8082dacbbbb53ea5426512ae7eabaa420e7d456895a81f528df184c4a605049b24d9162548be28436d4640a4dd9ed8a95a6a531ba503aa2e2e4eb3ea20dccecf22451b293d2bdff1e7c4ec88c4d190b45b0e38c4d59cc1d96b2f63f0c27b6a1d502eb39ac13e54b4f01f150a81490796a54ab05f6f45ffbec54ea7d1ae57f721781ed4e4c6121fef2e731023bdae730a59c13e44b8b3ffd938fc35e87a77778747a74d83c1da2f0e8cfe2461ea9c362c141e0a75e89f881d2e67938b03ccba5f22543fa635046bdc4216d4900124e16bf6469490012a2742833776d872b5b126d54d71c7c9c33a196fa5622d2eb5e71507dfa41fdfe4ac4cf41bfac795913b36cf0ef1f42bf622418091251a63680fa1ae67b414440feaa77fce32feeb90fb31a50df1f087c1a44f4e1a0987f12fa4874de5f1cebc33f00d507ff0629aefcc53d888394884a4415264afafaaa1f755e8a2aec538435c37bcfa261be27566c63442c1ab2e7bd878b78ffe18c840888beea91b0bec748286675cffa5498d5e1131e5239141e42bf9fc8c3720901dd7b78034ab217440444dfddfd1d2761797feea5bfdc41ee8fe49312960d14a83efd1810a5943ec549589e7ee59e62d92f8c412087640ef9c4f28525a9499b347f71dcbf10ab3496f7f56df030507d6701d197a27798c34566d1894895a3089eb52b1ecf3be7b0876587eedf94045de6dcdd391f478ea3dc7b1dd2aee3bee3be4827a9e7755d877bec5ed2ee69f7947b493b0584cbf3698df2499774e952ba5c1dba6060fb471b9a1d6d7bf7c83e856d9d9e8de4dd537af744e5cfc13e79f9ab20953be47760e5f2d7815c3d819ee70f059e3e1005821feac422a9120d29a5f4a314b79c5d37e7a4a0e7469f748d769416e6854e9f4e29a5547e9613bbcb59b914cc3961f29cb5dd51681031e79c52da6491ecb4b6bbd396d6d199754533b650297be8083ee91a53d46335362876f4d85196f43da9e6a3c76c465ee4d9d5d7d86c79ec9a3cc6c5fc45ff34cbf444cbf454cbf46bbc3c7e37141032fdd1f3787a41a63f764d3fdfcd5f4fecc8c56e993e17a3b645b32347cbf4a9c768a72c73401bcaa3c792523432be3997d8cfd21b57597665d0ac2021d3ef9875fab30d2b60e439a717df51763cd6ecd45b4e1a0aec286dd30476a443cee4477ee4477e94fb670aecd8b159023b97965869ab62050879bef4ec2ad48181ed38aa845a2bc5deec6ab2446a4112f2ea8ba349619e28796459e5c624bb6459850968d8b98507f64e07682e32bd6cc45ff36f44987e947ca80d48acb061821898808818088914ebf82b05a47b927552905190c7f0491e794e9047796482dc95208f92899647792b628333aa88918510a2388138491000368a88410c84c858c2429cff0e53628411b880c20a95279ac4f93a0eeb418d1c58a1c610cc407a82e8042a7ad0c5121334390115e2fc1f8749329a78c2065c1c618829ec409c4fc461048862850645008293245811e71b719812d9154a4235228258029338bd69133e155ab044138804ca14faa0f62e2fa69022828b0244820825513441d1148232ccb0c2065e283141954220d4595cf19674162c682a05c9892cb809cc94208115319011c500c90918e4962c919c78b2c6f785c005267840b931840763dfc24061888e895d59186bb358630913bb9ac91289b67402a3ec0418ec8e1d3c9a60e5cb8e0a1624640a4e45e8808956c4fc25a6b0c9126989a2fc6589b4040ab6d08101820d6c10cc80414bcc7a9655cc78419319443142d8ceb28a192c9842984102ceca14ac34a18a130e898984094e2c109ad189c40f28480399d4220a149c617482275a5c912236454ec0cce70aad0a3676e0848b32446e3291b993109a9854a86ca004c735814ae6baccfda0886e04527cdd0890684e48339086c0c44509aa18a20ad1756eb24422811a48244042ed62e49e8a8a44adb5561a861494133630c2892c3c1a46955b12e428a75431e3097f1d30cb2ab72cb22acb2a372f3822609840f07e06d41e440181365c4006164458e1822a3a2773befd2c53538a4f8e3a2218537c411b01911511884090374cc1b96e2b462a8861a568c5030bcbf25c5d0df34ccb00c27c11e0f0cbcdecef9282937368d8c4c2abfc204af65af61f7fc8a3ec5de864afb5de196d6ab57175e816c666c35fa5f1b5883342cbf48cf186a13ef4622b75a9554ad488a2b9b7789c759fee2a0481e594fa2c0c6ad1f70c04794461d1660fa594274b592bed6696f573f77a07e55e4f3351f0549d949c7bb89a415377af776453fa02da18e1f9cb613cd877eb90bf64ee2ef39cbb3be5a60c78430a29675618c26ca795ebbc13ea03532c2adbe2f202132333b3a2a961d9c8ccc8a87023e6c60dcccdbffce5a0b864f9dc0924ac26712ca84933f36254d2ac491c0de5058a2653936270cbcad96a499b2a4b99c5f460643c5ae0c2619939ccc747066726079524032325894a9233871ec91e3a933d2e05460b30721ff9f8f8501c21286d920849f74b9a12c6d1381a6744a3525229a9943ae4c10218194773588c4ac96152f4acabc2977ccf7be538e558c1934d72c7cc19332b742d843a3a01087978421ddccdf1af1c619863070bd57b28e7b0ee24ffe423e4a680fcf840c1ce6f99b13432ab554ccb9ef974ca53ca8799bda11d3c74e4d14a4a2136994fa70590725610c1c7932d0000f4c2ab49f5071206b354875087381a0eb27c6a83598259ca5dc5aa524154c82bc8d0c8fd234793a14d53c8c81e090017409df0f5d7769241c2544adc19b9a5125644b05e644f6ee12a25152a49a574230a3b7b662513c3cdbcec91304db479ccbe530f3c8f761ccdd569763e7deaee3c50e0ee1e95d246b1f3c1c9e3899def7528dd4fa31214119cbabbd3155849e70bdc9d52eaee945277a7947e2c2e30333537626456352dd4cae5e5e5592adce0d8504a9dd65a5b75add6b4e8ba4ed6994a5da80b6e79ee920242a54b9f76e1d49a29086887e339eed4678d6d515bb6703929b9b83c0ef7aa91776b51adddaaad2db496962b2787bfb0a854392056c3be56705a650d3fc1ceaf2d95a668ea05d2861d713005c48f38acd6662cc70a94aa202baeb5140d4f29a5b516e446767c52a2c29e50b2e8a3cce44f5082524e4f0556fe689bf8a46b6c69da7d580d6b5d4029cb5f4d8b0bcc0d944d4cc73975ca751c751b99191a970e3077837de240d98332d0e6a895ec8d1aea5cd7c2754ebde36edc7837f2b99f9d57abf3439dbcaefa043947a2422eb2626473cc02d69b69cbb03e373b0e578f06d52d6addc2da5fc38ead44b30567a90b0e81b25a55a92026f86e1ec7595c58f5011ddafcbc2d36d6ab1a1df64c923d5df48e634ea8c3e664398fe31fe7c6dfd8bc0af5e55df2c45c0c498958964fc1af7504ea4e59589ec7206e052a368f5c14991a5899e78c5c1373461df68f5c11d76d45c5c432466a099c3e5d7eceffdf781b30f2f731416676fdbc302f5ddc930edb276571c5648febfbb9aa9483c715cee3d1dee0d1e354c03678ecfcc2bd74f91717aeb58b1dd8512e4de1847aa7251634a5dc6ff3fd944aefcc0fbf9b26b35afde8e5d5cbb4301eedc5e36ae6c73efa990222e38468a6584e86b95887fd29336c4ff9f01ed1bc47f337f068696853ba6db0ec108f358f625d996baecc2395d1d51d5d7986ca643f50983bba5eee0f93e362b9dfe5fe305b92925c79ac49ce543beaf78b831a0899ec99474e448719e548d34eca33099446e4b6c2284bc95c520ad480e91db7604ee390c2b338e30ba9f1f9de61125b8b1b9452be66f7dc73a0a738e7e26b296f10767e83f1497715b8953bfaadec91f3069f9cd327ed39c715a51e766125974610754adb29472bad5cad7808ce29a5b48a2a3cb9e3e4814cb9231ec8e43450d94e57dd3dd751a74eb90e01d4adf4ab9dc909767aad778ab329cfdb0d527429e67881339de40ef9aa270f7872e9eb814e0f5884789280228fd22693a592a4c2832aa93cea38e20ba45ca1209106931a247144ae3050a4de4cf623a464973b5c12b4dcd4cfa048473c5141c18e56a747c3266de61184f94196a38e5c7be4fae38f5c59c82ac83508b9be47441bb9be5d45912b5211644824590d8c5022d7e71123d7f7a94846a49154eb1a4db9d65a6bad2d7891e97bb7214a90e9775656272c753252106283abb59ed184aa3c90327d0a849332ae54b1a40512142b70c20f52b5d65a2b151a41a631c8242d84600291104738c208b1aa20d733cac8b5e65a6bad398460471d1d28b83c1b988293ec0929304d3193c22c06579a908aa00121891878c06404264b3168c184240413c088a107638049b4ec80074ae8c2da6459850769bc44c1be6459a5074b7455b8a0659a65953180e04ad467438d366cb57a66418aa55d75926308169c43b3c99c4d5aad774ea793f7420c50ffa777ba5cff88cc507da0de915b3431c9f54aae5e5791962891845c3f86de7130726a062e8912e4893df9198809c3599ba2817b9b0e576734b93a94471d7e23ecf8b9b2c2aca3c3faed841ffca83c1dd637c28e3cb928096e297b77677f1ac54af6a74a6334ec469323c5c6c86eb3bb2d76052c420a6296470ac648a764ffa61dcb9ffba0bf435920437fc91d5aebd05f9e8a44f961eb724354c82e539f9c144e4a2d940203ebf22317c50573459c94919341f6cf61043bea14392c7cb7ae1f57383f7ece8fe18f3a21c681c39cdbe334ad5c98a37135ae8833923db6de9148414821fb73537ae7c5614a44179f5ecf491d7a171d5e156c767243d347cbfb73495c17fd5c1313e6329f957b393d9697d33e4debfdbbc8612eae8b7ee186260fae89f99a346ff1e836c8aec2238745f6ef58f0c8cd20fba73c3c722fc8fe2087476e05d9ffab78e44c40f1c84991fd4f5c920a52d891362773d573df511a569bfce55c1a438ca184a529b4d6b4e6d19aa7c2f706760429cd2cd7ebb0654c798ef396615d9b4579ce245833cd39abf9ffe68f86798f3c75d834cc8ffce53f5b38effaf9abde51fdc478f4ee2dd37f712e068f56068fab0f1f8f3a3778e4c9ce84b1cb8e1c4665fef25fbdce0d976557018f9e0d1eed51d0903b93124fd2a87314cae0f1e9c7e0715564b9219937c44471aeeaba2ead39ec878d7cd157438752b15e8f4c7558972cfbb76279faa061e619b8a5ef192b1d764fe9ee12a4d4bdfbd1e3569472553a953d2567c5275d172757079c3a7c7dca79ba79391e8dad5c666005cc1317c09061b92cabb4c1022f63d50612b9cbb24a1b429d46d10d2d82f388095be1d4e471caa6cb7c5465c1c1fc71d56ea443ff1d3a6cad6177e82e918b4387926987a321ff59e4b0ef57df14692acf5976d83442be441e972e53b9c353a6238605fffba09e14d8a288c544107b33651421264a9168761e69ff47c1bef33f26297aaafff3c0914eda2c5052dfa921534ce1a0242288a7ec4e5b91bf7ca89643874e24a5043b4ea9e4596f167d5b8422e3932e2c869d7406fc31a67a3f7259d61610f3e01eb9a3ff43e56fa67ab46151dfedb444addcab9cf4214169650fb8f37d67d4ed809751bf1a808421000332033d6840f238ad010539c3514621f70b9153c8331c2711479e0a82f2062064be3a4806e48efeefe4729e4ea73e9d288a85be4ff32077340b363b73066e0f9347ab7113324a36727fcb3672efe0030b43eafb7320f2de9db9937962eaeb8522b3628a6228b21753d89bd9d1cb766826552a79a4b9e7f79dbf39c1965c0a08573393a67475383bd3940c61763c931a4af760129cd63b4f7aa762ec4ffad5b30b999d4addb22c9b054a37648a1d0e4a22cee94fbce6b40edbf138bda896215bec993029a70f8a82950f240f72bf9c120b1e37c2d6efdea75563a86e4d1d766b0d3baa9a72ab6eaa2667ea37d23bfd401cc912e675dfef237b288fdcb25b7e919b0b2dba67e6b0910e1b081fb983059be5b26c31f308f3171de6f1974a0d27aceaa66a6259d264631e934f3e4bce2cb14aee98df796620aa3c5fbeec9107f86cdee6a394521e404941eb29f8a46ba4dd6afae87058237b26ce217754d4444954f7a7793a9d7c708ec6a60f8e2ef942eef4ada520c3fa304f8b9452fd5405b2cab0fea8c33b7d7ddaa47766bde384d27ae749ef784f8b60f0a8a357351566c134208d4d500c3b8eb4c8003e1dd6f72b2486fba3796c9061e8857dbba16e1cc5dd9f5cc99572c89ef9f543577221704727afe35027af7bd4c9eb38d4c9eb38d4c9eb38d4e7ad2a618e0a02a95fa77458dfbbe24ed9cdf7a4e77d373937923deda37bd9b7e9a3c3b468f2a85fbfa3207b26a62f903bead7ff3850ce3f15853d5b38b0f406f3e2d2dd644216eef4a3471386322f1f58fade4731302f2edd51d782c75fa9f0682d883f8e16d14e0713cbd3332f65c115f75fc76ab3acfa99af4f552077e81732ac5f67302df257fdefbcd3a38a74e491c66a3c8fc6dad56a3543bf62aa2477d4262c7d4ff25a7dcfdad57f65323232323232329a320f654f2befbbfa1c0efeaaef79ab955452caf514851d69514cc9c87694a4e4032696a5d7728dc6640e5322d6a7430ea3a2b4d5a7b4a735d93373a54b2ebdc21a272c860fcef3ac5dadfe431d1f4e5bfc51fd7c2e15a465b34eeb34baa9d1f05ad8caddd44dddd44d2f2eece8198da7d8cc3e3d931c760209f364b2a704d28792c3589ee2d1230af1e8d55a3496377b9179d2546f241b86991d32dac1b32baefee7f9e880976b7d69247b529e67ed8aca78704e31ebddd0e20e5fd86f8a1d3d996784849d62fe92c99ef9ab12c853cc8be2146322d3ff62a75867cfb376b5c2c9e3c4c9238f27f3177d4f265bdd161b0cc3ceeb357fd14f1565fa9ecc613bd01cd669c817fdd5ca5a4fd652f084b0485027e88f530799fe97b2a14515769abf287dee361a9207fdd60dac7f9f5e49b6621d489c52364851f532f503be4faf6cf6a9c7386c9dd80f04b98bcaf57e4bd8b19bbaa97746c9a4aad23699243ed15e9c4c165017c4ba418a5e8759f3adbfa618d462f7b39b7fa518bb32f4e4c67ad7e4e27584ddd3c791c9a3878e136c2d1604351c84a1480a53fa0acf8f5a1e67139e187c6c35b4ac490806981fd43bd2b674458dd93f404939e0d0dd6f8d269bc85d297e484936242a61e40f4c1ef3fb06a1f9c8e4317fd6666d4619234f9a3c7f2a09606842818a6c4ea14b5060238f33291585136459a9cfd8d06dd63ba38c429ecf22a24333e99d0e4a935a631e4027362700096accfc81e9c3318d14fb79be910ee71f993e68536d8bc6952b576a000a0186ab97747bfe373aba8774c997fc76eab32758d1c117052bfd5fa7804c1957df3d074f32ccec79728794a1ab184a477192ec180a8765dfce65ac21250b7068a98694832679a38229d3645985093639012b9164d0946db24492c111f953b17e3e25be42cd07fa1e07115116fd9f1fd48dfa5e89f83aa8079fc3a365f951a78a524c3d37bbfff117f7c30d521482837c1e95d49805f4c5c0ff7e62a21e4496e3ab9dbb7ae1ae7adc950b97060097a6854ba37369785c1a162ecd8e4ba3e3d2ac70698e2ecdebd2285d1a1c9726e7d2e05c1ad7ad89f9abff6f4deb7b295d007f65847ca11f5f21225e1422c28a52b630522ba40b2385411e636864205fe89734376ebdb716f9abffe656a32aabb93792582d911b06d48a3ce2d90f13c715388e6ebc4cc9a042cc8e36339b273587a9f0364f3a6c9bda4ac97a1e6d6e5c29d2cc2214010a34a2ccd270c2d6e0a0f98b16c1ca6e32ebf0bed7dcbfb176e6261d768cacc366d2613780f534ffa51a60f337300be8c67c420a2488d15c1610cdd77c0d0e92413843a47925e247c384f5fc34c90525ace7b11e359ee1a0d1e41cdd50c1083bc6c8b0cc61377fe3476fb4313187dd603cb3ab235879f2bf52bc71a5582fcca5b952b4b93194886b319438622a6c970a426bae1469ae14698eece359ee161ea064a681a6e8620a4f701c31dbbba5aca5cb889c3099ec40643e8e22664e67944e2793368fba984fe6d094328da6add2dea9484449e4fa3af48e5c82e319ebb01ef1577ddc8495484d5cd19202322d0e26769c33224b188a95484dd032a76261826d422805e6b0c2327d28700ddbbd4462824ae65efe89e34e2f330abc6008381a0642045340a008621c575896b872054965225fcb0b9bbdb0dccfecfd111dee0e1d562234652ce9e0423cc54a24268a32e74d2772c97b22bdc342bdcca7399b349a654149bd14c11fe7d5e9e1fd389d304f08ecc8e3e3affae015e2affa3432fbb9660f03d59fa4da6589e484a55a1f07c9c35ccacd71f3a58a4f051e589ffd2de50dc2938f844fba3c1c52d8511eadc0882558e4495d4b3c161c52582135485800e40c72870a41064f7690a7b4fd90302138dc0191fcd93ee02ac7d59713d82e4b242666797c1db848b1354b24269c50a24a29c7d95b7c2de7e468e6169e5d652ae58e96fec4e7b3a5d3e9a392ec1d31a94f7f222f69138124e78dc04b29695359a7a4799e770513249aaa3882e94876c42c2909264c61306152431a5a021148ccfc253d940d74565d7395e32a8c1ad625cb2a48576092b650c2e5b6c514b22d8866c040d1f2046b38fb746aa951c8d7ac331e4cb033093bd43bf5bbce7e00c2cbb30e1109e5117491de0e78d96f8b75e63039c6e4b1cebe539e31b96d268a469b605b5bd82977b060b34ea71fe9d0833af42347e6dde11ae990498adee9bd3924f36982ec538a268cb0de4f267348270c2b8f7ccaba6790b0d085c81e3a80ec63cb78caa33c62c166a93b0b94ef3b35a4c5ef51771ecd29d3d6e190dce19f0243874d26e4cbdf07904bc0f3e129f31735c08f900e5996588f05df0c93f93fdcdddd79dce93b1edd3df497f4bcd5aabb714b7fb5926ebf7d3c3f7ca44ecb0fab0cb99fb3625a41d0e5a04cbf1bcb577d548e5cff7f04c0016c9e9d6b12d5af5a90617d8186ecaf7365fd16103046e055d2615e651169e3aad0e12aad95b634d95396484355bc0c61bf2c91866a5a7479d471e29086a21862c2042dcfff78dcec0766a3a08923f27c3997e4c9c3937d78faee60a443c924a5fe047ffe1187713f6bf303f1e779286b4fab95f7dff1641e87799eb5ab556834a54c91de398bb899397c64faf074389f6589953ff2d4ae4a1023ca9cab2558886003e248ef8c3af2fc69d4e1acf96b823de7120f3af43b66f5cfdc78d65ef36ba08099b5595212d16187de1972d2301b1e6a57f2945dd43bb336a5e4d9ffd323cfafb7c7d5e970b60e1dce1da6d831cc93e52f73c3ac96a66c72c9a5ff0e05df0cc2ce972d492d989244e464b504cb12ac0e111d7e6a1df69cf98b07a72c9196402553b03d296c0e1346206a7d182a66c9773c9df4abbf060a4bb19cac391da8cb2d6d96729809e5f69913f93ebd23918a64b93f86dee9e1ea577f436171c02c5360b8167cf23bb42a6995cc24ddf3f6e958816d4979d2a54b141e95784e054901691f927483cfeadc81ce6350eec2702faef08a00c294ae880e69092294b84289f8a4cb660973b5f37d3108f109c2ce9f5eab4f6125d62177b8661f8196abc702c5ea903d33cf20c741f4413c83ec3b8883589ece5bccadbfeadb771ce48f047c8ac22a1cc48291a4debf533dcbd777dbf22c3fa4e559548fc4beea87a8dee55d3012969f16acf32ae92f1f9d1e6cd0af5f7f84a86c8a7449128a9118f11726e22fb94463acce31ab1be24f3112222096a78fc4bebf86d0f7778c8405c9a4f9abbe7fea413c87fc555f8ae38ca570d82fcc077f0988299e2fc4ac14fd476808fae00fe19fa20f3e907fea7d9ac96129ccea5ef54d0e0331abfbd44bb1b26056f72c2fc5caeaf08f7df0a5d8a4c3cae4b3c95ff5596ec768b6b03dc33dd4617d9620ec387b6809fcd14b4d5bea7e32f57ddfddd3a3504afa4fb748f73e7e5157a66ae8efb8a67d4bdc1d03d1d9ddddbdead4653fe1654f90f668f87e82299a5345be57f1c0a6f84a5685e791bf5a6ecffcd53fd97bbfb389d290079d7aadcca60e5b825c7dfad1ffc0d3f5282cee2e923aec9e32da5a6458966ff9ee19f5ea79b999624c4fbce63427eea49ba4e84c3e2b4db8993af41f9ba9873a9475485dc84614c4f2acefdbc86116b33e0ce5e5a568bf9fca3aac42fcf5e3af6e91220bcbdb1fd2f22a8c04232102527dcb23092202b2af52c5543889cbabde6220966fc141442a8c24b4cf823d7f893431db36dc46ab36accf2bfd427b2ee36a090be609632a234dc588a83e0f3ab985b2145b2896376958eea5147b268af3e6d883e95713d963237730cd261c37b0e3bc19cd9bec9141661bb963b6cab0631b11fd2083cc3d86dcd14f03fdb6b5cddfebbadb543ae49a8a47d15432876ba0184686d6a1432264d8969f4794eb3a1504fc540ddfa3fe43a540b05335744f67911477e7d70f36537f1b7553d923b327c53c3b67decc5b68ce79f39a6a0dae6c1aecd6129a699442b21e6aafaf20c3b60db75187a9302c87dbc8ae96b0bd6373184b101150cbbf4dd55031eba78d6c344256aaeeed88bf7a08244432c8ecaf219010391ea1b4b4b47c0b4e8264d65a5ec6922099b26f1925d312173c8dfc155361d98269bf30dfbeea47795ba2c273e62feb2fc7de6cc96711d8363f58ca1cc7f1b8591c18c91e96ece9ccadbeb05c646e6a91b991875e3153033b6d144fa31af73a45f626f3705fc510aa3c31842198d210b947dd9fd3fde89f7ec8f7278c840889f71f1e82e4f4282c45f7fef43edd61960da8ff1e08f51f0e22ea3e48ba0784faef3f91be8e1756149a18bf4c0e6c7d29e2d0a8388a89fd53a943eea3d058b1b3884ba9c08e3a3a0eb381e37e091dabeb8c7a6766eee7154c999b6a348cb534a78cb30ba5cccda5cc4d2b99ebe4acf58eec0194a5ccfd8cf2d371dca7626064973c243e1d3a1dca23a1d3a37e1c76fa7e210e43b5bc12f1047e0b0e6a791023493dea835cfec3488880528f7a242c7f7a2522121824dfb7e0088285f2f2484022a0ef5b5e88c35e30cb9f882912b1bc077a79f0bb9f22cb1f09cc7fdf1eb601fc970702ffe5fd91103906fa1ee69180fff248883a0c0483bf47f282756416c42eff61960daa7751bd0b0bc87ecbdb6f79caf22d37a574755c206aa644f45930cbfff4e0a7c06739063a3dc80242fd8f2b2110f3501ec29612fc25797440255d7347f8d0cfe775783ccf5623febab349bfeacb211dd85132e5c8ecc9f3c2fb52ac91fa3bf4ce4824d35fadac752cbdfa54ca71400031d261fd1b3eb023cf9c1cce40189962479ea1f6e1f9019322153068b33b29b24723b90810347bdff2bf2bc4f7e0682417f1613665206686f2814fcaeffb79fd8b65eadffbf4d712871dd607e2e14aa50e6a69c751a9434aeff6a043071e75c8eeee80f2093b28bd7b1e74d8de819977c54308963ea177b4c9f527ad093072e81db0e688880e4cf5757a38c9f57fb4011a36c5fa524789a23c8a15268f1614e39bad23d8feafdb9b5229dd55a094e4795426eb29bd473d520e98e4f930405645a024914922d138b3cb73e11522fcd77f453ae019ca07c27fdd202562587f421cfebc145fe92f10c4b287dc8777082522bdb3681675d82efd3ddf3f074e722bcb2a3c9892270f47ee182bf4c09e7cce29353ff22093327d943b2dcb241f64cdd71e64521e9514e522353f47254579e2c63535ef1d75d854024853dc72bf8d6b67be3f8142b69325d213b6ec42cb54bea44b6374481f7cb94134df3fd3b86dfd02fd84709648505cc9df7f94c72d0809eb21f9a24f3dd38622d36f27246c2ef98b3ea5f4712c81cd247f7d3b6097c424d3efa9d43b63fdd3d351498c39bbe890a63ea5ba2f0461bd9729ee02d1347866cd504f782a29c99f0c80fd50e0cb0da2799987b932d93e8512f33efdd337d9a323ab5439f52925f9a29f7a29c1e6543695ed599ab0b0f8cc69517c144521cb2ed2776961515263a9995a8dde615930c6e89dee894cbfadf44ed702fbd9b0ac5699ba8ab0375922cda86499fa11d209c3f6c3bc8e133bf62dd3d5ed5b3d85f9197fa12b4c9bb07498dba83cb692a525c8148a0c96bd18e329ef5373a943ead5facdc486a52fb344826229d3a6dd6e6d73286c9952b15e1972a1be79131e655899aac15ba9773a04481ce40efc6e14991518621ef56d8bb1d23b5792d6580ac0d21335f7a5c9d421d5e94194c77ef283a70667ea9d3a46a7807ce083ef799e973d7cfaaa045ed59faeea53aa996a52e990aa700fb1dc6e2202bbf33d95b948f7a7a71c1e93644fa66800bdf7e91df08b00d17972b7480dc2fda9bbe32cd27da521e57dd2384fefd3f344435287347c5fbfe2a9d4b1e81394b210a4d91559223d71bb9125d26c8a8cf2d8185d64511b838d2ed868425a820ad8a329044c63df6a6da559138d4aad77c699948b8c6090bd18d34af49d64fa72ec279946c9a34d96634bc9f457365a83734fbddea9315788c87b225eeca7b94930112ae676937e39a1f5930ee967c1ea3dab7634ef48a410c4328dc9544ac374245293a64c7fa849a6454df2e8b44c5f5639838a3c7a944ceb779cc792b49b5097d9d40cf4e5b68ac0854dfc5a5a5240bcef547d96140d3deb268d69d667bdd3b7bed24234168bc568227d1fea9d8e0599be37913d3353cffb540a88e779b2dee48b7ead3908828decf54d878fc84ca178a87887162964237207fd97cbc30b2c62344aa04e7822e8c8137694b721feac212fdfff8283e60870202699469021be7ce3a0191c3491a02226e981428cf924d208e2cbcbd490fe2056fd242f2f45d690d5cffc0a07cd22ae10418498a4b5d0028933c4979fc14149a614b0183a81f88289b897c1418ec5172b281293482a2898c10fc4184ce47595f6e976779b3563ac19a42d732f6d99c353a843da432b1b3563089949f72eab1982229571835c9b7873e8e6a443ba8652aef8c9a47548270bf80253dfa72795dea998356b1d46f9dc0426744ab9d9711665da4f5340fd30578a2f578a2e341d50ac2dcfff1a9c9ed7e0c4416776dfcf86d09cee75e6fd2840c274a40df35d90b01f1fbd435b06f91d86f27d7fff5559e7cf7781fa359fe606b6e671d66c7098b3e633d97363d2eeb8a5378285c91269891a799cb37abd3c67b26766f92350879349e69eabdd7f9d9c3fd09973b4ebfc027538b9d994cd3267b32124cc07f99ab227083003216133169bb33a82747acff29d7778f4feafc743bd3878995e201d3a9c32c887f2fd8fc33eaca393672a48e71892605fc72c0c6d315034b560cb2d25cb7f1f703fc9587a87e370119bef7310c4f47e4749b0239ee110819472e766f6b19c23d993fac7a9eff7fb9b59079145d031b247758f3b2577a4f09f5477ac4174a80f34651eea5011ff99a1dc789bcb02aa799bb7c1413eaabcc076630676b4999dfebb712b957ef54bf174bd2c53275c8f686af0833ce21a07c471ff87a3096b63836f6a374572c6cc723fbe211b65a50965a543555387aa25d518df04ddc6608a6ea2e4be29ca3db39283306af3088d9bd5dbef6666756f68374e6e6635b11a19985fe65be69663de490d93cdcc867663646363f39382dc9c6194306e393803470dcf648f2ad7dc09c50ba4c024831005d7cdec5744b03730c652e8d7c8644feadb0919762fe54ecdd05833a331923dfe2cbf92c91eef6d661fcc8f354cf5c82626e684eafb6b8654df2f3fd6782d3fd6cc5cdebb3833b9a3fd6f6237b36c632f31b9c3314c91dc814a4229a196504c1765a5c33e3add544baa2587a96caa25956a49a50243f6ccdcbf52b2a81f5536148e1b237f51d9d80fd3a505cf626435883cc6c83c39852e38909b75d83635342b50616d9e7438de70e01a91a906d87c0d6601d57409b07872136b5e8978e36fe0a06902216eb09a59bffabb9a59ee77d23b9e7d30a2811e32c0439111033b5cc00215d08102139000910840e0013938a0010cc0610143140094801b6cf841c0016a30000d059841880c4180c44000b1957d060083007ef8e809000f6ce7851e2e00a0051d1e2cecc03757c70a395ee18c56c351e4b01fbe1f87119ea9967ca896248f7eefbf9ceb7a9cfb1fde9cc771713ec70dff7571bc8e9be357b8afbf7785bfb93ade2f4bfee10addb0fc77736f88df046f2e287e0ddedc94f839a8ba5676edcd65116dac439578e3e486866f663733235768032f8491582c161339f03e5c205e03d787cfc0d5c0f7708d7c919b81e7e1f6f018b8457ebc3cfc052e067e873b7e05ee05de027787a7c0adc0eb702df012b814f8095c1d3e0257024fe44ee01f7023f010b844de01f7019fc385c033e03ae01b7073f8055c063c0eb701af80bb801f7271f8045c053cd01df236dc04fc8debfa1b2ed023e0daf03ff786afe122e00f707f9e865bc31be01ee067b8347c01ae015e863bc30bb905782057860f72853c012e90ffe1fec770837ceb12e0c51bc3fbdcd6e72b3e0cd7e70770f3ffb830bc00ee00bee7fe781f5700cf737b3e00d7c7ef5c9e87dd007c8fbbf32f5cd803e0f67817ee0baf7301f02d5c179e85abf33c6e0b8f2f0bbfe3f2f87bf1dfdc1def17cc35563a8462299ef5ce7833cb5dd3746b989ab0239e8d354cf9867653bb2902e2da0d5c5beb179a7633bb99dd38c13387f9f0fd98866b0ed3c0f7e32287f5f0fdd8c86119f8e6e1bbc8f7f88d81efc74b0edbe1fb3193c32ef0fdb8c96116f87e7c735805bebf1573980edfdf92398c02dfdf1a72d804bebf35739804bebf45731891ef6fd51c1681ef6f15390c02dfdf3272d803bebf6573580edfdf3a729803bebf95d4526a2db5985a4dad9b2be692b9865c3317cd557315b98c5c36d7912bc9a5e45a7231b99a5c379c188e0c67c861e2f7e3cc1cd6c24242cc3644a2d413e189adefc7a1e1d4708a708c706c383849384a384b384c384d38b79c584ece50ce2c879653cb29ca3172180bdf9f63cb397218c69e88bf3f27c9613abe3f47c9612b7c7fce92c3727c7f0e93c35edf9fd3947373188eefc7117358cef7e390390ce7fb710c39ccf5fd38660efb6fbff1eefe3a3ceef4b2e8dff8df7825e287c1b03446724703d5fc0d0e9253848074136bde2f10cddfc041928c9a48e3d9ff4b5d283fbc0f17cf300dd7701136c2367c8493b0125ec24cb809df5ab196ac35d49ab568ad5aaba865d4b239a095d4526a2db5985a4dad5b4c3634a3d58a8c6c47494aae251793abc975c389e1c87086fc8543c3a9e114e118e1d8708e7092709470967098709a706e39b11c59ce50ce2c879653cb29ca31cab1e524e528e52ce530e534e5dc70c470c8700ce1d34be902ea73b4902ff4ff1572c00b912426feb7aecc137fc038b3a37a64e4777cb3c0e35be7fb6b93c35af8fe7a731800de85eff1fddc90c35ef87e6ee6b09d877df37c3f57e4b0007c3f67e4b09eefe76c0ef3f1fddc91c37e384c00dfcf29390c861fc0b7cff7734d0ecbdfcfdd1c2612e0fb3b99c362f8fe6ec86140bebf9b392cc8f7773487c9f0fd5dcd6142bebf2b72d80cdfdf1939ac00dfdfd91c46c3f777470e33c0f777490eabe1fb3b25871de0fbbb258721e0fb3b2687fd7c7fd7e4301bbebfbb39ec864fc037d0f77b430e53c0f77b33870df97e8fe6b0057cbf5773180edfef15398c01dfef1939ac01dfefd91c868323c389b96eae2617936bc9a5e44a721db96c2e235791abe6a2b966ae2197cc156bdd5a4d2da6d6524ba995d43af257cbd6326a15b55ab4d6ac35d492b562f8869b305ec24a38091f611b36c24518d3f04ce680bb92c9167cb831466e8c066eccac871b43cbc08da9f170638a8adc18a3f1c6d83070638e76b8314917b8314a16b8314b15b8314c3adc98260adc98db04ae4c4c02574646e4ca0c45e0cacc207065680fb832b51cae4c9103ae8c5103ae0c03ae0c0e5766015766c89559ba324c324d09b832b71bee4ccc863b23fbb9334308b833b303dc195a0d77a666803b5344c39d312ac09d99e1ce08b93332dc99207706c89d89e1ce10e0ce887795baab7c57433e77351bc05dd160b8ab9a00eeaae8c75d19f9b8ab9ebb0ac05df1dc15ec06a71ea8146db486510d115224020000000001a314002028140c084542a1482c9e69c29e7b14800a86aa50705496cab22448621432c81062080080001918111899a16d00e3a27020421000ebea78bc1894cc21a6cf3eebbe01aab0b2992d6fd09561a3f1de133a9935ce6c1ec054b6f795f941d6912d8da016431dfc2632dd19b0390c5e72c34e59f295ce1a16365d3a2a65269901867639924c2bec378637b15042c64a7daf713a0361f46120408c45a4566660afd4756dcf84c862cfb784cecf71d350b20913ec9c3e83390a2f976bec8351fd06451920747715480249ab61db31b5f264806cdf0aa5014b767ace1c103a7dcab2d993423b6fd5deb7a2ff79cd187cf343240ce18f3068683090e1c93041d44661e616d9c371256dd95ad71db513baf5e4db7fa1d07d774b11e2b80e1ddf2babbb7213edeb1034d98e8c9c7d6dc6a3cf2879c85ae3cabd3cd289eaa3a5380bc40406cf0b05da10c0508d81051b2255ada0f11f302026791bc44fc8e9e19e4887ff73044a844a3dae4fe7696a7c41a186c019f1eddd057056eda8bd165d7c65afe644a0a2f3f15dbafdfb2c2c508e9e60f2941180c7a869e654cec4633073d4dfff2c4b0036d48ef2163d50978e10588c5aac7e603b28d7fd544495c28a29a36b08e01026bc7e139521d1bf7d8fab668bf0a4a4425fa4206c4b4f071ad8c1d2d804e33ef15230bceeb5086cab314f049511cbee070674ee96df525cd9dab1303b203beb01ba979d36ed400f48c0c35bff9c53966ed8f57ed2375342f6895a23537533d1ae179820f685632f206baf399b7010c8344be448f21c72d739d885e1f777602406e41acbf4003d431e48b1204e02849ec8f7dc3f5297606d68e6a7bd72930611ba14385d6c72f25e486504c2f52aa00704c1b5eba4020208f2976468b5577624cc85d35792c5a4a6daa0b8800630f8bb2c5d6b948e230f09f1619b826314763fceae9205c220e682cafaffa9f1b2da4829d469b670121c47c5f97d8bc1e02cfe948123576c5f13a61d3522b8c91b4f4a3b3c1d2d386a7d40a39cc56fdbae79eeefe9beb08c27ac78b51482572d643782cdd4dc11f6cd3cb11264097823e148f6b5eb2290a54d9a5a042e676621d90e390510576e0bba440bf64948a1b66949e60cd058e3ba37df8d7007a89b14979f69875ce5a65fcb11060e8aae9f1096aa00b11c4b5547483558d0fe90af27a643a6fe93832f7618f172b7519eedc96fa6b8879c28b4d5cac8dc3611835f817419f854b4ba39bad7ca9ea9f1219ae9d64d47b26ac58cec4216cffaa8e5393242d54b318b81cb85f7c2c183e743d5b55597179c8b5539332b98a03a603969e0e748a276701c764c3b5735291a4750fad9022e512de626bf376cc38b999eb6c09bafc97343955ed3fab8109ff8a4093ef590f2c77ca7fddad4c3cb2a03fdf18c3b10b7c550ba9aa9c7a5ae81989ea1d031e8c84e9d02434c05291644e49ce6cb34f66f9eb12341500815390445388983e72c5c7abe91eb1fd3940a6f65a0f2fc090d0ba0e045f34a273ca53f029aa6fcf536f95e580a55a36d2dc3b7a363c248770ce6bfcad111415f6ce1b99ee179b17fa2b6666f533b7e559a686535514d4f7230185dd0841fa14d2c7b9cf01272eda06a878b9578c37a00a3cd784172f7a01993a88368b996cb51e4cd98927ad38a81ab03b480c98cf7b37e68333de1ed019de302a31e7ebd122bf06f35275ce5981107c7044c79d317350255e484793e3d0839c66bbd2b0bfd571745165d0477add144307d268a4f56f74f9ca1f25c8bdb20c5760f2858d1d60069f383b8b3b58b7600d611c0491a64071282032611de34194b435a88c51ff576e09799143f200544dbb09221bca6e0e8e8f24cac782b263ef399a42e06ffd429b92e4054a82bc10305c217c3ee4944b42235b20f67c3139f1f0ec43bf690f8c94c50ffb9168e36d568c7dd96f7a7ffdc2c9c04348817c5686196ec78ab4e23c06e12bfe45ed9e98c10ebf67816ccc030ad2912a405794f21f48b6e8f4cc20a091666e9d7d7142177d99429407944858c7ec4f92ef261611d2a2d610c5d743b61602d4f5b8ceb4fe79455bfb80f9c8ae060fd9ae2e22a66b08c00397df7e6335769757ef8980c82425309fcd167c91db1d5b7b1c6dcdb57ce3dc048af46567b0efec453ee0fbe1a235c857cbf8efa740d25d112752b9018a1664dfd1ad49fc42065bc79a047a0243915e5d45544f46139b2cf4379568cc7b023fc07e3fd970d52119b655258c2bdc8c54a0f10842c558728800ce146256812f49ad3072c38b5f8b3d88996bccfd360e205a40f5ba8e861a5a6b54ccad40d790feb321e2ece247d15a92c48eb10fd055593f8b9c24deee61414555d2e7eb731c1745549a32baa998d1d7095572acddce2047635670809a53e00347e3f955ed9416fef6bd3589ab9228c8cd59ca4fd9a46bc01dfb385f3056e5a087dde148635e5da1471d843abf4c903fb17667619e16786022abc89425c189a221680c66e2d0914dbb3c410201e30369e1c01cbd7345104b8888bbf2b019ba4dda45ada0d8ebfe4503eff97743955cbb4775840feb0eed06b308d4c1dbd19a84532fedf86b7b068f480c4fafa277fe92da155ead48d2cd732baec6d314237df03863c8f32bb650171f037f44c2016c4e7057372adaad2db9f99d5c8a0f7f70f6ba83f4c79281d449f4ef79caa769227233fc5aa7b1f0598639b9451dfdca4f8cce42acb838fff867d35f8d75f45b2b7cdc7092ab6aebff75e76dd53f2bd48e437c3d0b5da4d194bcf6c8ad3d8d7d17a321b9975987d517dac4c96df7e81a711ce49f82f3f986bdc9bbdbe29645d2ee6607898dbbfbe0f2240a13b8f9a82002e18f278ffcbf120b8d9151b849e9e947e37f188eabc67c77b2d6cf7ed6aa2bc095b87eddbce521167f57a413666a86278e86c28752187a767dc45da999571645e96323137ffec3b6cbc85b9bdd17a32ecab44f3ee2e54e82279f0ee6d14adddb3036dd8856e42afda55f581ea7bcb2f6b8f8a82c4f25dbc9652e0eeedf3c3b5f27e574e82077d5f2fc50569c58c1e34fd00e9db45fb2bcc85a1a8f2498d063b4f0c2435de8405c1c21052d1c5851ada90a416c88731d3bcb0a8ca609aee4d0e0e88a414bd16c9b50ef486403704b870afeca00037f5f585b17206b11553b92477849f625da15f913a5886262151b7d74994ef5480e88a19245a7b1d186b7ef317e3c6850b110ed05a87217bb7b84d091cb0ebbbb94b3dfe36b36c581c80a039c2b7a96a7d9e6a8e01e42d10104a7fb126a73d788b0c223c41c82da42606959f4645909344a29d9ac38ca6b1c34417fd61b09bbbf634f5a034b3271f6d8a8a5b7467be936ebfae354663d3fe0edfbc93763a9873f4261767b07bb11e2ad7470a5b69b3acd1dcd1c756080fb79a07118b0fb19b4ad78f9cde0dad55541e3ae6dd051113e83bd3cebf1d733c09d49641836f9d4bcaf4572a3c656dbc0cf5ae080297df8699f0c1d4260c543a438c556b5d78911cf44140ad60d2e4d298075c9b33677db63cb62f7f63850e5c561f665d4ff7dea13952d5ffc2fa2e8f0d9c34fd50154971a38bb871b06ab6e84b3017c73d3d89136bc6a212951e80a8084df018e1f1e5f4dbc6414482a9af062a9dfd46cb38a505f4d843f76f7e9650027bd9dc9764a58565f6c357c86b8a8477d9abcde4471a3adc92a208cc7e335df7c5569fda8c4869bbe493597c290f1b6d3f7896331fbffc84d31f52b689ff94ab5a5169c8ec2d089e6fc66ca88b2bbb00d70b1146d0f5660eea36d28ebc37fd4db4f71dec453e280059c522c610e727be41108d5ce1d302a9c534ecfedc08d7861c238ddb8b538e9b794d1696f25ab792d0cd8ca72724d8aae924d115286ada288739b6ec572d42967b963b8eb181d60c1c035d91f6af97dd86563e3ac5206eeb4bdd57cbf632642aa0e64e0b00d5383afc15ed5864dcfb18038b0c084380086faaece46e203e01aa120258dc9b096c128b3f903c86ef2fc7ba8957ef564e55273aba93cc31d78643d0b190877525c61ca702ec54828025f53c9507ec05187c7f94f7566d43807c62a9a5a054f1bfb70295ab3e353a7570e6cfcb5e32368f600bb02b1920005d7c4dd79807b1616f2f1a0b4d2ff4f94d1a4c4da1d22903856d8d5c84b5777caf41cce3910d1ae9bd164cb5d9daf7f4a1758a60452935be84da4ac5ed577b9b00627ec0e006a1c35b9204710afb47cdd51a4bf69eaa4753a4ba75d45d8db16ae7b56a37458adb47cdd908af365eeb4b4bb4ae7dbcf29a7303566cbcd787967855f7a83b3711a5ed2b358337ecb1a2ee55736d928b5b33aad91a842017427522567e0329e511d21a629a354f45fd4dd0a7654571ee59597a8767ceace115650e8de124195b6613d0c421b0eafe7ecf4e2fedfdc96272755ea68b64a61611d39299df971b39d4e0e1c36abc06f518b05138b90eb59854c20c167997fe90cf9494c5eb289e27358aab6b317be8ca276f1121277111b5fac5e38eb77203f78d0c460e653737d90e8290c3ecb778859ed5ace654344645cea7f0b1292297426152601e8588456171280883c2c59f60ec09823b61302766dec4c29a189c891a63c2e24b24b6842fce95f04e12d245a21f24d2f7083f4764af11dd31e2b845fc5304e912810e11c21dc29e21b22b84fd08116f10cc0982be40447a80e07d3ee4030d87e0d67f40220de0602fa11565a9edd74e0d62a115250ba64a5b66a38588b1222d1aed42ad0c595c32ce191b39691d6abc542cf02b00e249e1d670bb3acf4f5a4869a7aaa750389e6898ec3ab5af2a1211cd7bbff5ce9eab5623a2be3fd4dcadd0969ce22059958da7a772e0be49d817bd1cd267097d23ad45b9cd60bfa82450be17e80b0dea3b0c0c87e4bca90c412f9021974257de6bccfd8a72586c989a6466b0783db51284288789be2523e7518ec6940eb6e22255573ebe42673fe272a6deda27df7529cb6bf2d705d049ce5e875cba12342ee7345400f7cf7e5fb1a356749ae8b22d048a7504db2229fde7b86cf24f69e36d3657f45927a94cd33a55cd96fdba6adaaa54aca955a896d984c82bf192591990a2725dc9e04dd82a913b7c0f8392738d0f30ae1a2eb81c6e54e072d7c06de391f7340870b233d738e05792811c6e246b2650f9d56461ceabb912b73b71bb82951f65687f24b98040c56e1c7d00665e8a8c2e8d25bd4ccb657ddf6e0cf100ebed9292d5982eac645e47931e974d5ba6639c9c9697e819ec3c609328195fb18332f1d78ecaafcc89ab0ebfdfabf7b19bd2729321408d2a006bcf79651e62eb93258e5c6bc99860afa460a5f981e37c4375dfdee90fda7d8999eb566d424bc4384b9075ff0ecb14a1140a17145846b23b8a07086a76a5e955bca75c5e9d3b12e389cc042d7fbb1a220560e67e9aebcd422f0b7cb8494fc9e92d3041535702a97a6bfd25c222084324286d21fb72d4314760bd265355d4076bd2fcb2bd0b732bf11e37877e14d10613aba06cbe95f9d1cab393eda9735930073320adadb2f8ec769c16dd9904d9e83d31bef9d5c47794855bbfd25333c7d0ab89dcbd21af479a15ed59cbcd61dfb2a939f6c058fc39e3f5ffdeb555a1c3637c3195eb7930d6badf77205b4bebee20427682dbd74dd161f4b9e4e27c48268ef1ae437240c22ce5d16e8160c68c48358f75889fcf9be083d4cec0d5944c3f1b991f9ba06f7ee3b83f8fe78ae414935b5962b537e00d9b9f7c827571ba2e5a0452d1414731c3d628ae09888f062486fdd5abd438142fd02afd2a6ef5c0caaa4973e231260e299a7f58614b5b72e0ad2ee06bac3c350eb71d872efeb8f90d99dc902bdea47fc3b7ca35164a0224fb95fda4ef5fdda39732bedd5839eb4b0adb8daea334327d9df635d0d814885a4197daf76b7f3af504c2f2349409c353e369c2bcd38862a7b9cd719dc60eb2873986eba6d398e39211578a40dc800ecf993788f6f109f6bbad4d36cd0915d169be892f04670f5262aaf7b73512c0c677b26007f869eff9eca4fbc838bcf41233bf6f26fcc48d3223dce6320e7ca8c77a992f2be24a659f4d1bf401c4565b1af1887fa100235cc5b9eb9850e82b42363ef4ab3d3186c2be86f61041c78ebc19dba7cc708f728284f851f2d5057e02b2795ad682e7f20b112fc1011a617933c181bee1d865c651dde0ba171b621b24aa624634f3068e062730fe2ec3e41130368f23bcc8decba6dff746969d3097f62486051df98e641fbad99887e1ea8ea268c2531909a858ad034c0d78aa0c5c36c36c2397fae594113043c281b231975fb301ca41379df3b438d56da317b74202f132b5296b04353d038fa0c2242ef6ece7d0260ff0bda32de3e67a0fdff58085800ae820845243b972560e0a40c9cadb6c76f9514db9c12b310936c0646cc7c2446196c32a1f63553d88c9e01ae03eaf0f7118b66b13b3499483f4e67b112823a27327483d52a28b17b815f6038657fab887395ee89ec5230d5790e77683171650cfc370e4f10461d5241cbad65979d8657b359d0523907e994931d3d7639fcbbb283b5534b7bc2b51b948bd9e5bbda34205c88f2b3da8a1478d359987233fdf112378cf117d4e0f1d0a6db93740d2a1bdfc06a9305daa30e226c0e2a876d6faf30937811d7986ddcc4175915c35182197549edacf40454f66e98d022412cdf68a3ac2027ffaf0f72df74e88b79b26672753367a655526b1c7cc81b16e318ac0ac910494b722a45a9a9b7a0b0c590cf7390c92c6cd2eec8a042551382317b6f05706e04e2b36f9581e3fcdcc563ce8c566134518d486f25112b31274e45ea29bc520d28c26fa0923bf131ad6fcd4c3885a8ec14970c3cf53f99498a04959d07ad25be3b68ffaeb3528970fc45fab86c40e790eaf436e52815bb7084deb17109aa4851f492ff1b40b45d6f85000e98a54260f8dd063aafb9c225471094f976051486106cf8e8aa2c961c06745259e68256c24f9a8cd38c9baaf75010aec59391b3abdb4feb692f809542a1922d31e4bf06f4a7633d689be76a9009f67fe01072b758f2c1c89458fa2e7b91fb46a14f178450d435ac89f40ece8ade4205b82e82d4ec043205cef359db064508fa2349d34f5e0bcbd34be6ea28762aca471fea39650a5cc558dbd17a0bb903eb5e6649bb5638b6217906022fa5dedbc8ae1f32fb7cd209df25a0199fb9657ad9df9b12738d0cdad503f3b8d897affa4bb3e2401b2303f1db9704f1f2ff4eac5dca2a9ee747678c0587839656b7fa4ff942d86cb8a2ba478ae581be02862b7574c243eb2f7ebb118fe86aa42f530447753adbcca6b9932423ed9a8f5f2219e1b55beb22b33323f1d84bcfe319a1b15ce85014a677013da29942e966ae18c68a186495c41a0a0ecc7367d4e03f103c6e3ffae56a86d3f52f72ddea99afff85b2889ade978ad111445dfdb30141ba66be45bec1d224221653e3db7c57e062a4f5e8483c430bafa8552f52ec73ec364aacf6e1fc2fa07740743028f71c93c0516dc72c4a23faceb3c38952ed39c601d05b0ecef8eb0ff69dfccad296049e6ace7e0e00eb46b7b5b23ea7986610d151b3441ea1b57430b30e71cfc106371e94381f948c1ff3989e2b58376c0ecb320bcf28450c181858ac39e4ca151ee974f4a288df38217c86cd364005043e07a1ecdc2fadefa3939164a007f905d07682af9074d0dd3e6aea464a960d2e78729aa8ad90984b5d0a1138b527626b47c710206fa2603b58f5f701b4d4de2da80036bcc547700408c22f6bc408575060b71443e7aa8585cd789c4a2fa7e9fcdda061d439c76fba9be0d9cd0b6fc8731986cc8ae8146ba044c04d943af11817834c318df0c23714cf2b111099ce40b8084f79578d17cabac8d8468cae4ebca601f1a2eacd1fa3c85a3155b63f92cf319e14a5e6b0c078e373936a77013804f7e76391294524a65cc9394c32ba3dc4c971a8fc7e5bc00aa32fb0b623606f77e0b83415087baf4414925d9d8d68e4c1dccfe3a68d74836acee586adc70b47ff3594b08b8e792d9bbb6388c5d3bc513ccf9b6b406dd98a95d1697df4b6db83d487d067a3b6559b11d497dc8976001c44c83ec55c8f6e59202c57e33a2e27f40fee8b660e04c589bc189f7c8b57e01e89e3f7044f3855b830ce252c7a02384092a4f782b5c407a111c67471f8b47fc4e1b0d5aeb58c3fe8e7889f8f84fac97d12ea61e61b34dc65b953931d328549cea98fe0f1a88bd7ea9dac0b5ad7ef121956115382ace743121fd96add0cdf1b4ae92ab005af06715a177a2f2e2f9a7d63d399454e1d852110a73effdff4f42d14afabf653185d2f64efc6083076fd489bdf9844720bf2bbb38b2fb84482d7a04928ecfad5070bd2c0e6d9a1ee7511a235e4196686b5a7ee9e3dee593af8296156e3b3c56883a909fef755664960788b7e0b642444d7ef18f25626bb298dca447556732dbca645332c10366d456eced4b433b51e46642054f12f23a0e780900cd22083ad4fb7444540c17c5a077323625f090808c02a8d6df1c1a64b236aaeb03f8f8dfa73a18e1183d38aaef72b0185daea25cddf3945a4b898ab335f3d267af5a6b5841434b2780d48145de482b059825b53d7bf033fd60494029c9c4561ad536aaa9bd907abcd6712150e7e9195f6f97b863b91ea191ba58114485b86ce2e66f87136035b2590e223d75d7aa2545d860b01f3c6e87dc0d2a611ebc9b4b11b65edf028eed0cf390130ee89c7f9472dd206983c1a99c955d4139102d15d39fb6a10d29b3c15ab628177c2cdc96d2b812c93e383fb76752b179cd0804b1e4c12f0bf39c9dc032c3420c9651e85a05fc5dbf299d9ecb150030be739e68675dab9341de3311bab7ef78b31a264cc95348637b5b2d4a4e965621f86e41d2c33f829378ab1453f94d54d908c6cb4eb9a4297a20964774e8ff1becafd75cf6d42d2c63b806d2f95d0d091f727e41889935863d017839605d494324fb8b63ad6603946601621916903a706b5f6f81bf2f5c4fff6cae22c43fcb79288925965a23b963086798db71c458f4a80a3e0c5686d5bcc71c328116242b7ccf82abf74753526ad21514a7698b46bd6f8ae1de968f8cfb04785e84392b7004df54e94c4ce00aa60921ba56b50bd2a78a62ef8fc3ecaacf98c9196d8f8e24d56479fffa425e029edbc60340627999c4d121fbcdb4b36a286b2bd79db8a7c6aafdd92983f59b48f6c78fbc496e94a593bb44f52fe97e7fa1832351d8a8d7c5095c4a1852ebc9d505df4b080ab48761989b7591ebe42161d3e5efb6548b421b79de85387f9945533d5cbe6b0fc1ad89d4ca214c308b73ed1dd4ac3ec79d23ffec511438c2884e95b10507de6e094e411ad162ad66b2ea21cd383296ccbc8ad6ce83f174dc7fa0e552ee0a03b4fe336d496482f68bbce96cbd9fd612215fd452c636d94b631da5fb9806087c6394e2fd63b80853b419e30a0c872759341c27d5b25969941e7b2fbb840e987aa5a78ffbbc38c5713562fe2b431ef38dc7d3175b01a9524dc8163cd30eff66caa1dc7a13f4c5c95ace03b6cc5426183ed6ee34da4bf4851bbba4dfcd85a4b4a2387564d6225b7c15b8d5ba3623f0b3c9ff7f95f19fa9a2b22872f341b86d5ed0f21769e6925c542693945a2327baba42bc0aaef5da406b9fb7f79935c858832dcece1f89d6fc56d10874feaf46ac973443054e3a3fca9e7cb3b9dafb5df13f1d9e1fdbef48a13a8b2474e83c892cfe73c330d9a2ef630df7c70f949b4282962cbc5f0efcc88bbdf3bf2af5a0adebfe8063abcede4bbc18dccce1430269603a31d5c82b906a0bace1959210c01a28b2538a3286bb25362dd5a7be17fcf72a47eeafc650061118cc2e1bdbee2cc856d46913a85a5067abb8540a08ccc9b8d88f4745bc7449c24a6cb9945b77c64c0a60027a7ac6ddab7513641aa81f119d048a992d23c128dfadcffac1c49ee354608f5e497c7d894d593780c6a31e4b8e6bec2c13df824eca1482bd529f2b6a7c884b9e3e32e2656d79d5324abefb105e931e94ab15b603fd1da1069de8c087fe0e3184de879e4f80bb7b3aa837b0421aba668952558301de807b46943b2014499d41f97c9a3aaf1e64d66fea00ed4f4c60cb437ce7132a9d493fc3ba8b130c2bc974b8a65c3d38f85b0178f71493dd86c3a229ae46deb293dd736dee1a40e5bf95c15ea00eb57d46b4d25f0af1885ea9a688a517a204a74d9c088b169147e7fe1ef59d57274d1a13f352e18af99e0c26ad9e3105c42e86c385f5ea038c8cf1579607d447410c17bf5cf30cd324eccd1258e7b1ca3f7dffe4d764dc72024a78849cb41d1cd1dd7edd4b0a972c338b66941167a1e00a3438ef6771fc7f695dfebf2056bfd6d50cc1e4da5831d522149d6246c156f2169023de4dd919896196537a5eb124cefb6c6ac5bfabe77425d6e8e798649d1fd8a0e7b0d36bbe326a6699c4fbae07ac82226272b1018ce490d14748feb664cb0e5e0f8d52ca854aacd042523ab973b6d4d3a5a6a115d78cb747a7109de1e3d3d663b2cba664adc12cde7efb1c16b1f506269a6bb47b4e0ed22a66453fbc12ac5e289790874fe496b5903e84eb93da7c71667291dcd9aa194f83c43e30d29cb9097702f3f842cf3eed8527f01bd09609743528f82e4aab4333e5515443582b82cdd8ee3fc89f61635e54cd1d8789c0663d9b41dd900bfe7fe251e40d54941c28856563b2866ef222599342f4cd3ebe157d0314378070cf29f2e1ae7112d765ce09b8cf5a767b1a8dd7d9cd13365a6c7b890394645f0b685287e884e2abd1c7b06145971ce7b2a9adae89aa8870193c5a379d0ed9a630605746304731cd3c952c5acba8717bcc2ec64e40bea6ea53c291b62ad3f6ea0900613731e8e2af0d814041e905921b4180098b17ab3ed28f4b29a5b7bd88c0b0f218fda82284dc6d70d68641f0dc437bc770a234bb86e369e7f7447b1fb330ebf727cfa88d89960674935d204d7c203878a15d8c760d152e0cb524d0faf1b09e9022430fa8d53fb4a058e3eb2f111b5478a650b83788effc04dbe6f931f74906dd7b6eb2335383aa787b57525c96b1e8cc1843d0af4dc5688ccf643cb007e4c7b7eed39c93eab68b261a728e6b4ef1da3ba5328995f569e8a99a62b98614b1265a273e0a03a6893c9af16a76959841c82a3ac1002f209f4d0603896f327330cd337a8e1033c9b3b38565251317eaccde59d682906cd615d8d6aebf4ab33cb58b86a8fc64c90358d4137bc49c54007da1b45b19ba5938dc21ad7b59e8e8111a4f2df29821a2c1db491ae666cff52147fc48e4d35a66d05a6714fa001d69da3c15245d1cd2d464f2681440f153d4dcf293271d1ea33be28c1b2c8f177f6871d7a95adbabb4e5a155b61261e354f0571c8ac96f2db520869a68e6fac715cc297720bd3adf0689ee8f181563d7a38682b1698258c940a2ea920222504bd2fc1df04cf82520488b734f0419aebf46a6eeed221be05ab2013e21e5cebd99b185b9c2b8be72efcb9b1038593fe06e03a1b604253f7673f4f48ddda216f8055727b91fd9f57b3b078d422daf058b9d4676ee216efdd9299e41660b00e6e5d1b82145b1b0f30f6453dae25a3c77553ecdb0d3ab1b79425c395d54baf47b5d7dfa3837e5d4a231ea25adcad0c6b4d55bfaa31119d83f060a4325ce78f454657845cb6b94363d18e3bdf10954eedf18478580b465e4f811ced7684b6cae21859270b26754909518cbeae497699c4fc8f42bba8f0f4afb7e3c370f1fb3175da9069675ba61ac872c8066d8ee45f3a6d0c95556dafe398994946fa7728894cf79c80c4460aad07406acc37783bf6f39944f28abf37558fdd053e9c8cd4b20a521cd350923162137e71bd18b44f126bef96baf849cffe28c867860c7838594162cb2a37b661884f65325a40880857f171652049f9e90d6ff84ebf3633e390aedf2c9eecbec0ef40f4e4a197dd0fd88485ee1808dbe96920d51b08f4a95787a790efe986100088118a9ceee84ad7c9f17d541317b5d79006545e5d1ea698c020fc5c26fc54848537fdfd2ba66a30d17b65e54a55e8072a63de1dba00c63bcc2dccfdfaa357adda62ee6d42b9b8972a1a8d522cdf33a0fcaa55e374fc2aa6cc0f79fd1efd4304b7eb581d40a4154202b26a92578ef2329b908004f78aeb534323e5ac921609983d4a9c7eade2ee8c818081b78b53d6e48a11f6d6bf36bc466688da900adfb75418cb2acbb32f5c1e668ff871619b06d000a7349b99df526b1f31dd6e1011d61235bedd294b6344f239f5ba789cf05f439647c3a733b739e96cd8efd42f6785362e6f73bf03f6f5cebc54807f57d8c6b667689d367bfc121b4fbb7af90772867f17983680a50f50a214b2a58edc24be6c9e99f13f2c44259e09ebb9ca2b9f67193120360f78895aaf29fe61c6c21855e370938b07f163f566f934800ad8e1e945d0863670d35f2e6bd678667dfa082a2dd4455b7dc79b4d546aaa14105de80630bc9d754d02ad769e68e9338d3538c14c2ae555c8d6d8cd0499bc0bef1c78548e54f5428199e50a9ac6fe86c50090d9ba46c020f85a849639607bb74b895964afcde622c0901320466ba2483205960f49f8c6567a7330e9d909094be64622fa1a64e06c3699074a92614ac247a409b08a8efa3d7a22edc3ebf7af67526a9c8f7e10534c5a33d2e6dc3217981de4460c8e4be8758dad1592ee00d84a37ba5018e77b10e2a2c2e4b33ce0dd7dda6df318c0a72085e49ba071748cb5ded0cafc8a4031d0b96b7aa5fdc74b61cbe9d45ea98ee47cc500ea23087d492b0e7fc273de35f1e982789902d2f6cdaadc87b015d32ac29b5af5c3853bcd8ecc804b6985fc62bced70740719720eae3fe22faba5042fa2abcd914882ac8cdb29516916870bbefd799110a365fde34c0ec03332a0eb68f92eba1a10857b1e2a8517a191b354af3d2b802094df462c135891f378e33732c2d37dcae6c96e0293b3457546b4aa5c692ecc71e9f257ea86117b2233bb9979edecdae79bc6002b6e6f0cc2151e75f11dcc148581040a5208cc21c7a71b00fdf70565010807d81fe80cdc77aea5a1c3c68d1cc6a8f974c7a8a34c666769fd4c797152caa503e61f17dd0b31aa1a62e4382c1b2e1788754022968d4bd7fc165c76dc2daade7828b1c90ce8c21155a7b301cddc7a7f6425942af93aafd0d9c6860edd8446fd0fef38d4e465df128d4862986f4b99357eef86d4400fcba9d04949602a1043d940d76e4ece86ccd83d1f826fdea0efa8c0966c7fe7075ce7c022dffba217570e20324fc7be6d7639b53ae41e4725a22a4781c8c0cdb6b352dac342ead5e2714c426606879dbea5c283f85033704c6471282a64a6a6f62a46143376e24d4361dddffeec5a86803b259f048ddd69d76750689ff6380557d12e5fc24dd1e6aebd9836dda5e6cc5a604497c6b413aa0e04691e8d4ea474ddfa90396b225070a299c3b912668338a509840ec1e14a5cf0314e19e859d4fc449490231d8bb7538f2b3098b335578c630fa7c44b785b6b98d5964bd3f711e2b9efa8220a1a3e68106477877c3766eb0e077460fd038944a7e02eb778cac877b42d18b38b3af98706e741e829601b0eac2470d155ce9dcf6e262034b52d364ea0d68c6e14fae982d5d683bacff5b6628163f345b5b50dffbf0869721d1c7802cfa9a2c01fb06654bcf1459f5f557a8382f05209d314d06ae41a32012219af16442b702bd2994a04677ac2cf7fc0cca401c44df685a193126cdb1388de287de05f32d0d2dacdb8336e36fc2a0631c0159ec99f1859a4e934ba9cb84c2305657893170107b905bf3f8f4e8824184d380a03a5d7af529491597c5d85dd59d5cada6b7d4e879caa6e4792b3074be86574af8f9d8937fdb45d0ef56bb10da75451581f1873f84df207a416127919e228b38b00b56f0f98f55ca89794562f86dbb7e5ec4f8e8e9b71ad72768465f7db18f88e56315ecf69e6b3f39972b2e89832e58fb8cef48138b23e09da0fc185acc1db0778a9b24ce674b9c1bc4738c35fe24c1cfd1a95f4ec0d1dadead6b5316e72ddc8ed5fd31c16ae4cb67e9b7fb35d5c63b906d540afee3ba58134b3437f94514cbaa4e492e9dbb56548e668be224db545a0b9be4bc6c0263483fe56921e8e94586bcd8fdd6727bb28117fe892d048dcc9ba1fbcd5a4280d7b45f1b6e17e255b216a403e0f47ff3683556a82b438bc5b3071dbf4cbfce8dc3f1d5db4b87f169021389c32a1c05166f307f3629220423729b637f4169b4dfce16043d1bbb490d20e62b3bd0b34f990a2e36dfdea130f277a0acf08f7668afc920958ad4c18e07f83f83d12a28ab0c14e82dbb66030b9d4140933d7c55b7588b263a545df5983e334509f0a766061d77f67ae93f8e18d9ee83e5aef564b4a39f6957f337f4694418c5808b3835e6616008dd0545eaa61cd063f30a013aa9e16af98b1678bee4dfdfa1804703a7c8c5920fef4cd121221ed7368e389b3334688aee410d30485a8a4db929712ae65e2286c643dd2e87acf2850e5def513a9c166283b00cca4832ba59b867e1947a7c01c17fe0155d19894a1cfd10eb6959f3581fb32f01b569ff89a5d4a7fa508dc67b740455b5c98bd633d20ce8fcb9e3f51a8152c8dceeae670e3c847b24af50a934b1611fa1cb9b5f829081aa7b13d0988c6970982296ae661e0ec74f3b3252ef6df05a5fdb0b5bba8420e08304339ebbc7084c8cb70e11fe19d7540e1fdbba44891b3be4ee42e74d284e5f87c76f93851343bc62f6a62eb47ff1263c8a6f1ddeed6e99b07524100077efe146006eded90c1231434251001fc1d9488ebe8d017c50ec7a6205afc15f7b4a021a995dd95056e47622e653e4f3ec71df090a834a0a8f0424085c065afbcf6f9ca4bab0361d276f1735b999b9ed3066c0a0fa87acd91935270bc3207c4e59abe4aaf0dbf6f3686fe5a16d1b3cf854a30ce3ec14e9905dac0a3f1a06830ec1348d8e1ac9db4f05ccd1349e367211f3e8f21d539df08102a550f6837a8b0aa267527f95e893b28e2c7327d0614fe045fad4dbe3e708058c5675c4e740a883985157c618ead001985f6085babf244c28fb7a705efd7c3954a3abffbf70fb90d82bf982925703e7b21f38c80b98490b88f7854e2c1c13e060ce069efc258cac96c50f853aa2c46bb6099a3b42de255b067cb7979ef844a6a551158ade7bbcf522cdc8ad6a7f79b83339646362c021360a52cdfb4bc5c6431d950c932f7bb46eb31ddb54c485f0ccf7f8df58a67b614d77eace47c658fd18dc76cf165634f4a25cf395416e8f3c70758698b71b07853bc76e3b920b8f1377969fee9e1d43cd8251535c22f683c3c2bfcdc255495fda79143d0b7aa61cacf6b0804c4926fe1e0c6961ef251ef776208b20193b43edba7ab8842e055bf43a398fd87ba3f0a2c4e19501a7f7f4213321929bac4781ce9e204237cfc6e2589a5040d5b5b4f5941ccb3c0548c4564f7d69ba6181a0aaf2721a83b8647176304670a029cf06b376703a44937cd8a6324140c558ffa0dfcd0236eae6fac1650c299f9cb8eb73e2c333848ee228c9c52674a7aeb5c9d491c6a3ccf9d843a60d96e6e1ca8caf524abfd5ad7a0777f4955ce0cf4d019f1eef3d941486e06d60e7889d490366a552714844f1543a0ab01b261b38ac1d1439fc6540c99fb23d72d7fb7b1322a54cdc11ec6eb72b758ae862955481974647adb60cc399279446663925b8a49f38219aaeb4181a6b3adb3bcbfe1e6fb98101823f9246f688f0c40317f728266121ee865b2e0a4632aca78c2bb5a6c10a2deafd1a4124d252908086ae74b35564ecb7ecbc2999196ced3271334d58aca54ee4cca08a8f72200adf09b538d56610ce8d74ae655e7c996defa7607d202c26db404c4a4a4529de5e1cd05a799a3c82e11d0432577ffa3af3dfe3bf277af879660b411681068a18365fb46d361bcc3f4b2c6df8bbb40599d67fa68f27b145011b529ec085b043ce86bde429a989ded1375c2144aaa6bad75a352fdd7a062568be8fb6c3977e8e551fe7961ca4d1fb614f512213b5e8a683a808cdff534f5440bb11c1daac4b4e17b47b1dbbb7478562a68e15ca8f8a5a99f89f65fb9976d3f9a763e61cdc486062ea24826a3aec0d0dce041f61032468cb87af110891c7434fc8f2853ee2a30843409b85f11fc4760a1d3fb2a1e42342ef68ca1aff51aab189a2cd68058346199f36fa5d4331c5d6419b53e97e4815dbb7a53b7022d217dcb6612299eb81124a072fd1cd6cd469f023e1c05d4f3855e21b933d255f2a2908f230959f9be711960a63c5fd6c3aa22ce931c0923d911bcf2b51ecd421e0fbc88d4c2657c8735cd61657d851291459fd840c9aaeb0daf544f401ba4f809af43b4dc66c6278007eeb42182d41e872ad1e0f45228bc345fbff47d92a7e47462c038d7ecb505ab823dca76728d64ed81d21260986f7480ad58864ebf884a243ab64f294661c48be2cb0a05f3e923cf7e58ca062ef6eef72403693bbbd4751ee614f5642d7f5713ed85ceaf3d8ecf6c55277d0cf1649cc4a4c50ab193923fbe67c418270afdb349880948f148f3e7068064f30f18cdf162bf19bc0549e6af387a7f8838b29f0771dd3c086f16725675dc78af8473ad5891cb71ccc22a5f45ec15da090e337bdfde393652bcddf50ea9386bd775b17f8cd9d07c0f185439ccc139ebd85f0c349b222b40a0fe5d374bf5e0a4b8652d65d424b63e42356130c15bd62c379913afdf4c1ebb77f31e7f0d870202456d8ba4dfb131005ebdd12e24c3568fd651ad926558e2031aeab13a9932dac51f0d8222e1ea48d9ef951310c1a85725755757d7f246cb38b1ea8e49d0a14268417795d724540253d19947de37fb6e7a18d209382b1205b25ddac4287823f9658509ee3a7a0ab8cabcc21a5984fc09b91628c302659ec11914dae321acc1409a747a6a169aba6981fc4c2d5c7a362b87e6eab10bbe169f6fb937a7717c6ce75779c8eb7742c09f547e29ac809385220425066e3075adc28cc0c53302c87ca4fb8d39cd32b51c5845fcd821d8555949656286a1adfe1dd55877edee1b53b4df0c3a589083950ad25d8adb84f1a554c66972e1af8f296e141eb22cbed9283358e68a3ff682cc4c52c37194f83fc73cb442a2555afea3be50c903820a4a342102bec93b42d92b2b0ca43bff550e98bc96987b9020766f6648293d6e269cff8501dbea752a2cece0b475ca55c6f31f2b9df72f9ade4c31153d37c061ab88396793ff33a873a1ac43c25df59a0395821ab49287a97ac4afcc3aa766a0d57c667722a0072dac7186e54f9ccaec8c8e47410f27d9e2a61436baa24f3e28989c9145628f219d8579a857ae9a8cf6f726f17564a131a5a2c89334936d9ff1eb7d2e92ebba9306b2c8a5f2b89e7e2684ba854463856c436ea3a34e4a6d29160ad18cffdd341f5773f789788a4950237c47ea074f96e4a038da775b214efe6339b37b3ed60fbc2ae2915d0a2838972569d51e67c653b307acb6a789ff90edb7fb855ad024eae0d69432ef1fe069aa07d198934d66d8292e01f6b21be80346c7999fe6b476df7078a30f185d15d612a0bc89182df530d6cc57d1b1b847f34613bdd08d8f17b4f932f7c610e140b1daff00144c959995f2f7eb79319549556d420914d7588c15d6e913074dc77fa42065ddc4f6e1a977fabb288743c4fa820b6fe03b5b9fbcb6a618806965a2b1052e12be0184abe3a444e88353ec8b39e38b2f0b7f896a7018fe1737d922801d1c3f0c0954f160101dc96d5659c785e20281d3649fc2ef831eaecc7eb4e2cd787c87237e96d93a529f1957ce607711401d315a46e15f207dda1c533f032371c77ea2f8c6127312616bb04461c459bfa63c6f732f37f74e5cc47c9538b9822f4b3689e821acd7074e6d4f2f5c44015d5fbc15374f60b11c59f6e2f2520ca0a3ba1bdd5b898ea65942b035406881da9f9723b560020067fa21e3e9a821531cf19c1350b3ed089ba298dcc1a18f598764349f2e35e0ad85601be70d2ac723944a5b9703948a42834702c7f118adef7c5b54c90d8fd94ae53ea11dee70d0f82fdd9153123f708845c15257a4053927125141332f5e57578a69fb9a506afb61d18d2e33250d5f75942d65d56ed35e82a368e735f839084869fd8f1fd9d4622477e6cc9c108a9861d21bdcb912aac874c223150eb4ea68e3a55d2c6854339c2ec226afea43aad98bac79a19fc3053b6fcd056b3b1cf160f0dc3ec7158eab05f0d3d67e9b064387006c4e86b51ae88f8e265f6eb920e8669e547534965261ad0a9bebb81d1289aa131f486e89c9a3d2642ed4bff43f7a588f5389ff20e32b1e39ee169443190da33a86a4147870995bf6c36c9002915d94446c038d74ca900d1cdc1bc84c73bcbc2f765bdc976d895c6442e14957e69467dfd890254ce36257981d477ba8bb2a9a34d439769acd21b5c4eaf33c4ee6620b44db2555cd3e4af18d7f7700a701e737eee25fa2274b2400a8fdfd38378bd8dc41f8f82c71f3a702433776de27a99fd2b93d4cdd5a0100a24ceba43717c60ad0fec4df56a66e3c10789597493a27226cfe3ae0ac09cec4845b85abe5e65172ba67d7e427749c1cf6900aa93c198be2cc24b6ddaba97dd6cba39bf537daf2123f6c888c1d779e8caafa52a71f70c565f3f9ae212d9359c5a4ba7e0ec1ad0eb2856303d95483a664ecf781671d4a2d8f2231702fa63d3e719094ffcb17db62f8c564cc26d82a6a6067cfbdc23c2c1bb09085ce1e606fc182760b6138c9fe48c4eb31d22b2c1075491a2b0389be94b5094aa356ff2b11de4fd04e1cd0314a3347f859a0e126b44e57bf2ae714965ba198474c6a28fc2aaf9389eaa5dd6bbe088c7c5104e1582093dfd021a4140e784e1bf8b88604ace0533c8a9b48c1105d1a35a11cc5254906c289a5e429db54c1dc7645d874a0809f68c1266cb08422911064150fd444bd522547d46a0b69089c8662f39d369c40d861d127932c33b04fec86b6cc03e973b20af238ac368fe85cb2939ae1e4892cfb58d4d4fac6eb14c0de4f5387138b11469d6007556adab75102b2a6bc69fef3240172f55d2060a014eef5a449d025b05efcd69b161a8b10ca96f3c575d8d7860a060d950b7e6e4f802582af28a8463745649c89ef54f037228418559599dbcd61e37d140c2bb5abb470b4138d28b7d3ae6d65ab6b9068a7b88d3bb58c7cdd879e9a818262e2b2df93a8dd44da88103b87753ff99ac4cc8f8392d0f333e9f652cb83bd16af08c74bfeae6f9f36fc51b1f23672301c882a600429e7a55e193be5d6042b995bd5f05c486d40f80ad5e53be4efdf935ab1fae4cb0f9fd3e35d2e8a448da0b412fa0493714bffc56a40ad790d3fa8c9ab37f17330d116dbc5f248fc54a84a2028d4463b125b7df7410db77bf78b709775e1b27e0ff753bfd5bfa4b0f86d63808316469086acd30c823baab811427e22fcd2efbde447983536ded47f548c28f9a99b53c20f5ca6426249ea89077bf7f138211857f139f6fecc345c91c5dd77be219c021a1f90d7fe7c089666716c99e50574242bd11e808e185f4b87f8b0f406a45f0073fb4b0a3aa58975f333cbc75d510512bdea9cab9189cb40166aa727aa88d4647a56edb033bcbfa4f39ccdf16ff2536aef9be46d517d0f92b5a11a36b72c8c36adc6e14cbcd1ce05635f42afc257c3e4045a15a46331cdc308762d76387faa570e921c208f2311d32b8d3901c32cb8b9e0124c866bdab4be0aabbe833fbec2e2b0b8a6f6229d74a8a835fc28164e513ab4b7f461849abe0a55f67c0216aec32473c999cf2a9ef1f0a41029a560b004ef5f5a285753796956aec0976e4869543a59a91e9c01a2a18104f4e764d9d79bb6f3c750db3ac9102dcb6a4b5689e0a3e7b99c79c1d238fa52cdf4f39a9daa284b20efa838fd373f87cdad9671b7e3ecae839dafc950c94ca19abd209e208902c803799a9eb1a1c26142f63304e3dbb209f8b85138de597a4bee98ae70005e098bca4439382e3428e25c544ef607bfd9cc9fc72b452c2b1a8ad25dcf7e2fc272c584ef57ad126dbe1a43588394ace5ebd87accbf3f268a705d8e77994b8478812451111880ac90afb64b4bcd51ef7efcff3dda319da44ad8bad8f54e1e0643a5762c3c9be5383d85b6b469ad26b872ee7999742e20b02844777ed403802b7a6e589e3f2b9bf2146317d48f43945665a79ef912763019ce5bb47770808b65472cc02dce0d574e7287500c3bb5c2089a64593d6e332e88fd03e0e68c5a835eb8130885d4e1f08f70bdc87b6027fa6bb80778cd1faa8f3c43906b795d4473c0630c70b586076c04eb4840e7c3c3215f60dc8de743ac8f64d003cc89b0cd8062398c794441ffc18aace5b6433e33f80cab63ede2647c3a4f3a9ab204705ea7e16eafce9be62e311be983da96941e13a6df018a0ea1089a332de9237241e2118d6f34bd40bf5a32cd046ec93bc15c8daac03165ab23b318b4446787e17d2ceae7dae98737ecfd12342495325f86b64a02722ce41d42e4bb5a201c4edc44d9f25334bb581336fd9c22a43eb6acdbc4ab18d3139dd4495bdf19f9b62e0767298f45c8f448501e5d9d39f78c44f347644939bcb3b0ad481c015beb190dd0f92832d8c083635de5a7cbf75b987cc6160ca46fd03466204c3fb5d2e4062489f3b6b6e2f310b9789e495454cee3fdf6b0c94a45ca96f4e4bc1435b0188165f10813c9f60ee4142bb8617a92ec1d8ed335a4f8e1e8ee4fa3137347551222a8a6302a4ea700dbc18c57cf6ddc541a1394aadc48f374ea94f9413cd321c957ac5da01905a4a5f3b687a3d5a01fe605fa4c58b2768c00e2e0e54ffeab63bf844b2b374881d5a8e413571a094b4fa4b9fa3f21ba666d2109f00ba1750ddc4851f9ac4f0c5e96abcd06501df5504c093272f9e51b144466f5739f5f88de432c4666e98673baeb13906d7671e72c42b6bfbe4f9bac64ccfd9721363ca0ed31858e20c51dd131b04c84d5ea80145ddf3fe32e6be3da4c5b0416a701c5eb24009e18fe7ab23b16d552f56708bc916a3ef2d032b98950427fefdcc4c9b3a49a643395e5ceeaeb0e01a47b59e77ce212fbb31a9ca46d0bb76bde975d0b69f76152b90c2456905abf81976599e5ec9a16fbe2e4d1c465f8482fe725b7a1c1fd7d35dce3feddda94000d253efbda7f0d817b70fcea19983cacc256591e3638f0aae8902151b4021d3227ee12e5188ff74f87b27388ed1645d337c58d21c3161309204dc54cc1c8cafcf654608fe74fd85de3f9b0a9e842328a60735f85fa99ff4fa0e569b2e73192fb0e2a96cf0ec0c918978ee0405f5426b41115e0c2d80c5ca29b4b1602c96eeb3ecbe6ae0863a9d2d3430885da8b69b0d7a2991b6f579afb8d427cd042b0259933a4d1147afaf945ccea741060a05f61cbada96f256140a455c56da999def374916c97cc92ca1bb6be1e8f8e99ad77473d786da30d033c5409c84560f28cd396ea402592b12c9e722fe412bbe3a2560ada8584c69c22182a1d717d4cfbc0e3e47d8c722fa9a41eef2286177676ddd511688cea7968f9eaf459e881ae43a94062e9132228e4641ba80670029c689197968933ffb4c176797615493c57c3fdd1a9c768240f612df1a94b26045d7e21060c58916aa733027e527f66179efa706555e03a1ddf40cd580de1338b61971643921ba24e1cfacad92e9622a97a516934c2c5b70b444030e83e27eec13eb640143b26cc9f86b9d3b516e0b591135a196671668b4fd195c22756f856561d969542762247cab7e965a6fc29614e3414b0b0f88391aa0789c9ded0de82d824dbf1214937361cf96b715863757566fe2c08c7f4036a3328efe4d2589347748a54588b917bc084e086a642191d50dd1ac3c645989486c6cba42e464ee4d4f0fe5dd1ebd16db13a33a0d35f758ad9cae7af8b0ce8f65a5cfabb5a5f19d365b696f797863fbb66db614debc8cbdffae7ff7de02a561c3506e147c7b41e19a3a12eaba7c6d2ad88f6d360054a72dce5422c8d1d542acd5117ef6760c3c631a073c48332189400e3b2e372d80ebc152c06e9e3ef7919dc034460a76b6e8dd448e3edd247a90babcf6a472f374a495823caabaa9e1111e12a9defc5a6ad24fd4cc0ea229b4b4344b30c23f8cac8a91f531465b7c8c24bbd0ea3e5829b9d06ae9c1b2d3fc543b84771e6983f8ff189aaef718e0b40f06e720bdb1344759b8bb54a8ca630af8056687bfd42666f9f5e4aea6a9193c375253e647bc3bc3f81ecc7f0fbe4390ec9febb20e803f289fae6ccf019e32ef36d6fd82a1a099935bb701fe0e83c11b7405345b354926f603319e888966aee5475875d9253677381332560db1d24e9eed0c241080035d7724f758641877897f256d6ea65b31c2378cdcd546f06f01ad6bfe46cd8ae6cccc6096979b845cd785b0665d65f94946e9384bcd343c733a883a09aeba05bac843644efdb5ac229e20639fad7d9f75b11947c44925bd9787b7ecd571273aab3794a997c1515964a774e7bf1252617924f3ff86f7d753a6c60238df23cf8ec556829b8f40997d04be983372937a985759c4ed62fe67e47c173c753be6c6f9f9279054a4f886195bec986131c613d77d4bb16e2db74cc337226794bef78010e8fb27cdd4125a2b2d4200dd79bf6011f3c8d5d9a031016aaafd36fa8454fa284bb6383977df766d7256ecfa012158e2aeaa28d19485bd23e8edbf5867fec0544e1b49e1c06aee3ce530b5808fa01b9d6342aa0799b5253b4e38eeb43c383d3426e4ae779dc0a1a11d87af5aadfefca10c30c0a79fa50c3e5e516ea9e4073f170c1edb4ff22a7132202e187c6c5983f7552bd5713b1232a9ca431564df80f21b7c5db39570cb959a3c64a103379c01c63e97320778cac143c3aec21f9d632e86bb6a5555b1b5dfe2058022ae3c31a8c5818385390ad84c7317b6ee2fcdd34e3b4e886e0e09cad6abc93c3155c123153b2f262750c715067dc4729acc9bb3a1110013dc64308ce3bfc70ca253bf80400e061ac562b465216c2b8c5617f66d4794e34d51e0e7100d5f0356969e78531fc8f7e59ab131e83fe1ae3515d524a8070ea765a55786f83254939da5a3c1a13598ed88915c30935a5c5b3598195b5cde665e377c2609a4d7f3c7d03364a86f87301c784a278a4b6bd9964e33dc8bb6c5d84368888ac0859f12e229e9787f599cab2b3f97b6d24327bd1fe004fdf2dafdb9af4bf31269c95726abf11f477f6718e2b26940a32440db46b4f9c4271d8495d42ba39e9ff188984654ede58acf8147dbfb0687a438525d11af4c1b1ae4ee8d0d7b5856f26502fcba8163a49b0d0c69f94cca1f6981612ed472559d2b44263024398d30b9df35f996342484a33a2cdae2414a63abfbe7ce85deee71d6e157408cce123dd73432a27795a089adc37868c33976539be0cf80889b44a41f271b88d38767b7543ed9a28ae924ed075a43b9815e7aa574065bce49197c39bb0caad4860bd55877b4aad538a385d7ca9a50c8c3fcc380bfb0a264c1f120f659daa9a36efd06a7d834579c6d58559ef52b09da0609bc6a50dd80a4571c61aabf99b4380d5e1641ff0b68656c9f54d967f95d0593be81652a1156ef34f9bd236b306cc5430d6f91ad7b81b4bc9a5e41fdad5342f6a917aba749e11f5d4103863be2028f52ddcc7d22ac02a8409c10e71e98cda1b7d0cca1e1297297c9581c7495ceb8bfb08f592aa5492d9885caffec394b7fd767b5d3262b0b262c9274a430ab18b0c33ebcf4f9196a43885da6cd0ffb04ba3b9fd9e7217ae356f2681d2efd1e1e4caa8642731de09497b4b72963224f5a83cd7120d33ba9c6714548f04aac7040d2562939ae1186586a57f22234ff4e48a2b443b9a09989a5b63c2287963353782d1ca14ce6af314444b44e2ab9d956a8345b48448b689a299f12757ccae8befa018420116e121c9052dc64b30d5f81686a16df5968da2ebdf351e3f39437ab7a4aabdeeaa380a229130b26d6040d5fbf78d7a00818dca4e3db6556bf183ae5293d9e6abdb7ecf5cd67de70d98226e7e3e67576b2fc94f18599a95d6db21c66cdb3e17ca9eb8ee04799c6e9b25bf426f76b2985ed615c1e55c92802bdfc94d2c9e45a29990fca52387372c94a6ff8b464dc95b58b4368ab82665f61727cdc9f4edf4cce065bb7da7367d21527b1a922b4211433d6743f51e206dd18f15b912aab7d0af342284c89f2ec28309c4778bb776cfcf9767d403852a9634bd7155323e09277ee3ca9e030b1b820f1a85325b2a61c7145f28337094e096a9da9b0a91a0c26b9d1692f2ee749dcd25612226c5673c78d690a04a3af4d8959c38e205ea1c54be0685935d4bc606cb33cb1f90e907b22ef2115f6606a577d7e6609e2a51a9a27959a2331face552a0635bdd07626d55b62c8db129789d50e5d3624adcba08e1b26ad8b8b46f4387dc4828f8de720d1401c2f754ea45e9e98e59d816ea33b93b56ad78f62ecdc844949b638aa39e83403e8e54f6dbe7daa5f4d353188c150eda4b8106e084154d8f794036d43f31345c7e1d59405f460f045efd88e99d962c5b12215e699c0f92a3fc26a9c04a8ab390122ac468a6aade00e1efe8710fb13179f8177b02d5c9cb6106956cdb7d2a8c07af174fe24f6441d9c4efa1f98d7bad7d3c206f86d582f819ae988917edb29446575100d90915f5cd43f6498c61df45a2c1ada548629d06f3cf9bb49d4fc1794c5c70155071acc026de88ab9d82a8ccb735e4270c269709c0453de0fe8e4780ca876412b8460ff42171989236936f4fb718613d5008df7e44f9a6b72584a21154e09e412e64c4218e52766c80ac4e39a04f854fcbb302813044f4a9c93f6dbc41a65f9af04e39674cebc23cda9185dcba29527647d24903b97db32a176724e8036d4f39079a6cb5b15ed9df6462362b94b0ddd9b89b21386a19bccce0ec47c0ccc9f62f58ba54764d9bd94bb3025700ecc6fad91c6bec4017700a0592714d08c8dba6c7aebc1167aad23fef8485712dba4ba926eed54af93eaa85f908a84691f6340cf72a77363788e935b1314d5fe823e87e9c1a80c8e691d4aa5aae5163fbc6c7bcea37b59288c76cc528df69c032545e7420a84f5599e4dbee05058ed605904e25c6b00dfad95d208def8e52d76baacd64848c96cdec469a633109ba376020739b8bbb1ca3f723df6f4e4922fc9a45d3339dcecd679f0d6aa1b493aac40691bcf6d2e3a6bc1d6f9b4b5db860f7857a0729d1524cf8119cc591fd59eef10fd2229a919bfabd680caf5764741889158903c91ebf366272f44a3d6fef86e6849e0f171289b78c9468c3d5df2703b5c637f0bbef2a097f24aa5b4f5ab5136ce9843c08996551399eb759a8936f249612b03aded3330293721a07994bd9bdac5b067357a46fb5a28bc6606e4c9ba6f28ab066e197e785ef8416208efc49872a89d5f30633ca68db0dc223062884566df8c5a6a04a4c9e5a19376a447f21d62580b37695742c280aff3692a26f2d587b06063d73df891fc813963dffea49a4db5a90a6f35b5994554fc53a74d3bbad2288a1f930e85bbfe48b0f2ccb8f955fb34a63da9c77486b3172c219d77a2c57d02bc75e0d45f43ef0682c108cd576678452d686d1fa06367b205f7c15b2e4850dde79b04cf0b3a9fb54dcf40e72211beb44a543f5c3664aeaa8147dc2217e03c674ae038d4a8f22a0e97c5ef407967e444de3361f08fc9319ac1be4380c95e744ded40f8c8da44700d9f86813ad2bdd7084e361088cee6756ae35b9d10f205ce4b17a06cb5262be08bb1070652f91b519a7d03cff4aa61f040ea37a8fbc64f4bc4c5a52e64affcdd993fed4b3134389b9916231c12202e4c0e0ffe88f88f09140e42a99b6c0c89f3c8a4dd47143ae61aa376614bf66adeb27232b01f63515b4c841189d43777333f8a1104a6e3c462a27919a73cceccc9cd271bef1761e9ba943ae9963d5047673183a0fa957c9199cb4d48eb72602ca76530511b5e177afde840ba1ad6a1719853168c538057909430189518d55a8dad35222174e119421e259ee1331640c49863cc83e14085a2ac8478a14a49ff790cfc7a382b8113fb64b16384ba721d06da22f61184b9cf4ed7e37745f9138649df108eef25f4f40a21fee276204f8a312e1da8f9d2872dd6ac3604123c209a09901e5078be04bc1042d6b5886cddc276730de0ee22ce8f353509c433c7e88a560e68aed1e891c3363cbae14bf47217aa1493c168deb4016458fca9bd59d243b9dfc80c29213bfb722cbd7e19ef7771c0d3be21d602f279536f70f5e939cafe3919f324e0a435e2e5b44118bc47c0a787c4e1538f7bb593723785185614cbc48d2461dd0ae25a8a98179d8a8723b4940e6a40a9c3bdc83064bf0b73db2fa7a1e56c878fbd3c04ac3e453d43c41d65fc307e9540674d124f6b06588e3ffe31e4d4f3540d0673dc98952baeb92bc194aacb4bdfe67a861b61e5e3da15942553d28dc4a515c8a515e753956710f135b156d118875d10deea12bf520de5e6971d2c77d1c06e9948a4f0572a85fb0e036e6d07f3e2acfe379f41df7088e6b312933ba35e8b4a62843cd29453ff3461c98072ee05f7f2943154cfd7e3eeb74848a4fa7a6701123c4e33745b94b95b1c03f9f5c02a64b9d4ebf2310c135966f0d7cd737aa210420a95f72651f8873d168c0eb2d20a5f0151b31023e69dd8936b07a65f0f17753938993de58ccc1554e57d65b49af37eaa2210f72daa25bfce3ceae7a9335fed79d3e75163de26ddde2e2fb7f1dacaabed80329546541e039036886b0ba936fdccd2b64bb401c363a0d3a4dee4b443be3a8f108ef22b5e8643250001bc99846dd261a5c73c844a3f4109da13063d96af54534c7a3075b40475ff9457a726c934ce4c8d9a7e9c7f40c631c5c369c50020d8478a3b99d67706f236e1b63d70627c23570b554631453b69ca249f91ec3d44fcfb2fd0285aea385cdceef0709c54a0823b4ce24d538f989cb0236376ff1cf446381a6b32e9efe01f99d9db1f6720b80346b99be39f99877c19df549c16daee34780e78840d2eb90c2277a519ffeb850b37518c3a8b507170c6dc44d047e923e5a592c01d1c6783f87ea129c9f81aeda0c1272fad0cee6439e16b889a03721d5a8cc7a97fc8c125b034d75c961040b03ac241a99f900e8b8fec1b43ffba0461ff9bb5ce0a3f4cc709ae56246fd84402494100d051091545c23dc5a685b5c5bd5e730230db88986a882bf9238ad597a11270541113c17925b0d3afd6ebca56072a8a220659a47c5483d5c7093a7f5ef555d61c5d68db7330ea3a278c121aaf32ac18ebb869fafe3efd58056c32df06c5a25a760b73066d291976e0dc3b04b2f5cf55fd00ee6ca34f8e112d6c5d2c9603efd7bea54cdefbd2fcd349df2b2c14631ede55e759913f5ffe0240923a8ca66d3e962d42379230421d600ae69f564175d21b27bf335c97311399be4a89d88ee8fce8403b7a012cdf97bca0477268644a3af6eeaa270d77250933afbd4558e90cb958f534d47231d520ed9566b3a07ef3f4d5b09fc4b02dc1fe1dd0ebf693ee07ac4ce8e18edb367dfb21d9ee5ab74dbba5419aade8ab4bd339b034a960dab7cefdc265c575a37e4e5b45bd6345f063ba8d1f372317ea4f9a1cd471d243502868193ce1a3492f02ebcbe0cc95e779eb3eb87e9e5e4d0541f9284a1dac1e1da55aa348fd24b7a5d0af7252222a425c1b54acddcc12dff9b492fdd37ef73d003d0ef834845f63cd500ba41d9a46fd8f52edd1e3cd32e6f039e5e9c9891271495ba161c984cd81ad520f036731ba83e3be4105dd568345514071d7ec4add8e252e4211258b0031d01610e1bf77ee19f52d65565b0ad45df739d136e7f2a37acc9a50ceb322b7af45c0b09d514ae7055cd822b0c0407533d2007c46501044ea02bd103568edca0352a2aa3b3904b7120e9967bde4b42be7987ed3c18e90a6ec9568ca5452294f3219cba15a476186322f053b95a75bbfa401233bb1d0905399402e0cb47c464470f438fc6d00469401110a683107f2f7bba0eca4610af5d4f701b2e46494d04d2b102b858654c4e7199ba8b3006fb8123562a800e80c902b975fd8264b128c5172119e8662ec1450179afe5e9fc8b13239c73221776e3a291cd786b99e27e36cea9390746da78cf8715640da3cb62dfc1071380825b2e72a7155372f8fead1d3f4fd698eb8684a1a899c0ad4562b5879f643dc0449eb0cf28412cb5619bbaf3c3ab2a1f94c00fb0f760494343ff6201ec29caa8732a43461a1d501dd81537d71960ceaf80463449282e282ce99171f0ce9eda6772fe5eee3481521e4cb680602ec8f03c12f46ee83dd578df0342fc0ca885b8d016a3377a7d0ec6c325a76b39c4eef5305091cfc4766302f366d2e0533511cd4ff7863de662452cbb1cb561af7700d4417891204304604d8da32c43cc2214d2e3ea47ec7411a6368d607e1f2f1c0b63c0e1fe3c828dce05a77a0e1afad0505214d0502871e8f99884aca5862afc5f39ebabf477124fd9f1a74ab44fcd12d9c1277965312af1ec215ebe1fa2c9e3dd2b38385a0df7fad73ae8d143a18272df288f67df3403745139b232829083a9bc9e316d34707b9a720c1512622cf961e8bd45ce569794fb081a4aa1d13f7ce7021639a9b507c029fb7fc0bba3579fcd941d20f737e938b5196cbd172d3aa0c015de7bd43c45c697bcf16adf125f22ecb933cd8c84b801b2e9d9973a00271e93fc5469f7927999174728851eead693020f469a8d011b16f0426652c1b1b3424344d49526924e73fbe02b93e7e83588fed24ea32eae500e20843b150435d9767ecc759d13d3826e53f604625bbcbede5ef9b949b0b583e450cd39c6687fa81960a7bc01306b2a2eaa3b16a1ff097e696054e247edcdc45527a9339a6d8a8373f5afee248aec8fa72582ede5b741111382c9d0450f18d533520b72d22367e193e6ae3a258087c765d3ceaa82f153b40110c020ec5f4a2d270b82418c2391319239c9943631fc66f8263eca6dd06a54f6691a1ca3be65b6d07121373441339062491cf10519ffead58909d47f2dfea57a997cc6e31b36e1231ff18750c484de7ed6a2b8fb42a8ef90fda59b405d1e020ca88c101df8699ab9549497bb96bf432bc6ba562d1ff738f2d31643a4e2f0ccda51bc6ad3024f79c342df39ba200981100b5bcbd98d84ebfc5dc519827357abbc4912cb9d8d77634d00375e08ee5c3eba32f3c62146d9b4549270f0115a454b68c386d929f80fde8721275fea18bf8c226395ff14126216162e3f14458c57a5901160f72477502878c2b7b5ed12671d14ffc8a088e4bf9befd0bbb1c9dbb6275f67a8ad0d604ef3f3c7489e8c07817cc4df98315f27668901f2d779a7c34b7ab612712291437ee4a8336107ee6cca424ca764fc25974c92ddf4e680c09fc40554657ca639830c5e6ca4c5af85e41affb9b67c43057325db0aeebf6856822c63f731afccd0ca7744b5766b1c99dc97ec629c45d2b68479852db75a35125878b12a188e31f36d266f083da1dc1ee6c23b21c45aff2de412409bbeb9e8294667cd9935420de172974660c99a7eabde36dc6dc29e03728ef0f8a9694ba2b50339205f528fa3fe3382cbea433abe34998ef70d50710ff8e24934a8c35811f3f73ec373fab472eb32bcf2cc7a501a66798226fa86859642a6d35665d0e68b99273ea80e88a09973c21062882df29b32eaf9f5ca3f2ca93c55b06ece69999e3185566e891667cb5cd3050b806ca67891c94a30cd174628a45dce7b4183ea1479bae330de56ca2735b35dacad8ec1ad20ededbee7d9aa8f192722717d90a14433ec80d33d02173cda513b8d2636a4d8342f04909345697ddfb22bf40a332dd74654ee818d840cceb400d8af430bb31a8183522e5ce4e8198d82e29b13345fd9c84bdf1e6d226254b180ae88a849a6d89e51e98391e0a32f6a26374af28c66ddc44b5a1c37fd9585d6a75675a5b4bc1c26befd6b4900d259d394e0bca5ec3b37a2b301e748c0cc1fdacbc8bf8df17b792b37dfdda8fe01ad8ca0999bb74226a3a565c98e5d0f595f0c0f842129c2bceb70f3b004836f2fb09d57d3f49c6300e3eb1b5388f8af236801049659b1dfab00c03cce03b1a715e373f58966eb2a510187baf4f051dc30e270907225433a45542590b63e27340557c292f798e0c85532b79a1c6089dfc9a37d912d2d7efeba9cd8a1304f93df0b69904fe07bac6c6ee70306865d0880357d117b624271fb18496f98b9ebcab6ca7115ae010488a3ed4453a2fc26c23c9ccdab2cdb3ce6e59038cd46b13414cbbc3403304fa281a147ebc3945fc81241f276fafe8f1f96afc074d3b09f38d90c3eeea744a90b911b39fb8bf8d7006ee47079aa31cebda10ccd78ec166cc1175632651bb55c9a2ad6e774673661029eb986115cb39a4bda9708776180efa317a928d299f91ee387e9565790b6a00430c61484b654459e120701f60a03211579dc4fbd1f2f851faefa4ae3751e0bf84bbbd4d8993e9d9a644628f3d81a9ab31de9b22215078648490e266164d11be72909f9ab345e9e5b3fd02539c648fbe618ae70b4237502200c876a275aa96c098205cb4fa84b5514be63cec66550358bb62d3bac5af7e3544edbac8d6185f40a4a5535d940339255ee3c35b7f78b68f5e0099d37e3eda13eaef17b4984f611c1836f0cbde495b8d0f22514c6da47486cfa4270472a12e3acd1e1959b3c99fe28ee7b1ef3c1968be0d5dca964331604a8510558aedf6e63f845faf8da2e0f2a44f6659be4f3685657d5fac8295a52e74c44260763b92c3486fd942bd3ee628c56717b3c096e6b6098aad14a558e70687cb37309f7a47f8bff8c202108a0bfa0abca6cc660d5d9abf21466b449288afa7049d73056371fab93e27558c095f75a040f49e187832b59a9f5300e39dc7955c72b831bfc85012ed77974f300f77ab699f8f7cbbbe7e356e5b56f393ddafa3780d83fbbff2555cef3d17377f8d3f07d3584217817955a11938f8b8ffd20440ce09e0ed746901377e53d918f185be7b9c171438c3360f925f877ffc5ab46cac5ecd3ac8e82914a5fa45e1c9e06dda09dbc1f5ce1de6f52aeb5f92b0730b4c0325c1b02df088bfa77ee55e9d078ab7324167b18f70b72fb7fa61de070fee220bdc4716b7fdc46d3d749cb525dd33f81543031118384293f142901251444e37f4bea2457dbbf916c15914fdde879035c012204ebc4b6f4e75855c42c4a0292eaecaf142a349e42f76e1d71a1487385e135b0fb94bdde05e4ef43520d746aa4f2e49a20178b2fbcdb2e174ecb433252fd8d48ae07544341899b17694e1148237aa1005494388a926bc8b0c828fcab67c5a7c361b615af2917187572b48ed8100136000c9946c2def8232ff73729137652adcdd5445b38543ccea8078d3915a567b9a39993e30ea89298fe331d6bb1f4ffba2c2b56d19b1b091f38dd98380b456e40b9acf9bbe7c57b69eb05227f67b4373f781ef0d92fbe3406194a64b018dcfbef1a2ec01781f29ddf3f6430336c6541423cd74990c76f2699f4b3cf3885753773861e002a42d0d74cf5e108d2efedcf09fc65121d095040d7d7786044c539002a4553c650916ef76dcbf2752a93a43e28400012cbd53509869e83f25fa2389b4195388612459be202525f75da4d8a5bd414e35c67a3b3f1f8a0d4e515c66ce1e4cfaba6f178379b9e695945023ada2ec81abddde807c510e3c337a0e518c97cd6c862f22f826620cd863f949d6020054c394f88e985fbc33659ea396c3d3f49acab3802d146aa79b6256e9b0b75fe1379048ce49deedbf7bc254b42fd9fdba40ea375d858c8e5cbc44d3fd404ad6a659b36c9e66a0dad757c9e4fa3bf0026ce7593bb7311ea973bcb0c23ea4566aa0be1362fc8b713d083588d3228ee5a71131bd2dcc4111a9aff1ecd96ed88b9545a6132bdbbfd23abd11d4e38058b70f503d18d53dac4ae9dfb9cf4d012553a63e81540f4dbef27451124da3e70cb585a6dbbde08f1531c78ff1faa35a9be629c665ef28bcb5dff727ee388a9f625990ac78f7de31e5dbbe708b2cce5280b23be0f36106b7e04047b7b8371e34231ed57dddd958cb37ebf4bc8ede64cd43f77533e27b0c684c94a11546246d5f3d50c1a7b33f2fbaa6b27e23d402b8559797f787e98d58807204a1ce95fc5dd5cfe92149a14e1e32a77b7ad2bdf36e763e79e327425f6a47d0d14b6ef8b11e259e379f6e7f8cc6f68583615b136784af024d815cd58615906d722260d8700781465223595bf6d269d43165a339c53865eaeba72f640c315ef758addf1d4cc4304230bd0b2e9a63ec33b11f2f5ae25c6fb217be1aeab09bf9fe3697a34756d9bae0944863ba5cbab718b7a75eff41a88ca49c2862bff2fd19b44f7963867abfeec535ff758c131a95b2cf2453a1e812f2bb5a5a53a32dc265d47542ada9e528b9f0c7926487928830777848e42ea0afa8b799bd52d008ea096f8333c08873d83ac71e22e2beaaecfc986f8b51a9ba44c4c8b883fca7f7c366a28c140e407a44c58c57e99a5c68fe8107745212882d285f0cf21c460e5988eebe3570e3e450a427f38efb8e1cb5c3f687af92591644fd66f76640d28c26d87f2ee163d6d0291c6cce42aebae2967223eb70cd4005d2dda39f960f5e6a31db929ca3e197eb04c5371f7c3ffe4a1ad5b19b7ce492c438811ae9abf330c00462c4cfcb361957de73a8a9e1c3a05ddeb4c2ee88e6320c1e3a3b9daffe79d9603b83defffb84e94d2058b1a4a431caa3be318bd2950229d4490f7dc08e841c470749144637f36fb934df21418ab621ef53a29ddd13d227245ba8b78a6998d6d321f72109fe50922e47ce260807b44294571ad21a3904da0d16cb06feea6b7f0e207b8607615038003ca34eb7135c14ea03106ffdba6ec630e73ca5a2e146175409715deac4da64d1142b3b3d02dc195f57425784b2f2792b04f6d3adf83d616612bab58b1b2b413c81e11fb0c25c47009e8407c03faf27c8a802958227cb1fe833afe625797e425238f071411bb19bbc212244dd79445f3035111ec0d2f897f4a284da63661527e7d87114f9d0829851b861d2caa55e225c4a163b913bd9bd7a021cfae16873cb22cf47f93660b2c980463817069de15e6becf3228fcae6f386081470e1ed8467319c2bf78ca2fe947dda014c185b56228f019a4107b72c721ff1daf7c13dd501bc32ebe9fc1f029dbbb441adf29fd4914b98ad84b6f5f0332aa61b769962149ea61381bbf1a082e1adabe66395994c80acdc22c54c40eae11125491f7eb0b82309e2e96ae7a8b1d306b12884c7517edf20b471b12e1e03f9e313cc14c011d067a32a2953da5b02a6ec370c39b5e93dab223a1b72b64dd93a08cb8532ff77ed028650c1c188b7f88710e230686eb3a5af354ce3971ae18aaafce001610dd737ca5161d27c38f10030093e835b9288abe07de26d7b8af3460bcd868030735e1cbb8e0ff587dc4a80973b0492c35e184d1cf7c43c845e9c8ac7db306217cde1e72fa84c76bca8c2b778c9b750641475cb4aaaa4fbda023ec8cb1045d85715cc5b0cefb4489764ad2419a33b5d3c61b0c970338f7415f0d1de2b1c800b8d716e40e0934f1c1213844be8d657b8f8c757c0b38385708bf22c1a9c4d215a89901ba26577efd0cc18c2867f5272723ca679eec58a4e2b098300d32a92171f0fc1a46e8b48bb675bf7253b2977d7da5d49e76986ca8d89a4f7d0cb204c582726dd6440ccdd3aea76830ab359a22596848fc482db8b516050db2d445d24ab991e9ab0f2d9e6f70182eceb9eac360b6b4270377ac8eb5691a446d5e6f41194ebdd964aa829c950fd333ef0da4ecaacf0597e0c30b0461ffd3eeab422b8b8ed538009c5e62fe7c5acba9b9ab16767c91e8c0b1353f0a39d49f8c71e83695ddacfb2fc95c5c6b3b96a22d99300dfd84a6c96b238b6d2bd014268170cf4c18f34d3e6d04759ebf59c09c1c712a4f093f1f9c441786963e7470030148242dc2babdae12a5f3cae393fdcf3d44185fcef878c924978570de6d7d0bccd1cee977c4ad2cf7543de52c4990c28e354eaf8207d421a409384895b39ff87a26efb2ce156d03e8d8914951cbda75530d1d6f7162cd9e26983209101ef308a94cf79dd111dae7a31d4fb3397f315f559512d35b3c0426a541d22f41bb45432e622c4b903e054e998ac0c773d7deb2ac07da78053fdcaa8eafe733c6bc4e952b7691c6e14b538f9cfcb27937b0279cd859b2934433f41c479728016dadb85ba720e307cfd12f6b4e2f93882b137ed16cb25e9258b8cfe4836f313435a6a373be781e9ab11b390198ead97c983e663094492eb3c7872950cc1c66b2f2ed4f7be73a503d2b4e1e66aaac0a24109b7d33bbbe190f36e8e331e6d78f4d2ab4ededa2f72365e72eb06ca0629505e158294f3b9c38fa5e33e94f203c632cd6a9027515b0dd3eaf06cb4066b1b15845052aa3d5c886c0d1911bdd94341ea55a8f7cdec482bbeef41e0fb7d185505fa54a201f9aa10e0fbb6f081175da07662dc836979a89183e6e1a017cbbf2b6f2cbd0b0b1d49f13237698eaa5ec0818b74898f073a594f7120a876b49a854ee234f21b76078491a745591d64ae71f6a280e125f4201d0ec485d4e3306b6b227fafb1fa4d8b5a99171fc50b6d5a42c652167d4f691b172dc27c3d2e612b3d37b595182d2ba0b4a7484b398c340458bb9dd9d2333f43b49f68e98f0e3e4c364aa31e0b482ead8f0b422e53e5ef384d2e5b0251aecf2e42f0eecf8827706535bf1d2c2ad1c4189859abf9a767cf01988102e0109be6d75317897298a62f3da8d8483d62501c2e1b0d868435695e61f92c3993cf249312bda3438e9bd024585a64621d4162e3234a75d2665ff49e3e6add0159c17d2ec33bdb17761f4c498aa2dc3f390d88e7386b551dd253a6e158ca8399149f789993c439359c8544508f88759f9f6e0a18001d509b6e00bcb8314121fd95549737736a9378bcc8648c226b25897c8130592c5856849f268118efcba78be481578baa83959883a726589b7d0e497cf004541f11401e65ef1831852e8a9ff4a032a6c966eddbab1b4e708f0271bf97de1028f25ad46730bcda765ca28b2106b2a7d76590d4c7c15a096db8b2eb5bb2341d35e375ff7d4737f1b3006024444fc4731916016759050ebab457fc8eef7ff7bb61312b59c301d7ac41f51561d28fe110f6e7f89abb5fadd2ce696fdde9028bceb9ca5c5a1063eddce0601af5a720979e19ac82732316415143a95fc6d1b28d6668c92632cfafdd25d0960ad04605fc875f8fa8359393ead3c7bd994326908e52cacf54aa32dfd3db443061d250eb39569400482fd8ad1e2c6e87389fdb195576e6932f38d6ade7591d66bfae89d11a61f4bc579eb498e418c1918745bfa9abb072eccf88e5a38bd3c622e529762d3e4833abca98b2d65f3a8378775f70566da09e28fca970afcef9d54d062c5304ea363ba5592010b34a0756c79e4fb4bf5fa9a2fcce53415dc71e4ce73312afc656b43630a736dd4d429e9033a11febe533086cb510ee9950010c0244728e3c9b51ea32091b60ab8f3dff9550b9d59c2822dfdf4a42f6de7b4b29b79449a6030a900a150a1f468f8529831aec64d8de282972d84ab303e2862b165a510d3572d84a393b208805d7adb08bf96b7e68af006cb0d97fd8b0cafe61cdf66b21e223ad433f07e9ba8ba76708cdcb4d3fcc327d9d256e1863148bbbc286a24afd4366daab306e09b638922553fa44a694894c8fc8f43ba443642ab55c1cfab592318629fd21d4726c2f63b97ded7fec6befaf75d83e1087697cb00576e3b6ef6b12c6b8217dcaf4e993663da73b3c5ccfabf9f2594fc4e8d7d8c5dc2d1b5cb7c26efa4827a5d90a0a48769acd2eb2c9899bca394e5c9b23f967835989931d09ca30cf18375cb19eb8f21b0d29a64451a306328a91e3ef64c9d96071e53b95440e3a0db5d23c71cb5df26f8cee2f9d70cd80db59b610cab284c597dcd559b6386549b964f96b44b5505f7bd66421b11b6b6224f5b5af3fb7f81c06026ab2c485188a2c3db593cc6f3ff10f90eab9a82ca7f669bf3d92f81c66c27d7c1c9f098fb73d10d24ae2c81fef3b0ce4616bc12cb555fc8964533df74c647e7b24dc77df6126b0900701de778fb9effef6794ca211aac140f18828e94888868826dd8f2e7dfe6124c2acec9bc06c8e302bc347826e7cc3529f7131f3314ae2c87f2020a4ede3d20c068a4f1828ca442d6d4b9b131755f2531f5dd23e56f695d5427d7f568691d47716122f625409a346512c064bfdcc23fddcd75ed33ef53fdaa7847eb49fc1402e2471e46bd88f88b40f5d48732216c5d991147a8a127aa2383eba64b33d7914f70de33e944235ef4e1247fe0321fd687f1f08e987e6352102c0666a3050cda7fe6220a41aec4b180379127612613358e6434f9281b56cca52aa58141f096a02a34b12474a169208d33ec234560baa97f91fd5cba8927e547f319024c23229858fb6efa961db879268c34c907eb8df8e3c2824145051c510130eb37ebcef36cc42e27df71e0642621dd9fe000d4342a3843083031c588ac56040420ee3fe86ed3bccd2bec3ac23de772fdf470e6ec39ce492c4d243144c4c5f623158c338cce4860dc7b4d730ebc84f2dee00829472739143f9946912d521e57e3e2329873596a51423a31c94322acbe7be59e494e56bd34b967368c6248e9c5a6ec3365894334da6db94e19a40d411ff88832eb86bfe1117d8b8fef31dc7e0a00b2fc0e0608c21babbe330fb171c8c31c0907dce18668ee185e8b027475cc00d3564a5cc19d7cbcd6484d30d6ec7856211c51ffc1668861789d33545189194c37bc3b836379311560c60c43c0137677737cda45dcd39e39c394e6efcf9b1bbbbbbbb632bab32e0b2d933b7a4d0af6f2e6870e70e3766198d3b3cf4bb329b2fb3496b27ff5d07078f7e591cd93fe6ecd093ff65e6452f6eac5d8fb876574f77f7e8ee45425cbb6bd22c739732d5314f77c91885c8fb17ee48ec9f750ee6ccc1a96387fb39fc9df9918d1d700a9739e77c1feea2c9f379821c99734e7c67099d829b1ce67c50841239fbd51791829c3d38a5899c7d179143ce5eea68baa10573fcd54d4c3e72d8ca5953ce888811c194b3ecde5eed7c91830678c0a20921d6a856ccc062da9b2324bbd1c61da26827cf4b71b002e34316ab41b0d1c32442461395b2030f120649990f3e786c4e891289c0004a52294c3c0e6568512d652495a154c6d2aa0b4fae7632716bee2e4a20c5a5ab12588181a8a5c971dac97ab94f2ba8ef12e538bd8298e3344fbef2f5b28b126cd1c0d3a1dc5bc620cab60b1a48a1444ca8d9947bba7422cbbf915966b5888d91e5f53580e29821b45082dd10a1d8c18c2658f7db071473f8027643434181750f4696e16576d1934f42d45dd23f4cc84eb367df4389bb69cf72f221675dad363bb1a0b84be4abbf4cc1812a650c61850da060fef58b58e29598f4c52a0a78725f70c1416f8b4b7103197558018ee33aa2308519414d8c3adc109ec444233433d4e409051639e208481fc96d2e3868c6e57233d9a004398c42371db5eac00d57158aae931dbb63c76e4a69ec7e3e757b0caaa3648cd925b667cf09950667ddd51d637b0f5cf76b4aef965fcb2876f498024e7677c7efeeeeee2902ae55689dd5cada6e9cf3d9d6e8f20bae5bd8ce9e1de377f7b74eec627f6c01275fae6acd3e1c6e7fb893af9452ba8cb30352cadb3a2f5b254e667708d2e32010eb2efbcdccb9673e1cec7f50c68cbe3277a7594e507c8aa175e6ef906ec9f0252d53ba92835784b8f2350d87d9ef0023777ffd92fb29762e73665a5a594669e65870d65d518cd6a1220e91fb7144b9f1c38e8a31fa55cd1f07fb718cb938dab87115bbf13d3029a53d3b3c4fb166a96635db866c1b11dc397138b3cc7e37c392beed577d2ba3b2f78fdd9d7ddd19c5386e70e5873e767a66ade99a0e4e1274dfaca894598d83f3a994f2c61ff15be70ca1186bd9ffe4c6ff40891b9f8873f9317cd6ea1fdcf7c720d4cfe3e6d5f9c78f609ee1da285a0e7e8c44526e96bc7d1657e4ead359a27b77848767672704b70fbfb7adb36091ab4f2b65cbe16e1ded35da4462f0032a566a8ccb11571287fc6cb635aba13475c6ca68775371a9cef3ba14a7daae2663672a0dadc96ce60d8923e6c8b1d2919a53c626d2b92f585a6f92dd10dd8fe0acbbbaef748f71b675e4c82b1c9ee763bb9a35ceb49e4fb5dac675292f73aaee76f2e33227d3d17495a6939fad996699c3d1c96f666e9583bbc1c1d2919278b6ce7cff28dd250bb86e8150c514367eb7003cd6906466a3180b4984651f611916ea60d9b37c5459be2a1b5920c356e2f48fce52760ec0cca928a5945a6b6d5118ab1cbfda23bcc8a2c4e5f8d4c6bf3a50d972fc2c892326951cffc61eb82871452ac7f7b6d67e36fe4521c7a60e3491dd5a2b03982bb67807199d61891d873f932398ad8d292e624c4a897d3a6223ade31f8ae05662b12d9a80f56bfb10486e1f5dcd4d2b77d8a7717cd222377eb83809285f3e8de3be93798ab48e2f99c14bba9dc3869a4170cde393db5ef02537930f5af2f6adfcc6fd01f07463535c5a2de5248ee3efef33643a806019894d72f418e68a1ba31c840293fdbd31ae7f04b3d4b494a397ecbf7ac10d1b8a7bff25ee9a5ac5a12be51a73a1288f41453dd5da54bfd65a3fe4d9a0b2dd70f842debe7e5995bf49a09bb2c561132b59866eaa1427c9f28c77c72804336e3863f689cbbe5b524a29a58c5c8d35d6586394517e11d3cec12cd7e0524ecf468c92f6d97dc7fb5ebbb88883ee60961b74d91e676ae61e9249f9d3c1a74eb3643dbb7b76cb4c6632935996cd0cd7c8960fb7b117391889da3bc0048a4f3f2f8ad3bd03489867606e187d082a8cbbbac1d09bdf5fe7a08f31433662ae8c6f23e67a7c0aa5907cff6c6e57d4c8c8fea57d6bf3abb3ce39ada5f4ad90dad50c71e7afa0298935cbcf9a04eb2eeb27b1396b1df934b4806c3b5b47be9d0fca973d1ccb47e67e2905c0148414333c4073300111c5b5a1250d252d917afbcc6636b339e77c8f39f6db2b1dd710f3ecd8c3874b7128434dce60411c87e53fb3e39e1c64dccf67a89143b0cb53ee6a95a371e277ebc45f6d5132c30339ec2625ae8d1b5e2f950a70e7dd4a293b32191113c10a1307a0ba4bc76164be7e7c925fdf872022bebc7188fa47ea7ffc28a1041144c058878e999c7d3893b79c7db8e5eb0d913985fdc8c1fa32f5bbaf56c9f555f55951dc306e5fbff386c8ec3da8dc2341405c161477abef44db97c433228383f525b0628a650db36ecdbdc9562b165e70b03eeb1dac5ffd48fbd089b4291389ea911c9138f52fab059957fd8f8cf640f3b7fadaab708fc4a9bf03038f13b54efd1efdf2211aa77eddc1c1f9be1c3964647672cd163b1707abadf56dadef6d71fdc3e8b18ecceffc235f46c9f342dbb1737a95524e3ab36c4a49a5ac3ea79d2d07e79452caed3a4597539b19b5e03783297fab9ba59dcadf3a4e6efc69b54cdbe6c649aef36ef3eeeaeffcb7eeffef6da94e6ddc96fa7f25ce899c2771da29abcf29a59cd99c524e97f289ad22ceb0b5e40445adb3ee9add3a339b825651edfbd32f04734eeed6545458e024c697f1cbc1049a990a9c644ea693289c48e9444a27d4099d73cec982289ccc4c054e3227d349144e3c21b8e87266b45a6de33a959674c54ab47265ba96894a490e7e63549ecf0c0d0552794ba262508c45285188c8280e5589473435599219a06cbae92e191c8ca12561782126c9170e57147f377004299245cc314879463d0a70a163ce0e8f9292b6149bbedc1f1b4797a4cef36ce7dd7bc3792b377aa909adb20793c6cc1432a0e172e997a62a27d052850b1b596ac6b832b999aa58996983a9cad1115538d0441521eeb57168a78730d9204c9511786006636031c9034a96e70358e4b095b43497179eaa3cc91f8af90053ae3fdd7542ae2e19468c0f24e181a53b27135f260c261360ecd818b0e155119830ca99053383eeae6ecdea68051953182b02232d72f693e9e805464ce8d0a146110336987e28d2c1da1c5944b93182a1951740d8641185c3053fd42859b941c506506846906443a58da1191124d5f80084cc1651344519b85928cd38a15292a962289525e972d183b744a44201944e8ba754144ec98ba7c40b84d84440d42911d583a6c511a74314ab14db86a4d42dda125484a8905da2ca0f3e24b0b2841519f0e16268092e443e5abc2cf1a5089f2c96183384cf164346443ff86c5992386dff83c14cd24de5ee220d2d425fcc4041814cd29d2c90697a8106d2908109aecddd451a5044dc2d77176944615123872ed6c8329df87097dc715766e377dca7defba6bd94c18803e8a5bb2410e8171843ff986d976cd6f399d90269d41b22b3c4ad33eed76a1cfaaa8ffa0855c8f31ba49f793eb7a580d4771b87bef7f9cc7fa1bbf9ce90e7b5f6887c59cb2d91feaf85155f9c519d45ceaec8a87f596e77d169e56aa53947c54e13471b143c6ce19aa8a22bea21851da23040c490962d6650a7405a434688034861a412b2872fe893115011539f504a69105ad5d24d54a6c8613b752942c5095550ac526998a83041e56ab999a814514ca5881db4cc47c1092e891be49b9b2989189c80f3a2559a4de9b1535ec76d9aada19991b9aa1c2b1c376c3ab861e9f81d1ee02ff7f78f0f3ef088d42c9f6b79f414c7836653a63810b84db333154296e2c0158e1b3635343415de90e5733835c5d9947c6e8408a8b2f0c0273e0d43ee7c4b098ad30a0a940d94282558501efcd60f357181a21daea0c1c61a691851a558a305a935ce489ae2660d2f86a050ba0692132a56fed6e4008b1f96bc7832c61731a2e062c150baab5b736fc64508281651300a6c4d02881568994ac8a4216b54208db29965138a932ccbb21920aecccd94040f39fe9dab9c25b143ce8260adac65469b8325a6bb26b6310bb17d03d7ee6ab0ed1230cba694598c38acfed650d17ac8b22ccb683044520dae88b28124bc6831489d08b0a840a2e004776b9068c10e52ca237106dce7fea17d7f03fa07fd950aee1724c8fdb07efd093dbad1c80ee857cf9c734abb49ab24bf29a5940bc8f4231572affc9e9e9da5091659feff02b27c2f5aac4cd13df775d755c15da9ef3fe22ed5f7ff44317470028314d12187f1a8c582786ba66753caacfa521c973598694a66373516369a5139a546633c8a4772b5e291002338e5964bd480b7a7e2900fce72f8b7dc1d37407077647f9e1dfda38dfc57d3385926674f19da5b4195a7ccc13dda7bb37597f4183f5b2ba394f2043fbc6374777737826bf9a25974f90d2342034ea69456b0289639e6216e4e99e16e9d397b882e5be62634c6a4212a08b41f660dd2289a6908fd30822e9a0d8ae0414341160fc801cacb165ec2a0c19509823574f0d61812a2b57811228c249650e3a9897a450d2a3835445085460927a2f801075fa021544150031926033862c4a658e2891c9acec87c50434a55a3082388a04fec7c2b8bb28217f0f0e0b7fe0df0841529c2a062832435a0c8928d6003501a04291d1efcd6df3f410bbc6861811754a640a33e11c68c6d4b9834a2fc4724b65412acdece39e7444229cff93593c60b1251542d371312557278bf09aeb9dede1f89f61cf614b516b35ae83a8ee39e89fcee1b56412f1747eddecca318d4f65c69938b3b5f22fd70aff9036918c862d0579ae6619e0df3d0d80ddd3329d261a4ecb91c38bc6117240e7dabe1ed91582648b40d5ffb4d7a7eb3517b24f637ed2d66b98659ee340ac5a11c6ddcf884a31707bd222eeb89eb422d04e55e85e0e27f38ad937d905bbe7c736751595954e29f1efc30243c8a9cd83206261f89cb77cc9a482410025ad094148349cc9af8c8f65afc52b9e58bf523df3110527c20895bf60fc7c94c82606b29066d1f98bdcdb22ccb223824d5b0b9e2e6f48760d64de3937b36bd5ffa10f2e582c4993f52b98b70bf2371e63f10d28ffff62effc74e8b813090c4402de50c6fb4b8e5c7ca7efb0cb35a906f1f89ff862d66c271b5165161e6f94b6290bf56503f73ced96268cffc2093c9c6847b560bf6e5236043fa79ed6362711cca8a38fb4de86bff43b5fa1603d9af0fa4bdcd961bae5831c60814bf6bb3e57af69cbb3ce79b554ef6195bc0d5ff205bf96282649f893fc784a5693c40e8f7c490c45fbd642508fda6944279295a23870d26d39a5124a864ea23d3ef03441c23b875ce973f1d90fc0e378165f4332e73fe117cdc72907a19e6d991c40d5b9982556b7fee7ffc39fb13dbcb2d776531363d8b712c1fcc92df3956703f3f0adadb39e2bad533c6d7bfcbeb7845bf73322b479611fbf52b66dd9a2c2d5ead7270e5115d72d84a3df283c822a72cff88fc9f2f593a9076d0478e5f9f7e37f9674ca54f643ec5ac9eb93d21501de0bac501e9951228db23216a47bea6affac70879feb7e6e7b84bcaf0b374c9a738e6c41c75e4ce71307e7c95120c2c2c6e2f1f89f68e993813a41f7fed99c8dfec67a586edd76a1b56a547363887c9a798097da41fcaa4bec4ac888f64b056021d742b7172b4e181607422e6835eae7c6f1e0727eb899b7d679ef9d917bd4cc1752b74a8fee1dd3841981c32b96315395291e37334c789ebc9a7de011a967a4e7e4462fed9a189504f7bce6bf454d8c463b5e0bdea7fb2bfcf65a00b6e7d96fc86ad58125f07eb372c47ec7e971cde5c7fd6af47527807fadeef90fd8d2d777b4ef535ec765eae6a86a3f95ad67cb5a1721caa9d64751df771a87e94523f12d58f4af52397fa714a18ab58c9554bae1d2767a89fa47ef48478e6eacf7c7148e2d497a99fe388bb04cfe060adef55716511f952611657592d78af7a24deab52cf84bef7489864f58fc8d7fd5abf688add93cb7dcbc1c9fd77bfb0bf686fdda1a86b5a8250a02505545233c7895b13aaf9342ad7986797da86cd98bbe8539d4ffa4567064069f4af4d4c9f5ac7dfeb77a514377e68677c9252fc2597e437c929d24952915e6415f924af4828894546492d336693674cbee8d3d3139025f9d2b0388ebf96262e4e6078e9a27dc92ea39edc1b62e3810ff4893ed1a76d481b13ce39673f14dadd3db3c63d250f735a1e1ac75669250f0df64fe92054916328876a4a8250454bec2e305268667366d14a916d93e7901b39fbc8f99c33da39e7c491336c3324eac718638c31c618638c71d639c7cca1d6914ce88d4ca3b531c624219b82ce9b84fa077df90ec6aacb7f01c178982cbfddc58432284b6cedbdab1598ec4fb4e4531cf450fa78a6324a30fea24512e7c95dd91f85490a5282929d2e49a14d53a813d16793a9937c699a53d68ebe1c6847de109b9c61ce03208ee28dfc6a72170edac571a80f0cdc5012492277c97f69551cc7dfb5a38d88db3991d13b1c705ca5b97f4636ee963bef40ccb51fbfcc58ebe77dfc648ede0a137b2b4cdcd52f02d9779824f47e7646e811f975ace79efbee6b39c8fdfc4007b9cf61c6bd0e729f61eb207793b967a1fec43f0edec820a46cfecc382e7b40dcd066eeb95fa1c7411d6d5cff18c4c1c8e108ec1059c9f58d849c48581c7213e87b4e92c02149a9033ec06892529ed18abbc24825270dc5e02e2a3153a629b864fa0af09784d17825d32af251d6d58f48e7faf2bb9187e46b091d52338f2937298b5f6e70f81ec4cadf3f2213ad439ffed7afe88188439f66e1a122d30f7b442832fd10271eb50efdd41a77be90a04cc5263b147f7950e3640f058c94303e454c04e32f183a7271faf1887e5c72d77c311e14464c98d8a54e995cbf9829d5b2a74f57ab7f90ca78d44edf24874d5c90d6ae9c3a0729a516d7d0eff17be3aee956a2b86e85b528bba432b3477ee4477ee455dc8afb15776549585c8b2fb92bce6ec995ba580123771726e1d22fb7d2715dec9e9b7ee447d2332233f7dd713804a2d34e31592c6a88ca8c9a510efa9d56dc8a9596026a51183b3bdf6a28177089424b0684c6c8c10343784b60400b2b2e0e476ca1c1215689b47832438464690a33a0cc08a20a2129c61942db96338010221a89314980051351a2165a589924d0e20a0f28b028c41ec2ac76be75e394c3dfe1c8c8220c0e95034e9604597891c224450a2208b999a47822ebc8cd24c506a4fdfe8ab3f851f3a153331b64cee69c73ce39a70757eead59dd3ca77d604d0866076dd6d262d6acad1f8b7efc4c8bede0f7d1f3e7a6fb470391ce42ee11777ee49ae370e84f43cd99866d90d9e2e960c52c8a7da257275cb7a05c9e22ee8aef60e7803b3038f8a402184d5c7a3a36eec9c96915f1978f9ffe41c3049a34cfddb9ab96410ccbf5c789ad96bbc2beab8937b0e360ccf1a666b5521e4d06541447d99bc26e8ac28a2d8ae20439ec2efe956691931d66e2866076db394e6cf99ab0f668f03c310f1e6eebc40d881b72e390027185f8dd5176c4b1c58d1fb6f26c1d1a94dc905be290066f8519ce1d77ff9cebee3e8c644f6d90597ee733c7305921d43343203f3f0896a23cbfc65f0e739bdaeed7b9c8fd707b99ef87c15f77e67b6436d79da15f4dc3924c7d4ca1eb7db7baf936995a6badb5dad8d4c84c192c53f336d806996b70f7057170ceedc356221244666666e66564645432aaaff38c6a66e65bdd3838ffc6f79b6a7b990fefa652cd5cd536f3a966be1a666666becee008dcb7b9d2fb3d418a6cdbc515dc7170c7d155cd5fdd5c99fbf970f0feb6cd601aaacccac17977dc35b9577d354c21def6d3fbf91fb3102fe696bbbaf7f0fceeebcf7ecc3c9fed81b86bc349e4b49827d3b781663a79f070e78ca764c0f5fbf4d727d27876eceef86dbfc7e717de1cb5f73b5ffbdac1ae8d7b7f627772177035c4afa1ce582c16443f0829be1310a6431cd32487f1fd06274f3d462838db044740fef47800cebaab49466f9226b93d21637293181fc05d7f05052185fef19decc09a64270f439a40939c84869ae737993ba06e88b101dcc775075cd7df9e53b17ce26b5fc364f9441c81afc173fd8a8dc4f9f1230e9be469e50f89d9f200e27adf7df6f100e2f200e2cab71e0d42bc1f12f3f63d8ec304b752916b08b2f7db6fdddddd5db7b76d9bf78d6d90d9fb3a1b9f4b7d3acc108b6d347c4c6dddf6c1e847037d6df52db0d2af94d24a354dcbbe523a3dd788b86e4597d3878ffee1d5cadebcef36cee6cd05488e3e9afb362d3ec5d341708867b075fc71bce03638ef0c5626c972366897c40934b3219e2b5ffc5f5725c66ce5b0c6831cdefcc8612bcf9c6c420e7d405194e71679cebf50c420cf5f45c9f3ff03b9994040c615a4a982c113607c706c48a1b7594b3077176a082dc1c92e4e6078131729a5635f72294472e825142864dcf05f5ef96a49293f172dc1c9cf98e0223f04929bc7afaffc411f3d67927c293408b195d376e46b2a81522639287766d28cd9d4f5ba0f1f9cb199e4cd988332cfa424f9ba32b4da27b138a8543f29971c94bf7d928b83586613cb549a611c9a312fc344524ae9711511378c43b19964eb9d1f7e12986d1cd241c60d67529634324fe5677188064729984a9e1f0f1ce44fa8ce0a2ec5d5d8f80b39c3a1f649b29cfa98ad96edf7b80759aefdf63ee599bba7b294a73219999ab9297b7f4797ab00fb4554f87e5cf85e80418619400ef3782eb89ef5bec73d96fccef339d5ab986231d567efaa2c3bee72556fbfb94db571aa8faa8faaaca3891907587237f485336fbf7d13185cc0a8f16dfe857fe353e1cda9d4dfb0a909fdb5cf3c219ef76d5df6edf9bce3bad1e078c569341cad70116300825c93dd13e2ab2bb2671f9ddcf073401ed0b18f9d940f32aeb6a49edcb093413cd12537d3135c320072333d31e5e626e3d577c9e1ca762b5b7304d7ad702ae5f846a92f03aa4fb86350f9e2e9092879d3bae811d1de71e8233b8f0e6c72bfd7f1bbfee142b70ba10cb24aee6ae49e14df521cda6cba93b41687da5bdb3a6e9ffb5a061fd5b1384e7ceb09b1d8937250113b0781e8869e1495c0b82be4f12b397e0c67929391f7d1dd9fc218c62f79ce8e01124e56e4ec33196415231d03249cb0e4ccaf441972b2c446def7d872e5f768ba12b3e474a71e4c777e354ef146c6a49393939393939314e3d47a1fae0a8e139f47be26ac888e28f2bb1c6e08aa50448bfb1287fee4c349a973cfa889e5df63cb65cd8f39ec5a9f4305f7731407a2cdd13d361991461306c18d36bbf2dfe3e1da1e8772224b6c1d0c27b944ec5aa46391d8931c941fa51492314f8d1bba8b7157d44183fb3d926ecb212fc2765f19c376c51d6cdc29b31ed7f27c032041699dcea135005250e374ce7088b55c8652ae1ea713c4a509e2de08e272b95f2788bbe57edbe90471ab4e1057cbad13c4b542b823baba09b9be17f58f25f9921f027fc299c8a9a31bee7030c410c21077887d2ed5e9047155a9ef4e27884b939b88f63138587f7a444240e37a433cfb3c4b7143277222a129ee0a1d4a51fd1abdb890f7f55d8abb3a56f6f79bc02a2bc39f65f84746e681e6abbe09ac7e084fdcd089f2fc0cfb8ed3a51f950010c6f5af1f0360674ac541498fb22c6a1d599f0ef9530e35a1226da7e9867428473a44a550220e6a7e6145088abfb8583f90bb681687669466c475778cb11ccaa22f541c477e8c922f8fdd33d51bb7d18d6e74a3945afad172eeeeeebec59f1ba7699ab669b2c885a6b85771f0e893521c24a2e260ebe8862ee45086a615ebde946e6aa595fd1b1b0c7772c8d311132047207a8cdf0eda18b1f5ef5086dbb49303b6be045ba49452fe4beab3a3cfd1f7239be6b5713b6e2e89ba94810b6550393ea7434ea12cbff93487a2a6946f0e3928a1244e7ca11c632cc7289dfa294a71304ea21cff93c1ad61e62cc866cc6d1cd085ecdf59be848ad1f1cca07010a3680cca56ed3b2a47cf23962c633b0dcedebe6f0f9edc9d23f9d6d50bd9bff154e2a6984a3426ade0babbcb88424d383621ddfe303615750160433a387792bcb0e24b96fff1b37c598953c695efd92b454024022aae1436931630d97112cf1458e5242b1abc498c35140dc5ec0f5c7d8fd52fa7880cb02a29801942a5e5aefadde3dbc1faae307e7157c53ec5c8e7b56fa3225fbf773c0aa1074f80ef9df72b0e766814e4b56f23d8d76f2321af7d1ba5f0d50b91d7b013190df98abd6800df3f817cbf15c0f7773fbeffe6f8fe9ad5f7af707cffcd8defef39e1fb83f07c7f91d7f7abe0fafe235ef82eb82bb40aa8007812be3f89bbc29cd7f9fe25237c3f90083113827c0805004a094332522e91ea9b7eea1b7c9cef10840f21890796d677e8dff23b9cdfd987dddb7c87dbd77cbf7d9aef8f5ddc1586f83ff80effbfeff0e63bf8fe28c65da18e677d034d9891ef27c087c2e747311c5f8e03bcf679475ef5c918e0693e9b4f4955e157df1759fd4e111c03008243ad88cc0a87401e47919da7c14a6e911d34322da88afc8d03ac7c7010920d65be870702c0218e5ffdb8e1015672af4f8f0f42c281430f2a101cfef81e45766c911d1e3c8e223870e030c7df2882e3c60721fdc0a15191efe1b353e477f8f4d8f920240f7088e30590038737fec7af5af091b13e342aef73f8acb092ce27c7cac8e7695a90f179550b3b3cf820240f87397e001a0e570fe4b98f838910d23c09588985c97c10d209301e1c72afc13ccd0676c2cbc044e091798f050e87de6bb0af47b82038082924e175b0121bdafc082368cfb96ad5f911b0921be48390341c92f0152ba95a7dfd0858893d01872358988b84d7609a8643d55798563f08e9854323d8bb828c007b11b0923b8208dabfeabb9e6341a58343129e27888d0da24323130487aae78278dc0721918043a320afc3824d90a76181049d0f4292c1a1f727a870c83dcfdfefa68083904214c2d6e3602536059b0f4232c1070eaf8c0a2785160e09f03629b47cd8bccc0a178732af4ae15306b84270105268421882c509000e557f4b48a5707088f33c42f82024150e4ba8283c0facc41200873c6c0a0128e15529a87048f3a914f00e4148aa202414706894c20740088f14be258447eb83904ac0a1ea0390fa12feae60c2fb10826385f8102283439abf4264ee07219980432321ef63051c211fc20a38217c10920d0e65de041cdef7f1350518c2c24aac45e1831a9a2136343d1f6025774846c1062ba91987364f33e4673e25351ba95043040721850408bfef002bb11f745093cdcc7cf0e1f083ef002bb9443e1a1ccedaf3d910d667acc40e0942a219424333338466e683907a706834e43b20c21af219910e581f84347148f373e6b3afc14a6a362a802480cd46443eb0445090446c7028bf062be932de2108c9a6e6839008804323228f42013ec0a191d0c388fc8795dcbc4310d207df0721390e6dde250e6b5e7e8f1c001001fc3881e7e532c147004ae8f1ddfccea7e349f800a0f3e54498911fe1bb79113e1d8ff381217c2038ccc8f3f86ebef5e9f80f3efc7d0f2461467010d20eec01f62adf0d362ac0b33e1d05b092fb737cddafbeed717cf66f7cf46dbeee6bbeed693efb331f7d99affb6b3fd56117fa36ec501cecd73e8b87b04b71b0dfe70bf2453ed813f982fc900ff642be2044b93f850f8669183890204c9e5fe54248055064f9c92170589a41f68ff690058a8cdc117b4ca649f66f0559b8e4cea225f7df3497dc3fdf00317e47a12b9fc715195042ba7b1fd2f59c88bc20620b1ba3c549dc1004631673ce2862929558e4d44bade452cabc33e79cfd146fc26e7e8f37895e629ed96c2263c4128944f91dec70e3f748970017bd64d9fc08703b38205c3b6601b71abafddde2bac3e48672b0bf894b95a3293483be7851c222a710458501a3c94a1587e2424f5fb428c5a238458c938f9dd8534b6e2507fba34a7a42765a47c56ae2f6e3e4ee584ddcfe2cf69c724a2ed2965eb318f184926901d7b3684e1113e504c6152b72484201f3b4a4858a17794c4c17a7a42b514a8692a1088f2b2e985b3caeb8337694a9f895098ea39f0ef4eb9cb4d277a7f4e7277168e3a4f1eba7dd38eb9c9cce5a3070c192b59f5900d1a58bf267cea2537dea2b6945e2b810a5e804f238e39a80c38d4e398c354b2a23b8563a499cf97311eaabf458d07e3acdfed146eafc6c88d9e2b01dec2a965ea8703caab8f1edeaa1b4326ef4da9b9f3db63f3d06a18c32c6490217d73f8773b266985e65e1845a5ab98b7e26ffb3644d2daba9e460248104f779405d12bcb8fe9fa7ddf9c0fe4143cc12e7c448532c2331bca1e599ca4a966fc541575de124109baf7c97b775e4ec8fd2d9abae75e473734e2febb24b69adb55aabdd6debb8ee76359e97dad9c97150e2165c55d79d7d7f326c4af2de3bef957ee3ed1ba99acd2abdc65a6bad9f6219b91142207b97ac93e59185e285b46c7ce7c4a1fc96f1a1e24728b799aea892b77fa8acbd3ff7db16c31c396a1fca845a8e1fb5c8233d9e9067fb22d752a673d07ad65e01eeeaacbdf627687fb3acb6d0344e0bfb4bd6721cd43e7e3ba083da673cf2a57958d336acadb2f4887cdeb88f316e5bdc7ad3b22eb86ecd077dc997c3c30d8150098ab2c7b2ff1298ff47e988c5ebd5fd095918a5f3e7c268964d6c44e9d712477ee12a703d3753121adc969b090934f2eac6a3bb096074bf5ef367bf7c2e48c8c2ec6b5f3f210babdf5f78c38fc760f6b5cf09cf8fd08699b367f9c4a29c6189c31b283e52bff3cf85d91f217d96cf9629a64195336c6463b13cc3ac82eb2ad3395823e5ab5be270befc6c9879e296fdfe3db25ba77b9c808b38f40d6ecea00277e6568a22a50237cbad1485284710624c54589971d594fd6fdcffe61a0763962944598a20f2a297f7e85e022ebe0dac786c451b84f9907c7c1eef028da6ec9fe32f09f32e6cf9526e610c702320dd6e565a690e8c085535166396f99dc12e91efb2664fa270da73ab819320ae73536274d39ac45872da3e39467bb2e4e42e192546bb240e96e498faa4ead02f0a45a36a0f146a0c85aa1b6823ec87aa29a3662490b31a255fde38f3fd6726a3dce50dcef7997dac724cbfb427d95fbb42716c9df97966356f9a8dfd23fba6294e54bc5479ba02854546c928f9aab1985097a62f4e60bc84791203156bf2e0871af31b3ab60d65ffd84a376e64a1fe313f3e8dd8041cae14ca472e3a4683feb449f2d02eeac5419f4f1ac7719aa4bb66ccc131da357568502e353da10135a35f5b6c134243c8169bc18d1f6e313aa6eb93ec1fa740a834ed56553d2b99a21900000002c314002020140c8844027150301e1637c13d14000e88963e7c54160cc320ca410a21630c2184106008316460406686b6011f8deb80cafd2f2b5ca4e439c16314d9fc011747c2b2ba0e11fca35ab938ad4cf3ee0b0c0742f380a1701c0c66f6cfe310fd813059532e59a12640bddfdf244164b82d5fafe8c107fc84ae051ba86f952a75b692a07914447b182dc892d00caa35b8d7737ec0fc179a8651c161314d6e87e9cfa586a7dc456e650ec400dcf85b9b95ff3d62cd708eac948d3ab660be82ea529de872b823bf41e382846828dcd014e4e38d8ce16c49ecf387c5b6bcaef2f144e35dacc8bb92c68066e6a47ff8324b6b978d35cbf01605cb335b0b7538b892b0dfde4c328155fecc5971eaa20728afaf92c2710816b4068fe4c1be834eb7b90d25bc2b838b9765273362d337a011e73ee3ee91fab4f781b7ca6e04393341798b430e26c2122d9c03efd554534d9afaf03cbeb69e7aa32c6a9956f294ec83f70ae9dd3c1b57932e5d811a4155c777d05710c7e9b714eb6b0543c2dbe7ff72f36728a7db3a527c4c2b568fd4a5a60c16a4ed5d7fb21c396a8048640fa623bc9ee28bfeb7537c188204582a290cc647e6b2b6bb858d46c3c5f82528402bd08ac9ad99d7928a66fb1719597ce4a81df8de204282040805af07a62edf2d1c78a8a5c061889d0f277599dcc29618d38cdbf15587f1a0f3cfc9824c210699f8b4b9cfffa482a474840a8159e16f9172a5c4cf5e41d488d681ef8a2e062a2a7d9952f088a019118684ae1dbe3a5dd596242dde499dfbfb79bd1eb194797c80ff5d18c65c416fc61725bcb232c7b1e5032ae1d66ec70b128c6caee4d3a828bbc231c5ae243be86b7dcd881dac0efed42fc854918f1d1f7ca8ab9e4fecbf871f5e0042917dad898a224ae55dde60c890c6d60b6e57940fbc78b12d0982976d05cdd6f1c00d166c21aecb549f855ee4b11c4c30ac78762397cb8bf78c6939a20d2f9bdf87e21e6a9b7bcf0e2e7d4f2f7b7b42ae0c28e485ed97c6ad7776c3a67ff2de9ddca809324c4731d89bce4fe53129820fd309b163b3b528d64eda38a29240b2cf6fe1b011ef94256c45ddc036a2c741b53ce6cfa16cebceebf8a3374c7f853d6edfae08f1219c50be1d6f46a9c1fd87506a3080c3b113492b0b932cb10aa580da02682a69a49c7898870732c9b3805558f81a21d9910fc1d7f9a3d10875bf4f4a0a923728572042c728fcfa2ee29cfd713fdd1aafd59afe5ffcd9d37a663aafeed4769ccdb3d5592aef263d4a9a5ba5ab9129433b4391b136c65844259ef6673d6757f18132aa6eb2ab0b7581d621d42505ef612892cfa08ea20c04ad1bba1c4e7bdf58ba8e6b1f929189e0eb7baad7e5d910e767048762bab637d77935810093c2cd6e51a40cb878408297e225789bb8f9b39ed5607b8929ac2b8652360c8ad9600bdf5eda2a7507f3806d73ad0893a5372290bac027cf009d9efdcabb138e4d2ac2ce9201867ef446cabfc81912feddbadceb56c524e81855531a7a9804b8b060ea68a5f437c1ec627ea38376f607372aaaac27f5cac02df9ce068c984cbcb96bac10e341be7888d5de483dd102bd1b7b3c51201334849d28c56aa2411eb9945bc2f9e090297a5044e0ed71420aa9d487bb212029251953bfad6c12f1851a7b8a144ef48316d8d939cd8cb27f629e3f068b871f140fbafa269012b169bed6ffdf11104c5bc45b728518d8b39945e8a148d7e541896ac0a7f966c1a869b165905ab1239701319b869866450ccc882112a7860bad65e3f3376fa0b16b42d3f3f4adbfab5097cf18760e46c5b2aeabfa1afea2d09b00c634703d5917c7c48f289acb059c21eafd65b4e16fb4505c86c7b6192096cb555d145d450e2cfaba1a07479f2696cd1fc7515ab7935e7eb4690b0706bc2ddff79886c8b972cf39b03c6dccdae29214982762fb4ee50edc6f62500716688e2e07ef86222c6c4053e2b00f49d6f537b30a5a6d8fbe92a5071d34037722b52f67727f5b5036ca2e98c4cb6c5fc4e2c805ddb6752c04286aba52e9651e8305d286cd504e8be4a91bdba09f91d7f8af432c9a951055aa890084bc753185ebaf3074921bda71644eeacb9bf53622f8711ebb13c172d6ad589214e040b4cd5fc4246c64102498d734c080456740b755029dfbddcf6d2a1d66a3ffa5000b38a0c5eb5621bcad127b0e0383e8d48262e6d1923e14db32ab665014bab2f420f4c3f3b4cc60316926adc1a411edb72b2a6fc48a0cc3934ee0a38d054206b47fc2bb1a507e985b25209a4d354de42d0d18e86b4ee73678eee2f7799b72d096c153cac872391c4d2bc509d50815652a858557e4d65a42b55edb65df0dad3c865841540787474dc77ce5bb80613841ac6f81b43377db125bda46cd7e3618e099b8ee6e2e7df015bfc048db755203735a747528d771a8c8664f241fa8a0168924a987a715a799c4e671665fe1671359d3a06c1005ef497985a7dd1ca5ad5845f972381010e40dd0f493654ef41624a27a689743d936a6415f657dd7954c8eeee7046f505a4a51f1ef2b5af20812926b92a01b0a5c3c03d2384a7cf9dd31dc180e967973148f0d72f1e4804a6110400ac91d0069a91ed6c0486b4042d82338525c75a0df841764da1db4e5b86110034a74d8951091d00205c8adac323a35f7c5bac92d45ee27287c82efa8ac3cf9d1d1c4442addf8885e365d491ed59c83e9bea3d522879beb1d271665262b9574341cf5f0a0f7629e7587dc7bd09078e39061e1ca86c82e0a7f45d8d04551d963586a3b6dc666fff5c01b1facbd6a43acefd8685263667556ee0e345b23fcb72aee6db482d20e62f5d14a72e274e0c069983e0a24cd885d28ca80c6348df799edeafaac31683ce7828d3bce38a983b5c1814e54efa6f90ef72c4fa4914fdec6e4be85cc0bbb707a80f706b65e4a7bdf61c9bb35cbd8b3df628a6bed40b3deed85b6e74f7db521a75ed12e8d66dddea2f2b525fc4d60bd03eb05c4bdeb78b450538f9122175ece8455a1a00da529a79b2cc4169a3464429ef9fd1209344603ce0d03b726fca8d9ad2e5e73b54a2af52f01a5eeacffe9fe440e85825d476cc12b4dee9140a01a9303bdd98d50eaf06d965d1810a8ef48f1d5aed585a1c076c2ccafa804c66ebc5c03d94462884a4dd4fc5862d61e5fa8f442ab68fa9297518365aca2c5712bd4010eb8d58a94263c6ce76b642a5c485458167be4a7c18a52d187837d3ddc386a067fc62bc4cf88f049df985e4f5164c8642f1c7fe9f49a2d77f635ae65a7ccd1ddadbc6b345df0dbad9787ed29435a929e210e93cd9f526cc121b86d2cc6a3dd35b648e441c8044d2c6a3a0f4c46a74269f62ffa69a185d0ea6f3649cdd0c32d0e360cbfca0375b4ff4995052fd58e796688687d1958c18f8cd604333a15b929d83e6d4374aed197d445f44880743da184ca3bea89e9debd6e51cf5a83c7608ac0cb48d0872cf158b90f0a6697078884a241a9e52a56e1683034b10941074d240dfb38adc73d4dc6c7d2b64f6953dc4ab879caa93e4c937d4adbbea53d71a326f9c034edbbb482db34d9f81414ed5126924182c3b5b07991295a81dbc1381c977901917602cc126d61840f983ef3d074c649042b639f984537c30d64deff0e37b1af00de497eb7d1457728d91ab796f64bc6127ea7b01f20ec5fb9abf0cb50c00256274d65d9eed4783072b18ee4ed6116d425bb1223114d959b3ac519b59c120452e305a23103a7e1ed0ba50c94460a442306a66b6e1f5d18388d1418d3d8deee97bd9a8cf261030faeeb16bc55aaa21c66d776b0c9910ece0ae435db96c78a002f8fa23c65f89cf3796c7f7dba617de357dbfac519a8984fe850e6d548952b3f2ecea83dfc6e3b119a02bcdf787d84e565c3cdc8d4c8c5e9c5c5195582ffd770b4b57f5deee21c70d8c0898c222bace671c98e4f04ad70b150a3083d87e4b82e77de58fa394365f31f25a2e51f999465eea1862e747ee224e92a55d986f4139712aab2b64dc48a02c130b248015a027dd30fbe62972580b0a6d8401b40667e68327a84a0e0e8c9f374ae83867e021bcc3255b927d4524cf31cd98f549ae34805ecc1a8c76c1cb32c9a7b770784979383ff9add91eca753da072fc2a7a989d20f46ad9a15d51271919c31cf4ee42072d7124d02b65e04e900323a8b1a092ff82ea46f44703565596543dc4024889ffaea20acf9c404c2dc8d1612dd276b38f37c9e23cefd95aad19b51f4af100f85883f3fe7339260ba34bd35c7f3014655e6d6178c54f0acb9c8ea2813eb71391d091b57dc5370e039609e69819aae4e9009587a180b418fc9b983997ef45ed824b9811e8ab2e29a3c2c513bc501db432584bc5f00292e2b5c05c601bce72cc0219b8cd8690e4d706181169d3d2f6d48bfddf1dc0fe8155cefff2bced8482a9fbd8245ef602ac638aa30cee4f85a8169adf1fce349500277b7eda5342d750feb8c9f644ad33afd42cb73df68b89bfb8928946d36c933057069553d8f9bac04a7fed716bfb899c51dedd760d1cf8836a206c013d8707e2ca7356c968248cbaa14daa56e97f22f8f46dfbd52fc613ae9ed1077b923ddb308b91049753a0f29c55356a049939a8308ef74636effaf250b5df14896632893684fae089e7ca9c3d98288ebeaa2da876a25c9d2778f5c1e8ee62a021f73263c117f459cd67120d13fbe5eae05c5ce2c4a55dd6fea30204c661d37dac55f2eab9dae39a85144dac1f295e8c8c4c5d94cb6985d23b650dec63b43cd5da22994cf0d4d865b8e332f138482ace00ed062ac2ae541753fc2dc19610259f57e2f945a9be47bcba3efb014e3143a8fc424177e4a8c493824c048e8e7ba7f539acd6d19206939caa18f369258644c145aef2433340f726810e948e53eae8f1830feea522dc522e1f31190aa97cb82449e24c34447919c8da6aea9ae36a2ed665d6d6129f071663821ee9d21ee699a9f98dab8c530c367ab0f5d8f2bf6e0d2132c9326008398465021f29b54dc3e9909ba179ed45cca1878d411b5f67e903f59fd96cdc3f19af0eed487ed21a4b0850c2f4b4c5a414b002077f1730e5516010f12124e719dfe66e08da3640c39241e88c92eb7a080a0875385f12270d55ffcc261449e6c81f8ae0e83cf1f731f2d2b0d4547adaaf2f00ca4578bc1e4605abcd49cb1fac4a7785762a6bf478f0ea1161afc3934d3161b5c52ee2fe342c5e3a2d0e2bec0705d6cc76d760bafa32902022dd20784f7f34d37f23ed0c4c65f8f2ce91b6f2cdfd20ce7bdac9fbfd0a97339db5cb954cb92d37da588c211d4e990b1039a6d8f190676c340a893e43ce7b5a3bb5ec7044d91e6a7beed263c50650a07b76490c67994ccc5ba221858e45544315b3d6f5be2583efaf22df6ff978aaebbc87e9643c5be8358a0b0de8231e807d7526110c24c9255d612228a8666c2effbd945d550891b0a8d40e0016eee80e3a8c03abff0ce34b5e49f4b3aece14c14e5cc1c14dea12b8bfc835835e0e6b3bab129341792710a5110b101e5682ba1b92f1217df8993bed21191ad03fd3d63e59fe2be51d02b53a95e43e82182b425e32c7529e3168cc7055e82c60dfea844634eff60e45f1b6d7127cdbaa989a47edc0a7f5f1e2c7eec440ae32bd55e5c202ded5554dd85d62236420e1818cddb296be330828350becd1c85a621eae2071609fab7df6f3b2645cfd596848362ef6ed054aa194f05e9c74d5be2cc81dd1f32b9194f97511523137940bfbb11d0504da29f4fad5465e28e3913147eac0aecb9413c81a040406b1f8ad5ef2a2d50a8d9a164a2cb3241f35f7b82599fd1f995cf39d67fe30114a68d155547ab68bf8b6164e8c10e19830065c163a85527a6958657206116cef344b3ecd0942cd0f33f18bbbe3f0dab92097855a0edeb2a7d107f98f582b9c0aa8a1275cdf706cece0ad094cf79a13c71aaee10f406c7a62ae1fff716c2651def6688a53f2ca20faf6ea0476fc750a534f58a1a662905df7bd5c9b333544581b4a284127c2ad4b20ac23f209d8a4449e8f955373c6c718f63d6f398e097242263a1fa14c7f542468b16b771fcae81a62ea103cdc23337729ecf770778cd524d7a1ad6e3fa3edb8aaae77388a87a0c43321a646ad239d89dcd1c613f104d12591f1669e07b61868daef17a93a20a2dad379890b77d8c00ca6225bee289f31b8824386b4c9c18ec8276fb067368cace84d605c8b5f2be5318c8c975bce771d39525178360446ea7497d6a3b0a0ca4ef8bac50e876cc2e011a546e9854f1496d7cf8fdc7370947ff81b1783f3c767752c3f45f09bd83d1d6216cf6ec138ba7b6e1ed47608a4cb5a99f26fdef009544781d8e937c2c0ec7b514f84bc8e95f5b9b76976e936ff19d1d1533545f336d0bd6fc0b613a897366baad6841c3961da216447812b736b0e9d7fa4f12cdb4abff3e4b5b41e0f37b2b0f416f6870fa1fad8818b3d0e82bf24dbe37b3e460a69a4c1121feb5f656e8005419f61b683042adad4131230253803c7182b442d7b5caef72c4b2e3213de4b7da704f50165ff050dbc71680c5e11b7eab82378d745cd24dd5f2279fdbe68e2ad0df3cdf3545b81f703f785666f9b69465c953b9f6da76c53bfbfd1db9f93b07c2d16eac37f11b4cc0385efc41d6ffec8528f991d35c4af2feb84b7f3abad14ba9c425958890ce5144ff07454dcc771d8683df02f468957b41ffa8b47a67ada5a72843fd83e275879ae58593b869923de235274a30c23959faf07343bb096b722d71b9a7c7c9b5a4f4924b8e73d5f1089bd18c6f7876e84f9e2b8f4d5968b713f728e8d7109ce74e52814cfb0fa73fbe2c92c57f153ba9310727710bfa9b8aabec793f4431746bf4b3c1de9048905b837cacd37a53b119704dcf840bd864953a0b72c850075694a8c56e211212223da7b2f34e0b78b2621f65513accafa5cc425f12767478a06896d6471648d3dce6c496489aac784cb6441aedaa398b47368a898b15c3f703e631b8e6370a9aeecb4890240f92c3e708026f0170709e9f77c8dfd2ebd2e9eefa6272b42046966dea5c9f3959888ad832e018b31ed0cca6c57fafbcfd206348a72fe35ad39a595cabd45b7b8cf10b1e0f7c16d200ed042b8bd915377cfabd641a89b46822846bf77482fef009736615913a7955741c61cce24c8d297d4c1af13eb01a18799120391e3162f0a6344aea9e54cef15050f8bb83b3d4234c0285018f125579905895abdfd87984fb541b5d21b5aa96c642578e599ae4450485aa3ec39513b5b8ce730bc3abb5487d4ba4eeed88736d69c5a2bed59368da7a825ae579cc4711aa26113939c5905d2363258503c6b5166ea326742f119b6f58a1855697009bfb8c6d48ef844f7096898d0bbf9fddc46b3a09aa9d4747437a1fed9f8d147ed0b209c6981974686e173491c57b07a884d57104258ee06806a7a27068286cef87701b6e7367039c45d021fe8e0fdfa9e5b594c6654be05d7c96e93876c383a3162b6a1846fe7e184c85cf1197e77b930b21cf93b796a2dd53c24c21384023271b0d4f5c9a2a1ba8280a15df9f40c9b653d189e3e9e8bfd1424cb6fa29013871f48064eebdfa1531b5229e422f11ecee3c5c60ab0bb226236a6151b32e6f4ab9918fa16b3de19dffd7d7cb6b85448248901dc44c94526c4ba879d99c9763b68ef407afc615c61253b110b2f0bb0e50d7bd02926765246a8ea282026f2e4d0bdf2f2456362cf0b859a20ecae53a17a905a239b26cb505440af683ace6b6f138e8ce447fde12dc702ac0e95c476c9313be4ded43e9e72a71e48b0224e5fd914893a0bca02c040d53bb3c5b504c4001716261a30e6f200a2eccea93eff9f4c245fcaf7f840a767ffbcd5904e18ec0b2c8ef03843f492bcd2c68ee83742d4fa8d9129617422454a294fec3ba38730fe4423a94c1330d0cdc1419294c0866561b294bcc5870ab1afcca990d26a9f1e9a1da36ec556dec945782c92d7c708303175b858b44db2b530a107456ea60040d905e4d94511a220c5e46b6749f18d797b3d0c64511e618fe1525a0510fd56e7cd57e3f7c8b26a6dda68941ef03e0bdbcad0d60bb4c80fc24ffc54e892401665fe19ecd9128d1d6af3aee77a52af51ba4cbc3eb03c25d1fc73ae20478c6a7760100da002bb055de2057cf0538ff5e2c19999c207296ac88c82648fa405515ea3a57f32761f84de7e59e9031d81996c3d55b8ad8a0b841b68da03f9bf7421d359762eab36db4e7dc4d9e77c12d81a91a2d1b8095385d0ad784001a04f3e958115089a95c6bb33b5ac3a0ce039634be223ac473392772a44cad698d2199fa53b5a4962529904342acc98fc0eef25e24463874f17a6e4573375e67a99c921f12ebe296920a608c2837691ae4d1876febcf0eaa740bfac5fd2d789a97d6a9c68a402a36dc17fb30a4b05a22464d08a25b84bc8e4ac9024827e965be8dc178f0d8b7b0fa59921f8e1660fda1f9efebf29d81c889e2091468396ee909046f78c45d7c1c24bfe6acdfe47c7a702101de8a5ed3fa96c0514ce16cfb4f4924c3902deaca64c026a9864e7fa66f140b960299156ce4619b837447be72a68c19f7d6f8dd29958cc3c8366c5c343b6ff38be69b841f77a4f9b5901e90c56743a2cccbcb39a8d920eca1225e846e613bdcadf99cfba65f0fb87adad9515de8d96c483f2c5ecb143fb12d179461ebee56683070b72c16feea4e3a6130e040f145dcd6899a8e61be7ace84bf405e03a5549f3724dccd089e6892962e89d326976231cd34ef7e33c83f46a7453f4c2cba65b2859662e62201a2c52c0cf028af03985ec5e15f17dd505c04dd143512c83d88e0d6d21cae007c2057bcbb06e61836da1a7d28ae2f67433b7809ae0c2dc73eaa9b38dee20382bdb48f821d36c2e8950b019ef73581e805c5405315585ac32ab601fdc20bcea04136bf71b1d94bf61c42a0e14fc00c38fddeadaa8d65902a28be57c50fa99d62dcd814c524498017d2919d52ee4e82ae0dcdd054efb37a86409ee5d7d296f65ed51ae1faa6c252da7c6171f44ddcc65647b6e0244e1a3ee0a3cbce9e0e1ef9e8d06f93fe12b5a730f0c0c168eb9461e173465c082e314e3210beac0c188914dd506cc0eae22800c5e68ab0a8bd38bfe803b291e4131f4e05ff0988e01390017a77e7354aa3a0ab61c0369be56c8efb86e64997b5048705e69bbb92513694b8484d067bf621039dfd350ee35209e475d4cd869f33ba65c014a796b6ae4890b4be2e7208f3a1232e5007cbcdad25669f5e9d2d03086a09e533400402e44912c9376a28992d68abea97186299160418e6dd714a3843e3881770e9c81c5536ccafd4a333acfdbbfb54615bed7d1f8044e26dfa25c50b5712223b27410182de62d3c4075e0aa0737f642b29127de086a0c25bf3294877a21c4d0f45686ee4d9481f2af7b36ce34590c9e1e6274a580f0bbc2f4d67c7e57db3286fe6edc1bd130f16a0f199a466f0efd779a60fa82cd2fb4cf5b6cf981a2a10e016ec03d67ca758c81f87f4e4683c4377e6bbd005b50512c3d36be6e3edae099228affe2fd205ab2c3ace097125374d9b26eec49ad68e948720e2399c48331adc0feae28bfd4020b4ec78db14126d248caec2ba9dc93c033e56bd95a2d632468b1d92a76ebcab8b1f8a92a9e375f9f7e43d40979ad0757478ac1d0d6def5a91cc08de37db330cf24e725e4e189b03e9fff9007297aa975b8d1389ef8ee0ab2daf2d524c3a104fe9bfcca2f88776b8b9e4aa59a28bb0c1c79b0bc837bb2dc4b66121facddcbc502e93b392c603710bb64c274179c5607765e20f9a27f3ff44d982351ae9f1944c1785b5d44ee46e917c55683f1ef781ed786139b2f2edba6ea9843c4d033d12de250a7abee201f8cf3dd364b8c9541a5d2494d8a1bbc6a591498b02b43bece59465d0873e8a64ed8a24d0448905559c0d3cbf31d2b292e8179f054ff5e175ca54358876b7abb125f4997a59cea9a009ce38cea75420f2aa37ef9b4fc1d3ca9492e3f551e744d73b874f6327e6ce0e3af5aed43f2a0a65cdb0856ed15d99160f9a8723f7dc4f9335578bbde0311bc48782d4d26fd6b692d1dd01c2490d193645f3516ac92ad6025a0e83361eb70c9ab368aebae20abadc05de21141b8fbae547953f2ecef6186401adba0766708302a08e7c4d24ef021b65f38583d2fc94d7573956f4d9e6486c8cfa68b9903d062bbde16c2feb5cddd39184855a0bca5bd274664d7e0b1b227a2ee9dee68e5246eb1066494cb4250054f59f81356e8ede2f5ca8bebdf1cce52710c5abe86c950c7f1a6e286fcef92b78617ea75f0631735274d7eb83ede365002b0b92f2de1a055e1ee7fe750b368eaa3fafae05355d525e1ebbca35b22e4a90a8f41e155d5a7d7f45c98b9ef5282b69b9797891445377c2d35f9556d2c2e2784586b8ec26e1be7085397690f856040facac1b6c28ebe75879fe8a1b539c6061ae413f30f5ae4538dbf56f6baeb4bd99c17b5810aed5c8ff97bdadae1a3917e14099475866634d82a360f6202efd24a9565b14b09704f492b318f9cdfea8bface30ae10e7c11fa8b49c71c87b9bc8e57433688974b70cdc9482c5dcc2609384995fd62102cffa4497f37f6b0b10f6f64fc86da61f5c3240fbbdae361d29b99eefa26647cee1906e8be5db03788de527bdafb5d6e20f6f4d3a0849c2319b55fcac00fe87dc481a9d4d77e9049f143351e5851a3d2650285f704d70319b223cb6ae5b20bc3342aeea4be93996ec5f9e069d36eb57fd4857bfb586a35763059a47da181890da13c82b66d68dcfe7ff4c9e3229bd63bfa509d028eacf99fb94faae9b1295bd101c111254b3ff39ba81c4c032a60e95c59bcaa23e929f5bcac4352f618cf3efe9dc96f70406cff4298cc862dfc54acd98197d4c0ded317fcd694f184957b43b48ca76889cceeafbf328a4c49ec78c2a414d8f6bd8fad63beafa951f539401e658b3bb8f0e74e9a1eecf29ec4c47548b4d9dfc4b56aa3050b9fa12112407f018e211479087f0c4d3632b1954218d5627cccc312be8af286dc3dbea501e8f5f9edf13ef87189485b7e09b4af2f2e05f947c1e25a4937dc22289a37aa19a04ff10ef398af886b2f05c3cd4b55027b278d925a3fad635bab34caa722d9de7500502cb3affca5fd7d1341a9a325f0ffd24ad42df161a8dbc1ce029d64ddfe0963d805b210371170abbcd01f1e5acce13ad3616a6ff6a7de3f5b33a1b8f100d3c9082cfa3c89c9b2160d55c93919b2967a329c56f3b378559390e0607b71ef4a4ccb82d1b05bc197bd53c8c55dc19823cf034c6e497685620388fa402fb9209579721fc8becc8d82ee3e8f911594b2ce26e835bea02228dd85179bccc59bb6f62075ecc98899eea35a90abc5da9baf904904539b3d7c76243ce3ac5ba6199fd34aa677b81983d8bf75ad443b20da408826e4889bdf8ab03e066ffc56a4a806a3b0d2bf33e706553455699f43b3b83c67b53ed8af4f76d4318b661705cce297b877db475eabba93ebf342978304e3767e878b0ec6784df83d326861fd7ee09f4fef2ae5dd521b00674e02cb5b209e5cec8b0ff9810a02f39553d6559550258a07ac75ab04e78356714e6a659ead2b68ff33b69e6725ec44518f420e7e16e615833518420290f7e346f7e76c5239d211fe70681d7d81426f9ba84e14ae5f0d75c5ef79af02a9135f4504d5f0ce569688b588431ee84f586f1866fa06da0008f0b74f089d4c7d49d158ffea2b2c412faf671ce9ba3165226087fe34bbe27b4c413ba304a9477ccaa3a8113587591f98a26cfb468262e362358e515e484ab336a444ef60ac42b1a98907c1ceb56c8a1f12abdd06723925ca5daa5229925110f5d17d58898b6a3f1b038e92b748bc4e6b6cb6883dd4429f29ab6452821814b90fc7917566ac2faad273e2fcc853f457429f57a23bab7c8c539e0518ada96192a5ea043978851c63d3e9a87585dcb5e4a4239d2798b7f286470905448338dd1903cd896a1559013a2dc5ff8b61ad1a0b4308569b05b48f99c731c1698372b4ea4086e70ba07a5f44e30db410fdfcf4698e110066a93c2ce9e9d2075fdd1fab25dfb019188244b0a0641a3e8d66aad2757cd92a93c50d0f9ace4c484c39a74d20e74f617d02440b1c6f10654aeb6a83b2ce1e6bcb88380c3c2f6a81b7761d07a5ad6931242f054173a10a67ded6953d24b7b4bcb12e933803aab6628ee27532730a49cccd017ce6ed0ed001e309c1941071c91ca2ce2f35da0944ab7f027a5ac14384b3aae43a0054e9c32057a3e922c5ffb0de6e856fd032fe2339b735c3b863172816cfa3e86f08de5057d49b4caf155bb6efcfd50a26bb4e56498a98d3bbbc8f7bbd366a8eaf49dbf089525efdc616cf7fa708569aa4a9b355760c5198fc7fadeeaeba1c30dc44bacea2aeba71ea216d97c8dec995ad86c91ee814a85867bd42b6b90b250b290b15164bd294c707f99f0b2930650ae8daa5dad3a73de14785f4f4149b2259c0631b25a11f683b238f9ff827c410f5f1883ef3c55fea8f0ae49d9e4cf1ac47a310f7e98d322570e2851779633ca38f338dc48b00e1d373edad5446019b716c5758c094d64c414e519888f6225707943e574687f64959fec49d50dab95d365692ddd6c76c85961d042cecd2bb658369774ea735481e8f26d598c867f8f93226321b327f7fcbd23e6909c6dba8ee079adc03a5abf9abb2fa4e302795403543e61a18c92363d53eb47de65454a0e9c90b84e1ce40feee220c3417276381f963429266f1da1d211723b30b17fa087d37af35b13814e08e645ca5e33317e445dc7a8f75fadc33b8f09d30676ec0bc5fa1e162b71480afc62d34b24f1e6f69578a233b233e63524dd0eb723eb85ca9f20ff6162f20703f5602caedf532668c2c53afab79a73ab93da7215ed144b84738d19b27e57beb642f97477a944e1e03230704e0f8eb978ab7c1052af265aedca95d153da565c904a20bc2a9b41ac668f5ce750b33e072e7c4b30ad753a352f790245ead043ba3899ebfb627054fbb3c4e785d24418ab6a37541577a4462506276683b35878d908d93e36b54298140fb5c3c3d383fa804cea811145ca9d3284b8f038a5ce01ca7888ff0c20fc344f41e46d511333871c37cec7475e37f01fa6eb30413ad719c04df1bb8cc7343c53087b027c734962de7f2ed6fac94802c77a9284c517a59ced1301e3d94feb0e00d8addbd7399f94eae49467aecdc6d2b9c489f9e2be174ec2a9aeef1aa46541c2e5ca985fe9d53fb5ccddaf61d8942e113368dbb30344bc9060cb53244e8f2f1854f0b7e55accce6c0871884aac85ca20a984b0c38e1124cf448eb5a511936c04475b6bb464624863e2a72368c268f7c01d5b1d2e0e01a115fac79f73877ca1439548448afe39a7a4e312c9f5b8005de4f8373df0771d4109856cfc4cf6e7587ae6cf8447adf594a21cd07706e8cb6f3be73606bebe041ed946a7b8c1425255357bd9042afc01259e31f1594c225d4f90d5d0c2d65dde7d88b2499653fc5aacda382217fd80d2baf1a18c50ba4a8738514dadbd452313528e08095c2401ccd33d51e81f5bb3930fd66a6c283fe8216d6669e476fd52b6a3cd205b6d67695055eb9d716d828dfddb27514dbe2bedd0fecd21f551f4411e20debdb74a577570c522161332ff80f115d1afaeef36ca9ed6bd6b5c161d7aaa075970e80b29848557056d3fd186d2ce37c97223da069077a14756d2989e7151cc4143d2b9117d0fcf25c1e1400810a4a54377505887d521e15aa5884c4ea38681c6217ef6c3ac091f2731e1a027c623806681ec50a1e35aad4638050194f33a999d138d79d4613a427e5b013a1e1093ab43c5671ffc0f67931e95f9b9147357ec29e266487b5e42571804910c50c7c41fd5c24c5069931f81a4cf9a1d732a3c601cfc6aa9698a56be036f787e4869fcda1ae25f04c166d2fd141da998d37723c05abe7f1b315bdda403b068932dc070823f7b272158eed6ba89c410ee22c243dd60d9399908e75fd0a7b483f05ea6e12998a513d3e57b7280056f1a98a92985daa1f77b760ec14b067bf910ad9667535cc0c57b6be402d9b55d905ecdb22eb7b0cf2a640e237b5af396fcd3d9c1b21c7e744d3d05bdf002089a0f6b0f7157731a1198e999cf092e7afd568ba1b05243a09c5518d61a8e2373f4853b644e8758a29abfe06595b06e5872f0450fccdcd052d9a1559d187c8440b5df06234744a5d3a4715943c74a733e88d99340ab9a774a7e7f157124f5f13ed8bdc9dd6de6ef98484876057ac221dd15b23ffb8338f1bf7be4f5a835405245c109863912a67b54c7d9f7a7e5183abcd0f2fc1466866aee8f911838a4fdc41bb3c5df16bdc44a587e8eef2be53feb77a58236010096b4b0e7b9b952d822824be471ba605ec4f914bc536ad9a9c8238839491ebe5bf42f9313f71a81ade08293f127573dbaf58e184cddfa9a4adc66c83e4c94335740b40f13352690a6ea43d9fd196c79fc28d4ec05bb25caf319d0db611825309e09ca2f8246fb1275a26fe8d8ce77099378b72616394e5333551ad39649d044785a551dcb851b3f5a23dc4962af506481c1d7b8b0012bbb82c7a47f853a7a49f34d36a14972bb94396360fffe29bf39fbe0e01f0386662fe3612133c48a2ca0aba55060b43825d53377ff0bd91097629792472db9f0fdcac1e515e9a045721531d5783abcf99ce0a585b01a010752e89896a6002f5e56c4525b7c8039e55c053f6ab257b2db89b254726b87646f539e2de632e11c7180773f8a89963a55de06f7792d3a75bbfcef8e52e91f1e5a14616e6db16e65ce0a3ab0e8dcbcf3a2dfd2f9c46c582b9823f8e53ca6e79e3ecd8bbd6fa141982d9dba3d046e12197b153d75c8d9e8f1f09d970e43fa57cc666210fd0ddd5e2cf90d2e5c07c67490f36a56c55126271b1eb8e92bff348fcd04d942c7de68b55010be4cec13ba80369105b3c5a963914f62234a8d1eacdbb8330aee36e9cc665bc949a5a69d39cec5047cdea6c5b23c79743e7dcb3f9808b4241ea14f7a001448c51c5e51f9e5abc928e35a58836bcf79787089881dfcab5a6031758efd68f76ddb2a1330449dc565e9c42ecfe22332d5c0ca1cbd0317b0971bbcc6c2b93999634b5fe97bdd1b893d3b38cd73242c462d364e23c1fa488a7b1249525ae60e870ea9e703fbbc0e001eeb5b5b24dbf3588c58743eb45a9dc1aada15dffe18a99bcd62a79901319f94e7ead2082597b27965167d7fbc73e4832c4cd920cb882abadfbc016e02d9c014092e109e4fcfbcd850a10a9d938691795fcd1b82a20df4558c0abd47bd3b63376fad2c0951b8366d05858fd0c49e85642579b0ec27591c384e3f0892e16748ddeba3eb97d40932dac22b5515978e92081f6ddd4833d20a82264bd147f415c1c2d39f8ca00d5876ea0b439f48861104a0fe8826f4b26f4069350c61ad35a99a54a74c262c2931938e8ee980cf0375836a32e862103564424755ae7ce9c06d25b36a428235440a707ce355d4c005e3772cda02097df8c6c22c4f7b0697d14674ee104714cf068c87982fd7fd8e335f755d202a42568b5d3924ead02b9fabe8b53f64d62490731fb81349394e37a26eff433d74169731b97eebfc3f3e7703d6d67de385872540aaf5bc58529ae8ac625fe9fc7e70bc3f14d149a5add23b2685a3f22503525c6e3e6f5650d40832600cb1a094e074a304ac62d9d33eb0fd24106bf7e02005c27422210d51cf2e5445df9e2d62b06af91580fe7938d02783ef6f4b72799406cea679e640a8f96b6c816519b890a18aa6eee8a404fa62a083d337b93fb011c4053fce627b895212a7b5d37bfce2104302206e87b3b0abdf47e4a9dce478cc8192077f76c84808425b8bf21b48094962c49df84b39d3fd1df0bc7d54811ccc6c0229bcb366daf840f9b4a8dd99f591bf494665024b3d8e78935db2ce8b0124e42c29e4ba75247e67128ef6251f25702d462970c1ab89dc3866861ef67d9fdcf274754cf8192efd7954e8cc54a48c268bdc5c9223c15ecfe39b5a4838ae57fa585c03bc8022a87c0d9b4ff1bfefc98445c66c0c9e87c7b72622bd953abd283214e812f6a898aed0ec677d8ec841279b9201e6b070ce2ee2da4bc525ac2d605bde8e2f9fa1bcfd78a3b449a458babab5059e341a16627ad30a88019846c7c13bfb228542ba01329571432ebb3af0d51d59196cc1c2e58a3b6084cad51405b6528ded5b5f922ef306d9173c1c7d535984f85487b3396d84f2e5014ccffb131267281e9a078a23ad80abda35a227a9486cd049991c67972470c2cd13ac6156384e8a0591bbb0841e2b8c17ceaef50501516cdbdb332b683a35ac9b765c603b8a3aea105a77c4d9fe9b186f9664c9aa3f90b3665d0d6567c68fc0e2b328f5b2c2eecd644936a469778e0a6408b1429142070bc6b2aa9cab41a873884b854a2c0f07c532de1a9137dfe361dfd95fe72ac615edfb06b03d9fdcaad117a30a9f277863c2745d2d6c2755cc8c3f59069fb0ed6abc5a5171f6fb7b154284d0f35e805209e709277cf1d08dce3232fa00f93a18343508c18b42a914e6b4e9446fdcb11639b5955f44548d463f4af6692dc4c1e3f1319df90c3690ca56f921d95c0058bb392c65b18bade3865f044d99fe76b93e438bb46327ebb73962745ac168ee0598225bfbf31f9b844ee114deba85c2693b64f741f2f4ab4e7b9a231a7f036df1684fa99b11854ab65a7f8b1d27c68ab3ead757dd7c7a0beec22ab6c525d6507e95a5b4a70c38b96acf5c47d6674c173bd65598b5c488a75354d4f239e02d677ad5d6e03005a0d8ad2ada19e71ad88a29d96c0b1a097c29bdd8c859cb3968a77034df40b6266691ac758e097e3425088d3eee0971218a1583837fa283a2b13e2630cf7a2f38326f478ffb5d4fcb4e2c5994101ffc678c88dd68e8c9cc10d77a94de2d09b74e75832cff256dc802fb75a28e532e95dea01b842069a145c603c48ccaa1508159f21a89e3387c88a625dde0a355c6411d515c57caa3bf652588a7f484715aedb0a812028e1d00590e3a690c3d4c13557d7e27be0490cf0afb90a214a4956e1b3ecc317c51e3a043fb85de41c821a53b3bed4eb6de2a61315bdd46ffa522cb674958083b8a4194dccc07720efceec93c73f87da2b22e5ac0861bb618065ce1abdcbff9ad8a8a49038965888cb5c420d330e81987ca8d3e7374c165c456d772fba10132849af596f00741e47455403695a37b984b943d36575dad22fa9cdd4a03808776604b1ae64dbb538ebbcc1e3ab07223fb2c87597d1a66ebc52f9ab5444ceb7e1720df60e04f2ae6543bb64ec6b13282abdc003c845735facabd4394bb5c9745c4691ed2500b30ff6b64dd07b1e7e9a4b80d0a52d1789e8332014fdef6866eed806218d0caec8a9ae6eca0ac134ad8b6f53a23883d722619c057c20d69e05020cac447e2ff32a7849a9811ca372a30ce566ef795ac7c13520590df9c63412d5fb3471ca3defe18344e99f6eebd16777e92b7e7403076625768be9851d5a4d858181c0a8dff246646fe034ae156044f61c215d91896f34fa0a7cdc82aa53bc43a540e8997ac72ad00055dd4c5f1783f8b33cfea4740e3ec4512b00c5cc4c3527aa89f92b358c797f11c7d294b21d94b1f7060e5fc318ee15e12eea1492c82cc541cdd469a4709d76442b85458ce0f89e92a0c9c422061c10ec6297aca9165f99e6560865503cb332b740fcc346cb0ed0d426ed393f089c2aa50f1f3c59a0f543889a1d7b966759c25b3b75084bd4584239096eb551e66d4c207e664ae946c63152e90a5d473f5ac7529842f3867c8f96a8dcbc6a6a4088470beb090a572230d765cebfde684d9ab5cde541a1f1695bd450f02d5928f12b9552987c282f83c12d2c686d1aa5266fd0e1324f97c2c43566f50ce0ca8c3178d5d8ec1f2fb93c0bc811aba529f28277d5440195749e24097c487bbd29598dd548de8eaeb9aae3cf5ae134cfc053227a8482dd458dde3d3aeb24e395ddfdbf25949a4f21128769b747f457a9bdc2a92fcd859e6130f515d0bcf7d568638651ed8eb9f4f2e550c67c05f0dab80bf963e48e9eb2259481031568ccf4f5a86c32f31eda920138bbce589bcd6fb9591ec1eaaa13773f1c5aa72a3ca462a953e453d3fa975ec926aa9e69dafa20481caf58e076142ce9bbdda21795d4c6af5b54c64e8be275628995d0e301a738a42eaf759da06dbf851e3917baf202bcb7a1cece07c26f4e095094330e74d722c988c0392ba0e974e8bad17146eca46a5c3c7ef374073d20abf434f1fd175179b3d215d285fc1b409f4e5b5546b533a96a43abc73232ab9ae4c768d0a94e350d8ad3bc049b74911cec4cfaf426d8f4a6b9593d6b3e1876bc6544acc2aae2ab486b04ac4be28e2ec90ad56abc14b32601b425d1235551c5abc89322cf105857891da5042b32e8ed1a84006515ad223f4552dc9d5a68f2ef58c8865982b785a0a391694502bad0180bcff3f5ed4e54912211a8a4eeabad881efd6d271622720452d4fdba4c4c47ecf4fc746a892745c0b07cdc515e4709ba2d173226a65124d2dc8f8d5c6d93c418468be4fb60e1d8a64cec678a4b56de075688ca042ee70cc4ac5187cd72e91aae0548f22d6a0af4fbc5a58ca001c9bb546ffc271d2a66cb3e80f328f04d77e47c9ad09550b28025132705751b4a1781358fd24c28588b38d9630ebd0e6569d76d699da2600bf81e4f25cf5e8a1d1b865b3abcbd724a859bccce97ec170241db9f128b97fdc7359f3cdebdebe2750b5b67d827d263016970aae77133b3505616d3af60ea72a97d04413f2536d231612eb7837c97aa3a62319e04ee8eb6d938dd347a283fc0f4341626a63c71c2d03ca6b05022d17ba16a7acf298365f07ba6d071da7f9ea1b267a7026026c84e553bb0c271fb3dfebbe13877e789ba81dd7211724c12f665ab005e97876b54162a23a2d3dee0d2f6fca6030a73da1aefae2e1da3a4d057ada5ec7c82eb322b061e240ada2aa42b92f167cb5bfb550af0b02aaeb832023bd921a52b8649922ac94c664d7f9483b813de1a4fda0fc0bf94d257d778bb10879eb38eba2d217ca42c4450c5b298b8fc7fc07b9a1f7fb157a5add4ab12da56757ee9242e67c404e29dce7326c7d59bda7a780961039fcd3e6bafdb44faffb2bca79d808d84c01a8ddd2992405c470ae98c2cd1cda0694d0e66eb8b8a2d05f08658f1f31a1822db29f861890620cd7671aeb115253a30ba6ca9fbb549b1ab43105a55d59c4aaf3a4e2c4e2d8b93c9ca2b79ce84060092eec4d461a9bb5d2eee4c87916817b050cb1af9c593560a55e3e899217cc3eee82cc4bf6a2f7e982a659adcca130c1a0fc303c57799a869168d069f819f4ef5bcad42a12068d0714411d191c6397deac0536d4ec7bf86529b36877bb41aea33d3e650fb38da7010bac97c6d794498141f4b587e02718497890a7b788b24b4f3d86a3989bfb419c85f5c8dbaac9d5c98975e33c4b8647ca89d47b093d083202af02279dbf713c2d82285e4c0edbfab24a4d145deb5015fbc7134ad0cf5a08b0687a5b434c9740d1936e4c00143071b3464e070c3060d1c6ad0b081430d1b3470b84143860e1b6a60c83ab4b9118a981231288f32fd58cdda9345bff7932365b44269bcf3d7407a7def657cd0b456772d70ec537a1010a964817f7f5e44b56aa1dbbfa0cde82e1731d73afebe2949a76693bd2645e377a5e841b04c83d448fe650157c048a1df6beee2628383d8c08b2e81bd734ab31551eb30058d581912ad09a4332ae4d14d4211fbe73b1d2699418f72818b123a00cd542f4e9852bcb831e00aa3051b27cbb4526b12beead53b793715cc781951b46d29d6d2cc1954582cb33b45bf735e2969dec600d2eb998eeaff590e7d32faf8d3c2d9a67358a48a70112b205ad62ca3716664c5fe21c6238a510ca651e0a915020667464cabd8093c54628843287e85cf68577a2e86e92f8d6d20f367ac2774ba047f9fe6efc0821e7f830d8ace153e36c627e9fcf3dd92785ce87466f094c80f169d7803673d51f7d2151ab2e3a8d398d6a622b97ae69aeb05ce48a7ec22b5916fc5db7149211df96e58d5c6f5f10d5a4c0faa1154004953814bc0162f9884215529bb838aba1b44546d96713f2495ddd222da26bda1ad896c970f9462c5080d57d46847fbd17281cf0f36fb1d251a05d0de33b4732424362510f1a4342ce6ff9c68dd314a65d44ac078ab87d10ad5607ff215c156eac01ab145b3f8063457b8841f91083cdcd46f2c023bd34540e859be1534b33d462f89d6b4315e86c224e290887630e9889d1dff65bf73d2cd6012edec237fdda5dd9a5fffbb750fc238973cb2c1edf262898e4017902fe0c2090c8cf9bb49ee22641e99433f351d1421d79fdd1b098fe295f17a5f8fb6e8f4aacd926935eb587f7c83c3595c71c94014086248128046fd7116e6d7dafd27e00336ad5706bcfa2533c5ac97f0b9123d08fd4e43910de6ca089834c6fcda72597b89054b0e5cb347abf6290e5f5860a19ea0a9c1a92168d18265cce1e997e7e13b9e00994f1bb975e468fbe702af435c4d3710354354abb46347362481b8c6b7b5ce292753bd5a359a5653df42602688b3ec99e4f5e72f24f1a0503dfcf5976947665989e812908cb6efc495feb1efaf19736ac9add1a2e19ddf8f36e9f8a0e5f7a73747fe878f29722c5f86094b8852050d2f1ba7ad25dcc9f74a958d7f89c6c6f88fbcb669830013dfcdfd895abe2f4ba9b5880a062cfbd6a94276eb2be64d81afb86e7136be0301aecc52502d57c97d68a1c7dbd76320ec41c80d3b241b77b59c417f190b31b6b72ce54b117f1828b825564252573f39a3f28c54b62e41ae1e652980f40a6af99f0531f6cbdab57c7e6bbf96fbeeeca596beb14d8f15731d84e257422458be2c2434c06e56fcb52e8b1e19804bb72119570afc29deac2ee074a4de71a37e1be7272e8243ab141e5e820249a5652319d00aa90ac99bd1ce15157589027cec452182650419ce5c2b99e01f6b604fcc09458a6f2a986c8d32ad69b3c45833e275826e5d389d2c250769a2bfa9cc1e4a8cad40826a5aa59020c6a9110d862eb78c670abf16eda5c971bda714a81f922b29fa8a24cb73a0be27fb12cc5442f8f516c9d3bcc91b73568f2e87ed7e5506b18c1800e6fe58eae1de022e564d9cbe25d3adfa6755a683a9219f20865014c35bae6bed2451a990cb7de2f2816ebc7477f6d2785677d552dc922aff84591631d36cf120ca245cc1ff11d81ed6d57d8ac487c78fba5096663c80ba7f7d5a3d8c6235e839b94e76c3e6062aa0d73a27bfd5df44e7f7d08d4ab587c193e74e720347af5a571fd10f024d364a48115413c25206283e5ef377855a20cf8ffa01aa0882a01853e019c5f663a3c5029dded948848398db60acf9f02fca6696788609ad96df0cee43e5c96f0a52f2b8314353240a7b09d400602b04d3f06e868155f357ee0f058b43e732632d20053d418ca2084b8433e2704e9201162be0f3517e2365e3084ca47b631ce0ef3e8e48f1da8370875c1355a3a20a798772ac908cda1a3ea5c2fa6e92ee67c9bbb616e1801ba5e9527ee396fd9fca12613f08679d42a2562e20d2843173b34d2b318e440639f6c3edfa052d01d148428430ea7fab8ca5218ae94995505364a0a00bbae083234e7954f5a64c634e4904bccbd6e449797659bdb49ffb184d076357252fff6154158deaf4ebc55de270543d1dd1257432404efc3b1aacb4bfb10c97d7406a83377658d28911c6c4f6f6af48fd87168ecc0616ed3a3cd1f1d42e9b9ad88ea7f88a8b0e489ac640b2c8977770b059174c733b4a9a316f3a576283ce6b398ddb1280032cb3b4a9d6362adcce6188b438cf079eee93fd0a460aef8ca7489d6f8d5874017a7e11fe9b250ad3044b1392b085d37468c4d418640781e57ad6a442611c3bfc28580ad6540a3676e4b4efbf7cef78b43a33ac5653bc638701f3f9215424c63ec800a5074f46840efdc0d5226a0920cb9eccef87426993b8818402379bf12ae1982d0cbcf45020e9a5a42a5c358e48c4ebc141991538201b73519510114729863c3554cb28719d9202564f2a2cee0ae17167c2d8ab0097294bce0fdb75f429367863c74af7da8e6607a7c03d36d2e561728f42143b345b969c655b906464cfee6c9c4dd9849dc51d05ca2c037b529796e2fcf7f5641f37a90651f11b84c3f930f3f80355d1b4e436c520fa85d19785b247c5615b268f51e593f504ffc21f40b1e8fa5dfa1086b267335a09cf42ae367ceddca88252a096085b5800e2b453d295d8905d4da9b52fc51456ae336df35a8e821df14eb32ab9bc6917a712c8ff809d235b4ec9b5d4a5a075728620ef57aa91e0f1a9dd2ef656fb6b6af300b7c3543ef24cd59d6b7d1478ee64d1a62f60740aa5c534f6caffdc0041ed2e71aae5a32e4dace2c87e3128114549912bf9ac4a1d775643cdd2d8509882a304796966aa78958ba106235e62c4715fd6eb6706301d5495b7eb2315b4144723c79982637098a77c96da21602b100c1b99b3fb804f07224beab1847fd36f32b30e9f8b08f3d9d80549fe2218dda3000e2d0c21e0cfab100b33bc01befa6d989a43382608d89c6e0069b61647eb985ef738848e5ad928bf750cf7016d06fdcd6aaa2816f3c5a29fd519ab31e8640e673c9982070b4957e33c7db1376334db16e1fd08b3396afda5e337c8c60a9634b38c58de06451b997fbbb6b93c6864b42869578abf27ba01f27a274515dea6f59dbbe441cfaffe91f2ed41c64de69a9cb46364a0fe58e033919376445fa500640999ace6e3d2bd0f290aec7cc949ba7b881a0a15e44c080f77463c226b8652e49ebeb9654a062ab05ad2e7577a7cc259bacef5ca5439730415bab336f4853558b293b3fbc8bd7c43b7109f20af3abaebd2d0da45779972e397ba5b0ca9bfec7ebee0b0fb0c5e438033695e4f690029c24d041f9101a3c3dcb5feb02b9ecc015c4b13ad71dae63b5614bf69ee61b2950ef1dbe0f20ba6ce7588880a0339d38fcc3d449e319249ccc7de2f4df4d0a3831817a28ad6526a9bebdc44e6e8bebef8661fc22484e51d91bb2385ac528c192ce356c43774b44acace77e6f62564ff3b15b9150b9cde87c36c00ceb45805ccfda320b72668ab4597088efdc87bb7baf4aa71947cab0846bee4614f749e1f6c220e5b6a68a7c9747b5451af74f7ce8838f025023c48cd13697e65122fbe04bed5473e06007904745ba317ec05e64cba5086284ac3faf458ac89ae41fd9482f636821b4f3082b053eba39e0a6d216acaececd6bfff041dd5826d16ee3919b9a1fb026b51b7cb25f6e234449c013da48c34b48ac55911d10c1a3b9a7c0b581252ea99629a7ff9e4cc60be2f80871c063da2b57a12631e5cb40e943aff0da999f489205fc780e60c8f9724bd21ec1a22aec7df6b508c01087af63c870496302bbaed39a1aa7a09451a168c0bcaf557e37671849afa1a36cc6589490f03300d8f0c7b185033be874193edfc193cbdc95107bb7d6d9b5ba7a4ccea7aeaa17a295551efecb4e32f7a30cee0b9dc1a3b2dfa68b7c685dfaad4b2f078a377c0faf55c553895b4c57a136f55321f414f974fead2560b2c312baa116baaa61484a4856483ce23bdcf2a91eab801dece8d4330f55e17b5d43ab85863809b841817188c24f6527c6218c0ac7d548b34c9337a7671f7f796d1f57ebf0b1d0fada7f19b4c5c59507c8dfbf83ca65eea33224e40630da157b42dd1b565c3f962489f276a5a41c46f931c8ceb984098d46457425a68c5cd05219d4a78088ea6fefcfb6defab221a89b3a8f6a48918d5aad851f9846cb9eb7b1d31f4159cb7bb13511e1173d503adc6a8e13f02c2aaf72015c6c864398db807c928eda12f9de887b75a58a543ee438cb6448ab7bfcf75534219ab6a6140c937e4142aa6ec0000e27ce8eba6ec38fefec92fa7dfbb7b8d0b43939e9e7bc51eed9e06496e1546c5bc7527946884b42468132e2e90fcf614faec5200168d0c6ddd75714389963b150174d19f6caf2e67bda707530ea86c9b10e39ed9e908b68e7c1b84465a1f82e65fb106be0a6b4898a83ad93a11510c6c73b0708c319281a9b0afbd16f47841883698494a79df141c9f2e81af1e5b42f2f37b5d5c1053812bac735d7598b486294aeee1cbf11d38a2dda30f37f8c0706db52ff71e02c9f3ce603164fbd8d6f0076b34a88df94b2ffc1e4244509b69ab6cec3ed8bdc8367c0171bcddc204dcedb7ad510c7a346abe9a3e7b5fb7b73c5a34ce79c01997de386e8aa4ff521cbee15f234c4ef6acb659f0cd555eeb5d23840347aeabcdeaea36c8c0cdc705e5266e033040a1ed571d59b15815db244e20f10f3fd1db137955d32d2d238c6c1b863a28c75c7035db2720a7ee5dcc8eb07797593b3cc50144888456f7f6f6989fd06a63e6acc0d04312c7dae1c2fe62bb62b12ba9dab2b09bdece0903ecbd2e9e23983b130a5cafdfe332488bf212233daf270e0141a966922341cc87e82af4e592312cfff432ff79f17c7ef0ebdee1fe313035e9ba76f745c7093a1ef27d3f461974ae886be551f35324e70cf35ed7ca6019bd6e3a576071afbe17a3b2faaf35cc7eccc734a144cfddba1cebfe3c035184a83b0bfa13ecca3a5aac3c959d04de09b5543d7b44ecc3da164bf44c7e3369040dcc42d7e9e0735f21fabf0e37b782c75ab92b4e360f938c79f4e750bef4a697a892cd81a01765d6e8251ff03f7c488e9c25ade6ea1ea2f730ab05188a2719d0ec1e6d8338d1367350f0ff8609acc6f8fe3e672c28a823a03ec69d834942970e26dc21c8b3053787842990b038330337fa5e22ce7c596064cd3454cdc420e7e0a15b0cc2ce4524f6f94cc47a936b398947b05743f89f6baa8f4d3086aad595f48afb8f3bcbff3168b4345229e04834e308ab7d3468e60ddf6b396e7e1e68c04b4c588d2fac9d1db30772e01a02b383c3d1aca03a112f3f13c4726d895590b009d79d61ececc403cc38bd7b488a005d27b1dadea5bd1d2411039f64895534a26e13471fd4ff5fa9c3d5c817c3f304ac19a2d4f4dd58ec5bfc299db42922955d19f1bc8abbc51dd67f151bf7f37a16a59b9279f88ca18fe80bcfbf05a31abced37f3d0a80f2bca996b21f240ebf8e08f883189ca65342360ff115e20f06ff16b1049453fdfd640b6eecfba9052789ab9a500c7121cf708b77342e2a740a6458fecc34b1dc144f0a7b310c15d37348ce35dd7fadd79c976c8b675c3a88eccbe11a7a9368f15399077a81b942b481b253b7324d6db6c4057242afb5f0c37cf2f899d392a20f13bd54d4056c47df29d0559f7e4eae533a6a35a787192db66712c2d3baccb04355a6daa421988692a878d55c363e31bdbd014fa7f61a6388ff7c394366f2c19dc403e3d7d7c9169ddc36c5d6dcc4f62cb1249fd184977aa4bfc756de8248b077a39ba41dd51643b9e60f919444dbcbad87beffca7df2782fe77b56163c38305a676d9fe317724c414506e5404db15dc1381473e7ad11f8cce6819371985c72e5c6f49282007e869e3b1e5356eebab892e85782c5654c6c2fb5cd8ddb68280944d747addbd1155af1fd953e354f9314a955e7fd5cf324681d39a5b963e6fd2cd17499bc71228680ad074f08d989e63937f387b08d3048b206917159ca71d68a63ff92fc8e2d478aa65841ce48b2471b30999f2bcfba624f6ed33a2ef18550276e36323b72427c52c6cf2373a4d358e43720009d28867c2e9d7b7032a2187d053637e9dab200f0954d3458ca2425bd59cd2969015e2e22a82f0759953ce6e118c4969a2b5c83b35ecb20d92c33ffa9b394d2f425fdf669d90d13c320ed6e712e4ab670d933cc26a91dfc2709b4eb2f8e976703453a0af924240fc526277b0317365fe3d1ff568a1d58ec013c99f87e5d5ea7e26428320354723674f27e362f96c133904a89761a8c19ad5adb9b35fb10637aa5cf126eb4298bbd28ef9c7cf05d245141e14e01e9e4d05dcbb54d94c127bafbe375f96eeae7b330f9f3d45b1fc140f8b4a5dbdb1a3c6e1da5f61d1fd4f409f78f70b5361e45732e5eb2dc80726de91970be39a4c9d0a2fbf2e21b23e51c550b4ae79844ae66766c1c402ab4aa0c18246dd1dc3de0efa1be25bc66504d32dff9f01d221dfbb2f03ba3f8daccd75e42b48f92228882d63cb3fb2ddeadb3337e462b93e183d9070a1593a7c68cccb8d530b4bb1a78b9a34c40c543da2c8f35c86d1bf5f3eab0b2e9a974d310c775f6efef1f0cba103f56ffa3f8be572835a2c72fea07af1e17add8cf9e8ec52b2eb06dffc0553cac8aa65ff61fbcd06c1aa7e47443565c784125c90754f3dd1cf18b94cdd6e622ac95036d54c590133c6704c482a6895dbce5b3089a6d81e02eb1ad1a987c44c5e08e8e31cd80f80e50334f04844c1da98d8b753b6285737c54da38a1fd1278ca44d8383e62ec480501bc15306ddc9a86ef33e4fa047c5fc83034ee5a1567dd40c653397da852db80e9734df0c912784dfe516f7ad43082b84e8d653b63d35e26b01a7190e7354014fbade4935adb24ff22b487fb394cdab9f9e82d642c448ec6865d93b4c7f8462d320f04b1a72cad011029d26d7d89126582878ee6c1577de348f772b94edf9afeeb282d091c8c5dce01ba1e7c26d0b04d90ece231f78a62da17d763cb9fad152468dcc8a5ca8057cfd6ecf08e68b5b142d059bcfb2ca9347c889d0c2a20f08cccd39aeb516d79731a4f628a2284bca93875581c0d49bd5e3a7e205341ccb8679ffdac9e3474132b62d351097e5c6ab1bf6d6f4212238d4798e166aadbb6c0391f10ddd7a228eb511adf26a2567fc6202f38065459a608bce65789f01d2a14dd3d66c14d7ef4af540a1d776aabb6a864269969b6059bfcc715748da418e955d0f975e707367d79c26886391572d3ad9c6c6269d4fd72f48bfeb2cb4d0b43d8c7a4149c4931128e4e658bd4e49553f95db6e12aff278f8d4fa7adeaced202ae1cab2961f0b6605084cd816dda4ee5771c6d6957a0ed7aeacc0a95b90f95d055bcb24c0d71dd693f661ce3492fc9c73c615a67b535f57b6d505ed8dc0e8ece8ded3236800e2553b162dad06de90db3a78c37d182232f9968676868f8a4c8d4ce3c5ae628a2c86a797feaffc3f2af0fce7bf7e2980b2ad45a83ff899f713e83d6e2d5630560103fd75eab67964a65b038b1b5a74fd6952fc34065bb133dc75f82231d4810f4e91a8e2eb7b081b48effde17dc79de67b71febef134961ffb25a88614f3ae19282c20b83f9ff224cb4dfdf9f95e2a2e6ca5eeb7c6ca2a0b64fc74684af442bcf7d6874ce8d51e3f4612f9b91abfa0aae092f8bef03b0bce633f22fe90f882ba8dabc124129f3cc4fdb11a1b5c46cb88535bf1c7b991e1f596cc188d8f0219e1770f66556e3c48a4e6f8c12f3a07fe1f02a441b240261a7292f0787208c000a1b52b4a9aa85de9ac19019bb11be9e805ebdf6b967a5ef7cc20a4098711f13723ef251237f2cc6c9746d1c6241e425076a3586568e1895f94dc2961fcd88ae4908d263210073d36ba8b5f1a09e0b48b6db1565c0371068ecd40f650db9952694d301a08f1459e5c6fee5cf8eaccdb6167939f8a580939293cf2da5707ea73e572f244b1d88f6e3a31caaa9c96ac9098102432eb2e7d46a780c629c4a08cdf770f88d7390beb803d6cf033b0da60e32a6a33b87cbc88d913ec338237caf291c128c8bfb8fe18d7168d84294ebd755ff5406916ee038d8b292724b307a21dbe7f706ff6540a072d4e248e0cc123c96b9add46fa43295f359869cb7e73d3d2c5c679ee5ddba03259e2d609a3a212fe67c0feb2103f38be9d89ee7c99693081b29366056a132d63fa515829af32e196fd87b44988764c385968093d0813132235e1e5f8aa86713a79542427764e3540f31d9bacd8bb8b76764585a7bf1ca8971ac16825bca58316130e6f9fd5f25c09064fc30b09e1c9c2b25fbec4c0a57cfedf96c787dc9e0ea52527fd6c0af59c08cdd7d907cfcd55eb67876f51af37334d1b224f2242fadc87cdeed24513e5cd15734063b5440d85a7ad23d95d796373b5d81b00aa3bc021fd3871b906880db6828bde9dd5dd92711e94f0a427010be8d4ee56029454efef13f10667ea918a102585e4ffd80cc338151574e7f2081c33efa3cd185a0b0f97006ad0db631095694988dd621ca0038a9a8c9de47ccbee50d7997ecd50104732e8b63079a2cdfab5031ebef3ccc3914d7011f0a0594cd3d605f30af7a7b17232bb765d3244edb89b128679f4e34458814d38348e2a20fecaedec04d6618788ad74026252015dadc450a205ebcd908a682b1f9dc9cab64ee33aa11cc0ffa8137a351be2dd01989a46b3ae2ef197e8cbaf3bb7de29d700bc28b817da80d55299afff36d909c9e93ddd41b10f2e543bbca872f8624d1c9a3737119b09199575f5413732e8db876346c1c5e6705deec4f4671a4816435588a3d5b54a947327c29425d099a88ac456836ca73f1c3f8fe58cc74493805f0ec531f6610d8c5d86d8b7cb14ca40880ace9779186ac533fd4ce3bab963c079c63c9cd338ec71f1e160676f98af699b52fa690639b7ceea417a192346d602029234eaa6784cbb7fbe9b6a0baaff6bcf3e2369e107a7e0a515f01caa0117867a4c1db38db9e4373918e1e5dbec4c38eab67ca8d1f7945e9d60c5b67caa2ac5045bff230442db1a9c81867ba219b5853c545556a9ab6a01c53cb7804ce54968aab6ac6483879d46513cfda1302c647f79126cbcb85d000252cf65dd4e0a915f7962e8a910bd1b4b880bfba697e3f792dee898120a5f6775e9fe581059f7ca6289031f2d62d4d89fa6b817c62e43ee11f4354834ae68d5dbb7e665851e0aad8712dbea68453da6bacf47ff38e56be3c74cd6a6753ae78d75207bf72d50856a44e356614034c8b7d587da0d7e82d34dbb39b927a782593fb7f6ae93b30fc6f59ac31a69498cab5fbe9c5d04a4febf95c6ba0675c852ef2ab6e2b5051da2caeaaa70cecaab4a9c62f1b81b9f7ebd354f02c690053baccb15d92fd3636375eea5ab4e99b013766834270b5effedc84b220639caf9f513e9a3ab8dd640d1f9e93db2ba44e02f33e7cd93b43bf9efeaf647148b6fe9973276f6e249501502769c4cca0025f6496b19dcf4fb0b0ba57742cf0f01630bc436c3c2236d01deab9ab29f834962ead8d7a4a19ee28b1208770c892b0878e753f8084fd125ba7e1672d936eb7a29ac7a6cf3e1c82836777cdd74cb915ac0fd125a143b7bef75fa2cf35474951c1ee9115a10d564c8752befd19421e138315003afaef9a92381aa3f73bfec854516fd311ee25fbc20f3b448c0562c16f9cd291534713ded166a0b688d83f11c19be1ea558b828fa511ca5dc9f59c5f2d025cae5afdfb99ad5ef54fb3c56bad6fae04c7dccd09a7f2834e3b7745ee6675c9ddf2b4a2b05d4cc6801ef000f6882c0ede2676a3cc036c3fad94c53419de784a5ca3e1dc11ddb7628bbf3dc24491f2f26d22bc262f4e5c4b008e4f28dc985c5299abc0e2f6ca2d0b0c1dbb1a4ba9c7680e6fd540a43c1ab20b94d3687e32d317f10a372be9950146e5fde19bdcd16bb31155d428264859e55efee77edbf59fa87a52e63bc7e738af4f167e22b7d3ddd25180cf579249166019326c6bd3cb5485c1ada50d0e0e4b381356c45fcf8e18eda90971d4e79051d6cb79b2a7175aa9eda1283db7349d3027720614558f0ae72db9cacecf1ba3901da224a166b8c6f984bcef8c8312adab7f35cb5f329429d1e38bedd4735203b9a82473394596875bb75d0df7b0fee2309f72793d46fab4627b7b0535e7fead3b37cf71fa81321a4f3d6bd74f7052b5cfe14af5956a80b3af23f1b5203dc8eec32b85f8750eedf01fb4ded9c6fb2c6b5bc8b8ceb5f49823c40d92b1830f61d9bc1f473325d8374b6b270da10419dcd10861847253a5a38b9199976788b8e06d1b8b585914ef37e334ddbea05e2b77334dca464a280af1ae31a3a55293ad7cb5dd90a1b02851817c32f2a595d5af0dd68d277cb82d0927901e53e196297a6b9c037efa9c7986d1aafcb166525fb94aec0cd3e80b9d5034d873482d64e78ffcc84b53e02a809231b168746067d862c99f41ef2518de479b442a21814bff60d9d49d56bea6cef6ce95704496f7689d29e56e61ab7c38f1ea020577d7c95cfcb2f3206b05839dc194b53a9f6227d4608835a27171cae57b0f84869ba5351e53b0940ea5de66308a12c4e3d949e2b390f1a664ceba555f0630d6d4cea585d6d7e294359287985d0db717391ad76e3df68fa09f71e0b54f1643ebead915956c160c22649592d0675927cb0b3b975ffa86a709081df45d16c274d47ebb6309a41e5b0a880a14d72cb688e534cc8a0f42db33bcf83b7be99df28cca1bf077921f601a184b42a52a37aad4c8979c2f3293c463a1f32b4c8da71d6b064dfd71f4659cf7472bbf5c4579625674ed51218028f9aea2abc2ed7a5ae9cbd33139ea4b8912ff640cce7b3f59c276df3c88d7d8be4d59c3ff38ce8ce7fa31c74647988bf276cc4ad465f0f2828bc89663f2e7286ce34c2974d30eb9751c9233374e92ae76a2014dcb1eab293b8c8566554bd9ef812feb126c7241749f194fad7b6b3a0dd75df534c89ab51d1ce2a81aedce2c82671d48f16c2e204abb10840da053bdf2e0cd069851166d42188829f9205b7ddc109d179e4fc463ad5da7a1087044aba25fdc73715f41a12c13039ff7712320cb93fd460255cd1418851c5110abeb2e56c38c9adc0f9d570a656c2d3f403260b696264599a19ec234ff5eed3249f89b485cdbb9cde35842c4c0d365f2559f6a62e13fabd8943ad4bfd062f73761cee515770cc238f3a83012bf9405b26abca1fc22015891ae141662de2a6d334b9e3e06c28442ceafd13f2eb7b598ee06eedfeb2593e154ae00e0eb8ad10a3f1c6531668f0f7fbcd1d7ae0812e31eaf0198976c2667c6cb7f8096c29da99a718df07b37e86dab16af4baa39b58c8e3c20dd54ee8a4edc9371ece6a3b81be366d547c39102aa81fc9481190c3a72e1ef1fe000cdd0d9c573c49eb8bd863f65afa6174a0ff57935b3d4ac50425418af48e3ec1c2df998df9f3b421676cf63554840e301125cf42fd0b22a769dfb7868b739537723b8f86ce8ba3d531554a78c869d21db89e5d5e922d6bc418400db931cf57e0038fa738f443c68d0618947bf74a3445e0f8d476d9db2c2d3ad58368648a725139b861c6a17f1c52b0fe2dbb36c0f134e4dd4885e7f7b70f5048fa93fcdf99d38f4c4cd67aefd1729d59d9b1c35df793bcf29e176105331bd8f48fd16eeba6089969ce7163f66090d0e6883a68aac1c09dc70f907232e694b6d8971e22299df6355eee76357228da3525f0a8125d8047c1c6dcc1882e4bac948b9ed041f35dc706809ec6b1d4fe8723107580ee39846af61d0743bad6d0013f79456e8a0456880b4faa99dc96f1160ed80c8983944c1c85e609d20e585cff84afecfa6c348673afb7c1c1e230d05db8d0ea9939bb66d8f590ee2bd581592a564b3258a1d0edbfd7ef2bdf5fefdd89ec6ed16d9ff1af160f9b0cf48c5b61d5fb6e88b1b6264d67c2e58ed38139a5896e0e43d29ddf436d77f51782b68cfbafcbfa941d09ca15909171a3d18aeb0b437515e8509fa50200e010f604dff297da946042c5a11ba7e28ee101dd3d8cbf80f3578e381434806075e457f836d346b24c6f0f929e90264d213e5f435b3bc242ea4667093f97f0e1654481b8c3f5d2e7412168968426ebe4ab9cc3d5c6ed09c5122a3f541c6f42222617a5a238fd1e565b1f9eaf5424e6586e2a887b37767e2862215bd852486d4c796784381417052e84c09a9bb9a8d48787663869e15017beb470a80a474a3854852b255caac291123e75e1490b87ba70a5844755f852c2a36e029cf80d4b4323f0eb6b4f386a85c24207100e8c581afc692f3445b9d1d475fb96ccf46543c700107f7365bb3e1782fd17fa15229412414029ee3274b45231792151a099d96fcc0fab001a13e0c9f4cf03ce189d43d26c111a00cc68a9fd26575c569533fa4c6f95af3b6d7a81146655f40e341f7aa51f9174652747f30294bec5b22955dc8be4e71848eb6963642d8b3e0d49eab6cd1e7e47df7cd43658cee935ee12253e9b3d08dd11455a62ee2b8379320ce5b1c9a931d70dde220974ea771efa8f26703bce46b01569b0fef5854593d5444dd4c4fc45ca2b4c83dd3d6b31b7990aeecf3b8ea28eda5bbeeba4726d5381540f3b96f1d7ba71bb840f64d7d9bafe71020a232b5d06e45002d77d2e80b384c5868d23618a6b2b6624f829322e874c87eee564bd41d198ae5d93f18b8a9d892446e739926ee067096c9fbe566f5227993e0d61389181f12a936014ad9b42236a7721cc8371b585700eea18d16cf0b6149b7a2e922926882ccacf2a2afa096563715d3cfca31d9aff49b8cabb72c4a1cf0efa820cd4bc19f85c782c5fb6e45f9cf1bafd9f3366427160822394487b70a2397c82987e2fddb4f2ae6fbd27a991d4e35c03f13c5a52bd78181c1bec843741ed14a16b3887f2fe541cd59da844915bfe5c2355750c7095e2a4e964542fa7fc94f59d581652fc8a7f264e2444c3a21600ce6b3cd1f122d3491f1374ef1a415f7a237b992fcacbdf8d6611fbb36011fcc105cac619e1c07396525b4befa466477de3fe4bed8fe68b40389b312c0775fbf8252a9dd5f53d9e726d5b2466d3800e5cbca2d1e7582a6c9c66d7eccd643207b17285c41f3955ec1b1da52ff2809a582c3385d01c6f9ee4a4e844f223dab60bd1e03d06a82105775f467c137a9f11cc0848fe91c90f78028fb9ddc16584229c75f5e5ebbe75edacfb1a9fce2340d0dc58e184cca6a15a9f89bbdd869c6679ca9a7a877eab8b09cca99a9d142f143700eca6243a776e34217437eb3450c810cdd6658ddae97fa5efaec3f5a0c30a615dbc62586daa1809529993a344ec788d934bca7cc132d5413b4b8273815ef374ce34aa670c71093ae7e099e19861671a98e23ca1da097acb526e9dc53b2ce738798697a167689614648da8dd847bd00ab37bcab6cd1544a6e2e1e9a414d77d4186e79705c71b2a4953f07349cf840af366c9cee48a429fba8f25b827ef23a4264a8f980df1a8ffec828535cd9ccc57a31554899d65332e16744ae25ecc18ec2fea1192281acf8f9b2e74a6674281e6193a2ef1117187f9478a33e1ebd22737d1bc6cb1f738e2b6da2d4438f3a7c5c8e67e4c6b1ec900d6f970165118e6c200628fe2f87f57e7c2f97d3681c1cf1e5b6ed75968ce3ad9edec0153dbf903b917c6f3dd1aeff81c77fefc04e3ab8ecc4863fdc82fa2cb82b5688290d81326ef716bced01d318a4f83498dcac0063561ddb963e4d0fe9394e69c8e283212c2b55251fb0609d10848e528562c9b87aaa2b71700db64bd09c54b1e4c6afc560cc3dcef764724bd0cd294bc5d5fce31926330900994000b7bb10c9c5819b4e414f233687b4d2dec4c0197872e99e9a420a5030fb5abbca72d368c476b0ab8c7cc3a55d54f2b333ee56d90780f6415227b0860b760c9ce60c61daefe6461b7b70b74f10add57c5183868f4b60f4b2f31bfb31cfdcff64cb0672a651fac98b0bbbd369b6a309289a3d354968dbb76a3a35bbe9ddfae82a21fd12db76e24a16ad24aeabb24f163caf5f4eddda9002e24ec3f505a18dc474a22a66028998c257715697ab78c97aa5cad70beaa715b271daa7fdb6fb7b4aa8363dd0ad7549f9456162dda5ac8f96aed8f925f2c9a8c4a27c10ef7ddc0c58397783f45575bb664e5f4dbfc7690a42d596764e985e4d580c0326ba7d1b5573c9069bbaca4611da989f96660ba00085522fdfe597c8a3ed124b65f1f535edabb99834e13547a25531ecfd08591847bf5e69519617b3f8ec344bf5fc13508984864ab95e634d81712a00cfdfdb41a27684440592f3b7b6841ce47047be9a0441564aabfff07811a6b281511b5d13713ee2019df77a3cf7473a9141077f0c9e110833253909e76dc5094b522c384a767eac9d2090fd6d48248078532efa0b01c49a6757385c0b0981de33e304927c34f5da2aeab3c78ac6b6ca59f950ff1aa068da4d1b17e37030ab2c9414fa2a4d8313bd89016734b489b48f0465fab75f4295e613f072d58d364a34d0610a1622ee25e40e85161168de6112a61f4d8d5c69d11c1a06d9ef21a54805e0df03603b38cd450dfbee60cdb3f1a5cbf4d4ee6c1548a92a3cb2e0b364292dde6051bebcec7b50ea7a339e21fa344ff54b125959062501b3a0caa534c2b35edea28e7e3a836c016fcd75d3927971d1bdb540d334e01332b9d8e0ba57ef9108aaf8d9caf196f2ec31349b50159069a15de9f165fa74237c8a68798913966c2bf35a1f8c8b951391d8846f56ec66bd087fd0d47d8aea174ed55c689accf593c0e19d6a57ea60e6723a74cc4af3f92b4c51590a8447a05c271149db0c20e22fd9598611d70fef8337821bd1bae8e184eb139936ad4fe295bfef5434bb9c1d77793270d67e295dd376aa6265b834e5eb7389bb4fcb7e3ad2b73c3c0d467a3ac86cf3fc3d7d0b0ff829d1f916c666bb6525c9ab2479c03940a8d4c5d11fe35008a40493740050fa9c1e103f59a1d1876c714576b0d9e325895abd6c16fba1c3448013f1d8144c5b730c42ade7ca7ad16210688f22d88bbf8ef4f40fb926cc7457a795cb7eb7109300550f7bea94c0354427fb4669b03aa925803bc92d4f54687ac01403d5e49dc05de362057048babe1d30ea014255ea0f0e738432d805933192d17b59dc1466b1ef1aea14b8dee76e32503a923a2fd07d6e0c3fc5c5ce1e657b76cc17ac2a0651b93f7b70bc5470cc5ab350b5f562900ff0074209d0e28351c042d81e87214cb5d1c0394d1034aae72895c3052b29a0ea5f16852a53ac96d48aaf458324b0b1aa515d5e8d1644661a93f04bfad4661c4394587d40632804c7db7331e020000c41de60883e543ee7412d4be2f7d6abe68e1d38f3fc8eb898e0d953631696def6e5bee2da54c32052f086908ca081d8c0a46a55a7974b6a66cb2a1c7c55bfeab998c3773d17321333c227f094999199aa1d2746688a6e0835b7f9c11a293869ec7853c31fbd122343bb8334213d64e67846684fc353f945d1a3e71e9cf34eba6891e86bac6b19170a6f9eb0af0ef4ad38e64b8e05ddd1b012c82b081952a9cf0f112d392b2f617d45ef457e719d8306e90e2879ea32f68082336af6469c20626bee4f14aa5147539b55a467fc73cf1e1e606d88dcb87036c1d8e3f16c9f26db86d1b4f9645793a113367c22412d7a43fedcfef518f038677b63cfc1de6accbd3e9743adda7dac693378dce39e54febb389ce46082ab83308ea3bdb7efa3f61a9bf4f71c0d9a228fcf3c33da5920374ae587beb96e338cfc39ee78df2ede93b9edc85520593a394cafaeda207baaef139e50136ffd3cb203cd9bf1d3ab2299fbe7b9e2cc3f1c722d925b53cd9432535dbfa320c47253ea95333e82cd7ef388dd2a159346da556638910c67045f88e45c41521ac12f314c73cfd98ef2f084f8ef998a7e1f44e1ef557fb4b7a545c3cc9a7396ee910b54f433ae462b56f3195dccb900e512a9e8286dd8f09ef50c574686ca61d17ebc3e0e2fca0229da3334c762be66b7357627e5ce5981ffdc84527dab88ff91ac27b0ac0af6e91b84c3814994bae32b43a403ec7e098af7131e6650ff994c76b5c5b5ce322d7929ba86e1c31d98942b7e262fd11c6b827a69f18dc75be7bcb89aa30d74526b986f021fc30f1d77c1898ee0541a6ea8d7a7905f5d25f2719f3527e07c213912b685fbf019d837e7d07f4ebf455841f2dec7fbc3954dff2557f0a678bf2b5d086f23f7c11421913d63f85de10b7b3e534db594f7e3deccbe7a89c8886864627724ff22b7ee45abc737415e2623542451bca7568dbf2a1529b081bca65ccfb15191627a24642f824326f3288bf6c96258e320a8a53a15c69d06cd5efa0118eb8346cfc7ca7434314f65feb53246ac55f1f965c51f54aae5aead2213a44a9f4f0231e7df04727b24ff1128b83b818026e1743f88d09e66870b9f8abcb8489f0633351fa33612e74aaff21edf9e8c7437b8e06d7881017eb2b913988110ac5c5a009732194d775524dfb2517eb8b104e2fdeaa4339e6c766722c2ef32b2ed677a11ac2098f36e74c0dcf2f4f5cf9344fe367de893a87a4d962b3cc95f96e1891cf7110c21d196a9f1359228a6170b17e8db7e148c8972da05efe24f2d7f7f5dd8a17758e06827ab944668a47efb97c6b84ab708a083f13d20865c224d28b0989926c442a0cbed5fa983011707b8b7ea55dabcb86dff0a1db280c9daf7da7751c0e6ed17d19caa2f9d45fac8979ccbf7f310f9e3c8bfcb55d915a340ce4e2c3c85a19417832ddb64f851d526beffc8eba8864c2643ee6bdeef432a1ecd6fc55b8d556c1f810f817548514e62668ca84a6cbc5ed4fa71accf942d775322be5f9f7bb6d3b55cac5e8886aa15b2897941116190a25efc54b649640fe1ab797402e6aaf6d7894f9be7ced99d892e7abbeb3b6f354a16b6aff20a8ed18218cebfde83f4ee5e62e132683fc67c2625e7b6f22c55fdb7dd56b4059fbe94259d33e26dce42ce8843f1c94c230780473d7b309fb92b7f7e9c2a43661cadadf2f084fbeaf62bab85d4c1da9c92c1d99617e09cdf3755279aabe880a2f9119068f92c7c546247ec145ed95c8acfdc75971b71f21e976288f66cdbe869ab4f69cdbc3f8f86bfbe6c40ff0cf869e85417d675942298fb2887ed1161916d9246baf21f1b44e6be2afd4172691a0a7755a91d7a42e8a9f87d082eebbbe7de2de47e7f0dce5ee61f0d7fcee7f74df7dd775636b91bb2fb693e11257722be5cea20452eee467bdc850baa84bad35cd5a4c975cec3c17bb6f0bd35994b892479caf38feeac01c1f1d9841f99d676fd364f92c22ba771f2e76b65bf2b5203cf983f84bc3f47df8cb7ed7fdaa6b3a0d89ae25703b4b1256b28ddc59920872dab00b936d38101172c77e84a24b5d941c0753a386ece10454be00033593f96bfbf974896271a4a3242db32d79725db24f2ce38ca6b5f782366e6d9d4960c9f3df4952d6bc2d3f5a9926b9c87dd93826cd880423eec819811c53c51c1314d989cb0f15a4ce4bd0156fe63374d4391cd5a473f80849dcf9a8a2d9f2b7f4cbf50199e426b9dfc8c8c8a8685a7b2f088ea8a22d4ebd50254ab9647feeebf139a9bec5b5bef69d731cf3f0dc54964750725efcd59c1109439763f2d7882ae2e2af11ece2af9133cafe1abd2df228cffcd5a08e223e434772a949916fe991623495803a8795cee1aece31abb5f782e0ff28725f5cf44702ef19297065f4097804a54b937cb20a325cba04724cb3e5ff43a0d00d02adbd1704ff93b27f47e337ab3c821af8155f8fbbe2b296d0bcbd4e2a6f98a905fa1b5e32ab91d996633a49a14bfef24959409c037962d48c86930a257f4b927ec9f2a91787cda2252879164dd887c55bfef4cb94f94c232679b4481329d72679b44833492927a1934bb648935a248b346176ce26ac63e08b364ffcc946f0e2dab0ade589db628c5258c7a40879ce74deba357fb638e777a8820c52d2d9dfd28682ae6b58dacfe789d1d05f7e95a93dcbdaadd65a6badb5d64debfa3d6466b9e04f7f883fc53b55b43fe11dfafe1a1e229fc33bf3e5fb966ecab26cead559fe36bc8683ee345cc6555ec47dc75fabf7f692dd3dffbcb1340dcd3b0831ef337ab364e111741b78b435f078f10882781465f0e852e1114726e5f5998747b0c3e38b9c9442ddd3cfc2a3edb781c7db5f038f603f88c7ef9fc1a3d84fa3a75fa627cb5f853b2ef6c7842e9bb309d3d1dd7fb12ccb0f8fe0f8a378c2a3ab9fc3234e082108218d9cb5eca6339ab03f97b1dc93d2d16e1cf4850994e9bf30619c73d1b0493d8c32a60c4c94e21f7b890bb79fd99a9a509e3f81ea17c159dc712bcad303ad127e14b34c5842d8b3d50a7bb6c4701576e388e15eb525730f9394b9bf5a32f729a4cc3d2ae4fe33ca9c2c737f83044ee47e04ee6bb80700f798fb30ecc9fd3365ee45087bb642087b723421f7ac1a2118f66ccd843d5b343c9f308747fbf6a70c0f8c6b5020e5fa36d5eb9c3fb355dfda10057714732dba5fbdaf47708679efebcb21493461c22244c207b8584f5f4fa8aebf16dac52640f207887b12bffa9e475dfc70bbd8758c88bfbea7f1323fda55ccab7efccc61ea2defabe7791eb636d9c3dacb1b26a1e1e821d52ae4bb3a20b05ca13e98690d4cc32cd730fb2ed249808e8d9c8e8e8d5d9edf5d808ec9157cebd68b9dc31b871c696ef58bca19abfd6c6aca1b1e2b6e79c2284fd87ec32da911d714996c45536373a3434777e2eca655776985342a325b17e6479cb961ee53a954cc4e4c77614296e32130af7a7fd57d19bcb3fafb3b310ff32fb837ddf178c495353b018b8158a7ff989075c24986c4fcea9324a063497454bfc23b3b313c3124d62ebd0ceed8eae7b384ac9e75c209e8d8ca355b138fdbd3c913ebe93ae2ca6e3f6df3979e8a1599c00b8eadbdb7730b850f7f5d98efef98d5fc2f325bfe9fb4d2a54229259443928a8b3d61a7d3cbd0e6d3490834e3ca221496444caeccfb87471b33ebf008cae018acc2b3392b65b50997cb86b39b56dde511b343ea62e3e81922ae4f952432ebcc1ff4e4daefb8438bc79adc3c342037cf57628fd4e4f97476937f1a1abbc4c4616013704201a528390c07c7cb977e3f721891d99a6fb55caf4495c89b38944a4425c9add4599e0dc36ccdb75ebee496cd25c3a4b50293b6c985922405943e471c2699883cc0b77f24d0e8bacc83666bca26a5d4b3d69b220a954cbf7e942517f060250f2077135e88b2d1f4dfb64f6747e77d280f9a306b641464146414646485666bbeb5f782a0906cbeb544266c1255a2099b48d6de6bedbd20f8a25889802811459aadf9404c40444144414441484548b402292d2d252d25310149a6f9dd655dccc3f395f94ae42f2b4df2fc5ae4af1a797e9df94bc9619d504f21c44403aa323924a12cf258b954d90bbce4b11ee5f957a6fee8bc7271a210c5bd2e7371ca7a3816ef68d0f84b83c60d479e2c3bd3083b2623f3dd382a51bd61bd5265c26ae5be7ffd4ac44afdea65bebbf7fafd917a995f85acef3c5cb9fbc7780cca8d52a111d58fa9f78709815c6c04e6efcb5049e56a85fa18bc6466954b9810097d1690fbf52b368282919175d14514a010da7aec4f9e0f94e707e5e95db62de1367371e3b275d9945cf4e2e2dc64409b11d086b4056942db2422caf367b6cb5d02917a987ec6b9e164ad68b6b4a0ca0464042403220287ae10518ffdc91329572b2ecea74b1356936ad16ccdaf4b382ebaf4207881f2dc925ae82980e8c902c83d45942b5b928bf3b5242155226b94e75ba3cf2ec5388c05d466f3e1934a5641863ba96491841daeedf1978fbfe87727cebeecc2f147b07b361ffe9adeb474d59266d536df93cf2d332f37d5ec8dc83a611e9e4f20cdfcc7c999ef23080da01a2679be384fdced2c5d86721979bee7f9c7c605fd766c5f109973a6369b434831a349ee2988c83d051143b94b75727b4bf326a56cef73fc6befeec4bdf597f75d3f1ededb16b6a6a6a6a6f0148ed762af3e0c44b1c66e5a7597526a6afe795cca1c6e6e5caeca335d3e81e3ad686a6c6e6e4e9cddb42a9fe81a4b63d191d3ad8d67baeca2dd7d864edcc61a672b6d2459868d8d28529ee9f2884e870e9806db6016c69ddedfc66138cd85ddf2fe18349ee992065dd78cb2dedc2c17ee7fdf80d4a35ebeec1cf2ded44d75ee7521cb05d57fbf80d5a34216cb05d57f3f64f5a8b033cb0598ff7e88cca31ee6bff780cca39e27b35c8079d52f0006b33c1566751ff33cb105c8609607f30bb80f8339c001d5df5f40ea655e06730066756ff73baabfdfe121a91d984f09f151e19d2a43540ff33d5b30309f80be31afc23d5b31b85d85ed550fa38209427da72fe24aa87475118ea7f32a8593a94d04c8267982942b781df5a4b5feaa1a7d1dd9e4a3cdb78d82ff9474881d42b17515fca9f50f0131b3ca94527a4555bcc4b54d4e147d2a7bc2aa6c43a80a94fe10d9c361d92d0e03b6e3c22a9452492925f2534507271b11334f8cfbaebf1ff3756453f5e19e862a3a3859c944a16ab4074ff62d4663c84062e4d7c2ccf3b7f9cddfb8b0ca12dfe69cb6a22c5ee2f4a9bb667df313348b5bd8a1f5070780d49fdd040e49f9ebc1b3fdc4a3670f3b5beb6dafbd9c8f80ba8467843ecd9d47aad9a01eb55a9dac1f599bded9b861baa36723f29bfe463f5441864b718f3975e417fbadb3f50c9d9474c3b2c55a37eadffcba6d8fa5496ad9bf73c8157ae62432dbce41ff07edd931e7a472a226ae357b2d4236752e5790525be23fbc274bac339b9ab2ff5ce25d53531e777a7ab29f50e9562595eed3718991e10303a4583b414495a4d84ef74006455d6231a4aac4da5518bd3398c861281683941440c5e08e9e8792e2d575cd18ceb273d67d3c61c9a7a37cb23f9eb46c9227d675fbd7a3be5b5b6b3d9db2d082699c11aa52b2fc1aa55fb60a758e4a6390e553a4ce315ffe14ea1cda4b1f9a2a25bbdcb64d7e3dac37c5bee58975dfc7fdc006494141de2468ca265064172e59aeb4c852fe49d63960f278c2b29ab515415e01e59916f947d52ccb5adffa9df9c88a41141b428dc3855848de6c4668f684de4cca19a114cc8852b3cea1bd7c4d52a26e18986f87c7c545f766de10fb27303ac0139356145102a41887677862fd25a063331ef2c4c0292e9759b3ff8c942efbcf20cd6c71183743e58a2c3b910ef238b321c1c00666e47146cb0c51f6a7530a551b64197a5d661e974942943bba5075e5fa1718bd596ec18c902bbb50e37017921370111f79a1dbec66eda93b755de5427fc24b12b3980da593a15827e371e1ecc0123ff889d950ee50450c852761748975339ee542d60682621cb6012e1cd1e5547c1a5c1a93299ef2f331ed6538457b239876e3ad223e985e7459713ff7c0885c71107704b3db2a32d002f3a1e989d1f0b12afe3a685efed49b653d1a1962a0ec7e1e69b234238f3634d77590c2ca9dc5cb184d3a202b340ef903e813e47b9578002dcaafee5867e68a71469eccaad9b1f46014b0c551ae79c40171fcd55947962b2777fe54a18402af872da8d88244e7032ec468e2c606f9d53d8b0c4e18752773ce3947b1fe9c5f23a5ac61084c5cfa3ee9fce1fa4b3266378836ae9b895303d230e1c36c045db25de170697e65bd2e3904185cfbb26eafd97049cd46ead3e734f91d17befd06274cc6b81696c825356fd888f6f3250f6d07ba7271d27c8dbfe68b2b2b43b872e96b3f565c5ffe58a9fb20536c44becf12482672b0c2bb97f2bb29a7a39af5e3f4f47b9c70779e5d4ac6e8565cebeca1bde4c9947eb51cad9f3337b16767ff1ccb18b952d7b22954902445d16f6fb7ed7bdb9408a2cb170d824a6a4ee2ca9be3aff9f4a47d75593b9148244b394876f89fc209788bc3b2490e08fa920472e27a766bad3692b934979e6ee19a618cc18473f2014dd3344ddb6eb02e18c3e7e7863cfe910c6cf2fc9e5e4891e7afc07957598e587044258ff73ba059a0a1982189d6a59c7adf3c6d16c569a0ff560d683ad30b47b100e8cb195b986cc1c5f6e5851eacf4e0e0065a7ab8c424524e72461ec1ad0ba333907c9a6c30860d2cf86781197904b38b3bbed8420697232766eca00af01f893290760045892b663060420a2d4c3f5292c0011659be3401022d3d304203104114e144172642c003095c61c696258abac842872ac820b2830f6e40060e3d686146155e784ef08359d11832083241146330110228a6b86188288c88811631da8c1b9481c5d21363006111c6931d3871344490121fd86249e767072678a0b2431541558081840880609a155d79e2072d5f6c33339e10c3878619962abce041d00c335cd1248355ca5b17d979aa5cd154250b2a64800446cca9a0810bb21811021a388122e6ef5ac0baed20d75a8fb832e8a2943d1c9d2083598e8270824a0d6658c83043fe80c309baf44cca0a5214216332814c16933908811355a6608ad1e020ca0f320cc56e00828889580c9a0962ed8414cda5c90ba4ded28109c58a1f9c88c5e01921e110f3df711b40108b212629e6b85d056bc30c475d624e8ae074c002054cb1183e15c4445741fe1423ca8e3c6206298e88c560030431eb2ac8974cc0d89e34b121686699e2402c7d199250a824811485c88633264cc32208273db11e8c88c9a0469e2cab0d5c40208d68a302a23c825108e5d18234a28dd613e3fe6e36f4a10127a6fd8f8d711cb6af4d2429e4fe80b40e72e3292cfa3b2d8b6540fb0c6cf445a7ffd3673516b23dc509e8d8868fe8ba060aa11c3463e57696287ab2404194050a267f9a21de13dbbe67ff940d87469478a61f93fb037223379e9201faa2d3675393777753534cfb29daf7b70003c4347ac59ceeeeee3ebd8694d78860c3952ee9d3d0bb72e763185c1174b33b89e7d9322b71cfb9dd7b8ec0353799bdca12fff92c17b8b74f84b35bf5d93ef4b1dfb3a5c5be51f81fc16a715f6a639df56cd8b1b9e17715fc2b0252c8937e9bfdef1ced1243d6754d15fbdd65af4adb7088acf5d6ae569bbdb063b6c3b255a81ba03624f261ef89f4ab308311253373df5d324f8c27e6dbdfd9f2f7421fef6f7a70759a292b8975a3a0d58e7561c74ee108e64a43ed039242ee34af7ef46d175659e2daac734efaa138bcc4eb7f0b94648b879fae6be82561ca9daa30157447d197666be2b009005702d7e2549cbbbbfb295c0202e1e64ef4578d9f363c9647e31179e037f3c97c4817b5f434e88e2df3251c78b43678290078a904dcc2209188477b038ff749c023a8fa11f0f835aa07001e5d2a3ce6e0cc103fe611caf569e0252211804200e2e9c9f541c04f835944e0ac460c50727d8a349f2ea91e44a2f1334834f018832cd797c1aa25d5920a8f31086189fa42afa3228ff27cce86a353c9f3372e3c2a506e00f004407a53baee860f4032340565ab5d72ff0d1f6e4d514a29bde1c3b65acdaeb59e1da2a101bbbee1c375efad77bfd5fd1b3edcf1e61dde61175762947c9a2c310d23206badb5d67e3be7e48ecdd45d60d881f9fad247e661befe0b1356e3eb2391097374b858bfaba19ab299577d4c38f2ec8801bf0331cb4767f07b74069f27cb84e3afc2d1e6d5cf84a3cc330f8633218defeffe4cc8936fa8a4661a3802f204d5cb8463cf4618b2ea73e6bd38c76b841dc3f117ccfdd4fdeffb7e07732fd652a8cfc3c9381668e5558819d4cef68a263f9560ee429fee69f898f75d373d89bb8ecea7df0f1432c79dbe1d9be7c95c3dff80b832fd3fbdccf2e3fedb5183cc340af4bb9046a3405f0b57f204fa12bc69badc9fb0e722a6177b348aa0a12f4818820421481042fbaed14299bb236188ab699aa67918c43b6aafcddf8f9ee9ce0a35f284f9148f19c89af630dfa99e04216ebba83222285f973e51aa972adc8d03068f30644dac912bb0204f9873a2be1a1e33888bf345eee7b370fa098ed67353478d31641f8d63fe084a977ebbd85ea6328f6d644316909bb7bf79c356bb52fbe9e4acfd3c640bf4ed7f3a3bb6e721f30839dcfe896f441b978ec6389d87101122fa8f3535d2a517f1fd5d2a95ea537f3dbed7c2ef6d28afa436d4b49fd33e554d4d75dfde8ed2d1a652a86f14aef6c465f976fbb2c451714a5223baaea95ca6ffa3611f9640a8948d01ea5ad15a340cf5f3350b340c85e508ba4571b738c6c47cb8bb357679945deece6688bbca49a854e222a55f43ca8501c90bee2881beebfa5a58b5222f79681944a62f9bc81596581d46d03948d02f20b942f7f4650e9d637bfa9a6c16e40a1d967d82f6b446d6fe84471532d5281efb894c93c8209d4366fa547bab7926d29ea073de7b6f27bd54ca4314cae7d4e409a9f79c09f3f7dec94326d1d9f2c9e549763c793cfd0ed477fded40e928b92f33cc4f981b8e4b66d6914c19c5e37b1dd9e4fd9c3adb9289ed0c15d7b60b0997df69e128bb64ee3be98d168f721b2ff8234ede369aad977e933d789341236c72e8f4a3a49237497463fbdfe451deb66ddb7e943331469e6c3c06b9c9f2bb55d6b41737980ffc9d16baa0b8da23b1f86d095989f632f707a466adf3d9cd69a5233e8c2090e5737824f2f1f0ac3d80ca1cc7a9c1022e0910877652f63f39b1f4e4c96c5642f61f4579827f5777d15f88cc05da883728a46082f6f2ebf165894f5952496d962fa467ab62ed3b36c45b2ced6dd6b0102bee23c9eab5425ffef7f3fdf8175f1c659b9f67d8ac308d8e1a9b77490610100f8038633652fb3634b2d55abf47e78a6f9aee122a8f5c53babb697777d31674519a491a57dc98cc6bb26709dce18e6de485343e702f8d37018bbba2f7d280790bf064d5c7b49156ad54abd4f750a9fe7eea79728ec3bc2a8481c198f6773fb0032c68c014134340c903c8cd841042b9d66f87a4e2a29443515cb1116535f37f0a8dcf712983e49085d198016b884866ebc80c8db0674b0ac92028ee945090cc1f654e6129c545d941fe6a8ee6584c0ef9dcef6e643a52f644a5a87f5e2a3c62935be7c7f6fd279bbbf014f6689ca12cdf6e61eb681cf23f7059f569aeddf3e5038abc20537b89e2f1b2e0d570c796c9957f4470e9987c7c9e3c99410e3572670943280725a52698b80e59b858810e6570e14206113740ea9a7af0a56ba21b6b7ead62e3f772b550c99cded5704fa7762aa778dfd9b861828b80fe958b4e021977bc2e216efb448d3c99c5832ed1f83805857b628855e1fe48c7a0061ff464c97da59d4347369d944c22b04071d1c621f16b5fbf1d3cea6fdf199172d2efefd1dda9f614ef6f9abe398c59f0965b9c43821a27778af7d5fb1b87791a0bde72178e8673644a66b82f7145d82cc445224dc45f441a6663ab4b46d074d9affffdd51888ffa4df16b77f7475d760d60f2c5d676cc963cb68240e77043d2093fddcae4f709470d597ff4566e3e607775432c7895d3caa914a2b0c6017f08b2bbf7357adebaf05c7aeb1a9a9c9662533f787808a1b07e36c13a75d134fb97198fd66c15b92ba701ae7d428ba9fc9c8411e7b66236e3cfc408a8cedc8d87c1bc858b2c1e7cb131ea4e0c90c9ae4a0b4440e1e10128a128573373a1d21b928dffd64c4bd6f27a3bcbdfc93d13c21f9ab57f7e61a1e3851ac3ffe92f9645481b2f82fb8f373154b1c653177154a70c9dd746510723f4f6e3ccafa93b7bfe1c31d6b510f1032d7a571c83fc9fc257f6a11027a8b5cdfd5699f820ddbef480187bb11350ef9dbd78f070899b3df0384cc613a6b1cf27980902ddef0d83f8f54a0194e39fde974e42df936bb4e462ecaaf27a36a548bc014d295a3a32e88f2d8327a44a783253f9b9ede869b9c271bb612cf1ad66a38cec72c67f9d31a4d7c07a62c2d3d19e30915b3289dc50b1744912f316bbecc3ef8e0031f00f5d3e789b11ab35cf88977aa341e329f62567fc7b4df995f31abf194ed3b569f27e60e86357aa2e4c6ed35f06f411f21cd6f877c723366b9e03150de1cc5fd8cb4427a92822d4b67702183337abe10bd904964c2b6777db9a30d796276c23cc0130b7962da6f3f44fb0def5499bfd3b3d53d4a3f79bcdd973b8ad37d0b2b42686c62d614ca13f31f45e7019c2dd1cb0c59f63f121319e4e0458b1c668e9b8005ed1d243d3d237d0bf0e4ed6dbfd6daa6d5efa161ed6787ef9273b595cd2150b8c1fd221bcaf5dff1578e8f09b3f782e08b3e84526c87d4a75f3b771ffe37c74693dbd856213066a9f67e5072325e26294dc0a2bb3b4362d1a37d008c2cf2d848dd1952be7cf9a251eb6a212cf8984d6a50c592a963719136d5e939b68c05a3bbba77f6c3c2e96f6ca4041b773f29a5b47fa3dbcfee42d126bce170cde9bbb79ba6f3f45ca8d3a390dc1570e551483642bff11157b6989b78a41cd6347cc312057fee087a9eec6230f3b94e3c27f36b64b85a7ffd5abf7e1657e66a84cfeca6e71f8ffa40da48bfffb4daf7d870bbb8f1e4da93be4825172ea2e4b1919c5425bcca4a5d2db5d780747f15e3cb161a91fd833ef73de8733377e778879a36e5f4ab7bda68d65cef140922222f8a17c584d9fd5c57b7913ceaeeeef3c760b2fdd4ec9cdfa3f3c4f3ebf63e85fb1a87712fffb9964dfa3dc796798da3fb79290499621a70a6e5804b3f0b8864cda72f99ae07ca687abcb93d6044248692093aa09e7780fbd0c87ccd5b9efe5f14ff6598e343883bd2d038c7854ae68fef3620fdfed46bc8828b32a439359ab80f860a5637cbaa0319479f8b4c189d2fbd115c5aa382cb922c896bf4dcf7624a763cdedc8fe38de08e384b4552a46cd92dbb3b8f0cc860205da02c652eca1416b07148f93d6606677bd848724569569ab66d9bb51c783a79377c30f2408fe6fb746489936ff470c7f752d2fdc8a9bbf1ed492165f9b767c2524b28c81f53b3942cf75f204e082b30f25d3fdaee777588d37f435fee9487ae5b1626a13e3beccf917147dcb191840cf197b74a923ec45f49641e2d1128d406180f15b04513173bfbdf19e40a4850038d2b6af862a93ba3c9eabe0e9bec230d0f77b44c50b2801283834f6a87181c7cbe7ca9a1434abdbafbd71c7f1f5cafa768300dcba68c070f74f1622df779c8578288586eb2f428a5476eb2fc19d4704304355001e4e960421732318a9051990e17045f74b516af76eed9645a6badf5ce70e7538aad50a103a5ff1a563740f0178c1df2e8caf261d46ff7954258b0d09653e69730dfc7c7d6e656fbbc20c9dd4958c1d5e19a38603c917d0736d9cb52d248ef59ea760d2ce5fa1d9a50451fa940d9ff79f01a4871450e4053cc40ca144f40d11804b7afd875e3a2cb4529494042c725c11d6d3b095e60d4dddd2d739c0342122a043adcd1cad972b69c2d67cbd97e97727006d18dcbc58b83e3a274c9581734e4f177808124a51402018a3b4aa1217ff5500266cc761ee3863c4a2026222a5e0959b4cd2269c9070c21264a59284def9f3df7730f94f278ffe80b0846e9c205445166abc6cffd2f50d0c417b3268cb2007fb823c884c90b2a84a22c315d0165aadc597af0441e9f0a298edc7ba28154775d9d1445ff1918dc51fe94b155bd52a4ccb193e88f1dc41d6dfd765897f3f8b2cb04f7cba3abc6d539beec7f2306a1bda8844e1475450c15a21900000000f313002030100c8643229150382215cbab7d1400098d9e4086501648b320c96118849031c41800000140001991119a992608973f31f399d7afd1177f736610b44c9dac9fc2fef589614e9146bd6cccad9e2dda689b3d12a949783cc042495026bfe114031899ac033f8c497e3270f8454604da32586981da396319c8043120108a12a6939ce3b16518bce02f4e107c67fea8beee22b260a52e37f0a05d90605d459c2e031d36fbb32e2bdc2d3cc053e924b0cf7bb81c9d1319d83d42087d117f3033540a32eccd702d426f28bb28efe290899c3e4f5c75f7c10d1d2ba4721c83214720051993b6cb52c363e92924306a7d727cac8c70281224639d09550d851bd07d0274f579c117640b60dbbf05349cdcd87d71f3c080a24cbcf8049a24ee3112900e88048f77ca100515fcad10f7b9ca1b789b2f3cd0840add371af23150feaf52576bb23177059762fd521f6cadcf628f41777f375394c0e27b592a68f9f7b04c431717c144971aefc879a2b69955324d0ce1c1c5c53d43fa7c03acc3d93c7280382dde9cff116fadff723507452044a42f825026eb8d5dc037a75fa6f366081138e717053dbf1ab13a4b39abd33bb9a829802683f58efab217ea0536f1c6c4f0c1affc537e1f1613c870fd805751e98790e80432deee0c323c3aaa3219ea12fe5fe4575650516bee442273a2b31905b463d1164b482ff8e6aea13bedf2b2d6045190030217ea964bb0f3c74f5642c9567e08228613d6920b3dae5090ea90cbbfae601b0bf9b3f28d5eb2a1e72ea83304f4ec6194a252f3da2baa2d82e4b04000a9a77c500a62d1d35b15a5e781aa6357290a02b31c703a59a2cac0b8ec888117124b92eb8754ff6cb3bffac2a58a4a4094e5b481db23ecb86769566422e1c784f6ac056b621002226664681268ea41fd19326e7070d5febce3652b67c6c7f1e2e70bccc68a24178870f4f829023e47884fc56e653ed20c3502fcbbd44d553a82421f46eff0da03178265a34a4bcca71be167060427a1ae806b46d845dc825bb2d2894f659f4ea926099dd26e57404f82c9332bdebca30e6c9cc65d770cd88171658e1a9745934b4b37b0638719bd43af362b228564e5e768e4432e0b88a6c2d0cf29ec0908b94aa20261d1879f2352e3ae927bc70d72061a7b5b9dca1425a3e6ad68a31df72e31f5a4f1b2ed1345ab398796b0a84153275f02ced716b37d06693eae93a0d41312ae044ca00787bd7dacc2166ee83ed0f3cc27a16415f07fe52d427ee78eed95594d2b8bf75e9fcfe150e36605d9bc2bbfd59bd737fe022ecd7107abe70ad44ecc3c0f77736ce84d30a50530121f877110dc8a3206706d38bd69660a0eebad91312a1a4c8f7d986d2641a8d0c81644931378aa4aaead1aee80e44a21bb31262a46c00d218422b02d1066e21a91e85219449e8352121e1346e1fb6e2f3c5c00527ba9f63c8a529c75d8dea9ef6b0583692b0e1c641f138507cda24e0fad80b88cb3e76752ce0ef620e1485f23deee88ed8b302e6ad53d34258094ea08b0303fc09b55315632e54993fe35767376a510d1514b3ed253994bdbba53decd876169372f2e543cc871030711285a74dd3070bd440e6c05fa03842216add6a4d6a9e84900f000c709473b11f98bdbc6ded3c1a43abc3c6f049a3311019d351d8943a57ee27f4e123b76497e6850c3780c829e5490315980074531a6350120d6a7b6b6b00e8d45bae72c570089f95b01df7ec64e1b9aad9af5d54b74919a93a6bbe7896802d26519fc00ab9f57f174dfda0fe31f0380bce32bc94635c90b666d1bf4b1698127442847dc4dd57028529e12b63df80e35b8c53e6f5446177616b17e4c7a112514cb955fcacebe7c03b441efe44994bd0f6fd096a3bdc0a0ab4e94647c5a79d57aad6bc95ca425c8fcfbf9222e7a74c281e9557d442986312b7245deec90c1df9aabd5b6c3b51ae89e8995e37d94fddeca22e413854a4cd9441285b70ee3942962b18a769fd746bc137b29b24afa1994abab7c1359ef3600853cfc8c0609d57ce270a9810595248cdb7761cd7ce0dad78a7ed9f827b2f53c4de994a4b12ec5e46bd86ef6bb9a58c033d9c7d040c24b951b9bdfda37f329a3c69980ce1850f04cc600ccaa51808009f09cfca768c2b451be50b3a07c2f57d2d44494c6eeef4d9ea083afc5d8bf38a1d43d5fc53fa8b29ef19ea3086c8ed10fd82023b18f7b04962bb5b14cca40d320f739975b172a982030e19521c722b5b3f8fa06d1965bd55dbc3ab03e3f819d4dcb6dd360ec10e675812085c6316473f93ebf5281952ab6d49c34608e5dd430678d6f98df47d0ada531722a996d18c27114a7f7522220996b549cc8c4db5801b90cdbfc640ac4f75aa71c7527f120ca65baf25310152b672ae7446bde25471aecc0a79c4591c00f44d6b9bb32e53c294e3346614899bb55ebf2ae0d4402a830529158ab31fbee580236e9a0ffa20e7a4328cf0aed3fa0f9b55f97bf357a0b0fac238e098858112d1de994e27e41320d4400a09bde1ff59c8aad08e51baf1d607b9665c43ee125b668a5fa612007bc048a5affbc64cdf118d4f5b608bd12204f4fb48bfed47b3bf47c9413eb6f5d4309ab6daad3acfed9f99d8328629434fcaf7c9feb9e232ea0833e0aac78e9dd8f85dc3cfc9781f65f517ae247a4a43150ac3711ed9be7c1dc0f888a5971e5c4881a88a05921aef3470b48444f9fe0d70e1270e7fea98ce217ac4680e89fe91c864534cd30a0a08858cb4da4d18a81a23590bbf52595acb6a739d5a5b6a6b109d202c8cad68c07852bdb9615175fb80b80cc41970a069db8adf5bf1c5b81724cd33b82de62d9d7843c0574d4e9cc1e35d5da0ae79d21f176f50ae3e4dc868c5338ab9a00649c17a8dd50daf7646ccd142e123302457b78c85964129772288b67f5ea6881c59868785e24cd7e2da54d35a007edf48cb17a7b405f732b5728d647600da92bbd1c6ea289d65ec5c670ac933981970ba16f79ab5f3a0ad55666338e82ca5f80ee09445a3282510ee20c906761291f7add6f8d69a4c34d407cce462d8e3429204c2917a642860c9c34631fa209396a8bb4d192d00fa4166fec8b6b069848a8cb451b09f6672c637d26156716755ba22b89b33a2b8dcc2a55d6259aede558d306c81e75ea95d87d9692ea567bc6177767bddf629989af979910f35201fcda8345ad03b74fa931b521981e770d66811c68c1de9611ca809c4117d0873d105bf4c5a842cb691904ea58a469d8e97b09ef000e243dc78a4ae6829d8c753996f4fe5a310d218a53897be3cc2801d8500c1c1762516b87062acebd23c683729df6c773a8498dfc9241a04f53860b47d416b535c6fcba4404f772506fab89032e6b692be7fba05aad9cf0b784c60cc6cdf94112e1dea024c2d7699e5e6e252a394a307fb98fbae66b89a504ba024f647f43b6c37980654429f6aff33179c06f1963d0e09b8e5a2277c964c4ca791fa516f562bf06da4830114d1616fdfd4e5ce9257d5d5c9f8fbc930922cc8b9b86fd13acf04905362bd5ccb590f058eaf7fc250a81d82003621b4470f6c4b6370488481e6238093395bf465acdf3431133c514bc2f0a769cf92529fb0ca5de868ee3b0678e22922454b172e1ec5c76c528fe802e7908945773219b328e6251982116b41f72042be8e581f8a6536c50d4f934b40d01a13b118ea7fcde3cd52ee80b442b1542d96fb67574f4049ebb3595906856eab2450287e3400a12f2b8fdbbf26bfc4e700b19ec67ce357c2a3d0bcb513c4e9e03883b43fbf1eb96c78aedf5512d70e3fc4cea45519bc37577fff8f439de72814156c3839fc37b3f7242d7ea50a9e7ef799396631dff0948ad2ece2b764168f5ee3f16eecba0e61af09ef9bac64601667fd5ef6fdd7fbc0226b825d10aef7dfe9034aac7d443ce77d18d188280debe035f0fcc190440e2bd32dedb93e4878806ab707601765c870161e66ceaf25f7aa25b0ac9aab38ff191581b2bcf8bd34f3cad6a4661e1b9c4b9110aa53fb7ed12c2f040672bc6e0a76adbd01a8883c9b63de5a22ebf566f8888a852be7bec2ed50887d5208869b3182ae291ebd374b19d2e44da8488246c256871438ea54ed450fe62c39d8f92afbb0a9b79d57f82862691b5aa23525d8aa5c02406bbd8f6cd1d2947267dd01b1188e81265baf2a3376881f7403632976db1f87d5ff127d34d3dff046b62117048e35030900a6648a107eca027f1962e76f03fb9332778b4859e2d7fd763d84dbd9b43d9bccdb720902f69525e36283819ec4ab50be0c853e801159e59d3617c40e7a6f8c01a8867bff93180f65f7cedfe1d7c3e2b3b451cf7012f886b170896bdcdd1b8ac8ff95d308b318989d0baf0da22343b04253fc3e37f262594d7ef7e941713494c0cc1d9844267c46a92697d9c9b47ef637cd85b423ced047644b81fee61cc5d220b88761cda20a0a859eee6f938b40222ca962c30a83f9256d4d0e6afe98da0a80658f6dd470acd0356d53ff1eb6613803ba53124e184c520ffc883b7d96d71db9b01e76b9846ff41539151220c0b123fa23932012b9627c424ed5e04ca148b4a2070f28c50a4132d00e9e06d89441aed8640c5832f67b74d7ec4f1a8ca34160a3f4db68b8d5cd9aab32e802b4e22c9594ad55bd102a07e25dca11b1f3052193c5a80c6f482e5d9128ee11ab92ca17b308b04e186930c375f7a35cf61cb3dd6cce9a3735351fe15fe5b7526573e00a35c7137fe11f076007f2c1c5a186117a6b9d35cc062b08a753f3ba1179af1344a97621c47f9b5da00b2f562b0dfefa157c2ee0843f891c3b788e624dc92d6d0db34c1cb58e2c453c6438d68c8c630515840a453082b283d5c315172608077427e2028f7fa9917259206d992d4ff5400aa634fa75302ff0354a81301be53c10a5a25630fbe42701b43d758eab9df8ab41720b64f2b723e0b5515d1f1a3fb59e6e31ecd671eff90d74fecb54d1bc2c9ec81f1f8669228de5855810f920e7a5185769bcb5bf5a180706aed8a6894a4b94abe6c2a9f32090658d2ebe035088957e50f26d29f3abb24b67a9eb9d5cba49958dd79a014076f7867e4eccdc5d108c98a5f2079b855ed4fbf8074b252af1bba71047d9c5ed55ab82fa571ffeae144815518464f404d44f50480eee8bf50e84d3f5a008f3eca0b1be179b139bcae52ff43121e4d72fc24b12fff08d95be6615744cd73219098b3367632bb942a321479c819ad06dbcd208398ecb96c113e18022c1f9b0f67f2318e7619a551b2a0d7eacf8e2e292a4f40ce82405ce6e76b55af4535ace95939e4c332d452a064a29c90a3b126bbaed214934208aad343e5fac06894de47809006656edab29792a843b2c4af89ffaec58403506b57d525d26b78a0b4447230dbf0bca2f7d680f19f5061768eeee1d204c861778a3125815426d360a8502ee649e7ada94a71376686cf3404a4b89175ec02fa8848fc3fcc19c5a29b3ecc8560c207c13f3941749a8a4a62e3a935ec1bbbb1f6c41d00fbd37bdb80d861f468e78a05e08328c1585a3625fbb5c0b88ede9a2ae0ed8a042694b5e451d938440a12839e381bdc8d3ba015569f420416d4dfe9554c56c167c4f3d11a0040389b67229ce416da8f117b1b43278d1cc306011fe8faf7ce385150a2daee21047d94fb6de5042a62f56667bf6c7d936c7c133bb5b6c1b61ab2c08176271f395fdd2fc09ef93888c2d6cb2d9cb096e7ebb8dd9e499bee7baccc74711cc15e6ded023ccfa8e530c8652e2d7a98e148f8e8c0adf629c4558f842ac07b122d1e731d9317a068a1f3333b2bb356de457b3243229e468adc169610fc1fcc1a6cd8f0f8dff47b3b307871041b39ddb068811e4e0f217f565680d7069bb430fe5eb0e0e67fb17c306bac70386c9b5ff51309b1041fad634a098f9e9aa33c64815ca43114e21d306e4a7b3daf9b6273c4689b58507ecd21c3c1a16d849a8eb04e6f4056375697c8aa0dff3d26b88e4aaeceec56f9cb9a0942f0ac222dab143cb91a56840de0e5b1886489b5c17ccb62083aa17069d953afc3050e9ef4e87e5c908c4dccc2f8c37541d908d532b5bdce095e32c870184e317031a4386f4dcfe8c2f43ccee34106170cb8ad0e6900ccee55734f669a2cb6b9b5aa739e36e4311d1af630298829f23857075701a8699e751c1a610a6bab69f5819cacaa56375224d1ab67d7f2f1a917ce510a92012a041791495cddd1e7f9e6c6efd8f7b1a81213fca253bd03064707a44a4a206fd4eb34c028dbfc14155c614bde9dc3c5f8dfe3588881068ed97cb39e97b38308e1138f65d616301cd5fbf053bafa17440a7829279ca127b666c227ad69336cdfae1015dd7c9756e10af4bcae165b52b784b8e3c217fdf600738ebf0a02d694bceeb0b4879f8d81907e114ddbcb37cf6275e742bd8700e31c4de05e920a2ba18e90e350ebb6e12905504ab17e8d3e5a1df9d0de52d0e590d9d3ed3d4e0e364ad213bd2a4c618144e5e0ac83684cb23321c93feae26d1236b28603e46f20fa370736e8f7852805c19ef31c4ee44fde53ec03b5c404c7fec220cfc7e266581a61f7f504de2ff32df6a6ca274df78531976479c2b36f803405468aacc2dcdc902531fb0bbed69d656db2a532209dbb55256017dcba36e76c6233511776f815808d4b94bc8b8f1d6622314a06d39943b535b1e6e635b1c33bcc42272de7c548abc113ee46cb1ca10c3d4510ea6887d7134297d51c667d087d5e215ed10d4679d060e79a81b14fce999165589030fb3074c89e89cdb072d34ed2f1d0fa5df95669a7aed194ba255240266f3fd63e01705b19fdcae01ae241390e2c0951b0b1fd1adc4ce79ad1ef69890754eb7ea391d691d880eec3a7c1c4c087feb7653107bcf4a495a809460ebb100a9ed05af4c430c0755047627d4ec85188b80c5d318a52581b45375b6e001fe8daaaf3002fe60c97356042761b576dfe0d81a7163671010c9781acb3a7b9ea9427d7b12c558eb4d58cc619142202ac776d81ab401c15bebe1657e059dd0e7f5c6b249722270a7397cefcb8f010406a1eaacf849e2c4ca162db1ff3c088a05eb925ab8115ec38e119ba2e00ef34e77cad7918c611a145b3843543d1597bde7b25242653b4cf18fbabdb5e119642e7f0c81e15ff83f3ad78cc21bcfafc486f46cdc44b6d66f37568580f44a24fd55659a04d7451164708647c6f4686e79095b9f954293fc619b5c683de549de51a231e9d4a530d66aeb192d36246603954ae8b30019b50c683c2bbdd32374ac1932a468e9c8f0c3eee25d0c4adcc9748f9c56403c3c60547bd91a9d455306ea59114cd98514e8100360392720d1884a9d77448b21cef2c85e44c0721dedf4f41161afe5e9b2cb94bc760659e05739c6d212ea4542638596c686588be5612ff7d69915824392d3b01399fe93dcb97fea012b0aaa04b00a4a253c5bb2ffbc741f9d28a79236de0edffc045edb93387078ac77e0a7be936b64fe440527f7c97bda9c0b3c6d09578059553cc3196b09b3d1b30469c4b3eabfa1172f2c34d7338e837faa0e70b6e72bd70568f8a3bec615fd7aa553ff083d47555a40485746d7b5e3ea5c1a7e7da04eb9150c19b7a6903b7e095b00ed83a2b947c5ec02f08ca24d53a7088d87b13f9af97466e2d50b53d26c515387a8a7c852968d4ffd486199297486ec6e6185cef24e52ab4c2ad44a65f4db7971e733c262ebbcd10de25288504e121d7be2f3a5406ecbb56336be878e40e8e49e55391d8edacb325297f73529456d74cf99b416f87a1f92fab82c4536634daa1aa7a08702082e4681939af4428bbcdea84309e3f88dd02562e16d249fc49fbff267181f4f6d8053c579c5897065decce608f146327eb7427074184553176b5dc352e51d0027b36938a356b0324e4ae184e61d85338cda3c77165183ba72a0f66050bead6df3d2f7c5e0d3cd212bef7b3f24054341d2f74641a464e446746ef47f48d31e70cc427e6b69e37dafd000dfa4045fefcf23e1e7c6ba875ff70519ec1d70830a07d943089d414df54611182c35791ca236961b4eb5e52a3b17fb5c96b9fed07b2fcf3cd78e96139399b096d1f16ad2c71957645b0e94e8d9b87182f16c7f927534e70105338a521427486d0b25f251e5796607a0172e91a5196bf3c44274e20d5bf4226b54e5ba4b6c4e30d7062d27fb9baa4f432857ec1a490e84ad36f75caf8cdf7f0c81f394732e6935126f955c9de3cd86f2619a938aed2b42325dca63b8a38b702d3cfd768117b2c2a6300c437777558b0873f5799673404aefc503de01afaadfc9dfd87645bef7990381c6b545b7d236c43580a21680895822628db0e4229c2c922a2b613508e7414f3770e693435b253fcc4b3e14a76d13fe97d062edfd138449598a6949b88b414f391218417ca12ce42d730c6fb61a0088945b12039686c8eaeefe810e98685cf89b59aec5983b9c3b717589d1ab54b4787a1291c6435f07ec769501bda72d588b93f6c2507e5d88b280944d8fe5fdf0b1cf1943113c058416e72dd7764a35666ac96c7ec7f6578bf7c78a0091bc37d4d12c3d9dd36bfc38dfbdefdc4e3577ea8aa08a22519c5eb4ef674fecd2be50843ff4bd421a592c9e978400e39ac34b724a3644c4c39ce14b6cee209f14e45299fb0bdd29c0ff7a24b6d7579ad2104818973d27a5fcc9c708d1e62b37fc0f26d7772645618c2454b300eff86fb8e709f4f331ca69d186ef3d2ce025d2ba6075897ed74799a219d41a3d610ccdcb42043b37fdbe424103c66237490c57e851816bfbf1502e63c2113f401305b924c88f328eb28abd4bc33eb83c5975367167110c83544915124895a4f888709854156606b36d42418840c2a69f7159d0b40d7edee30bf49f10fdee62062e8586d7a133305359b15f1063cf98e0701e14cce0ef0535ce556161ba5ca68d4e51cc4fd0f441926843ab9ce5937377da89c4bb90650e2835211dfa8ecbef5b397f312eac18353106d4a2e6092e8a131685026f4d295f1a0ef219e00bb9e85f6246e34d0266f8df35ba7d3c80c55a3172b1949638a0c7500d6c3a6d6b08d23e11fb81b551fc69a24498f8c18ea17e9bd91c8a1ca26235e94c11681b54dda7358c14e48af74af447bcef11adca939755ba9ce6047878af72b77884fe3dd670af8ceb8fc0182ad6bd3c424d797967c26477cd2898ee67ffb174ed475c84819e83b5e0f3d514932bf67eeb85f7fe2359d6cb213c37a71274eb770af392b16359170e82717ba328f2e225c0df246ac4ce1c25ce792a0a29d908d6a4116c240e78f35c3903fb71688b64f68cc27d976a37954fd2ce91febeb0780f376fa85437a0993a4698ea448404422935fff5f84a339c9455bb02d3c7b43d69889ed9cd82df29305466ef6dade3663fa957b0ff071baec0910f678ef338090472c3002687cf22ab5aaede5268926145f8843bc2da89559822cf988edc8cf9463770293d618d9f95708b0c64873ab69441b835688bbba822e6ea8c3d99a7035a7e57cee8a9c132da0871544b5af5b16390ad8484356eac2dd2c3288abdc5adcc4d24dc0f5a8b504c57a01a4ebaad8ab0c1ce4e16c5f64a5010f31b5f915a41e8a0f065031ee2af75de130f9423553c7ee482273c3c14187fecf0590342976daa522b6c60158a737093754917a0ab56beed57112533152a7cb757fd67ed62c011569c584a4f72282a13a4a04e7e94010b1acfa167ca8ec859da271a96653c53044dbe65238b7f6fdca4df5fbc798d0c076e86542bc46a5db7522e8274523afc29689d48f1161a7d7b7345561aaf6863f6416acde2c3859690b70c45445e93e3d54efb74768533af2821af1df05c63620b07bec492cca29d7d5aabce008c777b8545038302a65cbf09d9985201a73c4966540a4aebf7c6dcacfa89611cdbd8dcb0966ea9d30b03e2f486dc59be198bee293f8a6361a51ee3fcdd4855fc462e25263ea61d623e5fe544617c12604e757f48318b954880b9e29440967dc85c40824550cfd739a463b1d75ac24ef4a8a4d898834cab4a03c2074d5e4e9d116da1672482a92e9a3606f240f73efbd58f909ca2cee76ed09c7a062fa81de24cf61ab38a632cdb48c9e00e843df25c9203af2cb417aa1bd0e95b7303e4d0ea96778484dcc070abc230a6369223954c20d10188ff367568650665f0ec2749b2584993cccf14d6ba8b6550268d46d9a95fa72c791a928904bd018ce27745d4c397ae4231b2d691f3df526b72b8103310428202816b7d5a63072fbdd0452cbe17d1985db5cc2de2e869da7d06e16a208ac34c616a4589487676e7ddf71fc54e28f0e2b9f2200ca889adcf2211d1407c4e1b8159ecf1f32d4e50bdc923ee5dbfbc91385c778e93d83ad4896163e5b9844a54757101f15ab2403648d22651a40a2cd9b26ed1b30b07214c03a99aed0f117f01838464520f4de57f37be852d210db4b83c13098b8f73a67f9c6c14099209b11ce2e30f2281c89d5df1053428a97784cfe4d59f15d34387407e68f84c36955b6e4c28a44fca6f07060b97f74c37b3e36a0b80fb5b921e84d860aaacf347663d588328a1def31f2dc94f4d08328b7beb096da2d78b112f7b3699401e94406f880abb7ddb00124ad3f54d6ce5e8d3dac7bb732f6bc114295fc56493fbd35c2404b045a14dbc7da441dbe5cb9dc8bedc44653d6ac6728fe80e8e98f99003cc384a57c2c1ac1e573ffe8ebf7d589dd77575c47f363d485198682e274bd468c0186ccb4e3c2d6e297f7a2857bf55aa3e846e146c5348f84e5a4cd490d2e0a09bfb829a746ecb3fcdf6e86678cb14e8d2ffa39736709765ee65f9f75c90a6b33558003b50549a9db7a9e7b02f6225e683c297d3cceabfe4ec2af0a5b4fc826130c847c8aab55572cf11fd7afd786046a871e32bd42b864b76286da6bd454d9bdb7ee84af7f39850228d3953e17821ac5720a8810d799042535bebbe763bf706954696af8bc77bce571c2414c0ad3a62b27fd59685f429e509ba9acd360dfc5657d01cb2b3989a304a16977f4ddcf96aaf12521c904754199231bc0b42acccb88eaa0d8b04ec9e6c49838e39ab93f36673a96fb4897cc90f0ba74ff5fb5ed55db247f346e61fefd982ac32103e6fc86887787603f542085e93c083704e2bc0982301e65e6120d347b0e4da19656778895be4d2b36a44cfa940abe70cb43691a6beb33512c6152c1ba5d912168a0c1d5599b2d06178f5ba6e6b7b01721598c4f94e6ec4a4ccf6b5e2603cb2106cb73819d084ab6d9817b1c11888a5d310e8e658d68489c1efd42b3afbafc379eae812cdcabf51629457f691011331a2930c3defb608ddbea2a3b0c7d56b0ecaa1998c26f67f18aae477d368867f0c40ad85c9dba023c8d262203955d606d38d2af62159acee5a8f579dd617c08a003a142838305e91157ce962051b8d10bb13221d69f4445d0afa6a5e4a482a132748982ca9626d3e9d16bb5f7d2e07a0d416ddb2b42c56ed405b526776277d31cd0919c6289d8a351abb04eea2d5a22203ff0959e82c60bffd51362129023aa883d7ac959ea6cc4ed2601629f2460b73a7d6661f2ea722e3a48893c7ec8709d97a90b1a0cfd42f51daca500e3ceedf19b1cb87c646953531149e781378a81d60f2cff01cedc95ff0215afdf940e4fc038b05297f1c14c9423bfeb06d80f9089bc0e43104540516e51f21cf799a1f71d615bf90dfaf895b6846388bc9b5506343fb2de1b5e0cd0c90c82a05c5f4f8875364a3cc249c83864f9708c283fdac413b91b00605ce8da06eaeda981b37f5a4209c4d8bd0291a9dc005840f28548f243d82b1201bb46feea2cd23a8520f66b205ba864848fb24ed7f22d648f29947393379387ffb34ce9d11399c170072b4ad288e6c3540c5b80a8b11312f384633f190ac158a7aef65a7124a959260db9ff0f1ceb30007b89e58ce0faac3008ec9476a331d8f945fd54d243784e7c0a915ec5c6f3acefa80d96cb87d05a78dd6813dd7c20fd19a680340c38aa3f1f65df495331a1638421557e1966472bce569993e3ad7d9cd6350ac146ad923eaf26983644968b7215e1dab99a03f4cbfc186f09fa60c5240a2e4a7cef4430017c9481ea26289b089f3cd6ca06a926ea2ccec756e3d11044b5f47a9586052eec4c3441d79bedc1f42bd1c49705769e4c53883f8614065c4f170d61c04181f38ceb2e97e334c92684895c815647a5889cc1aa19d3480765fb6b809036b7f236e24acf959e577a5ee9bdd2eb4a8f2b3d577a5fe979a5e74a7a5e1f7a386d6d718159038267ac6ab5dc18633bcfafc3e9181f840606337ac2fcd1cc06a9ef04a80ef59ff84f841e575ee099537049531d442ea54f0a9e75ce6d87dd2cd9fed4eb2909a403020b7b1a2fd15433fec110cb5a0e4168942aa636d3a0d16a2fb7c22c28c0a615d2e2b25143bcb14df79f4c58fc04b3f3e07214baa8d2f59de2a08fae8cfd81203e1f6ebbddf0c00ad7c96c8f6db0562ce246f558ad0e778bdc18ac75abc3dde2c660acb0a0b72bb271bf99b6d8f0670595beb925883b4bba81de556b979bb5ee2608c9fd65da3636fc58ac2e779b1b854ed956bc1381ecabcb137fb4d316baf243b362e35c64108bd16aecfef00b4029fc9d01f85e11d26781517fffd79de89b0496cb8cadaa36a16341533e035e97d04a4e635c5404ca971c5286091a9b8b97f4f834248930b9ab388a3600e0d4b4fa65d0278bc6a96bcd071352c071be8cb51683453bbd48c364e8a57c2fd679ad708d191ed0b17205f8c9a34052b7180761c155e6ac7f0ed2d29efbb27c5ff0df2e48c91d519c85f8e17bbd6b1698644ff2e210ac37bae4e0fecc1268136011307e7b68da2a42947954a843e69e5585e986ce85902c0a24709b289aa815516f5e8cc84eb338f390239e12c0be03a6002b27c0d345240217a92b32145012d1e538a2a4de72ccf50da0e64446cb5431428ef24cc0a02a2029638f04feea44cbf8212b62e0f772aa18f7d108dcc49099112dcf9a05a1a4d713afd77618755a87ba84ceaff15f8a64d2badf750e66b9018eeb45040905e4ce65553a42b8c73902520b607639e0618f842a9f77107465a8449530b7471a87feda3e10fcbf32c836fc28f8aa8a8d69b0c574b8c7acf8a9eeafc80f320260292d7bc0ce86b9ec81fbf537e93dfa513deb06a6f73b057735792353575a15fbeda7e4fe01ccaf107c4af6cae8645c7356a85d36b557ba317cb6359e50f76389117240fb696e7ca8c411038dc3149ff0532904e5853655deab8a3edc289463506ae0a3f99d68ad2f11d45dd0676e5ae9004499a980f04080e73cc93fe7f814c45033ad5192e7f1f25bf8278640389e7da261a683b2be03d32dad0a4ae6a0549649a8cec7b02c80ea16930842e0c167c9740a4b82a168c0bb6a6e27a9bcbc3a514edb7fe799fe9f95d033d466c54674fa0ce92702b2fbd86a2e3607948557b061cea3dc8ae75e98f2b2d5679c1083ce8278f1a343c799f7c8ba2fe1c7170818c88503cceb988f3217c968f7361f6d669e4b4a1b0af29995f6638db0283f81a91e3126178d32f2a318cb1b53dfc3da335744d471c27e4fb05fc35ab70f3ca5efa08f09e6335da4d8ba4d30f26379e2e47072515c3863a4632df4f8412d2441ac82c05a71cb2b9a09a03ecac9b07a5cc448735a934e86652b2136c73f61c8874d236952c5b28a9e1b3355a4f8c15ebc99dab1ca62b49f886dcf1e002c8c1eeb9caff07ef65642fe1f0a5bda422b5d1c35ae002b1d5efa12345c157a681ae758f5a4f3c87106946bb4e46bba353ad7c448de02d821d27910d214bbc9eb7316c926415760078899f1ed3bf4c703c5a53dc3e6e008f97fe9169d05fae3830e80832222152919d387f692ef7e0588e5c62266c0078d431867fd9951710b0caea668dc33a4174cc915eca79d0f965a9d3cdbe88e510b0670a1039c2a24514a50f00b0d25e51df134ef7046a0fcf4075fd32f3f7309b930739212ae6bc2505125a5f6286c8fe7990f6c29b605d435eba46984cdf213502d0a070d14f0aa68761625604aef788edce95add0aae7ae120d5be1dcbd77dae86e6ffcf52328b58f50cd0f464004821644c91a7e4638ca9d5cb1a3971534575bf0b99582305d8a9046f2200cca10ce14777ebe60362c62e0a65e0617fc25c0681bfa9a0e2a1f13fb521cf6ae3463be2d023e0720305a42712e53ecb3d45506bf6498d3e4a4874e6b612d83161af87dcd66929ee7886997f8233843e4048b707450c6c1656a24ef24cdb12704680c06c8d5628516c036d26baad78a36713d003531b36b998dc78125554c9990a472ca7139d4bad352468bcc0927fd0871cbba9cd6a977aa16325a364d1ed59c25b8e2afb45911ef663cc78cfe2856239bfb485a839de4724451a8e3a9394d78daeea1a9306d2c251eab473cd4c0bba8c884da352bb7b061c192f74c527b1a17bb88711b7256a1aa79ab699694b135563786dd8020206d4b244d5b155e8c15234a7f544346055b943024356d4634eb7b4feec613f7e6d9d2c1a1189a943aa251191734d91c98e888427a57d077fd6e1e5c3a83baf7ffba375fa6a9247f015bc16477e42244d43c07611476dd05bb3fb3f3ed98ceba6a17e07e9bdf845e552d8a631ab48536060cb7dd48280812d33a0b46051b06507d656186220191aa75a91aec2d2171d442ee48c4cf5e72bf524b96997e64bbef5adb6e9d44683d580448782d2cc61540e800187993e99ef48f4fa756f111b3292865e1e01a32d5ef862064a9c571a16df3fc67d7c562b51532a03bb67390923a364ec92c8b6c2d04b940c07097feead2e8271eb2cd313ee7c581d5ea493881e7dbb899e371abf405c10242bb242721b945a1abe3041617937f7d78e1a27de81ae46ea3dff4b446afefa41e835cd2fcf8138f6c64983c031f5bb7fab077da8343c005859c234c88f77cd8c250726bdc77bf24ff5849e79702103093905bddf36e44a3c349028865dd50aad4aa61bebed8fb23160a34b567cde7687009ceb067db719723d4975698fdd637d74ffb0323aa2b9dfd2e76fef9c7f9a392b14474e12d922e66cbc230f31f038072d01e856cec0b08b0c401208564e24c46c39524e9de39d18fcbbdafde5ec6a9ba3b98bec8e147bc25f22cbd508483ceb8a58c8340193328e51195471e57a0d5717993ce8137b4270f974a3306188fe86ce26aef57d2c6569c21c6a8c9e91e5ad8572a1081000f764bedd877e49def87c1149c7d49df0ac614270e808a2de337c995d6a346057cee0505d47fd4829f6691b91646f7b21f1b3704aef3667222462c8469b424064d1c627ff6f2eb5fa6ed900bd373eef8fe9ac0bdef6abf7f883779dbf405aa68bc586ed8b58ed9078d9cd5673b0276a164608314c8db416913c2acbb87521730b69cf091d10598e2424b103e5d85691a8474b6ebff63d7e0f00754b0614801aeb0f1b7bb98884d779b417a73b5594c6ecba221576925887f31e112bd4f8e485e65932a7ea624ae81863d615426bce8529105342e7cd10f1024d06bae2abaf1ad54af10466d184ae9693c9204f66aa3d67126b58034d62123495dbd329ca01064507225164856dd2ea956ec3f49a44af58442f4526bd01a8d4b7a92c3dc9342789c93a0fc89a0764cd03b2e64159e74159f380ac7940d63c50d63c206b1e94350fca2a0fc88a3c18c0aa74c2c1ac567af119aede0b77ef87abf787abf7c3d5fbe1eafd70f7fe70f77eb87a3f5cbd1fe2ea7f5f5cbeefdedf79cffa818459acdab8fa627194d9f2a78f6713b757fca271521acb273ef76cef3d0b284f328886e4b1f5baa7cf400b44eb01f9a0b583a18c8bba20d8c0e013b2471545cd82f1a1fae4d6787911c43b7800f7675a3208131ae4651a2dc1f0e4b686c5a06ae2d08883909332f64a8c0e3b21bafb73a8c177c7ea764b39296a1dce64eaa3d2cc2b3023539761342985b7e152fdb187ac7069ffdffb9733dfa555cafdc91f805014abeeef8e87fa84f4b875ff540177f53eab2eea0b015ee9ab54aec0f8977d7e0e0f882478e1e32d9333921ed112698cd1936767165288513a54d1fae358a02c98496232492103e742aa7a792da14feee90d94ef78a26833a8f91a55b8f0328f650170ab0eff4f5780d557b8e287147d5619cabd95733a8c671ed58cc0d0d106ccc5529fce6361e379e79f1ef28913b7047275a3c0bf69233b7514204227314f1b3b726b45cfb2421bb8f5fd277e9b240a4544aaf2fa1d227e7d2101369a90df8cbb10ff83d024bd4731072831356c2116554e34de69342760194213a2b10580bc66501d79a81662cec48ebd8e5096c7fa3863d01c4225a95dc63d72b5faa1b9643990741484ade64b8ad2e81123f593f70d1738f7f46b6f922fb9c66c9b34bf38826476272a0cb2a19e7fa8404ea2643cf09d353c21759f02bc7c1fae07c563c06fa3c99640ebddbf44763c0a84f01932cb4e99578cc4c469c8fd598a42d7b4c4b3dba2c4635f07f16fabfed90f7ea8858c3bd89146947ac962112c364e90508b103d4769a636701716193a6d1b7c7e87bf648b167b3a86dc14a8bbafd7108c3210f76c33256d056af790d86233943189cf5e3fa170f708b9a057cdff0eda542d1060d724ee6407947341197d81d033cc0653932ff84d32dd9c51ce729d852eef7f32f79c5c2066aa68af2985934255e1507cdd25d449ed3e8f2930086b2e530e96bea4c3cd4f7c31a0e3b806f4ac0753a96dd3ef66ce3dc8257517ff836ad21fc827028bdd9a1d7cbd1a04b5ca401ddd225cbafc17ac75a0711471c103a28f1f26e9bb32e44412d86f9487c154f3f57ec9b907ea0a2b2b6e6950b98030e248e532cc2a11a4229187aea5e81a9bcb4b9f024e9aeeb006af73083226e84bf0d094c2ee04cf9c5483049e9792dd00df903bc1dff3cd9a3c1a24560528aaa3167691493399f818de6a5089ed1e5f81cdeb8117b1dcba725c308d3433014ddaf10ddf4c4372b9c5c29883f643133c49292f1e4c51837830030fe24038fb602054cc69a9d0942b2c323f1c0dea23e13036aefc6070279e238cc1e035ffe098bfd6898c8ad297e83901e18c598e6483bd305448a93c7c786e01650947c84d1784641ab57fe4ac50f31e18a81de89d9b20bd2b9502fc3a57c633a2b717cec4ea933deddaa8a5a9523cd492126aa79819e61042a821132856ae4e32d3841783856c766f79aafd4d871399c7725a4729116db4ea5035ec0161d9640c53658cefe2e75f523b1c1152a58d8c6e92edae878a13b17af3acec268c7b5a31e5ad66079815a773169f3d8408e3ea5ec34a500377370284434fe2a73d916141c2c478d4dd7c8c976fc29f26e6e7875a80346dd630f1bf1008db5c8c59b786fbd3c4c84f38f237f8698938b61504af1fb60d5a31620e2dcf3562e41335c1da254634c22e2e6035d61aee2c8f091039c9e853dc85ac6d201e03274d21f6525cc30259d5f9a53044b496168802aded96ee2867a244d39a2f22cc3f5a35785548e41d3e11413ce791ed2390d105175d34ab176451e74f9d1f1c79f7062422ad1f9d7af62333fa0ff65c4e33bef56b811f05cb1ff0028a6b39fd94598f4b8fe2581e4516854c70f8bd4391e68d354814e8f2a32ba88072fe6e6822a625c33a7e3b3aa2dea61174e9ca95061d80b60dfd785ea6c188b0cf65ca58840e083c3d547aede6243a1780903fd6b80cc4aded69ae7618a58e8f770499589199b7346be6d235c91a3e89eed8529d4dbc528f490cf4c82a7f4037050d22c1ba4e87d47c9f686f3df69c82c9f1309b264d98e494c7e13bb6abbc4f3e2131c6855285179b82e0237e785e9c0d5674e9a97cf3a0d5cf54324d164519e24c86d46f2e69d5a04ffd2138042e7765cd677596ae16d5c9a47b1ec07b847740f279ce81d72feed92215af141dbf5f0eed6d79266d561bc608d03c5b9ae156d4284b3ef198da83e69d774392e59b1e96f5687cb43487d19c55e9dc9de2abcf1d47f45e83ed8db5f0e167534d9dad4e60ea863b0f4ed5084ae09397ea6adf9bf621cbce2f46e8b402506d25013ad465d56e0fc40a933fc68aebea2d2d13ef9173b2d35f5f7573cbb5a57cfe5a26d278a032e72e782723d64c951a91aa7bebd9a6badd848526107539acc87d8cdb17b00c35d82c6d1001a40ddb34682bd5727a8b85fa030ba3fffc0417e90fa4b20b6b06eb60d4d71f89637e4ad0d3a943dd5a0db7ab935c3a758ebbdf94f20ce676172707eb386d17f70b70285f0ef4c3a457c6cc6c563180c63b0d4e274ca29a7121d8be8cbbba6e3bc4bcaddc5c5a39f0a68eb96b4aebd2079662c337f69c09913d141a0d19235f0119b6066c975714406e23004119b075bf1323fa5122479c0590804c1981ce0ea3df65deb86e305792c2b85f81936ad322daed83a1609730a44bfee79bcbe672cb4cd3709a8083ba0b1c520745b45ed806b0fe17b68c13bf63aa128febcaccd9f4073367871afd1357d394b2a1403496c096daf8aa5e1b05d3cc3a2001c2150a74540d0d963e65c9c3f49bd39f2d12f4baa976c898d39921de7dbeba178147d656f273d1ca9d35e64072d48a15904801afb85e63d3a7dc18e728309e3e1da6770d16d7cd67623a1000c3eade88e67bb019595f3b83ac33efd5ec31765f5ca9f2e8fe16f9b9d26aaa73340c4998431d1d973cb94929c88114f3c4ed686915b4add03ffb74e4ce26ee6ef6070c5343900e0e859920d9d9981fadd3fc2ee30763a8713cb8b9fa2ee747f20c7e5016703351672138b2ac14175000b9d88ae3b49d0b8c2115c0ca6016245683323c36135e13611f3bc88428a2b81da263efdbd8d9db4aa3de2d07cd9453dcce1302f2ae0d39c620a888bd830b3fbca1fdaae5bd15fa8d36efd4ee0ffe41ee4068694ad32339f48c78fbdd0ececf7830066f0c3f02c2b70742f9ba3cf3f609b76831b91db1412ff496f6bdc1b07d3f40ba829bc2a74a7fb026893b8cb9b68f6cc66d5b1eaea6ca4ae18f3a66284658999d8f188071cc7091b40127d7aaef880197367c144154ab6309e24e9b7597704cf1628d2a47d2a57cf4797db049e3b9a01bf5bc60a25fe6897d668bbd205d3b3e9ac4387d8773aa299bf12bf4d1e8c1bc84643e2118cc4e71d36911fd85dca8a258e4cb35f753b5b33564fd0c01007886e98eadd70eb1299cfbc14f7e60b2e47a60846a6b11c639b78b1b0a06a95e48aa8e71f58b34363032ae1cbcdc00486862e9a101fdb3cd18d93d34199cc053d161016af742b57e1aa0c502006227209e639a8786bc86de35bb1e95993fe8996ebb1fedcbd9083890af4d9e9d376734251caa3991c6774113858043989270f08d499385dc9c3f036750bfc95efabaad9535daac52b935f91bf203e1f6c8741d82fc9878874629ce703cc95a13d6721016f0090df771ce678c03930172f4036196ca09af213ff742e77311961a19c532a7117409f13660da2f3f3449283c0361c209b1e7113717a80e77ed6434ca72f7990ad1c43e61180c32686694e072b7c2ddd248bb6f65c9c85cedad5fc275b689458893e40e5f4c9b1f7d14142a97e6fb90568c261f70c348a2d731579130755382b5113ab2369977cbbb642f561e2bb16cfe240ced2e282f7ec7c3471d9e7d04da0cc10c6952784c490349c8efeddd4e52c3a8c9cd25c4f03ded3086efa94afd83537763c2416547d57f429af2c1841d11ee0d6319656d0a21ebd484225382268f28deb6e375929c1f1d6bf8eec4efe96d7672972f533caa5902cb4ca41b8fabd9a4e3c5f4b28dac1cfd37b6d372f7e324036e45fe76033aef2fe613bae432b077efe3f5f8e9d46ce7caa95ae464acd0280f7c3cc20a1e6a8a25b417b881d76e47d1a319c273c21079d2bc914f6b967cea2735916dacb2dec6b048333fb6f57b913daa226649ef756bf58f940c70a92bdbd795c4fe925ab6c21c0e5775827a815c46915bb0add8e03c33ea472c5707cf76f1e5c729a29c493d1bdfc7a9402389d12386381d86fe3228cee0a26c3761fe184114cc23e101479dad3965602e6596d1b8bf1783d032c2e75c7f5ce191802693c5ec8e0cf721291b95d258b12f84db549a71275a58a09e294239c0280cbef7d9c61a1139707c1b122de53e7987d9b7f2dc2e31b512cde74efdd60e94c81263347afb3eea2d9505f874897aade9592727aa20104026407e3f57d7cf1d8daea6aedd47192427b2bebac8b80c4c708db47870e3bbd9ebf8bc14b01cad21db592205216dc199b3064cdb6a988be6e78877abda8a2ffd9d2aa707c79ae952635f7caadad709d27563f41f3b6b7eb097253e2d38eaa9cf9df80c82f5331df54ba28ef2438f3fe2d5d31df86881677ee488cdf8b8545aa63be51f31000b4e987a12d9ecfa17e625d0d7f215ad2d0c54274f802e48a71132a2d374b2caf8ec06a4939f816c96164daadc0cbd9c81b7e716ba7fa4ec77810bfa418f30702bc3c1d4853e96649977ed27ec4ff35aaf97fa32e32d0439dae3089a3977cd27868d27341316c6297618513980861a55cc3132bf456596422a129dc2ce2ae56872cb8c7519c4ddab5d6c17dd7bc2ab49810288a1c257bb6bbad781036603e6e692966477e485e42e61856839c1538500f6d123f6ab8646ae3e502f80b7acfcca2c93c690c6fdbf8d49cd51835b82ab162dafd9a1be94afef409cea1d274f2a4d223155a105a5092a69f89028ee5fc739c92952dbbe7e5be7c69dbca5122971b4e8fdaae24b4c19abaf8a629589d6e07a54484531d67e5d3d45436ebce32621a7f068f530f903bd2afb031bbe5b85e17385b7e86f22b97dd79f49753deb827593732470d05b42a769dab2c271b2c22a5c6193ea2b943039b573d484b8f8d4f089b30d50983826710434907dde7b6b93ab113020d7e6e8945c7f772013218c2e275eb2637acac706ba02857411c161c5fd21c7fdec8842e2f6adbe40f623d82d3f1c2a86a9b4341664ee8cf7a2e31fb9efe5b372fd3463a58ac5f7de99385733e6d5c61d7a52c8ed9aa2e9404fac005b472fbd2f6c31c476359292e29508b355509b9f06a55a5d4c10015c8a653f1e5409b209237ee3b9cf797cb5bef9f36bf0b97b24b659bee7a49c7800884a180b6003ff7e07920222a9d2a311494422d314000c32dcce4151e94b01309ec9e56311047a1e248d86e384389354029eef12175cd27dca133a4bd3cb0581916cc9907a7aeda63ac3c683bb7d41ca69abb304536e9b10a20051f4e91f2ba94edb6d9f44b9f3598194e8862cc3afa374cee84e5e216196274de5bb63ca2167ccaf1fa2e6272d5631e469e25f2364b547f7996febd8b1ee3a767ba77b1ae55a98902a0388edf5e1281536b5706c7efd8006d9c764bcc64729d8f0f34f2da3dabd4ec2c328d7a98179163ca21a349cb03aaae11916c08098a694412d4124d85b7088ebda9daa7958669b105fdadba74bce1ccec1cd156809ff84124f291652d0ca0d33c690a40c0163ac6e7e5d066c342fd66241d2ebb37daf46fe63d74ba438e7c929c880a2772eba68d92b5cdc986d161588214409569c75f6fbf4b1ab6d9b10ee6c6d2d300996ec4552937f6730b514be2d09e5200694cba830aa99c007d8ded9d3073f705ec5ce25ecc839c05b2b160e29d8ef5671b54a81ecf6f5a5a88af8824a47a43dab66694c8ac9b5ff426393f926ec9b13913527470085e4b7f042f612527e8f103b4c6347a395eb1c40517b21a455a4a488dd1290f878f3baf75e9c91eac7792d1996ab2e72df636fa41cda449da4d9a00483d135112a8fb7751675d19c0295e821bf3409d4f5695790a972e227839594dfc27ce662712648a014945113c8f34985750eaf1e24994da7f2676194c91fa755d2cc6111bfc08e8d904b934568b414b9a7535b33df1c18373e87154b7b6a706b9714120d37508c40069d1ec8dadc89117fda4daacc95a4eefd81265031330ada549efb2c0aee5121dcc31fcc003bb45b9d82d23b7d54ecd5c4f2537fe34b4573f021357052352862cdc91955e820ea1a514d483c299e80d06827b6aeb473339265a311b002ea91ea0e32637b858a2b116acc56064f60202c7a67e242c1d224f8aa2c39d9ea59b8e6b4ff6a3090eac2393e4102e9599df2f4ed4f03714f305c455593ffa970be306cfa2a7cd2aa08b6206254d0e678a75584a08c31fd1d437100b847e0f70fbcff4e6df3c0cb9ac4f987f04a0572421fe8946447a275aa363082446c120068d802daa436f287ca329c08deb13400a2b35a2300a5041c59824583546669703f7e2f71c1b5cd0f8036ffc4e44030d2f8a5db927b78b955d0f80a25999cd5bc86e3366bcd5fb2b15a5849a692d3f4e05cf07ab7cb7ddd559cdabf8ce590473dd9b5665c619e1c954e8691cec616ad2c71c8e340cecaa944c9e7437c7245f3ed9d50130b70aac5beb1e03f604f952449144f09ed8bfc7cb0516afb47476b3c35c8d5dfe60cb5c209e3a67b35160181fda6a4c34419b3d5a4648ca0366ca5aad15605c5d1682c9f28fc6a4d5c45d374c656b46b7665a66c51761cdd1b223b5ed0a84d1b9bd37fbf83a429fbf846e0d22e350e18815a95de013d67d87c611eaba6ecee849d97af0ceb9738973feb8b82f17ccefe00362d849a170ade0545201c0accbd5937bfe5e1067d6eb96cd32cf9b2d62c7598f389feaa561af02fc6c49f9947a15dd7d4560d5bdb0f279ab1c619b7b59f14590ec2cc4dd52bcfb658c620b23cdd98e1b95b5c7e9df184201b7b54e763900224bda97e141b13191b8e96a76638d5b8d9a6f9a72e08f3d697fc37654d22eca311caaeb22f33614b88e795f91c721500a922d8d370bebfc66094f2844b1ee03a6f1e8ee76dde266843cd5911670fee20d8c4a5a380554efe2fb51b27380ad7ca4146c03d90d57ede9c811ade0e314039eaf6d7fccb460c7cf9daf9ac9b97a74904e0ef6a697c31ec5efb8878adff3968f401fbef879ce2dcad20411804e09801011cb65bdb5faa047823172f7aaa805c1bc5df704d49c4f6eed7aba5a087869a62a49f432f8d9b433d9ac7a3263c165d105a567d87eb8d48d6e76de2fcf64b1829c5b8720c000c8420f638ccf33a843017ff4152f474e2ce8a7f52999b664a80e081618ac20307ffdeace50a242866ad5df5f838c41f5b3d8816f8fb744126200bc383f7c8988e9ef2a1dfc9bbfb035e798420520159bfb5a97eba6e0c80225a0df7b764b8792d19c162be09b22b093e3f19797b681281642aa64194ba3ec41b838dcfa5c77fd9e9821a72a132297fc70039691c1762ad373fd6779d0649d9631fb6030765068ec7ad4f1c21b1669177cde3c3446973f1e700c0afd7e27b0f0d8155bd983aa3255c299c73aed8de1f14a8bef9d2747652a10bb4889e607331ee06db04d82cabd81114310e9aca5c7882c6c80cf7078f05a949a618b3c9abc20d764c917cf29e6857451a4498a4856acaaaeeb302978c19e1318cfa099c8376d0e17d22f7a21f446f586c3e7450c69c80dda80188d59b4cb31e89ded101c3ea152b2dc1e4646263de5293613551a83c9863e4b6742537a648deb2436c263932b25077f38924afb4a6e5d2c41ae09d0d4c200dd3f5166b41e6d1a30b51c0074256a70076e7514972a8a9c487fce3c586c8e06dd590e9e982110929238607e32738830ce271c1400e93013fcc6ec4b8fc88b0f20ca021310646d0db47dc66e453a8ecb7a8c1d91c49b8608dbb9184a48c929d44b5ee3871a1adb11c7e778383a1dd7ae87018c643402601a643ebe5c268f773032cac33540332a2dee276a2afbe4769ceda4806843bf75a6420d2113e1d01e009b847f60fdd385e8397f9fe5e6146054cb0ebc13c614338886345749419cddefc18d262d32de37000876cf0fe986df392477030b6556e206530095ea14eda1bd223844f08a45c4b6e2d361b4f3d8c4ab25fdc16ce6b6c4e5fb66a1eeff3a80eaa06302a2bfb826ee23091baa11965380505792e2adcca2a05f5143de8dab2a5fbc60626dcb22b418ab7702bc22d758282ba47a6aedcd2110200692fe4d04b73115d60a7dc17d1fa04c5e92e94edb8bdeda2453a596c0e48eeea7d5a1ba6c91e8dcfd692bfbe81822096dd8c6ece35a3cc286f5b73ffc20125693b70a171965568b8c93adaef04dfb8667b2700fd3d5d6f44a69b0ed4e01ca0b703833bca003de6083a1b718a513b57ac37926080157b10c0d588dc84f22d9dce3225a03e14f66f9aa0c662602fa31730fbdee868a838abbae2c466cb14cfafcc81b18a4d711a07dbf391c025a9927766df7eb2022cd6b4814322507c3c70fd8b94fdff5573bd33a696f62fb8da6899b9fe45c50dc819fd90f0dc5a8d48ac9e09fd1d60f8cbc77719a8963c1d69b39e2557eaaf165ed10da60dbff697609e8ae4fdf66eb6c064f9d9a740d9bfa8a59136dfd79febfc6912ec2f5ccf7359461154889d908106b21c42e4ec23394a22a6af954460f0325a3d14369b21e1aae26160e177e2a7ce241b984e70bc85db565f3d88b123cc5880563fbd3f3f55557fd13dd04487ace35f5337b6cfe5e4531a74c67774cb9ad34646503123e1e036d26f6ec97559100911b7b0ec7fd4519b7ee74778b7344af96848c2e45d8b470fae2adc467d2751293cdc6cc775e02b093d267fb89f52de48628b75b864a97da136bb4ca8f76c72cbc44f72e4763cd49fd9fb3e2c605ebfa229191bbe067ac4aa7fb3a2950aa602a7cd9be927430732ec8ab2b438784cc7fb59ee46f3d50324b52f3d61950716ebe6e735cd11b977e69612d84b45456e3cea2e835bd283b9d3cf7039c02f7c395a2c01bdd3119e19bd96bd37e5818e2b2ebd091b71a89ecbab95f4ebddc77bb31cfa51a33efb4115a1c048a3110dc0839be7b961bafd05042142742f0c0dee004d749ac19c233727de7138542eeeb34ba5d5dd809fe586ff3a412e8c7a9785c87c7fda720ee11f8b85a80a4d20ce06d2165e5c2c836d413af174d1c28b5936e60265215f01e7e249307d545875434b1602dbfee2832538e993ccf0f8c4e1bf5accd08ef28227c59c7ff298afac999e0683b13b9995c4f078cc8f80dce518821eb58f5ee64f02de36e99cf68fd027dde032c1a3a7e8213aee99e59cd765e98891f0b154896070894d42275bb14489222e7a24dd7b77ede2bb07e92b94529aaf29648d40063f7ee4dadb99cd0d071939550cd3203091468571e00decfdbcc6f3050dcefa33c2bd153fd9cb78c718b9d943028ab490fba408d7fc10871bd3fdafcd4b3f66ac97bf872ef59bf5cc5645984c85dcc2ff268bb90ea61909341420a14e22da69145ec6b1654ded3d0dea7de148b662ed601c169067f083c05602aa54bdcc224818a33d5bfbb493087660f34fdf5cdd7a8b4945881e781208b2cec1b2a21a4a8f504b5e529d514cd187f0f0b9228d70c8ffaf7501188677f11b0e4531860434644040e4464b29788c86d3f1058c57a8ccaf845429b5900f69e5ada3f3269bbb4acdff6766066be386559d7c835367dbb670356c6e8fa304c711abefa1ae8dd7ccaaab807f268af3298ea86387ce2aa80f2fe95c623a1cc1c5065fdbebcdb433ca1dece1c8ee70904c2e1c7afb4a53a1856557fcba2e5262a02fded1313a44e7254733fca078777ca0401ad8dbac0ca75c71d73d49025536793b92d353cc3d13facba68f9c6378bf6db5f38b63f23a2d393f9ab78d62bb3497b444eeeb705d53b600e590c46ee55f4f554b69c132ca6a6d88702c1ea737eea3bcb09694f2a4a494bbc36d8903969dcc1b63831047a6b0ec9b40bf0d7c197977b41aa056936b6664164df15ca44bfa3e54781f56e4384fb65f087e8d0484eaf7fc6bbcfbddc53e55e8300cb8d422b5e2a2c6d4ff817853fc828faeab4933025f634ace1104cff99b6045e36cc31698e18bd29b17c4827d4d8412e6c10b1eff49e5a66ea55c30346176726a0eb78c0a460d671d086a054ce00181bb30d1d83339148efc78701566a710d79776dd634e21c816a15f7ced515c793133e5e9d2758d15c59e71bab8ad2e06b2f2584bc48727bba30db95a4369ec158a3dd96368a60d906a66f971c4e441ac8828cea7706b519718b26c0801f13a4f8ea1f6193d5db621d00f3d4b769db7b025e0184b8111ac75bf4d76fb90a10474cb7acf7bcf47630bd6d654ae07b059d5c832cd84d6bd3f6e8312083115e1607358b148152dcdbef29e87b769df46d2aefb2c63dce2aacb7f8b2f1d1a552ce2f98958d12d3d31943707b986d46348b3c3ada822d0518a03b329af3bc2b7ba8ba3e9f0e54926cf8736e87268499d30cd7febdf11958cc45f1ad7bac1c363b39b93568de5c58a8ffdf3129066451629aa8514451ffc370995bd781982f5e994e0bb62644742808cce5bc073e3ca85fc557d5052311b9924fbe74b76d71ce5835dac8a548bcbaf35d4a6ec3fc1a35b7d908290354838bd02264ab73bfd534bdb036cd7640e4340ba05454789952fdf4542bd6cc115005c2fcd2b97707b7caabda6237b23527305ff7bd65f304d85a31c2d717b8b071dd22cee6347a1e89138967552ea27955b58a4c68abaa9ad04892a81b10f3cf993e470e7510aceb307a4dcc5acbd33a3ba3904f5340976af1d7db438d988dd610a7c9e4b63c27088e5c2fb6a93cf01a098fddce243d309eadcac1a7ad7d4b1a69bf8be07d3f29333cae5262d245158e9ebf6215edc96e9d36a45e5343845b30afd106a73f3af0d9592f7e19a7027627b4864d2ca17827d9cfc5e1e40cb8467ed0ea2f04ec34fdf9dd73d0c01b684ff9cacf935a4efff28374d152c2c73130583cc6c88fc3391da78bcd3941ea200ce95252c7f3e7c363710cad250e68c8b273b812cf60ed3fee8151676810994229a53f696ca75b8c58db065120b86cdc537259431c9d7fe33652a63078bc5904c39fc5bb48dcdee7c3b8e324d2f6d8555458341104a14184e99855cb77a1b0a1d3565e5579ae72c36365f19691d10be00838659202084ba42246ddb80302cb5228c1149c5414a5dff688828b41597cf809afbef3ad2cd75578605b60d564ef955dc87d76bc39abdccb1e3b4a8cc7b5b6e2cfb6a751d6157dd58d7b7d584074615e174b5d2764deb87a062756c71cd66d09c016b9464ebba735a2b39deb2d5f384bef12a5c386f0d6de696163b7001eceb85a78a312f9870eebe18ea4d9e4c9a23ad4c6e5eafab08b91cb47bdf4c50d4a0c4331b134a9448d9f32858b4900bed55730d01ea0c03e25216c2be843ac327b3b8b0a4ced81fc20532ca9f694013dfd2a393935b5dedaa6d6a5235a7ccc0fc0b2c33a990fa70a304b813fb78007177e3200eaf2ea92a0b0e8585c133d12fe37262df58febd184b59b00452ffa0e0b469cdf425f036d7d68e2846177a8ab9e56f7695a86f014bbd40a551a2cd73d4f1cc94a1f439aeb0f140d2fb29066a36ddbd12aa92befa14600c654cde8ef1cb4c52be85e93160caaab0ee78e1076cfe3dfc73fec4771dec200b47de0c44ee85ce2bf170397680249b844dfd86558bd1ead0d9587e5b65123cfaf73d1ec25c67ad1b3c61f4f38fb6d960bcb482c9c514531f3adcb2f5f0cc91280fd920090a36ea7e526a038c866aafb163399e7b90cbbddb07b9a16897ca3440e04d789d1003dd8e093bfdefae836a0502d1a6a2367ff8a496687eae4160f64bdfaa02bb2eeb5ee627f11ea360fd14ac4dbce3fe4d9d1b492caacbcccdeca6d2574b06626c013f57374b314d96746d2e20d17e85a56e211ef90b1b3c139879252b69dca557c6c2ff93691afeb4576f0f821233916a5bf4416e027aacceba10bee95690222c0acd5b0a936778b257103165202d259889cd9d90116057e96450b4ae356a65ad6941e92d133cb5c63170731414dd65f6f4b78ad04346c25bf88cc2cc3e7e0086cf8ed0e355623cd5872a5c2f0c039071049aa85f551cba45bf542c039b322ccaf42ebf850fd28fca1263763ebfe53663a3f749f8295df1db4bf7956a9ecbaf16fa6a0b3e37096f12f118b5cc465b5581757b2722681ccd46175aa2a47cde469144a7389b05190b51f641445ba7a3024a8ab24b7f2766d179529260d22f1ebfd1a9c2c8d0e01a4550892748f349389b069846eff1daa9910fb1910f7c4768bd040026e5e3fc78b2607a2435e3239a969b94925e5a36f8915b0eea1fab53124afdccfb0478fac3c778ac83369271b245eb3e5fdd2538b6b6a0fa6121b0de85805ff35c656efd5136033c58d12dc0151db5475eb85e857840b090ff99b898c03f368ecc0a7e16f7cf91a6c48619d5033a0cbb56f748755c802363561bc6d7e9271c9a179a0fb3fd823d7f8902972eb2a84941318b6efe00614cc123457f01d4cee96c0881e83dd866d809e36401e503cd25b9c1e84afa371a0151777e79ec8f0b7cbc49f2f322bcf04435e7dde7e325757838cd60b15a67b0c8152a1ee33e19efd64e4281e2a98a53bb4317b7b0e200f78f100e724a878b202f40ea720f6c8612986c3338ab3388412cb3e46e953bc7f5fd6acfd0aa395e5dce83a37ce7eef015c0f961fdd429f2fccfab59086a5f467ab236f92aebbc1c2c86b361ec1dd6960ed6ef435fd65abb398345acd5db80ac766bbf9a4ac1230d1cb73da482cf6c41c703cc776cee281c5d0b1d473821737b7137981de3f9909b9694630afca1366907fb4098185c75abf63716f73b7a63951a8b3b3fcb1a5bf1326a721bb2bcb3bbd8b3b71ac2ad475a843667ad7483f4cc78827783544e63d8fb615daba56cf1caf86745001de0718cc1d5cc87538d96f0df4f7939a693a875b4a84f11ec2652fe9d13ef379a933258a8cfc4b1168b01732271fb0dde330a1e3ede4fb8952e9756a0b3c5f933607e6fb44283018d609b1a2fdc034bea98dba435aa6e93daa0345fbb29a65640916726c5765a2d1a5696967800b5f2605ac0c865e7f3b3d3a67f47b2a5b182ee86bbcf16c7127c4daea165c5f728be8fd9b769f8991e5643433a48fec75a96ed93e136fdbdd0d9bb44b11b5eb1a2921c48ffc90022718c6e69609f95c8a19a0b5c0c41ac24975bac83879a20d592308c6b4b1439b235c302a27f31591f4187dea036cdd0c465da043ac6a587f54a23e978912ec6320ecf620e8cf9b25ee09f3b88b92d98825485d4121ce7c45e441c468b75abd2964607fe2088763a123b766ccb19811c488c3d9f6e832ac068f48b0f00c6ebe1a521276bf3b674a8722982d6d6fd1377270e9d5357b44d11fe28aee789e16cd6c0a6cca854bed278950d0d4a1e318782d926b36d825460a158c7f6ec7d4787d39bf1821b21e9440c212cce280fcb8dc188759ea5e6b48b450d53fe8e2eda8528cbd8555cad6f0eccf9bb6e7eb5ef65a48b9f0a974df47b7d92cd94d0317aa30b9cf850f0a69e4375301542ca5600f25a873dd0d9268b8984f8b37baff3da852a727c7c97eb555145ab507aea91d17a9462a1ce1e9508c9b15029edd16e8fa9625626a4235ef733706d883c110f49e4dce2d7071a50e884278ce2d9ff03037f00aa21140ad8b7cb3804bc458afc82fb6071810b3fa0c0c6cf71a38a16e129eefcee5005ef98df5d50bda36ec82cfa4d16ffe119a8d61cad760143514d5565437781e5d33ff6c9ee43d704760009cd3ae6dc7c931940b1d850c9220a5c446ae97a5d6a1c36b84c4446b651e6cce3abe45c48199dceb241d0cd035dd024289c009fcfb04986671d4a8db99de2eb01b712afc900882e4809831c9f0707f2972a26c1444c02362623ec72fe7ca632f126cf31a676c6566a4594c397a3f447ac8c55089533cf0f0e8d8220d8228cfaf811fd0439ed5f65c1810ec53149173312e07f45e4ebc4e4e9967b3948a41692649a42164381a73f9939eb61bad6d7524ef8ce178848c46de58771ac17b5f42395484b632aaf4e3d20b4370ffdb4b15278f834ffa3a0e384f7ea9d089e5ccbe574201b44ac40ea36a4ef5294327001ff9780854a82e7bf64d0d6e196f2b5fd4e7a0f4ce30be5fc99c622c0c4c0110767d78704b61758820a703dc0ff96998849d31e08b45bb6230f745fbdbc6496679bb7474dcf69cd249e8f4037d0a277a7d856ad7477ed06853aab269a303a723ff1e3f24e3e252d37868f0a504da616553d82d3bcb73b467d648b7087dc8b3d3a90eae60e3b40e8d1efd55bc9f9a5fae3995e2b6d108947e5c5845bd801f97ca522464cb5dc6e6da6d134d4a281a63101f77d8cc40390b765e12422496550a592a3521750cd2b06e3c76ce8a6499b54efb32839a87274406b32bfdea8c20282f8e0d9831c957e21d1d9fadfdb8e342f2906512fc942b5b5ce54ee4b9dc52ce2f586967c6549cfd07bdbc3dc7ad4c3188d8c5d9552211847265c50a869303a5727aa888e7381c97402fef5d786f1c23490447111a685e4dbdc6f17396dceef76cd8fac1f1c933d098b094d694655e11e4fcb4e82efdc443fe5045c5a4aa06be6cd84e7a8c90346568d756b37a76deb61c0e58f672db5775a2ee24c1cdcfe08d4fa985cfb6e346869ded1b55dbddb9df3232d86a17d42e9606345d6a5943d10a0350984525b9af684f252d55a6fdc7411ebb2010eee8fceaef4520f6d0d080e6c8e6391a632c0e6ca8a2acbf6f841b1f2564c51cdf543ccbdc0e5137ab528a143f7857bb781f1cf002aaa0f5dac192c44c57040a7236cd3eaae1eacc5a94c88a7fce5175cb213d80074c882f12267cf864cc5b2cd594524a90caf59d841341cfea653ec7f41de1c798921ae152ea5b218958f61babaf9ff6bf09ea3fc992c26d7e24ad1993503e6af2ad7f4e8194ca501d8f129a4fbdff4c5210d02a6b48c40fe8058646431a429a84259729c9f311c39bcacb96ecd51931b9daa25e79739f4e3e6727eb299b1bb1addde9ecd3c9255a2949b362c007382097c9e9f10e5347cd1e9ac322857b0b428b0f73c3845a424010ee17a766cfc1c47daacc2825829040bbf15042a4833a5df744f84e14458b4d01302ef8abdce1de1ed9e3fd5776a5e3be79da1831c46d098dea12cf672d1f8c10afa151d29918bd2c415f6652807e2e5a7f76e80bf0bb81091dac329f4b4423f566f482b6dcad07559f1c0eea0551400a17c09c811a48d7e7bc8eb1fb6b2eba77e03199872f847764a9e6733534ef690d9080ff408f0ebe19e2fed193fa46ba49aa7f05d2e146a55f9737ff45f48bfed122062e46095f88bbe8887ef90d49f4e1a9cc5c2f8b4f7ac4f7d1c907e642527cfa0f5f8a189f94d40c187b340ccbcb7c2cf3ea60f809443d9f10a121443c182b09e5f837452a8fbb81a49a853a0d15d7d57a922bf942054f01297f6121602c0425b7f5481aa43219bf4dbcb30250a8efff1f92d3f32ceecd06d29d79af70cf96e4f1d8511499e23cc6894ebaf26e6a5452df9e4f9a9be55e8d1a93d4a83dea5e4788bb354b6cf0fc1a9ddf0842f23dfdd13d8a30af198f7fee6cc796969285a5dd815030a8ca48d053151afb9e0ca8f9b492ddb21f13cfa0ce55cc44684bb9b918dc872332d1924893d9e2549646396e1ccbc8ebfb3dde16f195e205a4c5a845d7cb00bb40781259b288174442fc1c774b1bba2ffaab2cac18648992f1ccd119496df951c88f012397cec0598dde7615871cc6c0fe8840aad050a221768bca7a232b9fe80f02bb90f6982743afbdac1e35365fa98e51eaf44496a4d33049c86031a9dea875d30d6594d0789d965f86e0a2e61c18d036c3bd19ba5eea09ea50e04d432953762fea15531d3559d823336e482798b70c68b03300880000886e2b81f6cc624a831ffbad315746a2af3dfe05e3a868d5ddeb558896defb626b794322529034e08bb085208ee9209b1bb6e0c2855e1489353cf72479a179496bf8fd32de0f843226d1144d336b9d573e02a957a97aefbc9f2f8fda57a168861fe7b79172f61313f615ec24f01decb7c8f9877110e617dcccb84439c0cf9de9b38a7521f036277a59e056277a9de0355f8471c7976e00f79a4523d4ebba8c2fb181ce5133925a9e4542af51324a20269a783451576bfb1805fea59bed4b77c0cf8a53ee665de03bfd47befe23ff04bfdf72f9e057ea970c7d401663d0c2af3d4c5d39f46fe7af1d4fb8979daf294e5e9f7f461bc702133ff5520cd1f987a1698fa18507aaa7b9cf6d45c81e3cc1c0e478994f16f1b903ccaa9bf5dc3f25ff7314f04e67dcb637f7d2ca18eec7bd67f5df829c0fb98e7e17d4cb8e384e577bef0eb9e08cc6b097978acf0eb421dd924f242ec2e26b08b9779182fc45df47fc7098f98ff7ec7090fef637ec7098f17ef3d2bdc71c283f530c21d1feea2cffa2fdc71c2e37b56b84343b873b493e32efa2f92887e944594154aa7312176177e09c354c9850c11229926e357813fe411d6eeaf80dab6ddd46f620cebe70b65d9840a7cf238899a50c12c6f93f3e6cc5df43f90f5de138111818144605b731f4804c60289c062c009f374e8c858a1174e2a79ce30c55df9b5507f2ead1aa605a7ef9efbd27117fd19c629eb190fe051ccd4a9f6f4677840d7847e7ad9f5a976ad375b6abb3d48dbfe4d7adbfb227a3fa59003a663873f68e41bfad0f1002e004a22e51733c035cc097198e0512eed7c7701b60d0cd233e62efa960bd22e0aa4451ac3a3b83d45f96b0b71347d54289e5e5c304387a0086248b6da1f3659037fc0a35cca408208d9e99a188f06afb4280e6c37742ccb97394614d71ff5327cc0a2bf869cde1ff58f1aa2fdfd9126a3c24f01f6ef7ff279d4f7af5af8c99087fdfbf62fea3709db9c8bc103ec5d0c13a6bd043b7ff283ccfc71cf43fbd3773ff3053ff9439c6c3fe4f4da0f71d2fd10ed4fefe027c38f0b799c5e0b3ff9b02de4719ffe101a7e32fc16512fba0b45b302fc6dafbd847d9ba6d13f7d8f21fef7254ca2502d5493229444a0fe0faf5225fb77a7a3864d9ac6623077f72a55605da54a952a28008a4093fa367895a6fc434ec94de403cdcec76440f798728a9c32e50894e6d07fa16b5499feb6529bc49ca7bfbdd3f1e98457f6c720611d7ae8b9cbfe0d3f1dfb5f0c1266c31d273c7ec2ee7f3d26ecfe84ddfbfd4386dc50f48a68a10df59fdfa35f767bd0d6609398930874bb30bfc3cf85dc61045c6822734aa008264e98b4306a82053e593621e49391b26c8288a17cbb4636c102992af7e326b1fa9201588631daa386311a7e3d268cfe84d170c7490c72f6983af3a7165bcb1a2c6c521299f300483e6c4747464645454423067226610072c0934936f27964239720e2414f6c12f95432974c2642fde2e49c48a6aca9a9b492f89e1719c1e40c3853a8c5b9b443f7186511bb72729efcd9833ce2e9031e1fea989126b79216fb451b50f40cc0f7dbf80bc703e0fb6900d23535be3f48d7d098f132bec6f535e2c7f89ef9fed9d335287cb74e08c1ff86f1fde2dbc5f74fa13914f3cdfafef904e6fb2794ae79f9fe69349f98472ddf2cdffd33a96bf0f74fa5b934a970df1baa67f2e67e3f8e8ebf68fe868db70100690000aa00d2e0aefe1a201030488bfd3340212df6cb007740119c3270ce66c089046c8153497f08ce252df683e0fc797032e97f01ce206690cc07cea17904383d703e01671138a1b8807305cea32985059c485392fa53e0a4024e267036750cec13d8b34602e2e884a32c92479ea50ce713368c704f2bc91136b1f1b5da08bba79774d3e853948db331051ebd08cca45c037d6ea604f4479b15ba66fc319372d8e47ed750fa14b4c9b1044cdf05aac36316dd8a430783bc41775ab8d5bf470b4ce99eb23a355ad8b255b745eaa0a5395aec1bea84325c7184878508f2287a7987ba4550502c8845c4f2de6af7f4be62f5b07ec5f5b0582e2d2fffc2f22d2c2d302c600b4cf8b900f3aa6701e6a545c502ba7c0be8e2c20a776866ad42215c8f6a47480f12ae67944c3d5c0feb715a88bf58210dac984f4685c3f2b205af6c2c20101a587e94492c2190205d0384e5c79955214b28694875a9ef680022a487eb51b13c4ee3e095969045055259c3512f43a93d06c714934c62c1fb13386ae1ec219a3d4439886c6c609a9b22060ff8f348696b62e0c11df00a8e93b3c9508964c5d20b15f86b05c5162738c3a3249a4d450a031cd279657ce2f80944a1266b06d2e8074efcf8e0c39c53ccc9c1c9215a2127076705fa72e6f9ab21f74d6e9b44a1b64749140ab5a15e6e1bcadd7d3ad52c0de5db0751eeee1beaef76effb751ba44883c3266ff3044416b1ed01d95fd6777094c93f64b20b2af057aec73ba93c01fed38f58c391c9f51d861198d6170e6d95453b6593fb1594714dff293201bf880506f2288d5644983b011eb1d7a24dd23489c043041e2210414af9389f71c09c16e5afd0a2cc6951d67cfd14fce8cb5c6905390aded5e32538074708f86964def3686c69a971401e3cdad87072bcb98804b84915fd5543cf06a1a21058768dbc225f91373902c941e892e53ce78527b9bbdc2f0965a9a3e02088201c346a290a0ea4b49c3638c8fd384f7267af762cc82b9ad4f7ce0579459ef567c7c3090da5ccbb6c76400417e4511ead3ca86200fb807d489244d6b7dfc38738e1314476b36e46c58e1e7ae83144f61822bdf69a284f20ea9cb5d21f5fa7d6b9c2f5561f252528509c10c97b3a695af855a4f0bdf3745a2541c315818194268090650164d904103f36aac04e64fd390ac4d24e870d5bbc1ac0e3e7ae79d189a445d944d3b4f6511a116120043a902571916594a522923c7992244937eb66386de307742ac1329e2b3b88672d1c89f4df7075294b8a944de41f984be015c65cbdb749f7feea42b16eb683c17f9c99d3341947475568f6d46251e19554c7c170880e905314838e11037c1c396006a41fb3a21f1a0fe6c565d5c2a2c22ba98edb282084a9482c7664364bd2d3b3c48708cc3c05a1d9d3e39554c76d2718ce01728a62d03192c1875684c846e68b6179302f2eab1616155e4975dca6a16e9d470c191922fa268a08a9564b354954799e0706cf0b1e173c323c1f4f0c0f8bc7e381e179e171e159f1b4f0b0f0a87830cf0a4f8aa7e3e178361e14cfe5d178a638e239426d8eda5c5aebd6de6b51d46b7b3d692fdd9eee448231bba453d5c4550b8b0aafa43a2e061d2306f83872c00c48230d49370059daf1919a3dc9a43a6e3bd1d0dcd8d8c070885ec83922a6d9935865ab16198b4c25c3b215594ad6c938d9263bc950b22bb33b42669229d632ef0e9a3d55994c2693c96432994c2693c96432994c26939d64329995a14e1435edcf1345518afa79d2aea596d2d3b5d49e50a8d3adb3d812a59adb7a51307eccf13f1600fc5112853fca24e44f592803652f83217b21732193917db218194be6c960642f3217d94ad6226391f5e0e3f3c3cf0f104040430405193134940411d11245454e18194d7174d40324a48945922cc9518f43e7d75a47f9add5a2b45ac31b50da4f79834ed76a45698e428552d6d0bc56a73bd94ffeede10db72d509ffac956a04b00cdf7f457b321385328d85a2c2abc92eab84d43dd2a917ed0a039b1c9d61cc0e9c74b7f44d51fb9fb632a4bacfdc8b2fdb8e27e7ce97ef4523fc6acfc28837f7ca1faf1597e0c5b7e6cad7e9c71f9517cf95106cc8f34bc1f6db07ea489f95185ef471c323fd6b8f8d1e6c58f3793b67c01a2ae0b9005cb80319e177e0c284364812ad0d0dcd8d8c0705ec003e414c5a063c4001f2da003666001471a926e98372820049c45624766497a7af0d9c0f9c3cfcf04026888a02023868692983788e68dfe191eac6d54dba8a6c979baf7e5a4278a43e713ea711a154a59e3d61923d8c5dc8eb4e7a0d2aeb45b7936b79291eeee757a1136ea2da56c49e70df9efeeb4a607e09cb4361ffd68d8637bd4b6811fedba97b0f7b96d8fd3d73b70f31f361cf8d1104715d8fbeb4a577df7478193ebd1b02d94a826a8afe037ffe6f928f09ba19430da2eff1823df8100cf1f5f6b3ac552ee95262a19aeb744e6ee53e00117718ffa1388fa2d852458b678aaf659a8f5bb7aaaa7adfb2d051edcb5eadbcfae037dccb0812f8ec03e68a64184a860f8371a5a14faea9aa1c5f631830c92a60a3cc449f743563ef54358be0b53a10c79b084aa504c82ef49cce32c4a3d4e7748781a85b3e8ed7720c0b509f8874db6ef33bfc5cf7f6d38672df6b448916400d3f0a38308f19b83d4fc629c8b66d19c4191227976510e13b08f195c3e1ac0331ce7516e59246f0090fba791bc915ec30f2f4a2c7b3897b84e1db1252108543869821415fc800537960508565e78a4b9f3e88f5bf4b0d773494a2b6e307483288c28e900a50a169c0d2870ff88fb67d29190cc4ce693208db3842de916718f782eb916db251282a016142105500e3f27d09a3084a1cd69b5da0b9e8b99c6c63b80f30a776620bbd213d9dd7d09ec7def0d470de6ae4483bdff9648238724a400b34c51e980f113ab18d440f582232c8ef02acb284018228f9f04849fdce203202419025ec9320a107880c05b96517ec094c77ff1074b3f88096d7e9a94d28bb3bb7b8ebbbb534a414aa9e6d4bad35a29cdeede6ab1bdbbb57ed9dadd3853da27cec1cf7386dd7d726942a2f5e4d56a577e8e0052c8127bfe21c5f98b3ad913aaa236ba71957aa5cea1babedad53a54ea6a2c1c8c57389745d2289f50f385699b7901d52ee7f461b8bbbb1002cf977f8209f03c82e7d3ff29d8f3fc14d76d1bc7d116a5fbb6752869aba6d55a6b3d597094395d14aad65aebc59b8ad3b854b7b2625964cda3bbfb76e486565569e1b60ccc01dc307390f6db35eea194215d71bebd103922cb9823e413d93249c0d35d7a2b9a16ba1031b9665269a542b432a9b452215a9930a1b5aea032a19a11288c30b9468a30328b3082aac288bbf59e591ad6212298acb80c517fb20bacda95b556bb2d940526ebe9e6a0e162a8d2d183b4f09e542714b699403f9364f92ee400668f0c8e7ca55edb50e0b66d0ad060a71088bb82f0047dc8855a6c3fc2859037faa110d1774371d75f2b30f9277086ed45147876d2c671d25f29981c5a111565a3dc1c8f99596743d3d1d0bc90d3713087c947819e8bfe42c1a4bf4e303984839280ef55b170d2dba779c8f35b07f99a3dee9afd4a7541f390470f9a3eb9df87041e54875cb48065c8f320d183824e288107f9eb05af5423ad5747c0d49b57ce1d0c81eb1e1e3ce1ac684235673d3d28416a0e11155d91441ea7d17c22cfa32925cfb93499a614b52c4f6ebe1c1b499eb37d60642872f7b4fb3b0505b97baed3d1ad32f75ba783e3f2b65d71246f3d10226f8fea746c18f5a74e076a06f9f41249c7494ec9f7b54ec7a5da2a6b52640d090559b3a2080f8c34295f373d2c0144148359ccc40e96208bac586204a300ec6028fbab4b203bc212443158ffe46e9eac10bd1d007d03968842871448017485104c50021128ecc13081c7cfefaf29834a41caf429fd5b0511993e8e5245159952249490695121041b524090e97bef625e91c17c228fdc1432a0e203995222f08869f2b89242897ca7d880677dee4a074f78c04707588800e90803b3d9d1068c1875e0e7a8043c1009cd8e90d000d25114244753f4301d61b1c30e28988e9836302405068eec78e2480a1e8eec7082488a101cd9818323294c30db110592144e663b72002445114976e820e62e1f5c1fc4be24a2fd89c562476ac0c32c656bf00449aeef2c328c020111185502171460cdc910b8135211611512ab2d30ca899512523c80352c5445389565941a14a1c5881a54911329168bed5095e8c9b5d65a7fca1ab0a028d7ef95104a8ca04509e6b28ca2c4095ea0c4101e02a630317124d32ca34c81228ff8b50fc452b0c407cf902c65ba7518c7a42a55ea7ec7f278a6361b51f07dfae011cc3255aadf897955c883f5de7be10e4b158e9228c78072d6cf29d516031281792011588c0e958de8ddeb85a3c71ac5d4d591b9086537dc1d478b6e83365b1dd4b5c2164201ecec02656ea47e42e42f3a2bea1a7a744467ee4f8fe69c3de5be2739c1b6bf85fa25611de4af460ae90c860eb8f36dcd7a0aa5f48909a545a3301c6f8c70c4628c979d8e99c7e99628b6c496d8125bad274459e0d6d324a54973eb717a6b8132cfcead704eb9c18447aa01792381995bf22685dc4f95e48dc458d6a59f99096992143ccea5bc8409d7e9983f4b7aa6cfec49bab33be1e752f81d7e0dc3a7e1883becbf94e6a44b2f0d1b690a55127190d4e212131ac95f74e62ece9b61fff97dc4141a82617e7611c953c9cc4ccb5a3bd36ad518f367c9d77781482cf494166f6ea4ac71f3dbe4acb5744a18bd34e952902acdf76ffdccdf3933a7b7e6ccf48ff1b283c1c31a5a7f730c14c2166e166b27d59999f938b4c48f21de9f9969cdb45a4b4e1c94274541080505f103c4e4c795f812f771251e124fea9aced9912435556a0f5dd662bbcf94a82c29b9775223f98fd265178324e2310052ecc689f8389d927a8a26791e82acd1f4eb7f2d740d3f1ab240f30da9ace1de7a6fb57ef2e6e0d8ba69d99f748277e786d3dddd9f823678be2db0c9cd6f960e6000214d7257e77b0352267735046ca04af4ef537a9dd67ac3bdf7ed0d6bf0bfd9521664c6c16948937aa874e00f998482127c69849f3f8d772199a144972815ca449bba2656652d9a44a9d0251748959a70f82880f74570268c01ce8fc1c184e74b14c0fb159c5904658e01ca3c03cace9fda16fc290d8378ee991307a7fe74d635a75367838e195b6388bfe3d6905b3f3b1d4954892eb5488532b5d8546375764228fbeecb3c694f69b1931aa945edb5ca842a5126dad462accaeaacf65425d5bb8104770713d2a4169b26d1a872d0a4252f3efc85dba57d1fb5842c4c4937d44c9350f7dbd153b2a6d91f8bc4735e586a49d463d33db448f2e9fee9e7240c2df4cb0428e1210441fef8e1b763669186474405288fd20807cd8f3377f8ede83ca750b1b97f3a28c78f8dcd516e221fb46813e4bd187bde3f150a72f98e754dfdd46f96bbbfb2b5d6ba62b195e59d856543d5253a93a4203de121a1d135763ec99129d1148af1622693bf3c8f0906fbf565aae5eddb5066179766b15cba533039c09727df22d95fe6bd4f3d0c389b2693164d2d3ec803e4ffffb7c9ad56acfca0847d16b4e17bd9adc4d58336345aa42d936936c552bf4d2e054e1d303030dfafbebe7d99cf7edfea03a90bb20cfd6d057e37ac56ab4fad522bb0be05bf0f07cfa3b5fed6596f572b6b57a9effb0aa6acfdefb3df0ab4c1f3f7bfb14008d820658d15183601b3b57117d501014aedd7ef77f918975adbe5c11b6aad35aca1bd7701dbf3c2c98452c94308350135210494dc4bb31f265d3381a864fa4d044dc9f4675323c934888c84ad72b25b8523119fbc7a0b8e739412850ee0ff2a784421d78ff98ff5328f41aad8f254a6e5472f3f28765e03850fe0cfe5bdff5ae8ec859f4bc802cd1d4a592346e6efffbfdf58e083de129845c36f91fe17ca8e15baf0a556f62b0a4ab08541c5c5d3174f9f3e8dae89f9fa2cef61fe65f5f473bac6e5e9378c17a06cd105285b94f19a70cbcfadbe07c684413ca75832bf80d90365667de0ec5232b3be5f8132d7ae85d4af566110cfa9ff6aa7be97ba26c582f17ec7ccac2fe677ccfc3d4e4fa6ae49c0e782fd76f90f1469d0b0401c1ecc8b0b98d3225d852bb4b86a6a91766c32b548bd2d704b38e2c9941f40c3a645fa2b600e9c15727478b4482965123b2bb078a958a9c7647a668a0e946c317ab0e907658d1f1162044620c0a38de7f9396e30611b7993429e9f43de749e8ff32f577c8722c0adef981d64918c8d2702a424db9dbf6366293281fb4f7f92b9431c3ccf4721099ea14d8843a44193638524601d37729f46f37568a460ffb195bd65826d9360a5c161038e136549943d1c71b27d99c7dbe4032804fdf1622ab16cc31a6ad84e501b1a3e8c5a6b9552ca3ba994524aa9a3ce916628b64929fd0ee9fc16999c63f256bd0c93b7eb925779db38bc92eab8bce5ad65c5b9bcc078ac986f7321e3e2850b34cf5fe571677e7b0bb6eddeeeee7697d9a9c03377774bf795379552dea6f2e640732771ef59f764f92f66d86ad77f2e908f74853d84ed768dd3d098a72cb3fc9ef402b256292badb45229a594a07b75f7bc5a64d581160c442570f880060e722c4106051e097006be1271a9126cb2e06a0e383970581eea3d00da9336cdbc216988ad97f15698133b40f326658d53a794527797f54777f7eaeeeed49dce4ac5a96a345e5c562d2c2abc92eab84d43dd22b118921e9caae6719b8692218a2ad0d0e4306263a78a2dcfe5a958c6086a5226ac24ea5e168c633c2ffc3f699a466356836ab5547b12820fe3850b992f86e5c1bcb8ac5a92f4f4f4e0e3f3c3cf0f104040430405193134940411d11245454e18194d7174d40324242c9292844e557bbc92eab84d43dd18748c64308244b3278fdb4ea86b4f1475a2284a51274dd372d8d824e154359954c76d324451051a9a1b1b1b180ed10b466ab0d250b7d210ab11b6ce39e79c73ce69ad0d6fd828b5d66ef752bb6df4743a713325cd9e70c4b03c981797550b8b0aafa43aae482c7664364bd2d3d3838fcf0f3f3f41cc947a347ba2f1e2b26a6151e19554c76d27d42d128b2199b9cc7ba4d9938dcc17c3f2605e5c562d2c2abc92eab8edf4c3cf0f1040404304051d316341108e189607f3e2b26a6151e19554c71589c58ecc66497a7a7af0f1f9e1e727082020d63c2541b3271a0fe6c565d5c2a2c22ba98edb4e4562b123b359929e9e25b31f70cd0d3678109cb93ce79cf3decbddbbc5265be3f74b6604d3971979188f57a9027362df080dccc9fdf1e2da12752f0bc632de1f8152cdd6396532994c2693c96432994c2613c25424163b329b25e9e9e9c1c7e7879f1f20808086080a326268280922a2258a8a9c30329a22631305b50d6491ace1326b5f63b9c6fcfdc707f9724997bf24f20420441e4fef9d8e4c0b831a885acfb4dd5b17ac457632d97e9985ace11fa532ff3aab415d7344bf662c55245e9764af42d969ad196916d9fd2915da949d66216f6691bbbcce6acc5f4e1de4cdcf25957c69eff74756f6d145f6fb238cecdeaa505abca006ca8cd8c0eecf2eec54a5e45ae42eaf46d98f5a74a32e6ad1fdbb17e0feb18b564bb451596cb235d29e6c276764c10b4cd82c9229f4b76880a791bba49277a76316c91a4d3ff9f9bff653c3711ed19f0ea2323aa3b116279306ea9f5934c326d6b3a1c450a651b70596fd4339100513283aa9d47f3d1e859f1514c5601aea3ffb55fbe8d7630b3f1bf6e0be86b247f879d8847b0943bd167e1d3639bd8469d75dd30852727bd95d733e0a14c215b973d8d8e0b8ab43197cc8a3d8fdd9366e0b400cd3f7ffad4122b00912816df2c8870c33cc943c804652914c3124a8d335a2c191236b3a3c1af4de5d9a0cfb988186d9af3d8efdfae34dda5f22b00a1281dd17dda53d119805bf1bdec0a3bebfbb34ec2eed6d8841c2ec7bb8b3e3b94b23021b456dbc19e5839a3e4b807e8a9ee8d78b1e8944111f26a1a11564247896c8a8d3d590e0f9c9daef74cd14ca1a11799c3289c4054c90b597a16b50c8dafc7197f65593cd2077b978f06cedefc5d8a6ca70fdf1970512425f8369ef5a58c344aa5336d97a5102f76c8d603281fb471b04d09c42d97dd65649a96bfadda9a5d2a25b26bbd462a705a63fdaa56c7dac528bfeb3f6182fc09b4fab94fdad120b08fc1e64e393e74c0a9eb327d9df89ba86be5542ba5c5e7933ad0c40d6c9a964771742bafc4f2aec41a30dcaee4176488aecd6c76d516e22bb2fb5e81e246bf88b54b4d03ca9fb9007b5e85aecfeb0c9d61cc164c2af5fbf7e5d8925e4d3a61dce252d70ff38978ca6d20b80f0389594bae65940e0f1668ad41d53c2d946deccc8f57168a8f5a5ec17eae45a773bbadc343ae0cedc6fb40a40d6a84f63051895bdabd3abdabfbf3ebbc3fd29b4db9fc2a7271d19b7d100a3c2938dac51bf5584bf16b4b7af556b6bad154bf0f42b48b42d115b0f8dbca99f43a6205fb700f64701106024408e190dccfe68802c3fc6da8cf3a204deec072c9005b241d8207b64a75821eb639fd8226b64a5d821fbd335a3fd406ebbc432b11fe82e06090bc20659202db0ff68817237929cc91a1fa54b41424348af194b474031ca4da94f23796ca53c93377366da447bab3d0902434ae4030defcdb0f9da423ae4ae7efba30d2d07eaa738794e5c4c67f2d84836b44183dcc55a82bba7674ce0f963cfecd29602fbdb8b1278ab49a7296753b10a3c7aac3bc8e2f76a117890374a6a92bc9991edd7257933fd45ad0ed9da1cea12b2b53feff96be5bbaeebde67e89f0a3d6f0d6107eb528734ac4beeb2efcf5495b4ca94eddb9611b82849c77f7635490a1e3ba8a8593821b794c05f0be1775bb4ff692ff39dac2af0d7a4d38f35e974c1693f05e6ccb183b2fd27b27d1f39feaa3a907286e3b5cf92af0ae4c99b599b00f3e8b1f2e8716fadfd0ae4af1a04e9b27f526d1528a912d5a4f179c8f64771ac47d9fecf5879a08516a826c91af6452a5ec2ac80091b926d5daa4944c06ddfa5871b42ca175c21d42d451042cc59f344022285f8ebce1990eea734f4668f4713059630091b6974c0d23e4e07b13dee9aac2660212dce9f34b93b083428420b17002159324bc2405a8c718465a7a30bc2a35c02d23512c95b4382fee6ed80e5bfd00a01d60973e64c6a4fefd39f41f7ce592c460fb0ecba22622777709be519f206757a0e941caea1122e84f183e5dfd79c7e8e8f1774649881864b374a25534c43bd5c9ff4eb4362c209ee07c8672cc75d54b24a20bb2060fbe39c4d24ad2e09cb1fe70c075d1e6cfdb2b5cd396d10a3c0f7afbba6b4321c25150d14a3c03794ee9a3d9bfa257b3271799c4cb8918cbd24cf20f2e6fb537ba894d922d7cfe1d1af9aa0284c0822e3c815db645913753d4c10b22debd70be4fa5276aa055c516548816b7d4ff417fd205cce13df08db94319168685e14268b9ac6ce4a6b0b06f029cb284d4668d2248456130f9ad0d86a6d9540f841e203265600844fcf95298153e2083c4924144650b246b8006eda415315344d36681a8a59a28ba54b111e3d4f94791a3436fe55cedba20c8d30b4ead1d17171e75e7c84634e8bfd04bb3ce1695eb839e151a82c5fb2e49e2f0a4c21b246ff09e0042414a3272dce237ac88eddd354722f99f9a4add3e1b9bb5ff05993e52cf071da41d64f10fc625cc8c078d9e978f1308ed4624f29477302f2e684ef17b2894c60a5cbd12529d80678fed216b4c7e889e6868284baa889fac9921f5607669e4eb9f9eee15e7af7d2e94de8492f42e091cea8129fade7bbbbbb5f7aef0deb92caa4474995d51953938b18c81b9f39ca7d247231b8907b7ae414a92269f1a8c53e9292023cd2d918e49b2a29c2d3018f74e64ca9263cf3a333cdfce7c9b4d8dd1bb5d82b9ff21fe99d4f99744d53241a100d6a9245eea74347f4d327fd5409f5b9d32952d7487121e870147820f77b53d7c840b202cffcd8487d14dec00805570aaf82cc00c081a46b68bac695744d006abebffe30e99ad7f757a0aeb1e91a017cffe6027164ca7948eebd41422dd280f37180361e00e08c5701b4f1298033de410180f36d401bff02677c0d68e30300ce781870a099c685036c3d0040fb2a80ad4f01b42f3b1770a079865d14f6d0dc71bf7eede83bf8d170077d193fdf3f01379c1fa4738dc76918648be10d19329ed2194f6584367805676c978349028b2acc1b369e8236fe82366c7c77337e763a549837ee6ff7de2082a3c58be3b2d33163c65fd0c6df00472279c6cb4ec78cb08974d808fb498d508579e386e34e38ddb65e76b6f5136c6d276e762acca0b7a8748c196a92d4211a1101000020009315000028100c0643028158240d6361780f14800b79a046744e970a63518ec3308c8218cb1103880106008800c8d0d04403e1a4b7c526eb722558ba2e62efda67a563cf65a89dfa358934ee27a84f0bea3e46d05e37fe21b4962b3e359f5665127c65f92b8794f20e42a4cf3abafcf07587011b4f507765ad2cc166037cf89c0c04f2b47ce904f79d2652fb2391a9ac6a4140cc0374d70fc87e8f1d9fda0a1467deafba2fba6b7de5be086cffea4e7378873dc4ca80b0c886e3bc751f69b35a7769d926cd9be8c3bca3821e83bbf70f56e2c4cbc3f3db6e6941761081ba3f1dcca92c7fc3624115e604a6b08fd506f437052fc83e4ac61816ab6f3e56bb7813f21caad608267c1595907bdeb87508f5453f1fa305f740af64fbdd0301badfdee7657e4d5ad764a40aae06b0f79858d61edb8f10068e060ac827f7deda1c123e08eb13a96aa2ddab5077aa16f4a1c36a94fdb6d02c1241da2874bfd0710663696dce262e4aba9e2e5792872bca6fbf154ebae30462d11caaf431865410a18b9163c1bfe717775eabb86d1f8b1da29f96e96f561cbb3816c295b9d64f81170f176c916492192536249c4421c3fc94b2bc6a896f43a5a8a8410287e169070f232290af1f1c2676aa946f869c7350bf4a0ca2747323186ca913b4461831300d68e3843393897a75fb436f16699b003c4cd55e1ce8ef2d1393a7e79d97d3e820f8e012bbc30b89dfde941cab6e778b78c72cb35a01c47ebdfbf4add652770dcc2da9eafb713c275b2e1bcde330bd7c7b24102e252036ddcb834e7d876f154826c77e26478102b7cd35381d3cc2a940f0ed9eca96718bc5fadc25db1cbebfeb1500ffd84888ed8ad7ca82ffedf31a1da877993b3fba0810c5adc8bbde205756e233ba7242906f0ab9768b2cec1dad488216735184a8298db86b1fd182551e45bedeaf19e4ee3a3baabfa0cc75bbb9daaa8e80b1e7620468ab7a1d2c35a0620d0e2c0686d5572ba16a5ab103d8ff2fc55ab7e7f87fc809cc04cb894b09ac65aff91311eb359dacd45c6e816e177fbee9c42f1f17bf3a22871ace5ebbcc5f6aaad2d59790249bff3cf150012549d82aa2abf00b0e793951e538b95e05bfe861308dd85211760cc36ee5aa6438ecd9c92e654e75001cdae40d8be13866b6a4dfdb4b88d20c903b9754d02eb76e5cd70fe60727d3472f33919909cffcdcc140a3fa69842a612686a3e68318ff34267579afe11acd14ed0b6bb28e0241436abe9e219fbbce1a9b964d7ce3b1142b407befb7791501afe578adc61c605bb65734b242069bb41c61614bef453cf3c2f3b95e20e014d7a55644d78f9cff2e805af9396d2acc4695df0a6dd3fd89d27c6dd48bfc6ed5bbdaedc87e44e443e3dd5f8c5a67f384b571fa0c5458a162109cd62f0fce96db4f58d6a09970397dd537cd454949b169c9844b9ae25be2386b57916e2e159f2369f0e1d7833e9236016c6d7c8b0116deaf59a25b250ed4f7ff71ec3410ae353c9412e2f51fae5a491a9a5b4e2fc013937f6867d473f1fc2b46cdbb8a69224833c5eba31a2b3ac911efe8093fae2283a79a2b8b9bc8cdcfcc782720470f616cd60094295b9409a227253d2c99fc12b3001651e689b4e845ef204ecdba4629253d6184fe1b3d97b008d2791627e587dff48a399b0f2778a056f6296179f4fd942a5ee5d28213b8840bd4682e01477c3dc2c200b10743b4a552a461474a004fc00ea8ca3a27d064ad68fe495107a092b7bfd74153e1aa104e1e1ac9404e81fbca6b7f4ef4a019c806c682c9349d17a155394fe804e827d7fd17f9c44f9e8ecb616c059843f4861e44e5bf44e216bbb52db995e0ce52312b53270a292cfbbbe65575c4055ab78a446f032ac7e8a6eba63c8d6874ff5705b8bdb06c00a89a3cff011258facb3a49e0c8b57c3af04457cbb8a407b81a8a86461bec40a62eabd58441196c6e45af41b0379d1890dc065e0520eae6362834f4299b46745abe606dfa30472a463b55b7251a02ab7e4c93f24d93cc02ae6a804466243a201e67bc44c3f02c20f90e20dc21e0638f593340af13e5219c1a2a9480d1b31cb1dc14b21ad7f57aee928d1c6f61b2e1cbf01cc19913de7e4bf96ba17dd6473be1eb9cd32e4eedd0469653245f11a9c057d8f00c99922e7526e15a3e54112e579dd2b4feb36bd290599b362891d3d9d02e0e95d179dca672f212b128fc3f98ca392ac62d3fb59aa6a0c899f18c6b43cdc341479ca008992892f621931acfda271cd3e16fd4ea249cd854291edab523665776b459788ef4061124d83a1205d906d7a077b1acb8f5b38805450e323f6714f7039d8366c8ed06d45e5cebed3adbf86656e0abce3d47837ae367906c86d82a739363713d0b5e5df3b4e5c0870e4be68a71177f39571e78bacfea53e6e042f47146058434d970381f2fb48428837a464cd0de45174e6d88f80e4eae4a21dd0856e00718f5c440bd3c7a66971154587df5ada008b7dea33ddddebdf49f6d1f8fbed5d0f988ba429c8e5274ce0f4c115ce51e524213e982b8e179950d456cc50e31d8582e95b2b9b41ebd81c5a50080562b24bb96b52d34cfc595fca119f21a2b4d44efdf966a8e6114ece1a600aa5c5690fc7a1344d006589119a91b085301e60b928e75e9f13b86c6175679c2517dd5033c687c6ffac82a0f1434559fbd4f2fb4a303652f9b22b88fbbb33671a7c2aea1dde870ad48bd89a1996f1f47ad1e2d4533c375a4203248eaebaf890c7d4cc7a115313075533a7114902b279afcbccec10ef32a2de5877aed0fe676bd436e6fdac55c19714cfc0cf2b23939e54645b133bb4d644720a10670275948a1594b7ce0b3c6882f27cafc4b0714a44364c63cc7ac9fc3bc503661710700df7f7215d9dbcd1dc350da64e576c4096b8f8e7ca0507e1cd82d1bfc337c06e9ee154aaaf299e97d86311a899d47f09528d6ed205a3b9b31ce4e39b1c80b8cb1675748cb99340d732348f891cba326e0c467688e7a5882b937928b8e333d2f24d49f902e17ac10ea14ca75969dcd8a8ae1ae18065ef2093e4632dadbed3e259f9236bcdbff983f2b3f37202705118c636027c65d04f42644df98272855f6ab24777d329615522caa03a048fa7bb0ece945562c80ff29fa4b4aaaf3cc18ca62e3a436b83120a8e2c4b3332c65911d7050ac1e81d1a7971634aa7e2eb81bad7c41c3e61da694856ac2260dd3d0959337499c523c24fd29ad2b12421d857798c204d02d593657c4b57ab61f488ca4b3987f212e02f1fe13c6eae3d4907e1ef91afbc9c9d3d39c14852327e6cf74e0fab969a5b2a53afaf8b3154402e98454ea80b7280d21102eced0facb1cd06cc37334ee99f910930c8114caaadfdb80d5baafa94710d15f002444a794a76cea52893c0c4a0e9d6510ca92204bf0168ab557d612db22ea65c489f852ce924f870cb16409971b3778de7ab29808841c5a0c781233cf63b131a5d01752879f61a639f2dd94b351e5a3b63d47ff89490bd7c280861a27b97fd58103399921bd55295c3207835cd72e57b294adcfc98e9faa156b914f7fdfcb871fac40e22af018da5b84a6461b586bb7ad6c7e675f85f523bfb771f3bd7132dc6526d9f3f881bb54b0354a342dcd4f34c8b245838f47625ad4585a80f5d4d541c9f75e2a14cb42f3722c97ad5d43391787a166eeffb449735c3b586396cdcdf8ab64cd3ca56350735cdd63585d2fd830cfe4c7e2d1e5750fa528011c570ca5a40747e4cba0e0b05202d80130c302179bc9e4455a3b1126e264c2263e4567e07fcaaa8d0b70040f17c68997ba012872f072654b41c91b04becb438ab22a181b9447b741c893b67270378982a265fface043e86ea37a28aca1583fe06f6d4c789701fd7c90e0448bc9ae1da46b2a2a076c873a154f67f5d511dc77b10e1b818672db773159b370acb4ea4efe97c90cc299afa9d3d6a0b844c83c11e79e2103ffe887214453c8fdca296d43c8fb8a7e8977b681a10899b5dd50c7d789a3e0d6a314ad777b57549b4b1ac2282d5c606c8e7187e3b4fe641faf6fe6fad08460b57fcfb9e2956d0ab12f8df6755024f036ce3a960f630705e7e0888e78695ad0b0e4d0c462ad512e1c46fc5bc0864f2a501a56bbb1eaa6f4ba146677524f8bf8e43912ae4989e48da91b0fc08bfad9f120682998b5ce6f820d7528f3d0d59bcfb55aab6d904776f8a71be720da6b97713b6675967d231c531069b42ddcc03dd81786a1c1ab90c32777c85f35fe1acb274d9f7e6cbde1076ec12190b7d8014aff263f7aa28a565f6432a59dc098e177b291da23bada2eb1be068a8d3a065bc40e1c7f0f3ef0f36f94e35d01e2077ca614976af6c0a4f9600b172e146efadb631454a1ba5d8c338e9acf56c0e8d0736d7e3171fbdead3ea9a0ae95252857c289c26d86cf6ed10191862613833443b722d3f528499621aa7ad68a0438416e19947382a01a58f45f281f65599a993f8ec2cafa4ce4df6729711fa043ae8c766081d35e41edca0c9e01aa3640dff1ae67c4a2167f9e0c11744339723d4727b6c344b3c360c7a74064c297d68bde75d8a397730d990023323fe2969038d141ea80049e9e0d627aba6a6a8760e7a6cdde0c3b6c8be5bb89d9acc01a7d719256178352a0955b3e8afe44efe85ebd272905685b426ba87089f7368bef12159fa18d15e519ca639e697c055aab864dd0a802b35bb7b837fb28669220c0e05fa8e86f51fd5ab188ea7a65650b151820c6e10401f9309f7beb201255280bc16f6908cdf091577b6a62b4e5ee69f71a086934906f9d036acd6b7693421f86f39aadbd000273810bca5da56731b2ebd9a12337d7688d10f121f14803d01adb431cadb5178b5e09a8e821af5446dccc455cad6b72699cce56dd9c3791ddb26c02acda6951d49645bce6a936c21b7d6824def2ab719e007307363dcbe39932499fcce2f4d0102220f57ecbd6ef34a930717a7cbf47f9be4c2e854eff53b867b54e8eebc9e8933d50f75a02b43558f8120b90cbaf4b428e035a35b81c44bbcd93e316d5803122f10139ee3fa13c59c7ef37403aeda6b3b3bca2448124a7f80220a11c6f15660fac6a7db7fccc5d6352c969e8a756e97a8dfc8a928bb5e52c709519f1eb4484a81973e1102158d95158859bd0ffb09d3f6f79a6bcec70f497012c52aedd0c14623174fc827a3093cf856611492c52f602cd3695bb9dd911d5a07655e43be73bb5b6a2f076e5d4fd294827831b42d40aed36b13b1c0bec3967b2a1202aba7ba50b3491a18b8b747de28bcbc328ac8c71a33f6e087d9e1b2c08adf084c111bb34dd6d72f2d9442c2a150e6f497929d99f0c83a493c654e4230a5fe14aaaa990e01e58d013cfec7ee73073ccc16d6749263c0497ef467b7b7845c2314e5e8cd8f8457ba7940593021acbac0140bc4cd9a5db6e405167c7139046c43163f706796d75d2541f0dee762271432a33a80375fa06a8ab3a457b16698eb00a7af1c79964a1e8ccc3232e9535b1ff158b04df43bbbdbe72c4f4ac99c9e1ea589ac3f64ff8ce0c484c46fa30cb2e53f3b782eb0e292e384d20ca0659d160270bc2b7110561fea8c55d31d46aa1cdd8944e5629a9c15c77306b9dac12e2d009b6d8816a25d3f491852a10bb740cd93e279535ccd1e8980a0fb9eb1e82e8bb6a049c7d625320d533100fe4004ea4e40196c3aa36ce7e04de55995542bdc7c9d29b41cccdb0092896e716d6148ab913d99c8954b3fd967bd417416858918e3fe306a638f4ba4a7eb9696937af444b7b18f387db6c89d0d0d68aad3466d236c96b04b001152f63ca467f26b84e717819e340aadeaeeda561c17b901f301fdac366beac9ca5b69685c478e85d5a932de9de1a14fe67f03672244c5c466edeaaa11c9b1aaf476617d22a235fbce1244f207196e972dc9ebcc98a49d89441511094630839612485cfb1112803deb35c39816bf521fa8077a39b7d066a42dcc28290646d4afddef69cdfa22ffd7162cf7880b23549c03e7705b9094f98b8504ad09be76d6ef47140ad92d4c0219114682905e4827d922cf9a0cf3d8c2f0b87e6e8b4f18b4781e5cf00158e45db244f6d64fe900dc544527f3e2a9730082fe8a1f157e568e0dd9bb89463b2b35cb2fc48c950d4950c87696af7940f42e4799ac93c0ff6e510b5273af1cf75569295303a177bedb63985d440da3fb866b1a8d1fd483baf786a1eb548ad996d0d4055940fff39765916c3d94d81914ca68474bc3885db4788b81082a330496a3d31ceb19026346cb6b614efd663698ba51c27f303b62580c201dfb6942ff5931cc0fb88a95a3fe6f00bad0fa1bce7a022106902fe34a70404f6e979699f88f845319f221d06c36b7e9c7e0e5246cd7024b68be0089c9e508abd68756fa2bd604279be10a9a5d696b3ce201a16f25ad4009b9e6abc140c4a1c1e74902d61821f0ae3a1d5302d820b7883475661693ab5782645fd910588abe7239ab32b29787a3f6aa0499b2c1f6267a72c2c39395bc05369fd680c4e574bdccf46ff91e838e814561ecdb193662bc9411feb6d1cfb2437f8a83281e8f33a5bb34e5197fc9af9e8bcec9a0bb6588e3789d65e2e948f344f258f4723a601208519a4d5a16e598eafcb54c4bcd602c3425e13895fa7bd95e8e6a48a98b8244a9963bb0b2c6b957509d2532cb919d09dc82a29ecca9d3241e42fa20e9794c944e24b931711e32fcb6f8d323601daa01b9d02faaa048bb721563d72004f8b44af35ca5ab5e6cfe18456a4281d4aaf5990ee5f6f93f5245d263eeb0ff632f5d8b2e036615cdd64ecff9dab65428e8a9086ccff70c4dcb36a0c194d4f2202e1840095ac20d6ddc932f4323ac32af6efacc956464afb05cc735e9d749f7994d3ddaabe0243c09dec75b623c150c17a6f1b017b71350639210745f5f3349efa23b58f064a971950cc6633564d4a08a93a579fd157131f01f2c217d78efad748313de5a424c643f36122e03aaa2db54893d9a37ac665be651f4573fe1af37316df8b5aea0a1fbd95d10b54a11809a4b64de1af7dc1dd19682f2b9bc943b2c9b63c73120396d54c7d97160c9c2ed3b7872cc863be7a08ae3fb10857652dfed85349dd792ab1a29a5002abe29d7821b1e053bcad9d334038adb27310ccee408095b27f42c92bb7265756dd933b937d32bfb27c53c2d96ef686210043c08010fe75b42ab90593002b782df0ec8d6e30d407c4eaa2527f9b41c3a4b2c525834f0816fbff6da9907b9bdfd1c3bb8713e6ed905870454719897a9790d9886fceeac3a394daa6a108e303f172912b69d707c430e8342c26199fb7bfb4ff80d1770407700397d0135c61bc796c8d5fdc3c351c0adca8f012f4ce284bd3c89ec47c3adf113ef765f1ffa0031dd73f0a9712dfb0fae1b8008de6268afa13c035a1d26fb7a5225f98b8ec77c856eaf10f98feab2720513762b756f6cc467f28188abf0d03d9cc9d2ac2b8c853c5930d83a8987558b927f5aabd86cec310732292b96c070de9987804cb8d0ec3112f85b3d136c85a7382d24a6611d921a6831363163ea2477a7919ae1d8be216b39cf0fe852925ebb5d7952aeb0f7938f95123bb9236b01eb6fe255ebae52a11b2e9d7dab9aba9d275fac1ede44ccb8e5498a6f16a729b235d980329dabc0db34537cada7ea9eda38b6f89a651b24c54489b265ca64594e69a698263bb92af5901434e720072803278bd3f18a12b01d5f5d86f0c482523547193474f7ded087c95a83075a5bf4c34d28ceebb074bfe09e1943279d9a06273f8e0dc5346f92757d02173bd99021e79976cb023dfa70b72dc92297a1dbb6b936c50d74ce2511c2a52d1ee74c84e36981aafd307fbe35b11c21a65299c40bb2dc7900bd3fbc5cc96fb147e02850c89c369d6ba615af8f7ba41319760b73084f781f553658ff2c3283a303fc551e43e96d14fd21fcce51ae5dea934a3d782b72fdf6b99f87289beb65ac918119857abc4dcd61a851f64cc86d358fcb1054e3daf8b2be1407222b5595d1cfbd567472b205e820f4b4fc0dabf1ed5fed33598f8136fcbff67bbae9b210124610f632d2a3bca5c63af2cf6ac9bdef68121b2270b5ac71834d598a7130cd64d6c562c9d32d4927196c1ff9ee4d107141f5992041970f49439e364c48cf120ab23cbfcff39f1af97a2a955643fc189a70eeb24521beb029a3688cac646b1d1e769e047fc4d4ace673e1872499648176d0d1dfda638834bc2b977d6b1c7f1170afb99349b6b15100547272a47f45726e2936dd233da4304b0e74f0cc6ccaa400783858c5ff7d7d32729c48523efb58e19ccc36718ced40c8010a2e3dff29db2c18a64dec018c31b11f41a2654743328e78b4544c2c26a24be192844763f0c9f37720c7bc2887e95838c68c838d0b69866bbe14c0f157aa193ced4a337d67c980b9a655af80f2c58da85ee33ab69e1cca75a31b35171566ec35f44701cdbe6ee17d484652266bfe0c2289f5cea4f0e4f832807f3841c6c24e6d561a78b0dfbdbb52b0f99559eaeb003c9e091392a4ea6d76d5d351b7284000e2961e2f534f0ac0f19f858a15e8db7eac6338099d7e49db44bb9b34843f82bb4031b27171642400d46c7e2ce3716ee8d38669c5cfd788c5b7a72280c8124392cb7b124cbb1fa4140bf1e4ae617b88d05ef8dfcba8616171dcf12bfdc1a5dfaaeafe801bdb5f9132f4cb089dd32e20202ab8cdc4d9268ccc2d3bc6c535478068843345ffe3eef2090ff2b8e59be9ae45eb93df55329ef6e39a67b013ae0041db3251833d103c3587cd2c200b7d328c324913820054c3e37a8c1659f3040ae8dcd4ccd031960cc54661f2c2a2b28826f8f079125207252c5e9316b1ecd3b1afbc0660667c648d8d8df649991d0004f7f224e8cb8295eda85703497e1640d3a1279df64d53b924472b2819dfb0408c3f94b4e39b661987eaa95cf6da367ed5d9c1643ca3f15c90a0eb092eb94c404b21772faba447d1ce4fd553c255ea501be6cbf25da5d347e371c6e98a2fe46be87d5897bafe4662e746f0a4afcd7f8ffc2a4aede72abea31b4433c356a6d40012b698d9c3979aad091ce8223424c80edf88850dddde2278e623668bb7a42eacaf5bfc5956ffcde8762664c2d813c6421bf1796c08cf538fe076ba10f5b642b3810561d6001cbd9623ad78452caed5a5b73883d4a22f4ecc41c02d150d714cfb784f633720d21ed79805a458bf327dfdc5c2ab66a35f4cdd6887f73fd8528489b16550390755c37cb058c689457c51b4c35cc9c2585d8602ea7145b074703f5ea92e1970d3ccfae4e14dcb8622cc84c440c0175914110d29f7a9228ebad2deadc6e51ceadda5f22f6a352b075d1bf6902abeffce2f31bcd469c12531e921a486d49a93651e9d729a811a52de0c9b65e7804befa154af575c3f5025d51c499dc935fba6e2d55a02692abe369863265374a1d02d9edd0b55651fef608034ef7b09b0e90ad839289a7a5587623f791dac19610274e50efdbad45bf02883a9a2ecd468031294fe25df008612825c9b1ac66d6e75ebbe543696fd9d4f1c2c03c02b51ef80365716d04df6ed9eda8cdbd89265430a5ebfad086813d82c63bb41c39e9a15deb3ba65b64c520a1f035a25b1a27c75f0610f5ed2ff416ce77616029d8ba6f73bda522ed0c05ab94370812a9eb0d23b8014057e1f0d69d4c10596da613bbaeb47c86ab7b5e1619fc0e128d2ee385253206e8bf7371a0beddf50afdefd05a7e8f81247c32b1e1b160e0c7f926ad886826f3ead1784fbc03d239ac1becb22179babaf71752ed7cb46b00dc4cb388cd159a7b086d5e3da333382b9c3ebc89aee174be7df20e0fcac85a02e1683976eb62bbaed44e6564701aa3177c4ea090efb929880aebbe2f088af7d23a2eb9652b97fc46c9ba8584418df89aee77d83a18fb1ee171ba5c72185505e0385dce0fcc542c3f0029e16a101316916fe8984a5304d78ed30f738a496cc04366250fc958203196e18a445bf596da399e304bb8835a2cca5a57b8bb1f22217013196d899b3d018f7b6aad52bdd4ae839dd1a398f2f943cd2d07d87d1b7cd2d089af3eb2334d31871965baf7d49767f2b9e7654bffc16c1e2934328984fd96162f0089daec00556c7fc7008b16710b329196801dfa3ab2e48f2d0b0e4f8972958d788c1a703764f885152de48e0ea16872516c00a81e79a24b555f217a24912e711bcd69c489ab8ec43ab6c6ca53c38f7a6e655cfb32897d65af07a3753e081885a9a64cb86a2cc3f9386bd370169ca5bf8ef2366fc27dbb8cd4733c4c8908b2f5310096a78baa81a1818aaf1ca11995964454cceab65e7108426e89a8909558d971cca625a72b258a82d5e78abf89d633b7ba1174f7fd87f5b625b7242d213cb9c5bf8445ace0266ed1823d4b47199aa37331362ccc42410e97ec28056bb4b8f97c52470d3e25930300b1189dcc94d6a0ced707e2b36ae49d72f5cbc050908d26a073fce2a423199e05095448c2b5c278ee30f1fb03a35d5ed21170024ddcb9b5fc67c1d09193e32f71353f5f38dcfa82a02f0c59508f01548b5ac067de7e8b21d30deae233acaaa885392c6572a2f97c85bf24a6ba092bb6f0ed053568c293cc4abf32dadf7a6ccc79b7989ebe4005c763f9df3f9e369a2ba66ab0aa52be5120593263e6738053b81418135aedf1f82d53ce19aa8c47ade590dfc8ab48f48959c045f7d8d97ed64b29662401c15d782bcd22399b590cbb88214565b02f0c3d07d4191dbc2d112a16ee959c38082ffcaf468698a22686564b2cec815b6daec485c79c471755bc431a9a84ef170530ca99317affb2f235e218f8afe23d41ea60b3d270b28a9c8bb6fd9a9571a8d69b803c90fb4b5477836eeb726d04d8f9d43127c498ddeecf0e30b3e947c530e01cb022eaab03ab0cb6f4427d9f9b9818561381ab7c47135361232a8e75b14cc166fd449c80f811b6df8815ff478346552e7e2281ed87d0915f387854b680aca2dbb682a28c3ab9a3e565f439e649a0f1cf051fe8d5d7892b236d8e890e7e3faa330696be297fef87947c9f6cb671261766eaeaae63770f67270cfbc92e7277db8d7be2d04e806f09ce8cc712b568a3a743bf9aa76c7078badc79bc43a2dee5864258e47097a3d7fafe60babfebac688e9b4b86c63b288ccaf7703f8d3a11a77b0016e7d1c434d3b7da539d49dc1a72367837987a41639d10daa54fdea9d8df70115c54d8a4d23841cacc329b10604d2773b29131f1ae176d565096def96c863b20a728f12839cd62626dcc681679f9359297a825cea4575856b5bbc4335515f43f2a53fd8e2a4ccbdbb954613b574dcbb7764d50113a6509e79f5645a79ad125aab3964b127fc2645134cda0da0d7effffead94a4229631fd7359c88e7d525f1043000688d3ef853bfdddcd09339e42cc64eda212e08da5ddd96e0fa6221062ab0ba67e70abeb7ee71bb1f575c1b2984555d25e127b507ac9eef5d570beeaaf207c03698266857b1156ce36f03d2b25dc50d3cdca82a598092a09b87f93a6a57d40cdcb62de1a02838bba2b96ff0f6a2207a445020dbbbcc5dc9cf1d8d3349a43e5b15121786d2a52c7573257631847e5600232f60cbcf48d1e1f74478708931c13843f523243235f1e9319f7a6c9d6432d9e061b346207c822a4175ce244edb5c2d6646e57d706dbb27dadc356dafb15a96c2a7e729266e566fdac8768dc534bd60830f601c76a3e3b3bed6678d8eca4ddab49a6ac3b3e3a21258d681ece8ba9ea90cd8f9a3bfa5e687324cc85f131e2a801689bc914e09ec4fd105f33389b755760dd208fcef3ec92957220d2fabd5e7b941fc382ab678445e731d1fe8ca112d8c18650f65e110a310254645195ba6f18a5c2863f908007d301950f0a9b4d59ac471734baf11e469216353d35ac8ba27ff506de9ed1879524c434ec664e8cc61ee43a8f417683e0b299df998ad91db25e67b52f116a71f242560cc7e62bfdf132323124b9a18ba258d97e7970f249191ba5b1839588d6d073b13456d4cb7bda6e0d4d5ffdf29c8f261288a1b9a06e0d537dba5cde85ee4af8e20ca9607f598d4a3e749bb604ed6532d3c0e9f764805ea2612ae1c87054afacb98e03c28f5731535659952cf9c476159e4d533e79159168df5ce7950d7ace868e3e2e042eb359fd19a96d07bcd65f067ed60ed86fb4f2107434a50bb3187e42adfda0a9de7bd731048a9258a995c51ef9c8790b250d43be711ae6621d2cdca757be638e683c5757bce71a4c7592cb6c83e34280144cfdc184368fb4baf397084d339b112048e64a2e9e93b4f64efd9e06c7f8c63ebfe206b882adb6c69044966eb873252f2d5c7331e6497efc3f7e465184577ec7ce3f8f4c15b67a8607cb48605914cac5eea5d182cca396e5932a220472b219d2ce41a2b45c48e899ebadf5b8d8c8e0b960e39d00fc393dc6ca4aec6995de364eac0d25f30587d0858fee61d3cac5e706e93e60105576fae985dbb1fcddec4de211956209ae5ae47af4cd806bd9bc25ec92f013c3bf2f868e84453074dba58237cbc737c06e4fa4845fd832d2ee7b1200476f3c8e4fa12f703fbca7f578081e962f8cdf170b5fadea02c2401e1a516f60579df6e1f0aaa8e34e8912a2b27bb95dcee1ac4a68fb7b729e60e66b3f4ea9dc41b0dd4c4129b13bbcd592eeffe960632df7281544bfb60adf1f2b15610c2f01cd47f13326cba3cfe8a8f010cb926cf8879405ebeddb9fc3673efd47ca7e69d35eed4e8a6e7890350a6cdf19aecc5637e8b3fd227664ffbc399bf45cfe91bb7e63df1c83f77c04130175f0909ee1d1e031909c6a707c24388c7461e8e2db9d685c3ec4c7ffbd67b1b5248a96bc5d919b04c745c81ab35529b147084e4d9cc653e584690439c85e30f63d8f379472ce2e0f8af64db727ad46a93bdfe60d71aed71393bb26579a4e467075b3980418329568875843441b8729fc20f059c106f13bca5b93922ae84ed27fc918d746b3075bbda21ae5e3339a30d1756c5e26178bd47e18f82e9bd1ba2b5ccf0ad996d55eac75b4c686d0c8b96b6ef5d69587e38a7dfcae1848df16a3bd02acee0e79fe5255689f33a20b4ea6accbd171c84d16036d35c8346eee85d4c56a5e4077c2a6033a6c0135e5e6fcf614cad85a3bce6b66b05db719c3b0407101cabddb80a771d68d0917db153f04f00cf961c1e9a74d1d089862ea651322be707245c0e32430e1821f3009c6ab4ca7801cf8d6e5ce8be62d589272717a6de29f82790cf661ebf1a3a6ad65543a7d4186a93c90b98badeb5ec84fa0047251e18c896eba45bf5349293a87d2ce89ec2efc25c3672f269b44bc36e1a7668d805d6b232f62e4a73df72df7c83c27453cbf90c56807539afeb2f0728f908d857f82724c76e0ebe1abad5d055a38ed7609a52860f94b96dfc483ef175de0bd8753d0557a96cbd49c4cefe6bc4b4ea33846922fb287cbf821fe1b8f6f3f2d6dcb1e6ce9a77aec62a1d993e68e275b083339a6a56fb7cc0acc260a6fee131003fb487092e53637a0b6970ab8a22f869bbfd13453f661c587e498a0c374aa2adde37f1c62f8f5b49f44b2ff63f4b4adf3c8c1e5b8b0a8febded67e1c4e3cb21c7bc31c553da9584e16cace8d6eebc3d3faac44e4cb6614e5fe12bb0b5b67ec571d1ed4e8bbf01c68dc466c88ea8e6755d58fc87ffcc0600503d4db7e413c66bbb8ef82967e15e0b595974f935d1a76d2f03d08aaeaf7e126e78d633c133724470c9bf805299f89cc922b8ecc3ca290cfc446c91147c56a1edf0a8263974740460082d14be023c0612b0f67ec9c8151cc70cf72cf3c3ba8cfd61251574f7ee2272226892a41631bc4179c6b3b27bf6dee3b35efae71a734cea1d763a4367b5fdfc080bb317bda1bd0753d0526193a193b7e8e883b2a8bf2d928d2405d72aee6f4f008e2b1c98137fbdc796e26328752cb04b284bdf51b3f435d7f7187c33e39d51d10c1903ec53c0642ead3aef2efc2dc36f3f36bb85be3ce9a776ab84bd6b39af5acb27e592ce83bac155b7c7d8435b8ecdf4967bc91bbd8630bbf3ee94c0f5c5db9fb24be12df844fe22bf14d7806ee922dbf57b787d7d3f1f8de74fc3c61f1ffdc6bdcf49b71bd6c9eb8f877ee356ebe6ffb115d4e24c1fa116c372d0e09502233f85b8501381f993e01250a45b150b7ab9aa51b8263a6353ac9de72e2a1c1c124617b5f9dd78dce05e0ee686c9c3db9017d04ab1e10c3a1b90460a28664c423e404126aeeef2f45c8c50b3413901880d1644ac6bee7ddc0175625704168d316aea746f4b90d3e6ef92a212b1161608c83011a63455e07d9da8269bb0bff0971b4cbc96b1aa1b82ca656eecf9199e739f699935af76ebb6f3e6a6d5554c0e26546fad05e61afd4bff0f2f6fd48f75717355fa57755026c5cd79a0e3d51dd61d909d61c538bf5b852952d7cc80355e03a114a7ab5c2f3b7efa5cfc437d19bf04c7813bd89cf807db2e5f772f3f8323bafa5f8c460a10e261e6131bd1f1b1d4b202c5b92a0a8a27558f41db4d529ea52c91fa13c34edd06c87a69d3498465aa695c654c7f2d232ebca222134400b7eff624d081c3ee455d5e3ebfdd9bfbec282a944781c7529fa6bb3ef2661b82d427fb597d5dbef99bd8617cbeb22ced5d58b891112aa2f81198920f5eb34183e6947182c97fd83f4f25f9ace29b05147b8f2a8d86f46f774d3cc14456e73941fe7c872651c32ae2c57c62db310e9918dc4f564e3223db991589f88d9075316761992d33c2be3f18b31d18f2da365ea4694a54c4485f12813109b4aca54f0624f99c29731061414443c002552a64a4005a4e06b0b4c99b2596380066710330aad4333abbf49268b0f2b97c37a8801395faf08dadb8d922cedd7b4bf743675519e4cfe8f5803b0b21f02af7a9a4ea063d736ce8409f3c412ff2de0d4cdb95a865c9c4c10fae1df737106f2191681e8fedc85824553015f13fe782095958375e60eda942543c88e0c9f930d2c5c130fefa907e0ae2ae3d90f51535739b05c89bb4720713424051234f19a827eb9831902bf8ef9e7c08141d1fde86bd127a9e94ffdee1b033ef6c0509c6474437e7490fcd8d03040850cb11709f957624c409219aa5c1fbc114e019c76b9f26ce6d65143c72b4f503964b536e66e1ca3551f5bfb48db6b44a4d0b1a4eb5a8c45d4a08e26b574662934fa65b2193b74777506da7a9bf1b91704bdb4f1f48dbdce06bdfe66fcaeb7fed82c75e13e78769cff99da11bdb57839ad730e8dad7d3f3a374d582063b41ff17772698181295ac527b00d4912d9b8677abc886cb056a9e94692025e5a40d651c2410f201be31c8f668082d70d78799768ec9706062f77e5c247e0c1fc5a771669369e6968b08a8ef95ad01af8f523f138779c2e00f143630a0542b3c88c7d9026a768b2a59220ee5dd9a7e60995262aad86d18c54d3be61d03524bb07e0bace6745ebf38d8720b0d89ad0e94e06888c766a01c4dfc1acd74c34c6595757aacb448e06ff9b13519e834371608e095ca97c00fa9070f9a3a2656c60de04d9e5160d322cb09f61a601af03e88c116fdf89932de89aa81e4c4f6bfd4c7b35d8b448a1a6509ccc07aa0c8f3feac50d16bee3d8356012e332136e3602d1b0bdba2c476a95de13c55a0e456708c466446ffc246650d0bf7975f1e25ef182c17c21b68dafbac7b5f350723867c68742283cabfda53645220285e7993cb5d42be15ebeb44fb9e20098a940a5e5ba97b88068bf31d21c937228021dd83ddf72806f4dafada517d80cbb2a1ad616a0c8c34c8dcf43b874a6529b8a59b352305d7ba5904cac922438eae232ff51005d5b51feae1da666800ac486a30149daa42be6ff4c67adfa15d4f36951991bdeb543a955502f73415560d2d4656c90585abe3d2e8f5f5087f47ef6f26d841a3923c9394275b8702182aade207e31754c714875c40e000be0410d8bb2b6ef278027c6667f914140c440b2f171208dd46fea3b0bf61484e845a6ae778c34a49cad5a857c371d46c296dd6a1fa088261d2d72ed386a6487229386cf4b766d6547a40993c5e889e498fb6420225e023e70b087bb257dc3f88825c527e43b961c566fef1162186b04e2034ddfe6c46cc94b56786f23268a277e387faf3a1437e7a0e6e9aa89b0e02211d8eb4ed8841871d0dd6bb4b95f2a53cebc8454a60d661e945e7b0eaf4b12e4f72e11e1ca000aec951d39dc192cc0bd10aaea3eed2c8de0e279be0e6806099f63af1cf65815c7eeb0cbc4f0135e5aceea48d7673c7c9230d13a7b63c917a81491aba6c6a95deb6612d1ba4518ea1d192f19d856b7730eafd2233d585cf50c9008af9eaa0438a76f33be977736610262c6c7b92b80b0b79d69313d50bcb128c8c3c006afafae125a0d2e7324eac164b15a7114b50b45256cfb9c9ddb05682352156039170f30275ac54b8a9c7d57d1026b1467286aef638ee62962a953f764f35217bf1959fbf8de20bda8464abe2a1b1fb443a5dd8ac976725cf5f46d610cbd169b5520c5e7e39e35612fe26b51f62dc74e8c4af1700249727356c7a35b98f1819ea6290605df832459761d87900414346c73b1317a0182083d402b3427945cbb70a6fd175c553fff527780b3540f0862d470f08ba0ff0b996df435829a85cb4d88a209aeb211ae51310823be275e782a059dae0490276ec90851815ab2e154f8f4d040d88f00be474ec989bfe5b5646dbf0e9ed6d5adf508d28952fd8709b56ff8b9918c7193c65f4c3a7830c282e8970e39607576edb8269a125b553f20e6228287d43ab777014f916166da2d30f9fade050b3337da80dbe26d78e90c57f1ce6cd1be22146cf8635f27cb36b79ece5c0e1488ad849c475d1e59032a91b7fcb9cf8270be7fcaf6cf547d193e79f0e8e0a53dd62fdc1c2de186b990950cc56364f3fb41387914ea13306f20ebec0e435a080de480f59309e555d629bcdb88731391e901a3c6f89bfd6219bafd83fb93b462a3f8e083b9f4c4e2f099310305b695d07b2beb1488905fa0e7d7f9bed84bdb5fa1ae61a593fc2407d5ab360b80c0d2726452118ad5df256a52f89a121f1a4b21ea7d9126d580cf21ed579cd50462d190f7f1190800534e342e2bcf751d9fce487c31f6a73d801ab2643ac264afcb4193d4c1ce4493aff4ba9cdc09a901552d5cbe1bbd2143e09621cb9e010642eab986dd1119d6ace80a01514e806cf78755a387f329e65f58b9a96b6f58811d4158d443ae722621f50b06cc17fb90402a47231c7743da495d7c6146781bf72741742c9f11a39792bc99236bbb23e56bd624c52e02f93d4d668200349a516afdc3a236fbf6fa2709f371c05e5ca19ed40fc30607aeea624e94500ec5deb793b3a1e6023e39be2bf4902b14a354d45c8c1f90e4b2ba511b8aa37c507fe4dbb6b51b439df3b96f433784cae19acb6dfa58c5ddc5e84c7a16b525293f8334cd59445a0df25542b3c19de38d090db860191e3b403e6fd2d198b1ef205a982ae69407c72a2d6269e8449f0ab005a0cd6e30a362e24ab209802d4227181311ca95c65365811902518046540e9228d0426ee0cb6016906e7d517bf2d291c8d9deb735530cc3858621c07af1d090fd43dc6da8ef32024f350bfc4c066985f490715c44e5ce0dc6c1f2d919630b0a0211cf4dbdfeb50dbe75a9a7a3fb2a8f8bedd073b7e948299cd107d34655c1518c3d68315ee688aceacb0cdf4719b994d61fa26d9f8725bdb8a1e5387ca649262201de892103e2d3a38e930dbc8e9653f6891e5b5a3a5c145c4f9c12fbb433502aaa921f5434526a7ba9ecf1c7d88c70dcb3873bb9048e381af96d8a5d54e101438339673ae82dbf31797881c6826d9cbd986ac74f7f2d76f4913291c6ffe4b5106f1c34d740da01a348faf744afa65c826cf5fc23bd82f570ea0a4aa8656adcee40ac53afd9380c399f8341815789201bc97a89b911a31545c854bd5f4207da1d421135e5efaa5fa65e5e44228e660908577dcb47592f5ba90bc39a8e702851799995c3d1669e06b8efbd205b7e88fe6537cee1d26824ec23ad6a233fc12b6851cb41c832dc38968805a8cbdeb182f886c3b6b0e9fa5bc1fe93ca8b2517c73a32137029c04577b13dcd7687cc7e494caf56eb19e18d6d4c47f991407f021ccfe642aa1b8d5c4fc60213ceac231ede9a69570d986d61ee0919f72199606b3b9b1f3ea9fb83f5fa246063e47154fa243d02099d35a01535d51c0a27fe83bda56bab302fe26daf77966d723b8066232e475227ab8018ae24d387b12782491d82f00432f3135733451a8852b2337e8535f95a0bebf2a81c7b28e22fb6c3a2783c9068681da6e27184d33d1cd7398002fad3a793cf4575ef41b8f924617e2d7a4b92a60f460337d19138a24164abaa4d9a3b05b030616d32371f100c8f388233f81f5d06b48e2354307f25b2eac2693729e36b717d0779501c55f5e671326f084140f2898eaa7dd20efb270f4df36a75ad0a1da25b4dc48f09bcfbf60fca8404be6cdcad4d2e51c707ea673b8c430a1bf7ba569ba28c8021ae89c8bf13185381f11c4e07f75aedd58b0efbbf580ce0bc9e67763a345754806951fd1d9b644c2694eb25ee26a7953e6fb0859a206d5256b318b4de2330c5af8cc7faa28e004e6c8b62e7b18d16d0ac7c206b7fe8d956893973d1c3add8e3572fef0fc4d7b9ec62f4cfeff6e742645605026a18071ab74ac41c86756f818fbc15aebd952a92901f6fe11a85be5e2c39ba4af4d339f2e72d5da3d4f70e666b9efbcdc8c7c9696bc32b32763fb992bc91ebdcde3bda46d61085a65ddbdba91abf2ca13bd9d9e84f0135f107dbf02c8825a23273f29e74bb012e974e5734640a10c56e591295e285cd8ef2db158fb11b9db701ba09e6fbb79328748066dd381e44ba155fbf58f1874444beb241f223f2eaa8a5e0e9e7473692423e49d57d134c5d805ffedeb79079b76f9dcbaf7fa7924bf723ec3d920bf72db9441b09fab8ba402cc38018e79685f78f08c57530f359e796c9623a593217742503d4bfe85291fe3710b955a058f2aecb63d5e617dd3a098f1e19812e94bb692561ff28e32c90e815635a09d06d33ae181b77af95767c941c85fd6b37cfd3b415beef5f4da61fd1bbfe0babf907e5d7f3aa0c6514509b1bfe0b7db0f1a1da1d3b7927bf2a45e9f9d649a954e59307f28c3e997a51e707524ac78a28c3e20d592640f88929584cadd8d3e07267f07fae4ab38ff245789a9315ea54fbfe38be41e22b53ef6a4c1ec048f83d71d000c90f9d5813a0b68dee17493f81e1f0ce88a4511b9dc81042beaf60df6a8c14f53e7414acbe7459458d01fd2494c6d4dd62fe32c0648d94df7038ed48877bcb00134e1daf0c27314d9f63e9f3c062ddb51dcf09d820cfe0fe21bf347f973416f31c1b305a5a22319e57c94dcd2d2e47533cb41cde4ad4cd0de37ee124f0bb48be2eccab57bb39a5a6ce558a965e9da6e1209269494dada75e848c57865f48881782aef44d24c4ca45c23ccc35fc8bd76f4baec8c113e52a858cb8edc96234447ced1bf808d8b4e6d4fa9139ba9ebd7685ca5fa05604101c55dcdde43df3fa89bab1be1e9d8710e65c5197bca642094cf5f909746fee8a5a1079942aca42522bce84755b53e6e38dc8af5083c42ed5c4f0f30ac9f0ffec1efe21b3d437415998d7dd99a6bad0966343b882d87b3a03d65093bbc6d0face1bcf00de70eb9779db84f0450fcceb32ab3aeaa74971833a4376f66435f32f85fa5b3c0d933f8adb03ad333fafafbfbddeb9000a8fc34f949e19e74de33f856c6bdec55ff42264b169d790131d3795e935c3d093a3fb04d7339cf8f88d0384632421461dcb12ac29ba98fed26f3843ebeae5ea3ec26393c05b98cc01e4deb2abd325acd56d3300e05e7f14e1fac0bbfd8d068778c58664ec493e6c28e3fdbbd54026f00b341d55e1e8fc36224217e41c41f7124c68ff296583994b1115019817a6c101aea9a79eaccfb0f53ce50933f1c3037ddcdf9df88277422c088bf787eca8d1c529666ecaf950501a8da4d4077bd2073eb053801da60ca5c0f4e605dfc863abcf85758fcb3707a2a806b15ff7b124b8714260968e43fa5980825c4d4ae0c6ceac494c8f75ec131f10e1ba54098c37f2ae57443a2699ecafee9257e1af7e0dfcc86f226e284cd2d95fef3e1af985f36a355797df2678c600104f6c9572a3b03d2f5bcdf5d8e7233f9469ce76e6f6ac3740b22e4d668daab1064b4727aca5c4866845e590737d217fe414be75a90c275eeb4824c7f9581cd6021d23b4c4434256363beaf0d6cf5e69bd4a83a545fe746940795429369f46df062f9091cb15a0645fc7cdf35f2c5ffa96c66e4e4df3cd4e379cc804d48dca506efe11e1bd1fa083dbdf0814c1e04db0280165589944fed6dc9602d1757c7db0cae629019945f6efbd5117be0517d7a6d32abc182989446278e27dc61f2f15be5daa4d186eeac717cc7c59b991a292e428ce89b54d8ec2dbba933020eeb4691ae16da9e8c8f41131d774200d0412e416e5941d044b86e66a9f91c6263c9c5b047ec7192d2c7e4e4fc5a80b08cd1d2a90b9e2266e45d1d226b7c89b7c83edcccd040492f9e43efaebe36d60cd1f106b92bdd64cc278f7c5f633a3638b4a278fc1d0267d7ff51080b3081a894cc04357ae1a9af24a44e93ad3debf87a33adbe72278d7ddcfb7ec0b0af15b7040981eeb3f4de95197352aeeb5d4d43f31b7c5293356cc2ec9ea6c9d61160a546ce7f2874128501d9b0015b6a888aac12c4f5869e35cdeda204f23c71285b13b3d698880ee047f322df4dc0851892a83f944d8860295d919d061908b549fdd9c6ed458a6c2d82ae3a26adfd349d8c5051541ad3814aa9cb36258a212a9554359953eab0b0b2a965a73711e5b820d383b7191e3cf9f2206cdfb036f0e74e27ff72f166dfc023139db8382e96cfbb7c6bae85714ed525ed7b5e9063f39593d6aa1ac4cdfaceae88e46267855656e5dfa80f02a4a7855d7dcdaba1c4b17389969a91c16f9f7d164200a0492051e0f124b43497a7501bc848f064a3a5bba89b55877abc95d12f955471bdcfe2f8e8868996d85046ef59cf9f7a2499efb359e00988b13540dd05bfab12a82dcc71f6c94e4d2dbb5023466bfa969d2c8d6311c376da973724fe2e5e2a022abb213defb4f5badf49438b2b72ccab7e1741d3d3ff64f684b4f32144e90ac106e9d9eee0b5a1b3f55d5d2f86bf9358986910e9e52b4455502ccf98b3e84ed4f507b10823c250397626601c5e7ef10a6102e2e69c865631757e6ff042e6bd33c817487d14062e8c2fb3ff33b433e81c11eb45d841e8b7f3210f7f401baebb097f167c8cb0f19b3399e1bd16961946cc600b4027094fc5918fc022464de3c6a9f63d8a8d008ce85c3643588d5ddc0a7a9e4251e5c0965f78de5c2af2a1d630a2feebbd2a2c56c743dbaa14623669e4bd84aac10765a0920a0e8e24a0783165f3d7a4dfeeb9c118331548cd6085dd6a1657f914b673a43d382a5c94a1533100c969833211b2cce2f7d4c0e8a2b6ee43e48d7ebde490fa558268c32350b3f37d879e4df06280e1e5209dd02d6228ae1aea0b6a2ba1be722a5b87e54e8e040716c8acb74a05cd0fd828591d64cc8a212222bb48cbb33287d61859900038e28aba3aefa8127d37b331e230c89e950724a320a41d57473ac560ceca8be6d860eb3847570bb218fa4832b8383f1d07c6185cffdf4fbd050cc5bc3772627aa0c6c41dab08aa0d0e6606795c0f9312f8a3454ec4174b923aa4806453b5ba290516b975354f0f7b028fbc1837d4b112aca56a0ef542537e27a097388f30c19e73e0fa5fb5a7d4fc442e37ac9fc100f2c69202e8b0761a81ce2663b996f6537bfd80db0f1def2f6e53cc108a93a878012c09d22e99bfb6e8d6c6014e73bb5a0355255d69157168e366ff6f9dd84a3f91211bbc43578054f8d06aa242e00ebb4ef82810dd9f9f035525e62a06a0f455669a50cb20aa648a0a0a193a64281e0cdcec8e1bf508a5150e8c151876dc17ed5cfde5be063a408588ce1b234cde7e1c70cea13f3a9e91b0244dd6a07566b216101e78fd5d4e2f12e9a25d24c292311f001a348c5499ac580f765a158dc9ccde2b6ec9c2652ba639a7ee2239871e58c823f091b79a35fa9c7507bd62958388e10dc034d8a0aff79bdf919148efd6a943e37af41d02b77d6878120a5407d20ac5f564d3bd56b4b424379570b30c67b9bc76c0dbafa38e0c9dad206ba459710a77d939ad932c89bd9ada06a659b2ddeccabf256411d2201bcf18fc70f6bb7086eee165938051474c0add2e2a7ca88d09322f89dd192d10a9e3812e1b4520aa531abf09a9dd70cd45ce25ed875d875dd7c57fdf601bc9995aa874837b6e362b06ed6a451ecbc7c4e2153315d1b7e8ab006b63b625bf07d3d61e542c1d8d93a1e29f59eb779842fe0f8d53ba9f2050adbf83f55d4a8a2afc2bec688af909d3be772ecac5d7def4db22d562754957bcefe38374333fcca04001d4643a8de12952cc63118ed831af16c5302a8ab1ba86d6a50df16db9bbaef1c9acec01aecea33edcb3935d44ef725d36215acbbfefee97589f24e885746fc17c30823df55662e0b8a8aa5cf7b61a50d4c2d4a01de0b64fb861f5f25c7e6f650dd7594ae53261d5fca1dd0c3697eea9c17169600caa1a4f4720890c99bcb0c881a3914568814794e35ba664d406b1ca5a98482a9636ac5f87a48f8275a582814c580fa7de9196b85d04f99b88f6973f5444ee47426e9f43f7b039cbac09a562c2164d781443dcbc12abc25ef8cf9720199a543940b26f252cc24080edf93d2b9ad0b6946d23beb2c2c4aec216eae43972d4281a903ed104bcd4d329565f33c5b4394cbb05798003a042f92a5614d9c88c6312c2ec9f9191984cffd259aa02fa7514221eae4b4cdd99df5bc65d28e427c822c53ea9124a8632c294a68894b8f9997f1a659a11add9f28f4a067aabebb2b2f6155924ec76e4002f48879390a1bbe4097e639dceb78584dde6c7a1c6e2273bc45385373af6a407c5aa38854ac4dabc52c1ec31cef84ed9b9b9f78a0e72417d433386ad13b54e513c74a2d7c03a536abe3e0d60e0a36bed58a0c57beca67455b21f23d6fa6eabe63d742a39b8db660c02a4ffd2109f9deadc315d2f46ed6947e7a77783c6a9f1e96f4fd17904b8adcee813c2edcfd46baff928d13a26d0d6e70f012fb6c2c558f7b75498b19258b5a54895ed89ef5b0952132a5ede6ef052a0c39d332a3feba6881ebdab30b14646638c944f10b4a8a23fc2acca560eaf703a8be92aab7a374ad7a20493a559ee033e5d7179a3420ab667c23b92e090946356e54413d8a3f9f994d4951664902f087a7289a6526cccc3427d25a329223b7ef7cb5396146589679f0b8b0f9995420d86e42e1be8ab86b5542fda258a22112c38386ece17dff317f24dfcef2391f6fbacdb7eed273bf5eba5078f1adc3cc217213f9f8fbb44be1f7b76807d2fc356a8f12a3af3b4840f82e90f955b6ebd0ba01dfe48fe93182cf46b5a49a8319ae2a1e3d2f976544ca9e49904786e083557c3fe746744a55168afe2b995307b9f3a959cf67aef865143f5ebfe92529fbbb13ba7e4c62df270744817b8dd421bdefb22ccdc022caf0b3e623c0ddb3f26d2540fab3960b928c1dee616aa0943a661d0a00c0d617e829ca59274176ba3b03e376093d4779a3f5e31510f21b22f42e0a78584ab30ff9f46209a7e12ac47365a20e32b3bfb7ffc3021102cec3da550c45cfc3eab4dd773a75f6776fd784dc7c7a3d429c530752ec8665a440c484e5c1006173d9206fe3ced1f346176b0a96471c8f37dac4d9003fcb276e847edee027e111cc48cccb170ca3180eaae9eb064c16cfaf8b95c60688856c923a5fdfdbefa2a36091efc7389c874386fdf96cf074c0a219df3c8c2ee4b7dbbef903c812284a8c1def0e0cd0217a02e7d090a303d7a5b90e60fbd02075997c215d9120a01d96617a41789bc9d444b2ca31f7c02115e3c07d64bbfa66c31ffbf03d87b01d808b6c4aac10ac4144633f0c127254e09314c72b70e591a6bfb3fae21bf1398e2460846e693e940ff6e955fc81c995fbf7dc44024396a20bf9456db6b7178a73e7d6b517a76126a315adedf207f48c3f23c87f715a4774dcb888518cb737328bee0029630b4c2221641a88ecab53c13d53e4a49cd99a523cf084a39c44718a9c39e77abc75cc52518e258000908b4140d1e95155572f9fd10e4da92bfb20fcd5f05b1da861a0ed2d4d8db360064cfac216d9f6c3e852c8445889b7c0c75baad55da3d0bbc6bab11c864a4bd861ac1b2ce33b60a1297901c8e320c7065d915214dc0dbfa353ccf8c9079d627cbff24acd9d606c5a083d99bb4297ce9c8ac03b6702f6336acbcc78229d4e6b8ebf970445b6f13770f67ea19e1e331652135ea579fa83c0d19310a8f1c56410d86270b3c3f34e3d56d6f1b0e729d777719fd100970bc5aca1739e2fe874338448da3ed8ec3b420232e5bbb7e2047af4a1fd2c9a784ccc3bf9efed071d620a73f642e2c10a100cf2fd7a95cabb30683c3d35f01eede3f664be3f233cdb5f9c9ba4532a76ff9c116bc739697b59be0de5d853d373f6abbe6693f6cbe8a3c585663630933bab2c9cb62d1990791a73e9c930a8bcdcd964afd4a8070101ce5036e5c51015f37e763f4fe054f519d52b3af08939a0e1737eb8d1bd0430d64697e8e9eaa766a64176ad72d9c368cd9e43bbf5413b962827988180f7d1d7eaee639f025c6d90c9d38e9a8984edb54f0db9cd130cd04d8e8cad9501b337eb06af41e8f1cf8dca1c6cdaa51e4899483b2d540020a840aef48abb28f921b5fd0a8cc5a7a22705086d2801dbb142c341db6525d9883f0b29a9ec79e25e3f4a91320dafc1c8a89482f14882978dfea24f9a704b90745149931708339d30bacef9dc424315219ebb52473ba00cbb4a2cc3cbbf740bb26eb769b098841f2fa01c12e7ee19fc6bafbba694b5174fa1b158d773f7d3fc43371f40f35926267208d9f358a88f713ef006ec416f56cb48f8d8188ef2482bae7c70b714e67f9f68ea11181a5030eabf0f89076870baef388a7114a4ef1d8e9f6888e1bffecf83c58b8ddfe7f1fb5f4b5b598898affa272e74e756b1d5131cddff09e4c2156d636821abc50228b70e50fc8949de795717c6ad8e32f3666d2d0594c1028be824bf9480438a807266120e81e2f677e16588ba225742039163b980d7068a2319bc84f99e4a5aea44dfb6708016660d4b081fdca35e50aef2023e0d8a377650d676b567f0da3bf88f85b00911076559dd6363b52950ee26ff84e024c4bbf008d85420a60d1d0c072666d15280a29f61860ef73b191d1b9dc353d7da79da1b63763a9684d340a16f816b17fd27e8a6afccd8d9a83629f99f6fdebf92d2bec7429aa5da89b964627271891f98fbc60e6f935ff871ee2d3a691dd96c231130a15e448c34474c0fb22984cc09e95d857e417407b613d713551277c1c2b946e2fe8b49a0ec7bb7722cadd239f3ed34ef3ecefe9d6d1f0e5ff772f762bc7d766fcd5d3eb3f1dd8ca01b0a280470b6932ccde6630e2f9dd3d212a7cda07e1ab8f63218f4422c717bd26b945add588c9944804f4309ea331f0c2a4340cfc462e3e921913d3c30b2634cd845d09a2201e23b8408676af8d8c9e266a7c42d6d262d1e200442211b6876d4b9ef7efb0ae2ce18eb50522ebda62b10e76e8c59a8e549a5d89ff9f3d526354a27186f92828d830a6280739eca68b99421c7c69b041ec415724f940c8fcbdbc0d6964c9b4e24d353685c2fb1bfbd60d8638f8e37591d7ab3a723c7f04b2300a3aa968137a00e8cd4e9a73aa086d2a71ee6c17572bb66cce65d77968037077999338c56ece3f3b1f9d37b27a4109b89771ba8b22694817fba7d3589ae022b51da7dc814475d50d2f25db0b183d77cad673415467f5ae84b5dd488923483d7fed4fc27c6528ad1aadb30a6e5741604adda09e0bb17c9b9c5f2aa694a489f8f5ddd65fbb5a9b5a19eb90bf5fb42bc82d2e58253f4782e5240e7bd21f85b2bc8b082e94e88d7aa17fc488dfdd74f17bcbf3bd1ee77bf900f6ad306bd471318b240ffe8ab8b8de8c63c7c4d0ca54b585c06885973c1579a78e0af4c4b2d1f3a5170cf2ff88a3c528f182a6a4e4d70f109aceda3703ea2d0480f5b4621e6962bd11b92280f4513bf1b8421d77279b1f699b00b9d4cbcbd339394e78558a678ebce52e973180e0cb5f69e49055fca9d348d517963f062c2847c992566b0e258152b0f7660331a5b5539f201ca8400e14fa3c606bcc6b17cfa3b8235d99e81488eb80d7d693eec6a1650de61da495bc6677710bd88ed397fff89df8019e824f311cc46f1b563b24dedf6f9904652b3c036cb8aca9e50aa0b069adc24a819ae7b9ff3919fae778c1209dd9cf033a3856fb247b58fa7290134a70cf347befd96592005b30742d5d6da30220cfd9ef302a9edec22bd2c6108c66ddd5332ac241e895e6da402bbae0793acc3ffe8836e488ae41d4026c5e84fe89b30d48410ddec5208f5163d193d3f53567e0c970d29fddbe8c0f5dc6b5889dc64950062c8d6121ef77a8df466bd0744328ad684af186efc6b503740638dde679ad9075a888e82ba319389396f153c7a06d3e40228ec3edc8829368801892f7c8a4a4fcb64a6838b3eef293b6b93044388c36519903d1585ca869e2698883dcd7e0e736b82503674a41a4db114a29159d2a969c6c4960d5d4968f35056497825fc0b62fe386ff41df154ccde7e9f1608deef5eae44f92d6c0653bd02157f01dc92b3e263989c973076c62eabedf5482de9b6894e07343a5504b43c0a714183603f9a772681691ca06177c4858f0364112aa0202cd0686608f6ef6c2317c71ecb9420c14ce9b613ef37737c38498582c9096e678a6931b63f221ac51a88151049989cfc5e26cf5ff5a985da4765eb9b9aba0935b9aeaa111bf0c0fab80197143fea65fc72b8a20c92ef3e1e95f2f113964c62045d927ef5f8e6d6ecdfe1c1a61d76594b733aaa0d2aa5faf37d34b5419614747d3331019919ad44cb61e9249a64f1963a31961eb73f3fa241dd0b6458efc32ed027ad32001af542f88bbab901090bb050ff3209ae60261800af87a2b48781f1be9e217e73b312f65330199c986f0b410c96084e649927277a5821ca4fc0331d35e5e01cd2f9571e01be71f6e139ccf2955b7022e40aaa0b41231ecb04990b9faa8130f98b85fddf9d0104135d1404d345600c2a957224f54597bf6204f1c1c21ace37f53610a894f7d31b6157a926032ba6f10f196fb234ec92f83c9cd4cbf6564fb87a25373110262e8b1e1f0668039e15d3b87daaf56fffb89d8818e510ceaa04544f699ca41269f05f390dffc5a35bfaa793e1c855ab26e9ee94142d177b21907f16f1aca278622b8ea5cb3ef17e54581ae544affbe98657c34527a33593ccf329a87cccc376934032f810123119b9614706a022ca15c9e123ab099bf403d4360b851d9a1c9934e0d0e036623bf58c84ab038aea1a0f0d7b7a35eb1443d85bbde344d60673d47586833a43049d7936aca593c56912f442743ce9ba38c3f9edb0beabc760e53016774506254fc9066e1d806bf571b59e717df00b1ee9bd68f91ceda0f5e5e545055fc8230389403da48c1917014673d5003702619021c2081b164935cefafd5047804e60881b7f80e53feabb962d78fb45a987f49e78095bf405962e43a480f41fb99e1b5f011bf083c0aeb0c15f0f601104dbe20e704e82fcdfad678f62f63a6e6361ccf9fbf958af7bff86d3836b969e4b698c2b17ac5af258779ba0eb3471df7bf1ed86bc1dbafdd0e14e90b34d350cd01c9756cdb02e2678329ac523bf59aab524921ca61b583d5326c1d6be6866cd1ea0deced545f4483e5fd25cedc7a1e3d6d9c25c9419b52912d525d1c7a59e86dee1e6005727e8ccc3978cf9273f636c57ce75f014ffa255c29f68c2adc6b7d653986b064e5a7e9465e948d732a3d74204f3b5aae9c1814ceb47b15acb23e18aa8d6988fefe35a7e4cfc59e7b2d2de629f88a5eb3e603d2af9710711245d87e6a8da59f5dce253bc5e32db106e94a2d52d1b7b484aa9d4f4a602322a3462d11cd901ebf187b29b08cb62c603de9485b035a7b7cc88538408646033505409f3dea700264ea5c95cd20bdfb0e5f068ec36048f207c39e20c5a15f80e01b646e49299f186c2f3f17887911b72b338c07e6624754e7e03a1bbe95c6b627da6418a4869d3bc07c090dab8d810fb844114eea05d018380a33efbc47ea4fe2e60af20d909fdd3f47f1c6733ed55a14fa1c7ab05c6a5dde27880610484178df3bf7303b8d853e422c01a063613fc2f9d0be43f03ea428e765d3050ba9a0e453711f16fb08caec595276a9f8736024819a0896e708dbc4c207de2da0baf7d146f735f29db8a73098ea78b195740d82a541017424438c8c8586a40170d7e6bbc39bc1da0b0d785e753e5f6adc9bdf74a6b726f2965923268074d07dc0655c4d440777bd666b8d98c72066badd5fdabb5566f159b7ae5d81d8577ec2986911846621899734eeb3fc3e674ee5cadb6d6397b6cad3fb5be7aea9cfee274e95ac75add69adb5babbbb0c30540ca18e90a9577b39b0c3a7af029e7305e76514d622cc79de9c9ca2cb28d445a1bcd3e9240ed5564d6bfb753edb00bbb451f9e4d1b5e53f34c4e1d13ff03d345dfe3160a0a91e3377ede9b96b336a5207eb3884e9f49960d8d50c2de190d3a10b869cb461ea0ea554d6f6cc5431af9f3c545b617d592c23e638ea5f7b264489437ae64f3471c849bb87cad7eb872acf84551f9aae72de86a6cfbc6941a6d618b7d65ad52a96aa74ea5a6f686d4e159a7e053bcf7e39b7db4dd3e766063c3a1e61adcd51410e143950e440a1e9779f1385a68f3f278a4c766269bdea3b73ce69abb5d6666bad2dadb5f6f3e657476bad9d9352fae3ce9f1a7f3d9c9cdf75e56c02394e2c2db927165bafc5be3329a5b8524aadb5d65a4b5a6b73aab0d64e4a29b555acb5768635c55e7be9f50077ba270e45e97827796f8c17642e256a4da7cbedb5578a6b9f68b1578bbd5abe6b6d5bec96bb858ba53366559341183415b798e40dedc5f2e4c9b54fa8d8fbe4c9b54fa8d86befb54fecb597cab54f7cb6eeab505bb5016b474be5b68a70dbf649f1cc9e6f299571299e63052a66cf27e90ed2ca57ef3b061639a5fa2f4fa9554a7c127ec74c195da98ccc0c4da855af12a913d1488fd4c88bd17c6b65e5b30d40803d7fc4279daa117113fa893d1f8b3d9f01f3a8d41efbf2433ff2a39b1dc1ef83ba739cf86278ef3ba71e1fef8627e6533319a023094f68fa25ecf8d123c8f741dd7504529ba9ad984f840a8b19694e0a34fd92c6448765ea6991de7b67be204da757d88df970d4641b80351eb501a1c6a0e9d713123e12bf58ad3e1ce0af3e107c9fbec663793266602034e6cd21d47c08d0db08420f983ea7ce66a3351c23212161a135fcc3307cfa35043f2e1e6af413d7dd6cb7b761242c1d12878445e806e4b13e1cddb33e107c733266743d6c388fe31bb1d6f48022d11a8ec9e9c3d11b175ac33117fd00e7f1a3dd61a47fcad11b173c72e9093f1e5e19070747c60c8e3cfd3c9dec3e7991cbd8f6f30c6bf30cef677c38eccff840f039edabdbf3f5e4f1d89d1ed01bad91125faf568ee454bce5d4038f326bf85fd9fe788c6d2762bbcc7bb8f9d174f91f559fcaa3fa72b5752aa5e2c0a6634ac4540024d000a09919dd063a016c40b1f547c2b5b9a8369d95f376674471ef00ee9d5c76bcc89065635da464df5f15b560df7f12896cdf7f21b162dfeff19d594b22c4beff23c3be0f746b1f1062df9a12d9a6624d0628d87af15aafa8eea1351994844d47b86cee2df7331f0e2e0b9702ce88118f91dae67e7504cae65ee635c77d4f03388ee3381b45be6cfbaa0f8775a24896cd3df7a91a13319c2089224236f75a0b07030c9b9b6273351858b0b90f3f1c9ccd131cd3e66c645e90a25f206235c330758999a58cccc9b6af652d7021896d3fafbae8620645009b0b4d889c404c630b111c5ac0b2add5984b6d3b6b2d54196285b6d6be008bcf5a6b6d7953e674d9bf4b1b9bdcdc630d6673bf426273ff5c4d080b6a438cd8dc95cdf5ccc9c9c04ba61f8e3b806d6fc049382a0306d698c41a5b3ce3860fe03471938164e4260b0de70431b09028baa1d1608345c6ba01151fa8c9d26473832d70586999d5c850438ca4d591110dd013b318b325334036231a2d61a880071999154d754518377061865b93198c8040226b12e208d3172fc880ae2c5971a1698919806a342a8a002531c32e40edaeebba6e89db5d4249d7625e1401a2156121483abc05a9db92d46d99755b68dd161ba9044907c4c5c96907241c9012332cc4916b719622599474576ab047281db1a4a728026262068324404d14d556082309500a906a2c202902a485a90035cc5e74d5552ee6882cb427596eaa1db2c840054683436246a055496656d032505253689d84027d693345b250a1250a2f5abe38a18549860a8df7d4a2e50b15171db382ea07cd29494dd12926334d34cd9e5a6a3d245133e26b4a1449bae2ad890573d15e565f1e0cc985b78092c2786b6e9f6d1517b52764b468bba796da14648e19906c4e9a286dfca5e755b418e33fedf93154ac983e74fbe3afd1a48d98fea500338f62632736ce63e94b6cfc355274f7a527e59fbbf4a2a25c83a4bbb7b1d1b46ba2e8249f394d69e322cc8d45f7bbeecb196591cc162e9a2efca5e36701a11dc991307e95e8b3dac29f124f9f2739893fc7047a6efc81f06d3b7ad274e1cfa9a249d8f103bf0b993e73e327a913d4e3931148cc9deff1d325e68ef7f8e915d3670574077cfcd405d465bed33dd6f869acc680f24c8a45c514d760e32eb2d8f869166a453fd621fc2e5e0a34fe86d8185bfcdd57c344d3cfdd9723fc9a10e850f49cc4f88b1736d64e62cd12a17eea75d44f8db3b674f8afacd0397b93b3a2f728f1fb2cd29dedb4b482747fa215b19317754a297d5764fb389f5055e8fafe27db4befd74be6e7556b74d05f152f12924bc0b4e7d78468da73fce1f36376e9813d573ff5b3ce0f7ac69e32eccf7b69d3a700bbfeb571b935489a367952e561ddfa7e54ff3e0a3f6af4a4ee4f01618411c60db68f285018e43cb37572de68d9303d5f9b1238188d134bbe5a72a8a9638b6badd5d65aabb5a7d3e974aab5d65a6bad5c47bbaee3b8aed66a6badb5d66a6badb6d65a6dad15e3132a67cffbbe39e7e994ed0cf79611fd39fd72ce9e73e5b462cffb3ef0e569ec692c23f37defa54070d5d3d201d4614aab625229952a26464666868666b5aa51a9626264646666686862c458d9d8b05895c562b1626662b474909999a1a189b1aab1b161dddce05c1c1c1c8b835371707070701c0787e2e0e0e0ac563536362cd6cd0d0e8e0c193366fcafbc9a1acf864559363636ac1b1c19335e1c3525bade002679e6284dfadee64197da53aa23559aa2cb8a54912a15af94d699906e97fa8427593c1962852a3fc460d7caf665620643f6bd47c840b6ef5dd5a6ccb0bf3d6b5364bbd4ff84c8a949d6440bb8fe1379d1175ae4a636e7d1de06b2fd6cdd392de32d8b231c6fd0e1b66fff73e8bbcf9cf6c9ef41a0ad632af2ccc9f39df49cb49131455f9bef70f19deeebf7f56dcb484d72b2dab14c81b64bfba8cf4eb6683f1c1cf7dc13ed24ce23dab6044fda9e0d988dfa9ffacd3d3fd4153d19336b5b4774e94df46314e6e57ab46ba542a3093d77e94d4d1837616f2c57388f25e9348c6db575c4f867c516e3b2849e8d31f658f5ebc52dd05f7da0086442db2b2412fad2eecc494a6f598d68910bbafcfdca216556ab22f41f200404837e2773cb88b6329a2e7f7b978608ae46bee3458a9af88e13968fbbca76b5a3cc9539496b1105da97860ab4ff4ff59e2aabb2ca4361f46bd1bc6d7a336934d86beb9ad866f8bd89bdfad2374b1dcb39bb79ad6898b2c5b6654693d5aa43de38496b1de78d93eec5a0cb2e7bac9dfee49e5a605064ef388e409724ebfe6c40d5f9f11dfcc276a11148d0d55e72b6ac84610399e32bb0f316a959d2b7e8ca9cb4b66d1f8441df2fafcc075d5e59d155a2a534ed59eb810b1b7bd67a986da7718fb1d6abd57f75dab65dc7793f1c1e89b6ffa97bee7fdc45a24dfd943fd35a4716d176b1b4412a4739dfdee83f358ba8718aa7fff94ee38d104d975eaef5ddb7687f1ac20c63fbf56cee3bc98d7faf134e697bcb8876d924b368e3c6ae2ba4a379655722f69d3cb8b61ccaab0e7defefd36695a028bd26596decfa8050fc6965b8a05f42fb6a6f4567508f7def75e2b904fa31f9129aab15a774c1a0b100bb70398ea3180083065e7459420a2921c0b22c4ee0c9408b196ce7c21516b25420382dbbece92169977336afd47a38aaffaa349d3d6b2208b3f59e351180e9626ef2c67b745c39e94d1f3f4203bd933aafd94d8ce9f29fb1a2636caf3192b0112dbb437dc8e99a6f9374f6398e83cdddf8ad97bb95b363ce74dd59b99c6e5a1a32396ff634c2b6e612442598ad6d25507737e64c97a5404e05e6b4651372627b8a7373b13d45d67cdf73dbb838e93856f47c9a9a3409e5d1b5528e7a3f290f18bee6e2af1a4bacf558d2b19c496ca756e6cc1b4b6a821ed458d2116cff1315c1f6770a82ed762c2993ed5fc7d94465bea39d1451a05bb335b91896b0c23427075e94ec1054c62466a52e6cfa3ad3676e2c2e74b1eb780314995aafeed4ad739c2d57ea4e6b9dd9d2f1da7a6db52b983912a528e54394584ceb3d6b474c2e30fe48c9061183289cb5d67659acdc5ce1a18887298e72080116cea60b143bece0c40e5d6ceeef15008022c500766061736fa980c2a4622e8e52736ef766505a7882c4b637744670ee6ed71525c14728107ca02b3a9140062e86af24b90e280153074512eb80216cdc5251754092edd640d054833ba00a927d826409d293a31b1c6082251718e0926f017364a557ae3db56c01e3ade451095d4e4a77788f93f9655ef54332e3508c2b5af5310f941de527b746a9cfa9d4833077ea897615cb27418ba930f4be8aa058f6d85ecf78a347c67ae3d3c844d64f48d5a31a9fbe7dd5abde3a8b8e3d603fbce56f43440b01d570a38863c1abbbbbbb1299560e77139fdeffb1f8ddfd27ad9def5f79266c45ddef7ceb3f7de6757b44ced6add320bb79f85b51c7dddd4927dddd6bcd9917076bbd5aeb75de79113049fbf3a9f5a739d5565bed77b6daea95029cadd756af34c8d5c3397d68d7838049de9a0430a0038b807d41388901c0ae7ddf76106ecea7cf320014b8fb5614f49043d1161adcb396030b4a30c5a104d90b8a83950e2c0f0eb19d9596d8b6b5051abb81e9a502af94b9b1cb9b1c1d1b37ecb4d3cb6d97f38b0ab8e0d9e50cb324896ddfdac7da28866d7f65b4c5b6ff640d53b6fd179316b6fd9e1f26406cfb4036bcb0ed0b0dd920c4b64f020cdbd69a5cb1edf798b51b86d8f669cc06366697f400439051d1121220095aa274995c61b31dd9aa4811b169f1395b82f8573db2ff19e9b2bc61e3e6857ca75c8d30c43df7372c9d69fae59c511b2fce9f7b72e67041501eba771c268410f439e88b404bf0a79dd8c9f0986fbf136d882f3bb242b03ff7fd6d4344afcad79e7f455b5fdba0c9121a6692e8195e18e955152a21f40c134bb48c10315c74ee41a64a0e2926fadbb34643125510dadbb34603119514fa6403992ffac445ea0b8dc12cc9228ae6f6accd109e67c52002e8c2760b02a6c20d2fa4b001cc7e158378932e3015646e80d9717a07a5b6e2ace249132dc0aa3853500398165a1349db5fa767fb2b0186e2a9c17bad30214416581565acc0ea15491c6de2529bfb1c72380885617ffb29cc5a3f051f4f8f7a6c471d24e9a7f7510885a1de8e43882c00767a1985a17eb5eaa9ad11a8b63811260cf51db08043c87eae41464b3f8514f038545bdcd3ef8005fcfe3af0e8409af0773f02f6a71df51d321c42a7cfa820534640a13ea16c94476ad4010b2535f2e8375a1d4938b2fdfa320ac39fc711ea1864089105c0f0cb28ac7b3b0aa130ee6dd840e3efac5bd0070d83d26a8bc3df8d2c218c290c857a0a43a1be7e0a29a0467aab2dee6be5e8526dd9aff6fd75d8771c14e631eaa74e69578ff910bceb05e3adbab4851600115d527a99ee526dd5efee7b07367d6f8d13081d6d9898efd057aa3fd43a6badb5d62a525b6bb56e5d48bd077e9e6116b50fbab32bffec44716b71f6a8f9bea6e6cb5fc5ee5b51a268fb397ff9e9e7ba79d8f0e08c43a674a7cf3f2a8893bf10e704a24db4c30743ef432f7c4f7f5f8f301cbfb0c7076483ef7963f9a386dff77d1feb83f03dd0f3f2dbe9c37ad5835eae15bcf1bc5a6b8fd8fe51c11b96e8836ece7bf0f573a29daeea7df8856108e4531fd630280cc3f07f6a48c2063d30f4beef77ea3f71f5e5f8beeffbbeef0bdffbaa9681361fc883e66bde86e6c3ef6b6a80d488e08fe3cf9815f43dcd7f5e0d9fc8c38450d814d5377635fc4e04c72ff5def7e1b07ea29d2ebc81ec39bdefc3e158e295e8e3f41f0fceb73c6f87f204a2ed3dfd1c3e6843714ed737e6ffc459fea8294f83558bdd839ef760f54016a86b0ccf53e52c53bff27bf00b6bf83131c499a711651e0465de9b91a9aa30fc7e3e183e1886ddfe38d8f49f8860e5365f38eeadd2a6cf7d0ef3e6b86efc218edd98837bee6dd771dfcd6ed2d8a67f9936fd0f47bd9fa31bbf7e39e87341d6ef5b13e8ae220a74d3fa459e39397722d106bf70ecfe134b1bf48da9fff19fda89de875c97c227aa3e71be6a2cb9f1870affe3aa3135faf6c409c3ea696a484eb6f72ba48d2d04687ef59c98c3fbd4672a762d1e4ea76ff1a06d97f1a7445650f7f4bb9196348f826f1e359f1f87f3033f1e60a6d413c1cfe20c1f0cc33c3f4f85a0d453d04bfd7cf0a70a1bc8ce4fbf0f78783f774a836349b43d8f077d2f259e40b4e98763e6a727f3f3591f6c1e313f770cd57f9fe97485ff4d577e1abeab38ee0422d558e69fbeb3536399df0420b1fdc3f7f73fd5870a5b3f08beb30fba733e8168a7c6395d610c911594fafca991c7c883be170404cb203bf5a907c56a1d0c02c11efb8b09c792c6be9807c792c6760cf8e1ffb8c5a13f977aa7de13bd8add049aa37b4f25fa08b2657e7e4132ef8974bc5af451b7cc7be3a5cf4366cc2cfd319f5f4664e9cf2f6342d018e49544b19dc7203c64e791c7fcafe665441adb74ee70c78834b63f1e34bffa1a9a077ff5fe05d1fcea734ee54f89d9bd95f8fdfc42a0f9d55812edd5aa0c82fff49fc80af29ee6bda7412adaf8c1cf9312d1b3f6836dcf6c3af2e866f7aa0c7e27e6487562e8d92050e79448412d8a65903dbbe93dcd58d24d73bf1cdd4f27333896767b793c7d27d62f077ef04f621e41f06b8540d30e14cb13f2ce0f4f7df83cbeee274d8da5efd47b9f59ffc2c3a6b7bacfcf12a7b7f2a780ecefc3fdcd2f47dedfff388eef4191d5bdeaf3b3ba4f7d58766350eac3eeb518860f82af43b14c7da95363508c67b1b42a0c3f3f28b2c04e64c97ccc779f5722cdc71067fefbb248f32b317f8c995c8a253806d13c0bfcd47f5f821fbe1659e01894faefe7c7f8b24af4667ee66388ded3cc783f4da0dbfb502c65c620d5b3bef21b83523f3ffc1c23ea30257edf89fa63c4ee5562eae717942a89be39370886c0c1a6df0f5a42dddd8882ef16144d7f72ad23eda37bee4fe27c2cd2ed311a67c5f207a753d0e9e41b2596f81bed09c45ee7a1c43c96166fd4177efd94c8e3f4fede7fe38f7ff9515fcae6407d238f53cefaf4e91cf9bff7443d9641f6d30f47aa0c73e41e1bf5df6b31f55a9c5e4e8d650af5c391bdb75f8ed40fffdea242efb388fa4e1fa0442fc7f7de784205d9de7fe374d21bcb3cce47a14e38c82ee7fff04d4bd813a7a043fe8096e107d483a86f3c620884de6fee33fe1ca0881f25e2c86349048447f7fab593f9b10fba518f12ed74e507c512e7387d414e85ccd0ff7dfe3aa44d3f3f04c03f3d16bfb1044271798200b80307ea830038f2a0ff870abb0b72e7d1e3f456fe50fc9147baf307dd6711e7c8df61b13c05a13e9fc61f75773f9f5e3adf004ef4f9bdc2babbe377d25f50fbc1c90ee7a4d45213e8f6afcffd8f6f73f462cb0aa27f9ffae8461434501271e309f77fdc7dc713eea390da773c81fe8f6f53fa76f4d10538c8b0e08ce86084d8440b530b921b1607f0423667aeacebfe0fe48eb1d8da11b3edfe5e90b8c71bb702bf502fea9a812e5fb4ea4c78d54197af6bbb515c31a0ae7b34c75d71d204d63110caea54c1dd17670de7460d00cdcdaeae81567233c4bb615c4b682049ca9d4b0a0da8ebe8ed761bd2faa66faf5bcf50e559e13a6ffa48c764394597acad936d86903c7328b7e793b13dcbd59e74eaf8744cd1de5508f7fdba73234bc6ceaa0ed9f7ccc9039842d317a18bc0e9710a275c7952e8fe747afca711d716f7a7116bea34062f9ac090628217dbdcfbe6c0056d67d5d22a0f8971a5d1e8d7d1a3d526dc908e0902ddda49f23e698dbab1d4c9e5bc716379e346e378937376498ba0c6a9b34b5a44474699a08374a2c326d9a3daf2ff466b4404d3317508899e886bcbfbec8d137ccf7a6310cfbe7f23809fc720f9934a7b64f445808a4061df48d6d6f46acb3e46fd69d6166754de995d122bc65a1bd1d992ff4a2fd9a6a5ca33619549c78a36cdc8fafb0ffac783eed3bfe36a466b0111bbd4fbd6198201d483d2287aacf13f16ea2e2c5cab6a1626fc28fbd2d1429793e9252ba2956861b48303d3e7ce806837a521b0d81fafeb05d7437a022f2029542f0ea88aac88efec983ef54ab99673a27dc84719c976abe9f2de6c379b4de9968ff2d1cd9691bc59ea6857cf28739683ed7946d4833b7a48668dfaf5bd59e5398d5e6cfc00935db1e7cdbcd89193d593b2ebe56e77470f69d7fff1264ed6e99a4197de2cb6eba76e9e51ca8bef5ccfa8f26427db7372b4eb9167b46b4eda3569d7e7625cec72322ee6cdbc594a49ffa82810d447e0954bb5ae2cae8bc8e5a4e0ba77a9409773f6e58dd207ca2ee7cc877a508f76eef017616e4d941419b25f1ccc1a7f803fe95cd137a525a6db74a235e8cf60eef0252aa34ec307127a2ee0535d50743997fcebd2d2d28e1d4b4b4b3b969696260b3a06a5e8d485047c016e14c24226e444975466c354663ff85f99baa850b1357591e32daa230505ea41e56a6cdef333d21ad469d8809464a3224566b9f71f02425f745a837e156f48994c2693bd349661990c17a14b2a7352df3b9a16ac825198d220425d87065e996acf0e0460d203179096fc8147c75b14572f5cfc0616b73a449049f70b11b32f576e72746cdcc8c08e1e95a77bfdd3e904d1d1e081081decc3690b9c51b7722e35d12b1d285e0c9f0cf4684ce845c6481a2377d09c91af8e928e40da2519db3446871479a40e8d91218d22b8cb5deaa2015e4decd88f74396f5c12e92d262a7326a507ca8bfe12937397bb55270cfae22e77ad6b4893e52e772f8d1866d3bc5e93906d0695ba94e68fcfaa92caf3c2c36e65c2da6c3621219bcd2664b3d958a0cb695baaff433d705ac366b30909d96c36219bcd966ba5b46225277439673f0df039539100b58494a4842e6f12521212d22c09b5845a4a424231e11e540f75118131751a514a1e9ee933ab349834a9d6b50217b6db55e22e77af8e0bf4abe7a266f0638552a16f982fb41f4a63051a0d088846a301d16834265d4eda17279b56a4cbc551ed72e5ca152656c8c20a152a4b9ee05643362f4a230722ac12959b141b94ed94ba16af4ec8d9c2ab02f80adbd594d58b882b58b878d161d38f02c4a67fa7f077626ed92244807a60af5c8ec6142e5cf8cef4a93fc265b229d7e94cd1619d27f8e44ac2bfce3aee07eff1358f784d970e0eba9cb7cbd4642bf19d3a7dec439172a95c228ab6cfdbad61b6af110b0a961fc0ec89c5072e587c30228a21372eb2d2ae444ca5ddf75acf77885a46e8f232fd709256d1205c71fa0e91fdf995f452995f50f73297a9b6301499ded366a48bdd45c99eb62348bbccb75d7fb5a7ed88d12ee9b6df75dd13594c6467ea322d55dcd9ac04025718aa83c5adad77ec7175a93b767d1c25ac7a681cb1ae2357e709cf658356f29db2a78746a371401d108d4603a2d1689dc35c55f46a4f5c238411484f14a171b7309f437556e035914eaeaabd7af6b33b3acc858326ad40d9faa74ad1f66f805093ed7f630825dbdffacc06d9758c792cd03da99f1933a3f30637aab4e4fac267b3d9cf9cfd6ce2d9cf260d1cb4175aff7ed94c78490705141c3c50404701a79f4f5aa0f29cc6a0d9aa3f0d007b0044d1f6b9b7b0fa3d80d058fd21bc69b2b0df510f401417bb63100817e778ab7e0eaba608fddbbb37b7b9746b5d11a08ab6631109ba9f39e54b9cd355835edbe1dbdd7b7dd43ac5ce3b4a5b55f4fcf998d617cc18ccdad47e1478da1a1248ecbc67cd8899d1e98b4a290a2c60f062430d4b47b81778b102a7b9219ab20984ac5093c20a2ab07851f1c4eef6ac5181250c6c64841630e182054c2538e20517b980d2161958b182165808f12b2de04064d6a808e342e2059bbe02b0674d0b1f36b8674d0b27572cd02483152cb684e9f203cc1ffbebff2420129850a2331a107597124de8e564dbbdba90d4a12e96860cd5fe58b94de8a1cbd15882398f924056122a4c078c2ea75218dff19dfab585756a804f349e547a803d39703b3462561754f9d8d5a4ef5fa35d4e25eb2d27334ad4664cb599129b65072e6c464db61a8e86044103145b11b04ca17802a916050cacc0aa38b7e8024667e8e1871a868065eb5971002000a20a0fb02ad2102801cbd70bd9134e7041e4600258eea64081049bb3208a4802cba72850fc80f2ac38c17811032b60559c2e38012c67cf8a02f8e22507236055a4301401cb019ea9a804cd1865358568060000002001a315000020100c088522915014a689268c0f14800c7188426e52381a8623418ec33008821806328618048c014401828c52366403980969f055d0e3f422f42027216e65593d900d1915e9498e863093c76fb43146057df32006757c3fe2ff1780ad5c2cec58f95f59ea5030d6caf9759a8085d1c4671d21c36be5a5ab08616eedbf1f0ebf082508fa549c527e03b9010799b61a9b8bc8ce0b4a1f0a9675cd8b113f42518d27081c91ad4795676894cab8ba98ab11bcd650c82fd9828efb263bade1ae1167f423ebba0a72cac5b67a90e4ef32a4318c05592298751d232e58594073a25e80db4bdda5b64bf834ab20d900a6fef8664112201f9fb80e183dbc9651beb414dd5b59ce37b1dc6ef754c2ad9bf01ef2dadfaaabfa8e6fc7d406bd3468bee0f617b7f6310418443cf654d88c058f3edc657feb309d8d1e11eddd1d12f514bc165a0e58646fd21e9d201bce90ab0d6f9e0268493636d8b1573a559d379a89f851cd2f9a6646b3cef9e482d4834ab2aa7c008f359dc71ffcb8218562cc5bfa437eae15b48013d93a7ef95672d921c7822f89bd2315d21bc9ee5655d68254b1fb1301936373697003c0113a975efde615b434545552bb6fa1ab1dbd3cad9366554d32155ac3bbbfde0a3d9f0add7275c10d2fc1916c2f1e9d3f4876377e15788c9d8179852a2996adb0750e8b4bb861dd276bfddb58d1427f4dc5dfc8d8d0719dbdf7b8d2096f3f2476c8b40aea8eeeed7ae909008c6c7a42609945be9198c7a3471751302a6cad801303d23f26fd3b2436b9a7b239a4917193b4120493f04a295ab351f16b7c7183c40a94084195bf24094594bda3a11f2b3edef197117f1603fd0cebe12346806fc26641588de045d06dec943cb898b432d5d0fceee54d7ce52c1d24cd90fda14654c4d57a03b27a352f5d185794531d98fcc129574c486a1031c86cbf58ae399596702bcec955507d722dc54645481a28341d2415588dc15d70bf5a6c74ff224f913f8ee6bbc82d10947e573daff92765aaad4dddccdc880849bdfd7921a33f26250d9a85a471b3d5eb1f4b4ac92890db32daf8abb565e56beea71af348c1f8febe3a68af5a350d534d8e109b007d58150018f33c1ead6ebc325882d0128cd07054f8183f1ae0e940011ff3fd2df43fda418881dda91009133d912f24cb617c33fa89c87dafb584484950bed81e7d903d4e6c5ca4a324c217e7aa42e9ba848dd386dd87fddaa9c4445954db003081e1bdbb55fff201d030b5a19a7d68c8322c16e768d8564fcd66a74861c31574b4206c9be4be638dab6695184dc4253fba4a756b26bb9f16f9b3acd3b52431978cee9c0a29896d1e26428f5bd6761e18a5e90ac6661af50beb98fde4909e15a1d8f6aa41722c8b69b597ad75ba3ddb0b2314c16801b19336255870827f523bb2e4e823539dd12ada6443e95f63179eaa0f5e44156b2d1153e7b1e9b9ff835d3ddb8949ae7c388a0414862c3fc1bcf9eb1398a7060eed99bc85b685615fb668b4f120eff5ee3a045d2bc4e5ee3c921180b20579029056ff2b57a4287a8433226939a75e8a1364ec822f5dd37d1f8982d67a61d212c9082b4ea0606bc7fb780b92cf7065340040330bc4f031be3ca8f0caf139e0b8208c227d4df909bde7612020095d528b6c13ab238c85ffb3c72e0d89a8fff4631772743f1c1dc06a0ce62fd0b99c7561f95efd7e5d12d170ba45740398ae575247393ef30e8d4299bfe146e2bac74e8b15f8414bdb494312b021de76b8a6da88573b91bcaa09912c6e6420cc09b08289096d1c1f268e30d642f38dd56b3739cdbc606fd263e87d0543302454d7910df1c64efcf05278127b24ed63ae7a53de9134c71aac276c844052922001b60b2ac488eff304e3e551576b8d50763522e3517c7559ea99bac638c03efc4e234b258f985263be6299af4b14e777a61e4b714b3d7cdac4091affb2c08238471a0ce3401500216a28e72dc014f9285292931940013e0fc51eb9aa48848e647d17d949571242673b7ec4447d467fc48c04bb9885e3db3e3a49659123c32a3e277d2e60f19bb2626f8a3c701f65aa4db48778d991997545ba8144a050350d9f650731b30003a218a9089c19772b69a9318dcb15819e405ae46940751cd22355e6d830390249cafc32d699cf11ca7c5af334ab6ffa1f92a2c696dd8760cf5e2b37f02249001d89a4104c52c5f499ef9642336c22f12e697b2d202e415cb25b47b8faa371eb1e76a7c9820c3e4c8506429a72d4dbb5ae795b932cf334dfcc6caf7cf0cc2a4c74ccd53665fa98e1d0b5821d7565f6375d9b01125d774232f31a9f3aa349433ba234a6f067e161ae10f5c5cbc3d6acf115306c4edff1872fd4e233465ca0d3b6ce003d1c53b78352f29cf3a7814ccbf07e1c01edf76d7ac0fe508d8b514d14929751088b3e4096a4b15b58fa35d6fbfe45f1ecf69993b2afdea93c04ecd8292e8c4fca72645f1e956b8cff5ce47408c146f1aed80afbbb8be67d4c5d9e5f90956e965f73dfd49dd3a8736a9c4c8b90b832d44e1eb9d3eeed6d424f9b787e5dfc4bc2383b47d24e1728657941ed793f96c440a357e2fa2eebe9fdedd247f60c8179c3060d49c89fdcb3b6d240ca2d10a5d4473d30b13e4e8d720c5f03385a0cf9174c2e458339dd9058613773dd06b8f885b76bf7c9102fae825bd85da27ec85054a2bd30784177d83916bb807ed5a03c59873f01532e30a5ad4739f44dace4a3a6a1aebc24fe0678940c54b62bf46053bb83b3264c0c2c5b4e9c79b6d06ddbfb3d87bcd91647295a912cd78b1d89800add38954b53652b9e5054796564d9a213b24df92e80cbc4ab4aed8b6d25eefa085687f13663a347a7ea8169034c6b4c953f110614408f725bb90ffa7458ba0d06a39880f001c6aba0ed41b5877fee1e8a41748bebfa3bfa88aceb7138c587aad5dd140a806436ac5c0e1ce808c5a33bf2230a9b501472f0224fa083411b44e024b9308e5882bf1b0ec7d186025691c9c9130728002bd1524331893bc141ace38c1b7d04d17e095da4fbc182068bba071e20a0996fd8f9a1bb85d45412e7a072379384fd6c0813a5f80ba4ce67c02a61e7ced18230d5ed9b373c804244d7cfc7adf53f0de84e18fefba302716e097d0dee686252353fd01421a0d4987e2e3f5cf1b886b06e26c20ac94b2404eb1cdbfa5d7df5707c7324c54f1a1a7a0d01958a50464d86452b11f171ab80f7142db587d7b6052e53f56f1f3d3aaf45eddf00f0d4614e64f20db8808d5e3e8a1b2ed266b4c25889d3b75d2c4a428a41200d603ae53d170c289c028b2d85e2ea77ec8cc33d5e61b72b3891f78aa6b8e9ba1e3982449e7d2966987622339f65ea181b81986dc257c3543a7d6aca873e44625c52676dde01924642e27b3b9316695db14dda187c8c3ba3dd7cc6b7df175333ebbcaab53c42e06319883b91e4aed098e0e26678b3767b7563b7c03daf057ca5d51ee2e9a67dff2579cf0ebaec6c6b9da524328f1cee6ada08dca8176fc81f2ebdff308ed5b7fa47cf80de727ff6598ae2ac6298bd6d0e51706a0f3c1708c355f3d66d94c3d6146d84eaa2a3e850c59a663336d42961b0b0181017ff2280ba38655785a8d0306e4ff870c6f2c5cd7a9f8a68f4acd5805bec0d7809bdfb2383945c2f43f650e8014abdcf2b3f85720cecefd60264c81b4a821914a160bba1166b4ee6b82c4ceddd02fd08037bda34923c62901353c22cfdf74273173bd138c6f837d12e4e58457813732ae6bdb2438978c56a74b55be760c590d1a8a87484f89666a72db50d8b37608c9baac43e3824f9305e6e620da604342ce4ebd98cd6d2e03269997b7189d35983e7e4b02edbe510184d543a9dce5293157d8393ceb7071a8b4ae4bb0e60b56e92ac25da2b0d676d5a274df5b94c6423d202dd8f6e333abd08925c64f8da3b920a2b7c8b1a7c54daf77223cb0797ceda0052d7dcfe00b26d63de70155f2548c44f779c1e953d28bd821ada6bd42e7b133b23bbd0cf7dce2291c41fdc68819e4858861ca6646f98fd539d9886292f6c983d4c7de96404235ad9d9ecde46f6a998f0177ba86afe347a40cdba05120d63b4adcf9e0dc7a99f33b6302b7f212fce10e212a65e8985107635bd3f38bf2af198ae225fc415d2d62abe46446fa76ff8677886738430b305bbc9ed2a6b07875d1c919812da8235d54b5d187be660809ec661e0038b476c25ee3e06c28983f5338304cc40a0e434205407181450829d666fe68cfeed17f438ef64b08cb65a68fb179918096e3677ce3c4fa768b28ea931c1ee95563f13ae56b1f2c2dab378e2382824962c4e82ea239ee08b163d6b8c46ea04977a5ff1c80552cf944c760ff979310fe482cfafa7d35eaf264b0b03d45ff8c8dcdfdceb878153d4d8a956d126903541638f925dda36420a085dd4a2e5401965762dd6da93d91572fee97510b1b6d0f99a72ec00d18fcd07e1202e0b235d9445e4401964742d56db9329bbde2e18e2b88ecbeab8dd3405b3844fe4feb84abb4db739a08237d70e5fedba3221a441cd523f9e8469ca5821258d2aaa15c675045045d9b62b30f13abceea92145add5e88223f2f5a2523e7867214c8cf59168bc9e66928414d941acb370ec4efc5f5f6d06486edd11c2cc6970eafaf334e1051466701c22268cec002f9d85c25520903fbe5a1af9078f06ff30bab46fb94338a265685da5c25521a045d4c5e02b147b2ff47df4b7bc0ae0218c883efb2c6f2c4d88167779f6c3ed3068b53fe7d69d3de52083871fd42c092bbde6b8f4a325b1f4e1fd6a8f1a977891b7e7507f6196b34faf9832bf68952e81dfb9c6e877f47260dde0767569cf1eb21f959c16042716f89fdd52905745f5daca78489875da103d5444e2083262723dacd88b816b5abda6f02c2f6a52ba6a7e8ec376ac239196e0ceafc8a614691538d65abbf7d0b5f81cf4af960420d4daed16d3bb2b3a1f28452835bc74977ad3fdaaf73593ce0cd49803a7ee0c920716b3ccdf665f2390d1d6a9d081a52e18800570c8ed10e198fbac1b31c96cb59899e9a5f6a0cee5faa1f70a1e6edc0b5ad8850c043ce2b5d3a40be8a719ec5a20e6bbd2e12949bafc9732a0702909e3862dde6d8c8a868ebc3b56721d3105af497870971804b40dc7d2a1a4dbfc32406be5dc0cd4982ca014d5da611aba279d649260e602bf1158685944409dc46e8c50a53042edc7995470844f16c459a4796272433e5ebb48820be8ef02ba7f066e43fb82cc7a42910bb927afeac9d5ff1b25a067714a88756e7262acbf4e850dd083ea1fb67de2133613791ac4c6ab9974066ee3f528ed821172927141a1a8f961da67398e1dd2cd59dee2ad6cfa49e5209cf06448c920840ad15777b35382dffa41f6add15929e2e0044c3e7fa8e0e00b4e70cb1141f16cad6dcdc4b02ba969dea89771252acb6afec939f9266d1812d774ab7adf9dbf045b34dc422a048279d8e697d9327c813407d3309c0bf77439b286f4b1b23400a5ccd767430e369900a4b8de8724122b912e751bb93da902cd061effbd5fd4e2db5025d6865acfef8a9f1a9a8d41e52c328c7c9e24d619b58938c775315f20268cc4a31b586fe1588d42a09adf1368cb5cec1043a982b6404538ba78c796ebdea65e0d4f9b661377d8422ab0eaf45e6e3880fd1e2cb2a561dc0044502c0245c34efaa5eb567b64418743b83b348b9ff6e4ff46002f850f14216c311d2bf750b1b2618ac7e794e07f9ce52567c80fb88b9b40b7bca5a40c3aca5d11d8981ec354c4afc15f581051e076f1cdb8f701cfa9513f38d7515e83fc94dac25d3f0b9fc0b0628b758cef05f9a44bae129ef47ae6bb0ce13a3e070e3816629329416c3144cb342292926b81191d4206470d43f2df6c29deda31866abe35055cbca3a09a4693de4b896a00891e3650b7456a661472f503d222fa178b80fd676c8502dbc6ccc089dbc9ec79db5158f8c627a9b7192a9c8858550c604af7d4e8072d1b96c80ec1860f5050aaa5bbb51d95a92eb58d5168a0fbbdec5de552333628366424006cd4db75bd813ad7a10883c8988a135725b8a928e04649f68d2aedd080a01a5e36808341ab7348aa5de1c6d84f8f8647011e8b1e4a5845e25a2067300f1b23a6b64da8ecbba5ca8bf68eab24d67b3f130edbd000a2e39c7dfadb8edd6fd5ad7015691eb12ea5ae97ee526fb8dfac2525c4d30cc58dee79ce897133c4c7f85002b7c1f7cdb9f4231e9b25b9cc6b7e63dbd77d91fc0a879341fcda266f49f8a2c90d95d5b316ab96fb284d85de1e0c3f9627fbf389a49ebc1bb2f5713540ce711253dd0f8e72115d6ba09907d2dbe4d5895a1df1a0e491b96249093339e8858a2c56955df82eda7672b20c85c3d76b3dc4cc693195a11cdd6b676dfa28a26a20fe2d9ef28b9c3bfc383cc055a6cd8a2e863aff8f2ecd051973fcd28a5f484065b23f98b3f0035af38fef141aec70b7ee98fd6b63f52c5fdb02e9a8af5dc172ead926deef8bcfd519df415b3b198682d4dc077249b5905775e6d6c89c64d9e90f31ca7195727c8ed370dd3b68e78eb72e18d6a3837413c0117f3757ef47072c4f653d50a20a1ab489385b3ca5769864517b59516bcd3a0a15cbc2e49ea0b8f02e5d501e3acfa79ac1de86b1539235e395ce3f1446602307087f9286e5c10c8a7bd81b3638bef098cec5de1b4c7b10d1a196d14e28ddeac47aa2ccf8e172d4f580d27b96782cf31ed10b255e301026466c4b7dd3c87bc77c905d2de4cb502669ca4b3bb4e9f4361fa07fc90f123f51ae2b68bcecc75b6a14dccf141fc12958611d501da28f727f6984086c7eb2fa9e2edd6f3508a889112d96c172454744cef7a42f60f34d1f210f08b90c117a8ffa82fb48e7632045f93c422f751904895dc4f635114022b79c32d01947452ce29b56fbc1ffd1af8cf106b38631a36d10be8536e8065ebb242699fb24d4924e6916e0b8067a7c83e3504bf736825ab0c824ffd6d5d0fd5962ade8cd66ce818db9af643c3878a1825234f7161988c421857f26b22eb60a7e56f126bdf9b34d7f1e2f2956fc39c0c6e73e816b589ec00b4b3f5b2a13960ee6aec8d67135946a0434890b434541527776149b13bc68027c7ac87e450e3ce5f9b2ecf7cf6e4957f48279da3c60da8bcab90b7e2be3f20884fff04bc48bcfbfa50ccf275531f23da082efcc799ff3376d08703dff5ab3a45303d7ba1d87e6d0edbe1b022a9e9ba1ce6643942b8ebcfed50ad733c5e4b3faae8050cbc957029a75f2c8222b46d4c7643a77a8427ad24495296a9801ef7f90fb28080063391296ec18df4324412a1f932672b10a3430ae5b258b2245478f4cf405ca4056145f8e1132f92099c0f0136856a150841ad9cdbdc0381da5642e812c9842bc502c8f2c3330e19e33b0007208a7fa56e7726e5028ab3613b913ef03ba21badb89cf3e50d4cb474eef76749676ad8fa7e53bf582eaf9b54b408bd7a0497fe80ebb8f55b2aa8bf8433ac90f1f5d893b1382555d567eff657a91853b18b7c8a0984897bbd46a0a5af70742ef92d3ae5aaf27eea05e58d5c98dd10e31254938911ecc53e0693cc2040544e2b108399454e7af29d539af0078aed7c74e3cf7332e74c008a91725a92dd60e3a7ddb23930a077439f066c445802cd93463b6f7b4e9beef5350b92cb76d25f47ab9329aebb6e5a9cdf34f5504b3fecdbf90ac894bcb11a82fcf10fbfd85cfa43e658bb7e4c7835bb20bfc3ab09a04274408ccaf1ed56e687f4ffdfbc955bd722635caae581a1f47de28a95d5b91c34284c6cbaad1bc4605240749c8f1bc0b024743e393173fc4fc311c4c9306503bd6b5affffb0dfe0ea4a573a749fa5d944d92ddcdb113b6210b27adaf8006761d2f43935ddcb4958f34b6018a42ee01e4c6bfc91ec302fe861711a3a1e55e5554ad1aad8e7e1622f3dca9a85a2800f18f3ff51cba2b1bf92bf07ad54902230b5d513a5f98fde1c418bc0716437189a052b4c068aa7191552eb097b304fac080e29426f9bec0701760d40e6b57bebcc3a31e5f345f3d766f83076e3c6e87ec4e3581010e086866f56d8210a05e780b48b494b1db3159c0133c8ec9d406188abff2e4a7b9ff7ae9af4d2ec9b828a8ef0cfe5e187564adf205540c6720274ea721093bed859bb7b7f04ecf5a2028ae7730828b14711b6c212ba96d6f7c4d6aecda5e4ff3ce769be849f226ece115fba7fca60e73f802b52a46df1149936269527c398d5ae940cdaf66bc24eacfbcf6954d2a87bb472a18e8fe337b2715518f0f64e66e27c2c10e2a9784fd1ab0129640e984cd011b9559504f834b8dd0003a018b050e44026ed2b2cc83302c7cd74fae859cf7b6e950982df05d022a8c4e8cfc9d0b8e5e7ebd61973ff4a09b625a3597328176168ce7874af8ad49711dba1bdabfe1d76a5690ae1032d1f8080c3b65dcbce7e678c034542491b4cb1084e15123b4303bbaaa60c24aaf171d5075bfe12386018f6bdb45679c3e24a14907b382877f47d15ef6ef55b4f270c9bf7e823787b70d5691ad0eb88bef5079a6391344d897a0291bedbda21596d432fb82d16cb612c59cfeb16c831f10fc53e3ce6d4e2c078dcfb374a54918eeb252cabc3da3d4d702767de163e8f84b7708c5bb2c7c6567c07d8a95f97d7f9c39ef5fe7cc498a4e19337a5a470e56855d935ab83993abfcaa8e6c3739d06f73eadac6b0b65e0d0fb9008db2d62058f2b20e6c283ceffc4beef2e980f801f84e1ea154a736ff73f75e4c4d8e64c269fe03b86aba4fe37cfca6b5e09785c386ddf055bc35f99e7c1aa75da0918e1074f1ceb684d5d291bcf7d3344c93a5c1d680980a861525f74713714d3a2cef488a6855eae983513e356c66935c0a55f197ed52ef5c9333b0216f92236c27cafd48bb164d781883e240271581aabddc36298bd1c3ec98057c622680ec7b545a43e68b0801eadf0324192af2ea89591c9c2d4c2be3450483a51a18a0380269cd84621244aa437c76c94b71502a75f460e496a13a55408fafb77abb6d27ec12db8153406d897ddff2a519bed9ccd99de38097da82f996f4d8862ad368f91adea11120a1c1a9582d048009c34bda798d443edee9a3e191851752eb17f730a0c51c0e6f370aabf11dbf05b8e66e4e095a66f9f96e77f0f657d04d8f247c97e868b027d6b62d117838ef89fa31281cfaadc319097c0f4573206c20c8d22ddfbd24666022f3cafb9381c04e609924be3f6af4197c62d377222b2542858d8f674c457086d4ff1526a1afdccf38be631cf5fdb92fda83368bd6dddc039280fa173cc934a79b57ad1cd1f0d878d54b082a05e83477127f206074017a1e4ddb36f7bf48b20a7eb223e57febb57671417b14b0f42e205a705ec9a47b57656f0e71170099c0dcc177b1f09f58c062567536627e8d356b65a0f276a7a016cb3af88e1f070854765243631ea2c0ce2f5f48b16209544625602e1dc47b1d164d90acea281e0127c497fdc1b03f29fba3f3c889b1415ddfe74e2afb0ab44b14a20c7986de7f7eb71672d7df0232b5ece43bdba349b6d6497adee9ce64e658401b1c27dcaa2f1d4dabe152d4609397d4aa91b36174e3aa63532f668b734be5f430b9e9dd8d1e8611a01f213b5e41221bb8ed6e4a4bc752cf5af3418f2ff798d5cc595782395477d0ab303b91ceb4f6d0770a8a228089bb72be6878a9883adea4b5eb3788bc6b124fc9134e19c7cf6e04bea32929cb2be7f2f95d7a1289e4ed1c82a698777fc0b00fa012142c05e1dd09044027137c99915b6319a01bf5b1131e6e83df3d00e94c4ee1e82f775743067d05cd3cc6173497eba57e6ff88b1c1d6eda7a7b77f2ac551fffec5477d3895956870c3a18952db35f9b5d05787dbdf22160564035f996829b879fea8fc9151f18e70b02eb4254be8382ee845b73b4ff618f004c5ab3f711bdab619cffae9b911a2ebcde9761d802462a0597653d0d3661267e3cf70d80d255a18ea8a5d751f7327ea162b52712a785ba51bb41288735e243f12013fbf49dc71ab10a6f3bf449f36c09e6986e08532dde1cada150bebed0edf39bd5f35436eed9a54311850bbcc346cfcb6d192c8f10599e445f3ad859c3e6031acc025c9b4211d61963de5ac558b699acdee43af6cebad9b0af031e4ab923f202fcd145856e5d4faa023c599ceee0f94953811cb7ff6e010d62a92388810379803b0b417709c968e720dfaafd05b04afbd599476fecfcb3732b1140af6f5bd182d24720f39cf9174fcf9f91552fb0a091d197f425fc51cf60b9b8c4263c526c209b46031f31c9d2f391669285f6c7ec23caf7da04ccc9e1bde038fc80cb7a4a6079c3cdd35f9ba91acd0cbbe4cbb070532633b6275ed42e8ec71eb61c9a4c0e77ffeeeb98bdde5fcdc62a35e00102dddf16696db0fedaf7731197e3473bfedbda2c10c89840d84a3b566095e36854a740db09905afcca5aa37a81eaf9f4ce259159381c7235c3583397a6621e27d8c5ce62dc8a43ba641da02d1b750f39e7484af0e1a79a4bb45a88feb4a51199726ee14fe99a2b0a899bbf66b36502a3179cd292b87b07be2aa791120e69604e831c97c33dfc62cc360a2e4fcf443508e44703d1647314bc92faf81871ac5751daa48a0fd7c5dd8125eb7cbb3224c3cdaec8ec3b976b27d4b86f00eaf006dd1e57124a0fbaab37b8332b9f0654bccd1549f7834dd4716bafb5bb7a91da039d68dcab5bfb242a982a5dcbab5da23beb0c98d5c5881ab413543953c47086e032f42c20e6ef66c686c5e0ab1ba7706c085a0860b62c70b7c0ea41fc80c9b70ad2b992da10fa112bb094296b5c44945ed0c187d8b058d9d1e7c41583acd62801c607cbecdd1b0d2bbd978dfb3dd5a608d07ec41728bc8ed571e9897bcd97166719976c6cd6bcb3b8d609caee9073203970a9b218575f8b41da548241abe1a324fa05db08779a1b9b42158642673f54a05fd31e5c00ac5734933ee730e866f95f6c7146871a69a9e92b04211f706f3a8c57cf25335ca5bb8336a885af3c0b3e76b91184b890febfdcc584217e5fe432dd0b4ad090729374df0e4ee3faa508c5842e76c0759c54848020144c72a5d55439b2bf3ecb01c14b4e670f072bacb5c70c690fb374f625e1e2e02eaf7333bb1fcd30cbc42417098dfaabfef599526e5cb40ef22c998b52b158182b99a9d0183b080fa63c5177f7d62c2ad6b2591bf5d84577db205dd1a8202fe2e9d44dd70ba4d05c8ee2b81ccae3aea04cc527d87ad03707412ad2a18cca742c0577c328e4171ce32c581df50c76c7da1b094841b9325f17ab552cb3c0b67c19c7612ea428d76acb3d08e66931433f585ed36f1e232945f50e02808a44bfb1406ff772774d465135c2adf28cc245fc9c48ddba57f573cf1f3231e506b0f6e91fb4c3a58c62f8f930e3835aab242091cda1cdcde64b0a79d25b8dde1ea4483f8c0d3ea517c438220d918d4d9f809a3f70efedb02ffdc741c4e80ed03424bd5ba30b8e68014dfa682da16e70b130f0d223020f0c0e7145785b8427c9e897429d8d9f816896f6c087416dfa1a9a1cbee520437d582dc0c27b452dd5b936f0d96bdcceff821c947719e68731bd25d8dab5de6c7ce961a0a4fb35c53a6a4fb2597ff32bd8420a433895f4638163f0e5a53131248ee3d29b2fc0963572710d947f927c17f1e4c47a9331d646ae86eb66d325eda50bee13df2e315b677e1ed2f3fe642e7924c20f4517832339fe0b02bf13b4cf72a2bce346bdcf561584dfcd4b576fd256c605ae2dc254f78c7af183f8a5783ee63c769c6a93da401fd831ada4479abd0b6de8d55cc0a355eaa7fb363ca32cf6f5d3080cb4c77519a4321205bddb2f1144ea7acd07aa7aa8a52a13796ecd49f9726ea48f2baa6da420160bd48a0701a3193b93d048cff93c4cedd134f6ed29098d2de3b9fe7e4335ed34c03ac3b798851c14ec2a27c0ead9dde7fb37bb2d88a17c287998351f23ede626db5c9fec4e4068d5970be07dbaa2ef71b54a39ff16cfd040d7cc178b6f111d051c03fe2f96f354d3096ed5000cef038da09a5caf03b50411cd6b3e5780f2cf28f23ce0c5680fda19b18a71593a72ed81c2c0a84a0ea3feec2afa20e1b65265ad9a3dafb9dd15a6b3c3b8b669fc8b2e99d55a6a90674060f02613eeb2c0a96f51bee40526a90cef2c70f0194a8a524923e702d1ac5a595f1c7f2585f1cb40faefdebe3759add38f36889c93a2bacb1c0f4bbc0338e73427d1269533e0716a6f6bb13387f395ae85561ce7a8292bcb36f8001a65c4e5f7398c901e7b7256db787af8aec80caa5b24211ff9ace03d07e92a3e83f07b1b6755faf26626c4b64cc63215a81906a948f33d5646d655ea8a62caad80f5ccdb0c505aa81026dc00bb0c0817c9208b7cfb50b89cc7147a2fe8bb71ef3785499632dfb083cb631f49c6ba0351c79b90fe0dd7978e425adc47e60590f2607093f1181684887d37d1630f3574c9c8cacd6d9a2da36998db0f2f4ad63cb175a607e9e0f73f085468a3aeb18817b64d3c702fcb80a063f1ea6a8c4c3d408504b5e7a1a215b8d66edcf4bd3bac290661a8ab3c074d0954df10e81dc00c272ffcfd75005543670dba305286c27c4beaa6c7ba94276a73b140b4862997fe136d9452c3f16a5932d9e0f50398f9f8e51e5dcb6d7d82ca2640f7570419ebb3d1fb5a1e4a191905e110b9be7ec148b0a115cc0b85c5ea5f94e801a8e189caa022bb3234c096e106c5a940846134110822c645ee99615c9893140769f4e58a6d648e3144437167e83786c9c4fb11d00f8c18888c7161dae411985688180d4416c9e74106f9433f0c5eae93b359df1e02723016849728e0ae0ba6652e0f85e15c0b21c619ece3023e3ed27ca9c1e95294280a4cc6467dc6c9e376ec9738042ca29706d35fbe821730f9440ee6b593d812eb963c7b6e261a9915a64904153bf708f63b36c466b2c3a2cc08918078fa40f47a45ee3cc377708809280917071ca6f31db41e0475f03e4674c6ccc0dfdcccf22159ac9e9f259ed924e897a92f774027b59a6c1872ba605b146ffc602850b605ddabac97800451b22059fae3549e8d10929ec8eefc87572f97d2265f342c7e77c96cb4d541da4e9c375859b2e5dd4d5a6f4b5fd9ca9596cc84f38d1eda0d4d8eb5b8fe1525d35cce75bb38384b09045af27592dd52e840585b563dfeac96420402f03048da46107c49bd2d6fe6d30deba74802143b92b05444945e36ccd48a6a612b1528efa3dfab05c5a2ce95b4801b6379e36f6f4e6bba406b2b8a775d780da3065d04b2cb1f7a041d7e104c973c1ef14cae8bd5bd79cf114fab77331a98ef61f01cf12314b81020ecfbca45af2949cf275d26359f752b85240895f61b623f8c4455e97fa04a8e7ab07ad17753bcb28b89f11572c7799698a3a9b2fb27c022fe501007385ec1c02d1360dce9c86682553887ddfd1422403fb4e36415054ffff22d5b99359e72550e82fbc6fab6a7ecfde6561f5910357e72fd31ab5e427cdb1fbd158d2bdb5873803bcaa1f252df5e6f153fc639730c7a79e23e4d685bf0d48483427c18a2554a4e773c6aa32663711ec1ac057050388c3adb446794b6ab989245ac0bd532f9839c41d4ccde94d4a631492596bd2c5b05a00161c7c0f1864bb7f68d9a06e705019cd7975374bbd1c82a29976f322f68fc5b90a210682edfe59f77ba53d6d0d8e6a464e3097966cf480dbc6dbc74d72665a8e3b6d4e3f49a6d334634a30e1041bc6fbe76437b03201e696075e54afd505d3e08107329e3cc55361d1e904aee2c4110dca31f87d026df9937c559bb2246b5e7b2e4b945e1cac4e4d19a05821d4924f7315c4a8abe685c98d633078b97c7f6d2157554620b9720c16c8ddd7f6aba042193e514209ac2a2ce933ed91f17588e29890165005dbed91ccc001451ae29118d08551fbccb883a6a388581f4b381e238c097aee4b8eac3fff80de561ff995b4c2bda127726376cdfd04778cb6840c9ca004558559039eb44cc2ed4689ae8354309591ca8f3dd6af59998458ae9c6f8b1a4447533801b9f55de366e6d4a3051406200fe9d7bc3236784404b5f387dd100e4ca653e848302c64e8e87cce6fdd6ee2d85e515f415672d1504cb86f5d3b7fbbeddcce4099f98497dfd446024f3891841d3fa1e656933847a545e48b153c2029c7c11d3f9fe49f4bf0972c176bfa29629de9562e385ae2ac0b61b561df3f82d15ffec4a9c310c01e333bb606d013ca3dac8b58af69240bf882b56d9fa33e7f965ba4c0e7b04af87087012d1e39bcf9ab84ed71deaf0aeb2ed270a2a36805c768e81f06863816105701f41f1aa3af61f93b46c26e82debd8d35fab3c58dbe705f7feb571f613a63bdd8e0e3cd399207f74c3761f3aedc8f000aafa557530f427f16413f510212a7647621e071ebcb09920caffa8c05208f0cf76bc711caf0fe0de8896a29a6632c998061776da4f65636cf65b51742020d043bc15240a0e359d588b0186d47813f04ffb5951a62010b662f9ecf72fc3b5dc8341930bd4fdd573e2f01e3248bef1783891d3509a6e8aca660c7699f2fba430988b09ff76506a18854d4593b152b7ad3bda530339ebf9cd5915c82c8fa662279701993762f639149013b04fea4ab06b50e894dc47b7a1aeb867e69251b1b81d56a4890aadfbcc84aed1d02f12b4c0bc0cfa14180b974e88eb4e64530bdee451ebebf5c3f2dc93b5e888d6469b64361106a01c32cf93c0aaf68d10e6a01c8da065cf2c56002e19b60be86b100f45ea3cf43f6e43f1c21db26d0859807675b483567c0fa037bb3d42f052e730a0b0c990b47e34a6f53d92e4f32903ffd4b126da3c315f15e9542f1a4d2851f6e1a7b99d37499f0d186685b51abc4cfdcb9be8d15b4a102c7f423d1e696a6e0e49b85e3349b2166d1451aa9a590288b0a003bb29c9409580fb0363a34ccf15e1e254c9240392a6c4e7d820be8e2420ed7dbed25287dc131a21191e339b1cdac307a0d41017331a6f2b4a9943682a0da0990c730ea377cb8fdb5ae5c905b6da29eb64860808b5ba6977a40573990377577bf1d8cefa50327c9b1f3d58a6c51ad4eb893cc9c9870a82808e37ed90fde433821447b1e9b9492c01f2c308b76df9401cf3c217d4ddef7faa9148af9fec08a0326e67e44cdc4c195ed4045b1b40a5aa2170065af523bc0cd8408495255b9fde213e64350604d39590123034686ac22ad6723680fe245f5db1a00d7558f3ec729c286be212a97a6cc96da27e435168a6e9d7bc950071735f46f1f59075c812fbe0fd9324cac2c1328fdac61fcb7b3dae51162df2edc32c59a4c9f40c3ec11048d64addedb1bcedcbd1e2843091c43a049d51dc8cbbc64be0148daddb7b6d154bca4898cd401a45822c842d7c7c76c81923196b2302fe53f8b54a4103c2d342bdb5470edf66a7e8198724abb9ba27d078d703b3e6d22c55e75ecc884d6d4edcd070ab180b0817f0860202fe88438516a774415213c4736e48f5dbcb328e4f3a574bd15ae9497c67405e52354d0be18be9b61d20f52aa462a2e50d3d3f7e018bf20183c70b8601bb434c456c397d2543f512b879acf655adf6f19833f2a4961f310385af9078fc9edcc9ff999f483a8a39007b2e21aa2355bd4c65902e500fadeac94bb5fa350ca41d700e3350895281619765ba7bde20410cafc62a37d10bea8efc32da07f08dfe78e7cd8099e3425b48a1366cec7fef1271a7af4bc211b3861313043b284c24bac96d5ca864d2fadf7d2578811291971376801dd31d4a1a5f9b11a315bf45a2aee7d1e3354f6d9134b9fd13b17ebdf0dcf559474d1bfc6c1fd4e0bcc03a42369d7842767071b596c77e2a0b2eed9d6ff82f0412729164e76da3369ef79adb8e397d39952070e742b219a37e072b13f117c00ec8bdae242585044d548a4baa84369601ef1a21a96207056911753f33160ebe2e6aa41409d7a55aaf04b69681bc06e12e11d35dfc6533e4eba3745b52d1c66ebceba74d7d417069dfffc4e14058000718b42b291fea420829571a2c831c26625e36aeaf6dc071a2643e7ce87b566fc30c1735b80a4e764e0d780688c9de6ca8e76fb4010fc67c1deddc8d80d3be1a18a063a93636a87f9e8b1d651ab06df8b98bd4549d38e099d1735b2839e5834d25f12a55a49dcfb83bca4a05b14fbda35782ae42af1c346125abb6c38c06c4551d0c5d9e38d539242892de0e450aa5edfe3662f61fd459cfd2e52883420fd0cfea8de54f2441ad94271269ce10ec7c2d5e0f3674f4ef8faf97d7412032b96329423fd79a21066d95fc47e78b584d7de89ad97590f838609330a4d6c6403747e930a7ec7053c6a7831268eb31ba4d01ab3cd5583d46c91457032577d7b64583fe9d555506c45ae2fa6332f58e386353a7922d1c28602f4c7b1a8b445d860187608cd635d95cb068adf8ac30e4b2681eb3017d2425bd7f3ed680b54508e4a79351362b7c83d1fa148f4f8aedd8da82079c03125bdc5d390183cce7f52f9260936c646450ef0f444d97e3f5c38d353852a8161d86571f6f51e42054afbe759f6c2605a1685906224b89e7d5484afeb247a2ec790665a0ae381eb5c8d8031818918702f14d924a01a9a21ab81b649482e1d17c0beb447084856018daeb60bd293c4ab17b2b489d6d970391d75b7e96c1d3d4d37cf1380c80c3032e34271a35bd83a237c8e9d64eb94944b8fe2579b8862a1a5069f6d1da3d2f7b0aa21408f53e440708dd6d71d6fea69af8636910a1ba0d290f506eb33bf281dd03ca3ee9d7d7613d2418499310b2240e7d11826e6d9a8ebdaa43518a38e375aa162b48036d1525c3ea63441f4d489191b4f503f41e08e970b8bde000ed10e44c2a749b1cdffa91942113f5a1f069ae2fe53f2006bc8ce17afd47ff6fe5fe1f1fa4af5f012cb1e858d71b465751afe7123fa02485036cc8e9522165a402de274b8d6a2294dab816f5da981b421484be77c07c8c02282c9ad29542d41b329fa41910f5aa0a9a09dbe037acc2c316910970b3438f2710758de2e0436e0f8cb1af0fef2d1d38da6598a390cdd062f5e30e9c64cc2f3bcb013603e230b5425c323790a3c9e1311a9bc67be2015a235524c86cae76111ef33e7f8e74b5efd0c03747ba6532dfcd1ad16f691972c19fb93e7112c3edbc7e975e60d4e4ae82e0393f711d0e3f73c82c20e44da291c81ae64c3a4700c2edc0ae7850bb41b4de670c9f9e7f798620f6a87c1bd4e506cad93c87c4c673755a99f44d6419daccb33baa9ecd3a5072213d4b13aa5abce5ba59b4c71b63f2ae5333707f8c755900daad2e6654c69a4c676a2ba0310e63e745dd4c7a8e4e39fc9ab31accd2ed1418da3dce711ac60f4ce5d0b0dbd71002a325f9422637711c7c424448d7e8e56a2986feb11ae03b914bae975e6e1866840282e330169351032d0815fb982294bc84c5df2fa065a067aa012f341be15e29d9658eaacfac2d698073bb62978a6947dd016a0bf250626fa578d9fcb4d605fbbf9aa1c029a3c174cae34b7978e786a73b2a14eb8206117c4c2a1bbb64c505e323f3ba39401122f3aa4400388fc1add056f07266c6c4c04c4241392951841252f06979c781f8937f2eff0f2060e4d4cd833b5e4680deb032082f91e20dccf22205949fb20cc7163f0efc91702f39004c0fc0c6180e7220f034a9fce6c0f3ac5ab1bd27c24c43c387a3d8229cac53ee723c07304c00988cdaa5687e2f11b2dbee7ee9bcc6e5e67705edc6d6abdd84c85dd3d96c48153a360c99ea49f4f065edc4f5b3e9ddd5ff994b58abf07cd9e4d93d1b9d96344ea3b37949f4825226545c3f36ccebb62acdc9b8fc3c68750173b33b3f11aef81fbb6942ff1e24c1d22976bf5792fbe8a14d07d9ad8d8e7484aa66c3030b5db02d82ae043d1201dabae4cd4546c1d1899473990129e409b44ab31cfbd8317810def864d12a96f9aac5ccf3dd91d76d2de557cb3200388e3a687bf8eb1885324732b914c786cc1ab0cb61e4ef3bc22c1eec1009eaca1b325599458e2c26a29d77328d046e0014e6a3632c949948af3e2d25a59543071081fce973684c37ca302f960905876ec7140d2c2150fd41681be7629340ecd320400511e9158a89543966ff9f0e54877d63c72051983d4aa015a3cc3195830585bf759f0a5596600cc437682c1fe378df1a830311da8b6a66a7f8091452ef4ae5fa054bc35b038e1e2dc8397e7bcde6e733bbf387f9ebf22e4bee8cd4e19f49937be1303dfae259c8745a09b8f7951cca167dae375a01742b0dad87b1882cf9d3eda0de5daaed7c3c81df7a44e2dbda14ca1c8791c60314f4eea6f1d7b51df6234d60a6208186d586d5788832e29b7a6f247f6b47d87bec4f45bc2102f9018e08cef493036174b62f07d82974585a54771f973f0faf8ce55ba4d5072787e1c5a2692ffcfaa1bdf1d3c75f816c07150fcf8561660262766811f95da9dd3e07c69baf18bf49871b5f8b720a968644fffa30a9173fc01596b99da2834c79ccd1248ba5c551164344f454c8654acc45ceb70bef05e425eb210055d89555d2d7128d83bdd30e96aabc04474fe4cbfc0dacabe2d81e8ea3b023ad268872bda7b3db8494c2b9428143e86e3521a0019258b3b09559ee1c6dbe645a0ff786707be61b933026d461cdcc1acf162ffe2adc116ad6106d2a8aeda500a54ba3ce9459d15c8ad76ca4cf12b3305833760914f13e323e7495eee2405c908c658f03c0eb8091d61238202fb8e3125bd69321001ee14811d1af65ecc3d32e569dc02892f9ec7bb04531a6801846b60631b050a5ba29db9cbbef1991a2cf367baf979557780ff60df1f1bba2d3aae49f09c71212d5f0711f4858161d003d0ebe34581dc8125326c5796971bcacaa82801e3ec1cd88497dbb2241d02fc6c7d664f7dd3f26f8dd27a67b98d5dbd9eb00d69bc7a921be8ba311534bc39195a12b0ccfa9934616c1ec143af875bb186c5c0b8ffdb03c29ec18d356f73dd4d236cf61eb826b627a3c6074e1669f9349776e3ce7365aa4ab62f1e9a61e5326a17fa707c5daa13092624bfd4b8e4c6e77f5ff2cd8f4ed88c3eeef04b8358e4c0bbe895ac7ec8f037293445a5b13a9d65c47b35e0e92aba3c391aec96edd63efffb5e1412aafa5d78de7f925a994e4335b9bae0a62498b9349cfd9f26a9519f13eaa2dfe698e3265a8283754e516f75e05bcec45dcc32d32ca62fe6f8b5bef4c8d03088f1625bde0289294ed8ce1b1846e4ee5f1c1a8f0b82b69e4fc837e94a807bed68f6d66d9c85aabcd10847c02c373195bdea9000e21ee9c938bd74c28144fda619a96b06a45bca0773bc4b43b43731cf7ff72bf5ef7b8060df70dfebb469494b7e0b5cc999f00adc23bce913098636055ebca232fa85e7553894bad84e891218f06f60c2b3507cbc9a5dea28e28110bd8466ecbd17e40fe1e08117733ea35b59e86ff592116eb16d8aa2a38f48f994b187027d1c52a90708d3a8c49777e49258ccbb07f0b656d80feec8ac8eb9e6383a2dbb7412ae928e19980c3e1a4313682a722c28cb87ba5e0160136cfe8ba082e1e621379b5f14a0857349950325e0f530caecb314595eacdf1ea4f55a91eb0cd95d76015f8c1396fd0abaf2b5e2f37b86d5589f3b2df604d61bc5507f329fa89377a497141df362669afba960e370ddaf5ba686b57ff6ca490c9fa281a0021419fe17fea5e23814687adfe32b305176f77d2e7b0f852e7d2ed87d36f41e7f6f31c011a0a490de60740759067ffe04c0548b1d7554a5922e3e629802f27b933a233fe481d889edf3441fb6dfac3ec53eed64f5556f647f0e7693a31b0d978ba0d5a8ad41f1d0a3e2f0ae8f8eafbcdf8fa7e80a2e567b3fe13d05c1ca4e94ee6f1b6314e4d972611f2588215425e1831440f8567e2103da97329058014621639964e258cf57893889ef11402e89555a39f7f1affc0c79282f25134012d2e1ba6bb009749fc6b8b703a5331c623fdf4284bc48ca92d603f9ff2e70e72a8e83fe893a4c38c3b1608006f1443faa82b75ec27511471a581841fb49807e3dcf43ef815ca57ad9a606e5875e9aea59c8f69999501ab956edc7f9e2c20bf0bd142ff7c091f6f8a7bd2875ce26b431b61cfd0c2357655e8b405f6e0a4c2cc94feb1c44c4378d55ce53ac09fff5c8c0f3601424809b196cb1e64868ef68c04594892c93e1120893de22b62f88dd833941928d2de61c0c9b82753fea1afea14ce373bb14212c7d258dd29d59f80edefd8481e44c8873ab8b1f5ca29a395ca3c1c2b1e8da8c536dc1c52d23ffbcdcacd23b08fb75281630ce926fcfc91dbc12910d0c0d48585bc951b8e81cb6ceb31fd51b3978d8211916eb0f78c17df5f35edcf5aa759a72df326f1e0b205d40ba58cf0abfa3608810bb464d23c0c0bdc93ea97d5d41358106618fb736594fa27210d63da45396b1cf887722be7a94718e2ac0071551351548ba25427f31916e6d1bb63b3f72163887f11e1c65bb951553da5b52fc6add6e62902e3a519e86898230d74749a50d7ee441a334f888feec49354014956e90488d93d83323d977066881094bc89702727c87c7fb451ca7fa3d5df0e7571c6185ef0a9ff09684062847ba1179491871dec80971f4763f51297f0b462942827a5deb80fe06e9d74c50771b9855a4ac73973f6da51d84e3f295fe283ce4f4de7c4de782b3f0ba0203d2fa8a75b5937b3415449c99bc0ab44e381c2c4edccc04089b1f7280707549ff4144a00dff86df0321e5421cc30c575f02359398efc9234959d812143df5856ebbf93f34e20fe0eba1963a592948d9caa7b491fd9c81040649cc068d880c11b00711f105b5b9c50d7f2eea5fa2b42a16dd94e810437408ced686940ee5b23450c19ed46db11cbdc5dda3776300d729913224946cee72949b8b76ee59b8512e9c9ab505c532c949268d194c9d2a9888a4f9a92484fccad7123fea76087b1a58a781bb0b469385d5a6a611d2fe8b03e0316c492748a225c5dbf24b6760113da3d6d6b18bd3ff2b85dcb23c5c88b495bdf0c117699535b06fe9506f432628d7b2e7b08fd4522d35577c9eadc639b56766843645f2a66c05942b930c75720e10c0b925bb2f7904a387a33a8b274ce7c58a2e656bb02b12320efad4fdfd16491654817f3b7c8ded29b2aff985896431657a8e9c7ce832422501b60bcb98baae363d7017c1ba8184e5d48ef2db5aea99c10c052f7d23ff7d742b52d41ec74891944d0dd34b6d61a14f61fb2323827ec6045957f1eaef86713b06b6b44016bbf6da03ef71cb08e5fe96b01655b7e168e721396b26b369707f8f5700c619245fa60167d42e4f830310edf67c859b8712325a0a08500200b9abaf5f3105bb1e496c00e13b50b44f0716631af6d70bd6c865350a4bb9209ae2a685bad195906f6f2a7558ad06144f38a90d8a9de976df74378efb5c349810ad18cb9c7c5df3a2c162c68881e3201016588d68a6aaad5a7eba87a7a35f8845b67bebc6444b282f11f64c70c8e24da6284340c4cda52645b05628781a8df575b06795bcf23222f1bc2962a149f4b0092409133bc1cb365fa1a911ef819f547798be6997c7ca1a9990b27b30203f5e6b91c0b38be2e5e7f0e795a7eefd0cb67761b52a2220cbda019129877f5602f1fb5c6181f7fd39fd5885dec90716277ad3d8c84b8f92367d63251a7b8899faccc7e4f207fc54b5da60d55aac197a8806c833376491f7ceedcd2a4af6de4a639691c0968388dd78af42e43eeb3c52f2cca8ca7989b05d1d1107ac68c2fec6f39c1d2e188ad3877226668900b25e771781d89a79d7cbeeb2c399ea74b37a3cb60a9d89d939822ebac810796729d8cca51235c4873288f2e246f28f41f3cf33d027d403964969f1aa55acbb77fb1351a4739d90887e798af1882580f39b8e72a09f7b1ffa59d3a1dafa10972c0c9cbed3a45ea1b4feb4f5a8e5e4c25eeb24f49ada306af971430ac84026486893da58305c449d34e24cc0d1da7242a6fe0f4be8d912059af724577ceddc99d149b8762776a284f2ed5d608830829fd0aa9f76e10e6a8da6bb987fe353045cbcaac4db1c6c812637ba699dd1e348f2b6d9796b109c7b9e2b9370573daac45b49ced941849a0c5ae9d00ae75ff4c3cc811728b0d95b4537e6beabec228ccfad92389d48ac1a0a964f0d3ff678c179ef58bbcb1fc275d365d7bccdd212c80e542195fddf219ecaf6a3eaa9ec1b523d6fc2cdd7796a265198042c7324896529f4cdb8c2008b68953cac1e220c913241a8258676b63ebc48b8f26b8a3ea4fb3fa899e828fb8bda032f081d83d2a4b028ff09b99827b7f548d65e82135bf8c2c965286c704377ba058614bb814a56868c0e18430958a516ec25d159f57d8bec85447b1faaa968aaab1a364ffed6fe6db48bd55183c9fddcef83ec9e46c40ae32b5404362eab455f6e7e17c2dfc714c4ed098bc9eb1ad8ee2d43016d86002f4d830f98ac1320346b4019d34fa46b96a5dfc28239bb86832c32dbee252088b61e47402ab39df2ddf45c3d00c339852810a967faffe9c4e2f34703fb24e8673c2c3ab80b258eaf983b4f7f440e39e921b1db012775a7c128e7a0a849affd9db7877c0278a6cd333907ae4cd449d192d2ffaa6a40c5584ebf939e41dc0c895259c543f04c1d9d4d7f2396c05136cdab03080ccd15b4a3d9d5fd6d0c0f0101d7d5deb6f7966f56cefe15fc722003a90b9121b54ef9163dd1a13c6dff0553f4f72cd2adc547391344ad358530b48160e540ba7fd713bea404d7139b7086040566ef5e341ccf299e9611600e1575e151b2acc5631f8812d9419ed57ff9443b30380ce26f30dc6b0019616766cd21fd6b97812086111c1711f850c0b149d9ebff607864f7f9696566e5bb4a74a5a41fca9355d7f1c14f4d423e58dd10747696562ac2457b8b400f6cf0f7d68519e7250e3ad523fa42d445e5b2377ceae92766b65015846c569d94dd1f546c8c35715d76dd4b06c3af71b129c1793c925c3e841e988b9f455afd535d001e208e92f2485b5edd4097308ec60f2c90d69ff563f94f9a7c700c69f6623aa28e8aa4e5c9d814d506407d88040a08097d8657da94442fc00896044b6750395dac08fcc836fd7805187030e0737dd63b7bb009a98927654186cfbfe0327a43c983070f0bde0fb0ed993fa5f20667e77407239d4e6717da75cceb12a169bb228024d6cba1d2b6c7f07f70264e936ec8b286e543f0d02b65c7253db755346881db600908c1cdb74b5307379eca4c4b659a02916f16ccacf6960cf8ad8aab9ccd08869d9ccadc9a7996c79abe2f69b70dcf3ad3f077b95aab0b5d5e69e235160671474f5c05126ca5ba6246071bfaaecc0cbcc48f6b52e493f63a982b67c882adef506541b9b9f1a0c3f7e5d6671139ad265b88d9dae569305e55e1edb31bfb9b2050e02ca9a6d9935f68b1ab0d5781a0acc1256d2653164ccd74db5318ceaa0a13b5d1c43f11534e4e6e59044755089d1a8d62774c1a3b70ca66ee5c38c6ac2168925caff92a6d6ae7bbdf73e91194880e71013273433303c05ba724167155c21587f2567e3e6754e93d2b0830afaf78fc755f8da2b07677ed32a8ef6fae5bde53e93fecc61ab4e6e89cc2619a7b0dd9db7eaf8dbc8f2dce0cc90aad3878293834f1645e0004d2e0c59c1e3272fa0d14b5cc8eceacad07c8af460d5d973a286114fea5caf7a1e28a69005d3d06d843e19d4d2733ceb4e58cdf1dec8d6ea133627cdaa417ff03251be926763a296c6f6b25a11e49f7a2be3f4e5b011e4fcc35ec274bccb37d325ff4245c6e6d6ec107a6d4ca391fd484030844c2570e3e3a41d832181eafcfb6079537f419fd9d7ba21b8e74e042c8c5d77098c387b7b9c7d47595dbd68bb42911652dcbc2266f6ae69d245eae7998082a0238888dbff2350871bcaff2a48ee5aa1f39ebe0f7bc3c81150e507ee329084928fc74d14cbb8f7f1a0f73c366e050010af682de815283cc246b191052ea49ba656f03e4df37a5fe420653048b2408fc5a343eb524c65095a6c9c689aec20c8ab95de4baeafd780261d67a748efeb32eb2e4053a7360009c9190e388e1e59f4118a4ec610ca02840fcae9a9200a3e2cc1588c67a4b582aa8e015bfb522ca216d4197170519264bd6d25946ff8b792db5a1725dbbe24e9525dd0c0c73996939292e7be2b74074a89fd37a78677af59167f612c5de4f4fc86a9c384ddcd49ddbb0aaef7454765070661ba3d1d93741423efcc6bdb1a3ed2b89454838bb5a9a3bb7359e866046f34319d3cdbee2d33665e0f2bd9cff09bfae98ea5facf8fab02097207a7e4a919318d2f56ad11931907366a2e565b936a15c2ae5c99c21f1b15269d831052d9ce3feaa47780c63ccd5e273ee8389fce31b0065fa9dd0e0b328e5b55acf308a04797df7a29f302e19033b44143164848283ca35244f2380ce67ad25ee9f0533a1022067311f7a17c76f8f56b7f2234fa63c5e658833e09ba95210ba6a851311c03e4b12ff91df1cceec66cc625798f8a90451ba5f0debaa4e6dfab16ff4b8bfbc978dad0af896e1067e40c8361d7ee57306572bcccf3bf4ad85349bb20129d3579ce6a6bfa878e0576fd7285c94e06278d200eeb985447ba259216417ecf8b811da2dcd162cb4b3cf5dac7ee0da829e7cafd3bff2435cc697a3045c40c6b9586895a38fdc52cdd4fc842120ab8698e3d3f2371cfb9dbf374db8abf236b1c6b8eb4ae5bb91f1eda41e5b23f8e0833011343dec3ea345ba066ae2e9b9cc5226994b4d188c83dc15e02aeb05de90dad035b13ed7b0a58b7924974d245043a1a3f4679613583c08dd6983f4280887a71d0c7d75f79f0e24504a376b8abed0ee46ead8d085479129b238c3201a02c991bd0452ef2b04571cdd5de96796d3d4757a93aad4a080cc81274cbaa7a6ee8e11794727fc1afda4049516ff7db5de64a2b14580f3e1eeec4b8006c031681a4c4e78e49c860024b84ef8c1542ec62b88f3ead38e80da1812e2c846011a94d7641ea95313f750477fab771906b85e2eacc4ad5fe393254ebf3f3acec8c283bd3c64b929db34157e3bb82ab83ae4df0586a9e0e541426584db7b85a1df32948711bf40abb490baba937478514ab0d3a8575bac555574ff391134891dcc493de679bff7e285773292b95be5f1b8c6d4b92c66974362f895e506a329499f4e0041335041ea590a6765f4d9a29c1a1770cbacce0a62ae3a3e2f4615ad45e2a73ff69e7ab46402fd665f950dff9615d78488963d634762013b8101993760121b89e88642c7950b78aa9abeafcc92762947d5297b9e4f5342921bf801f9897bb4736c03976ff36ca2f9146b47a39557435bd8774f568d202736a397e91f19cb96b339ea65e1400ad8d73a79efdb9e59a3b94137c6b26a9e996a24f387f36df8feebfa04a8c50d5852944c2cad6fd4f9be6f10f0c1adee03ea89c2e059bf2c546c98d114b15ac9d32b0d9c052c5b2f8af935199e407f27daa723fed13137cbd0113bf6ad76bbc8eaf52c36259000b9c811f5e87c0ff924a837e00375301009c2d26a44ae70b7f8bc03f30abfe23d190e777e2c822f26aead3f838ce36d576291964ed648af2c59cc1a12b64a73c7205cac69290f4992ed6af159338bec9a13a0a86eec5ea99298105bca465a16447563ada474a03825861aed434c3bb3f770837e205663a6b2bfb3d34131b36a9ec9df2511efcbc412d892b11a43100b780951fbed464a39d1fee1a55c52deab8c2a4f1f7c4f6949381552568e84911931aa8b28f351fd2cbd7deacbbaa99018f82af15ae3d384ba03776c88b41d45189e58085a867f19ffec1e733d4f3074010db9931c86a0e9667c2014aaf1965fae75667e4776eff551cc9899a9560221ff3a7ced61b03d71b26d211dde0614bee19f69b85217faf3870c5d7d11af1c7c09b23d14dbad9250ad12d7300e97c50e70d357b5ea0c82ba87e18fd9ab69da17d878b61b644e870b35ae8452f628013cc197c5542510e278b1a532cb56cb48c13e94644e036dfe138b38ac414158a91c8ad88ab3365a743be8922f229e74076690acd58598a5e254cb2665755bd503e4132d60743bece57eef2fb4df9bf23c080aa0cb9a8b25a14b4213295a6db0b1945814c76ad356d84067a8f263f54341d898292e9046deaee4d3adc5da4eabe092955be6ac206a89eff4b06d635dfc1a78fb175eb7323f86f43d63f272fec48df3b8353176064cd44004bd2fa37cdc78be2f4f47160c7109cf15ca9ec621da51c93cf144d48492ceeed98b9ac9404d0140839f186eab17020fd94ea6d4cb975b13c95f56d0680ce35e415d2422503523a332087989d3e38cd710e3dca1552a1f825b884ca2076c07b47c1e4277f7251de5f5e77d92ad97182a7bd8acb0436bf9bd081dc4eb28b361575e39377df9b0974cc3e298a6c8fbd7bbf8ab0e72689a31ca6d7d82905c35c1ceb6a9f73a11410ca4473c84fcf084a74b5e29d056d1a679aa3996a292ec08afc6ddef7f9df328d33c5162cec36281c467e60b247a1b478cfad789c07ce6fa0661c67051b349aa83e0242c1ae81682271b00ce8c0d7027cac6aaf7161a8521cf8529db33beb6e0adb916d5939b842b523d04de00914b6b8eedcf9949f4c1fba87054bb964ad04cb685e7c481cdb2f7061b7877854c56e0e057f622294fdc89e3412f7d6b1023ba802a9ce7dca9a30e11a098da8a5ada873dbf7256064a42fbb7d15a7eb5fe88f15ab0a38400c7a1cbb6284f04996f21392a554b0198d0a504184b718b1c5b840804a0e842e02ba64094a5e408cc25107b9321708807f14c49900df8cd5193ba838fa5863869ae0694daef1e535c04aab4088243a65d176f4039b91cbed57e79d11fbf56eceb0bd82c811e4eaf5a46bfa11e66a40dd33b49f9dcea0369d735ca6f72e21522629a59432bf044a04a3043fbe5f307b3daee5baaee3eab594fcc49cf373af661a42eef75e7b35ddc17cf28edad89447e5a27beec2f300c460899d3b6934e23af759776c09358c4035d11d0d9839951739e023c1e3482b1b2c7e76d76790900e345436a9cf20a11b4e2034d620f4424d6b7b7d0609b142a19597561779aae4aec573ceafb8de3a81b0b5fad09cc9fb44d57b4bf75e5bf18b2d68b5585f8cf125d99d5b77b4c604104037cd2d130c4f8f35f5ccabc1efe1244976dd87eea00498a2a9cf9e6a6553ce4992c4d3c39387f330c7ebd8563af90ec337dffa869c0826f64d68040a938b1125e9081c486490181a41488c353c9aeedde85e9099ee795ef9a47ba516bcab4186c2caa7e3683a77a373fc725cd0099d0bf21274c40ea451180e828c00e2fd31bed10b80711163c74548d131172d4450c1f34bca9c606f746db7ccba25c2b505a85b6b8106c8987971346cf9a9224b4247bfbcbe28e276435c4142d642bf9cd66083d69aa4e50c0ba50a2cf0e8e1b544496c86243c62b4272c54718d80355661f1a0220b12028b0a3caad43e2f415d5a1e0ff4cbf6ffb55ff41944e506a3852d4305184bb68c0e98d93a4a4d0c3b78a0ca95cd7f22f9a02a5bfaffdf649955d3a4d56c3c9adc783859c5385325091951aa90a1b16ddc675015a22e2c7e7345887efb0c42824a1779ea476ede8833ca6e79ce6d67299307e3e89667287303d887c1fcaaa0a85fbc2027ca622070c943b75be6684d5eefc06c31bff4ab5b5e397765df4cd4ccb2c5fcca37ccfcca4074bb33956ef99c59ca8cd2fed06c04b145ce893ae637989f30d7a13bac0dfec8bdfb44420c0b7ddf120dbef3879b37dcdcc08a387ace8e8ee65c3e39bd5cf39b130cdb5a6a1f1449e8cf69a9a33b309839945fbe2f8e4eb9e8e2820b48244ee2aaaf7a1de48ec19b89a223fe2513420ecc250a500aaa7a4f9157c37e2fa3904fd12adf47c21726f5d5144a4a4a571df5d3f613fa845233222026944e61ea46b5012daf1cb238822462801c82b8f8182165f11971229cccc708e733a27d3ec68ff9bdf7de7b6de9f21f81b636b179cad633b05b90c7a246515f7ab0d41395d2e228b1d7c5812ea6628bb346472cdbc2bfc5e7130d356bd40e7a74a209417910304d94177184972f6294e8649ce8354c1e70d239b168d9c087a9c10e3347940890707e2acfadecca23902671ddcbf278377ba39a684fa82271382050c2e2ba21b6386bab1fdb795a7eb6921ecfc58987d2da99a5d99a4dccaf1f28cfe292a5aaacd4d494178dab1332c49ae8ea85125c84848686ca94e944445de6840990929b9017abba13c278628bb3b6caaf1e185862e4b0c559fbdb8ad572e1189d087feb7293812a818ccc9a280be6247fcb2c60f9dbdf7030f037cc697a44c05223822dcedaea5f7ad6645b75585b0543d3a2ada21535542d4e0a2b7a420942680a616857c8075e75477c5b5ef529248eeeb01d2f914a235de2f92f46c580511d9836212a95ea75c8538b2d5296aa42a953aa957ea3471980a2ed1e91ca6c91b27c7ca02a8253a17c50f5ca334befaf3b05d17951782ea0d82e5edcf0629fbe6c18526c91b26825c088815221868c106c91b24652060d8e934d5333660b5199998192691a6634423e5ff86a9ca28c3d5c8dae0421738ccd06ca862d52169d26ca6b4ffb592fb39033558f8f59e3a331c3466ed94a80334c04632c368e39a074498001138602797053eb8fcb31c7609d3d2a506da593ce496ba5738a34d5da2375da39e79c74560a060c1820272ca06a3b166f09479f1e08a48e8f74cbafd799a877c55bedec740ab5fc5a9a79e42b50f5c557d4c02eb09956b045528dcb86498b19326cb106d64965b7fc966a5a35f420ec97d7b82c4fcd946ab2dc8a353563b734af6eea17ac81d5d074664f2f4d08914a928c34239535b01a18a924c92eb0919ae01709d380710c8f980ac62fdcc30bbf26ca72540a5bc424695677f05877a68cc42ffcc2240d0cb58f5374877daa01a6ab14ad283016670925e1d4c5616310609d22a5fea4a5f23ce5f1638dc31627397b263ad5b462b538bee921e1d46868d7c61826d8aa182a8ab305ad29eccb6f89ab0320616e7350d8d6c692c8f2c5062b769018f4856db92dd518b6d5258cb1aeadabd6b9049d336bb6da6aabbd3a4f5d33ce696dabc2dd11d20fd5e683ca33afd805d467e9fae93afa2c619875ddd53aefb374c93a7739eb619c6f028e748c17307b55c011ca475c7b21983aa695eb0a8ab4db7c507be5bda786f0bc8262fda072093c68f31cf3077f841df3b0637072295db10ea3cfd2e5a47331f37bf3bc36f76d2e0ffbf30c5a2e5e9ee90d41bc3619fc11f60731382f684ac19600e57a3be1489224f72637b949156979be4d52b2dc222567435cb4d01efaca70a2b0e84cd75a16ba686d628b94bc0da1d22d0acbcc09948b0305bbf012f84f32d4a674ca7d641406b413164db2875016f490894ab2adf46abfdc44bac92c9587d20c2142abc663b715e527339692c2785419aa55a1a8b6ed35433464238b846eb31a462d51e1b22c4510a12c4b11cab224dae22c6be4e7efc1f264b6618bb354e9a0dde910d1d0fbe051c3fc5b05233f39cab2c559bed0cf7e888915aa3bb31faa3baaafb182d32a928a9249e954d40d3a3c5f6dcdde53988bb5d06182f3e77fd415afca9f3f93408749ce9f5f477112aa3b624ae785103d22581d25c5825a3ad2746475c5e1404ad4e995abc8f272f059d397bada593ad214348dc0cfea20e9cf7e27d91667f971e0058fad7b31c658ebd288a7527d869607cba660fcb226cb37b68d403c569f130e5bc436cc9a714aea8e75619ae51971cb71cdf251495b13e6fa73fe9c3fffbf77b9c52e171ef188f1bd185f57cb9b2244922550001e61ca75cddd7d6cefbdf6fe4d063cdc7b6d77efbd95e6d86bedb573d6f0de7b278a8e68071ee46e414a929144df7cb1fe8bf5d37299d7fa9be0abbfc9bdf84718e3cffa7ed64dfefecdba23715c898431c6e0bd9f7513fcfae2c78f39e5f93f5fdde47fe6e0fb1dd3fba0a594ebba8eab99d2129da1d69c3e1ef139c2df64641fd3c7968218d04660ee1abf6da2abcd0f2551ae1275e8602034a7f211ff74680e5733af2312d8c4c62a209306c0c9fe4f079c443af6de8b7f8431feacffff34779ecfd4e451e1e89363e0f1519fd7746fc660fe7c69ad2a6bea46259a9d63444feafda07d7473ce5e8facb1e53994721b3ecfff20d8fe01413f38f9e89783b6d767d010abe3a0211490f05c9f9a85e68c182bc498f9c972c59c600a1a9aba532d4ddda93cdc577774d7397fd6af75d6f9b91e7844346788f59039aa778c05ac189ff882c40b171974cba2c0b50cfd95314920e0c54993da0a5d56d0c26d67f981e5011744804fc057ba13c48cc162bc74197d068921a180d9229dcd59c991bb729b8f30b6d793d9ed71817a410a5c6ea22c1fcd1ccb2d1d4d94e53d3eb8abd5687e40bd5bea317b2d5da0e6ea8160710fdaa7772fc7b487eeb60ba573de209603a13e74f2226fc1112ec71cc8e5984f5e24ca0481fce59817895231e52916a594524a29e5947ff4f27fd066f2cc6daae51981fa7cdca68236235cfe1cc8e54f244a0589649008066d2a3802f599d3e71ef1a1b65a202ee76a87e3cbbed5c59320960709f58962833990a73e369717b9a00d0647a0fc3910caa35c10c8e49917a1fc799128160492c1220fd214cb47e5262d805d790f3e51ac2df23e39e5bd52be2a0189a5a29ad0c6c8483c9b283033470c1699ccca9a6c11d348376ec5b1b856b722e7ea565f7effd69086ad7585e326a353a4144571a96117ad34805cf6087b4f602e46b560afbdf5f59f5080a7743155a17431e524855dfd82784aef7858c5c81c336c11933132169391646c450515afd70bd672c9b6f8ae96abf56a8defea5eddeb5ddfea605dd775ff26d8e2ff7ff7ff5df7df79f1b21a1a8ac562e48c46db22a6cd68b3d8ac468bc56833920b975aad6683c57ed8228ec162b01a8c8cd56a3198cd246412ea5cdd58c5169fd562b15c2d97abf5ac6ec4b4532c45ab996a6ff69e6017a35af849f0f99435137a744a62443af940e24e63b8eef4a4f3504c3c17a7d2c50b94132f44488df0f578d1e0db3dfc3496700b06ca081831502bc4d05a06ce2b85391934a820686aaed4ccc0c9c00c1a58d0f84e4f7c354e45356c9cbad8b099c2e6064e0d372cc071c10200a06a0098f5027d01a7c24b283125f0073000a71d027073821b0cb66090011019e0403dc111e20085262fa61ca82ed3e6d07162c2e630853832d00182113996e45862b3b158ac16292b628b5846ca481659ca582c19d99acd66b49a4d055bc4b69aad36abdd6cb399ad46fb988c12df7bef78eff5c6dbd5bcd6f546cbd3d9ba9a3776356ff458ded8d5acc9fae0b8b6e88d5d0da88bd8257aa337c2620ff35ede18fb98373e4c4699851429441061abd1acb062f67acd4a18e7e24659a9658bb8949532d9ace45c9cab9471e32a4666d9222663642c2623c9d88a6391321fb68865a48c244b19c7e2583252bfc5165b3cb2246eb1ded692401f140f5b0c7b99ba63ed996e75517eadb3d6f973fefb209ed2a3f5ed80f65f793ffccda3652da33b309893cc1ccbed25a2db1bbdc5b6a8656f9b28cb7f467760f0a998395f45981a8d365155b6a86396eb98b6527934f9379bf3e7fcffffb5d7a596fded6f5a6631936af2682e58b7dbad5b6dd9e2af447de36edc8dbbdd3a994c46be8090c18208014c46be8090c18278c16418865a814b368e1acbb08c8e32d46a359bed66668bf866bbd96cab1b57e36a379b4d269391caf175db227e8daf5136c25e32d96b2495a4db2a465ad9222663642c26234937d28d8cadc67184c5a48c3a541947584ccaa8436c1cc7d3150122ef948ba97ec3ebf104730cda549bca69afa00d18c8949d81fb5ca24d28a9e1ec34357cfed5f0f9d79a2d11555bddb1b61d8e8bd556c3f0a9cf7fa75e0f0b5ed89613d526ea6d694dbf9a140665dd952d2d4ff5796ee3fef4e7369db31d474bd883dc29cfa5dc391c442bd36dff17b55dd51d91c3ea8e586b3b1a6806d1a6f4f7f9b116017591c6852eda1f1042983b1d7ffe63eebce0cf4fe10d67c0e4d9a17f16bc8a2f8afe64fa3fc75c9c43501b2da2674a94b462add1b02bbaa332a139cf2f286e0b82f92d0afd3945fd3f7f4177d42766cef34731e9cf2b288640fd957c3fd5d4710b8aba47018a7586fedcf6f0eb4fff5c57acfebca716d5f0e96da8865f1950c6c97317609dd5b00a1fc23cd16924d922b5cd94b42a10b5d59da26f135513cdf26cb01655d3f37aa60654aa630d9f73b97b8ff4b93a9911c57e5e892a0fb7a6af45fd791deb8e138e7a3f2e176dd7e09d61e67386790244bad66058c3e7357c4ebbc847fc39cd472ad5b9236ab0dac07a0116fb769b9e61b5b613c21257cc707691866dd7a669f61096a04dd3ec39dbe4e86153dee13286084a1ecc5ee384ce4dfb29b1c5ef069762ec935f4e85b2e0ee01352432030603c60cd570f61428ee13cb0a4b7418cf5ff4896585327d7a13a0bdeb13cb0a5e3a2ebdd741ed14b4c087cda965711fcd8308a93b934891c913640e99a24f2e7612983c434a46ec123e27f4a6d937be0f8af0f49121d6b1a91208f6cf93378006f5f3f9a00de59cb282d88001734029a87a61e79209d534f92d05a97165de7a6050800f569f93d3c967b9c720c4b4906c550f57551cf404a915e03167fde79653e48033ad622aa5c524d3313fe107f9d7c3ae9cf2ae411735e8996326fd45551f81306ccc7549e752a7d20fe2d52d97205980971eb28c1cd822881c9668a0c8038f04f390dff03f3202590c4950117249c184de4095167e01c923e1824e028521aafc2869a5a09564a19a422ab03c65f99cb2aecf2025a4b0c235be66a86a757b00136a38b915359da4d67b739e4aa95407b83b98db7b2f78cbabafe08a5444a932e11017fab8540eb8ce77da3737288dd6a84d9c37bdda2cdafaffffffc7506688396729cc48391a11e2565ba54aa5c2f0b9783167f1fdc6c75346fb7d7b8f3d96c34cefe36b2d27859b96c7476767f9b870a1702855868846dfd837e289d26c37ba624d2e6e55999285248f51187dd171dafab7fa85d2c13a50345252284684a892a2ee6386cfa7f7c1e432a55fd9e534ec4f822d4d4d7d8e8c8cd4fa31e06b2b9db3d65aebbc5ad49f13d72885dff7fded777e9efbfb3ac0c0fefefbe606f3fe709cabbb9773756336e5f65a6b2de7dad45a7be91b81f5e9c2d6dac93f042bacb0e9c5cf7aad2b196d3b4de9ec9cb0188f48a32b58a4c7d75a5dfb9a6ccdbb4fa5e3898e273a6c408019aee256e76a677340d99667def34c5953684d4a2ec634f772f9d61fd6f611aecf83b82cf3d9769aeea89d7ab8c4202ea3ec5b8d548dbf685c52dc8586e9158a7da76cd21d9f9f96c5500d4a7ad45c3450c4a07169e2c4e6fa0cb23206c60956a4d8c28a153296f89042d82595f5a8840d9af5a444331300100083160000200c0a060502711c88d26096e40314000d59723a6c503e2e8e0542c1381c46511044411c82300cc2300882338421869451f60c5432b44a52010004d5a5a99a47120d6b26d859bc974829dae373c394e11bbb227d1edc0a3b569664840a5967f64a3119466f83a469794528258e2c60492f431b1fae3a7369693178f75f085196852d3ea0779fe6e22eeac563ce10cba943026252b4eb2cbea4444f234a92ea1125716502864ac3b1deea7edb80c876b8e87f269606671e465a6d938843bc65b417cbeff0594303c63945d8ff564e470aa358b0e28d360bc108c88bf1bf73c0fd6c24b4840315f7beedaefe0ebccff50234d9ac9a6432289446b6cef06385b7f3721efc2dd2b88e449e937fc59ecd977c6375f1b46ff4a54bf69f6710769e26d74900f6c339f4e86a6d30986396c30a5062f757f4daaed15890d21fb7782d04b218dc1f96606d7245a195200a9ff0d3a277d5ee41b60798f973ca3162e3802b4b784af104c5c0be47bd1c8c0dbad3ba4cedd378c8c2e7ae07a3a7c33c9779eb0f3499bf14c7700bc14da007eb701b2a178be0ceb28b4530bf8731763b8615ff1434c349962831be724fd2a81390fbcf4a5589f0f617fe35daa74ec97429b1870c5b13bb6ded713a32502913afc9cb19912a3a2d55bd84ed4e067412c1b57545209e21f58afc204437d0644432c7454d6262fd9748310527a659ce1a04b16d0f3fab40e4aa75483c8c638052ad4a051ad474ed90039c34373bef12a8164a6a765b5df47a077349671ac271bc76a0c088424d7f10c518a1022d9802dba3bc93880c6861cc1c3e70b9b8b752d9970c511a6f91cff5b347d11f1fced1190606633563858c836511229d7515cba5137cb02462d43793fc191cd62a102446905f311b6eb6369000cf1abcbbd4c425608d0ac888b993d7c1422f5fe7fca2e5b5bcbd1343106ccd18f0f5bd9f8d8830ee3495674058f380ecb897bdeaaa8f451af54d168074754b8f03d21dfecaa4689630cf6673f1107e9a8ea3ce0f904e3439ed82e40229060a1537b0b94af14c8e686d5b08d93cb95ffdca619910c948aa68983e548d2d98d9f6a4b8d5bca27bc90bb621cc1610150205dd36974114f59b230d298678c8aebf9b1c8b40a9e12b37a825957ce889c143454d7f45042e2c1dda91fbb3872d5675fb5ae50e491e57448c5d0edb191d4db14c70294372170b1d748e1f476ad9894a43b7ddfc6bc4e48a6061981be515972ae865f1ee803128235262701a1c0f181c14967182c50afc4d4d2b289bc586b0b6758a22cec72d9d3670557507e20943d3ccdaa666c04cda9d876391270ad79501dc5e7c72d58580bb184649b045394d3e93baa82f26860ce8404cd387b2b741a0f985270a13ededa39dfd67ae40fd12bc6e1460a019266ebcd5f78d771650de38f4fa74c617bfc5afc181bef862e891c89e4d23fc52fd8e5d34087ff4d80744c32dbf5cd0b75b61463485a2d1727db799867dc9782dbf870c6d134dc4563a38418f7088f9928711e983ccc4db5f8c20abf03c4706fe6613d31ac4095dcf9f7bb8b9489eba82493a5bc7e750f529654b82b5535918be80e4d0c603de5a6373adbed34e877ea96213d168d1abf585cd11030c81b4b6d70b03d8cb7c26029ad55901d700ea8d6de73ac24cd281f0c2b0031d93629d43b8b0c59e98100db075263e9296db4a2a0b35a44308d6d2292da9fe5c156f0b3b1223b380a04d2630bb249192d02e94948abe163455f302595ad034c3c28b14159edb443038d0949aee49be837566087f0ccc6d82be66597b351a886e2823485df346f7e393015b560cacde6a2952a557f28f8d08e6de06f4b807b684a90451ac27ce4bd607b61039d4562c4df912e6c1730117102a2b3e0ea1d081f45563c2ac81edcf791758d0691e3c36097a9ce45db644de6b3e466856925fcb07b033e85fa51734352671c413d0eab49f6544aabd2232ed2bc830cc7036a5ab31bfab16d416838d7925ab16be191d741274b71410d94480a8aa93205efe189f2519364572ef4cf36ba2d22daea51fc307124758ba4f9d4ccfe11619ea9ec0fd2df3a649c27d81d9592755dc64054e5355cf2b740b250d0b7d99cf1a7e015b79ed2c0b35683f50d949260716bf8c8bb181729f5a99ece47bc50e12d5d83f0305d9e9656adf8eb570b1f438842a736b5478e99b51da90a3f1318f36a4421c2140f5db1f867d046a50a6696e5019c40fa11d5b0080cad0838ea511ab3d0b4d066c2d928ed6334a41f4fe2ee014e9f6bbeb0292b01a47bae99e38eaea3c6606680386e3c8c16cbce5cba8c9e431fd4f0090a93e226b41da638ae380c44abbc7a9729f5dc20e4b5d166e3d9600ea54dc67b234ad66cb976987d22c04dbb41d747ea8a2f670170073daa4d12986563d06b18a1d8218cb797407a4e3fe47c11e0b81d226b606e592b5b098736be2a97383e076cd5cf63011823b72f49cc934432364ab95ffe20ca424139f6b9a7949d7906fcf59e874f5a524abd9c855a6b9303e0a250481765e4af907992c0523df3eab85d647b3c123602bf63a2cf13396fafb530ab7ba9e42b501f5b95b451044ee79aa835a4ee140b4337ea647a48f721b82b86628a902e88fdfa022e043df56332443ac6fda14e421809ad8fb425e7dabf2b504b4f045ed841b983a0390b78944bb650d92aa8187b2ed0197c4d0e2f29822411b7a13fb2e8e1e3c3b2da4dc9cd6bc461feb8ef83471f06bc4d42aafbc85b8ad27e798111f6728d98ce3a4768c6a4e786dff6cab084172b068c9a814f6c8790c8517be09b46f64ea7c4abecade6caf5ab18ec02936f060ae81752dc9c9c06103e483dfc64216c10aa8f6d943c080c78c2690837cc0722ece95422558c8069de45cbeb93a660b88e271e5f9c5a2c933bcc2c458386bcb1eac0b6496b6b6484a1cfd52db03f860a857a27813834d0248c77a9cd964a5ea83849a7c2ef236722ec40059c88904564ff708c52b9a0ac62b115654b577b70499bc26de6cce447b0dbad17a80948829730d916559ea240c131ae990014a983ad037966ca94c1f3c4d1be95a117941e1b124be5b9fd68bc281b3495e8126580dd8d274bb0ae4b71709d3e799b8b59fa758e4ae2a71b9e39a2ceb39802a20c0bd798153fdc418230b01b3355064da4e55b70717e8f179747a04a33c52ac3eff8450946a92a2c88b48aca024e6179b3d5825a82f9a0639665680449dfb123ad047d1d98361e45628805623e92d3c24e1f14f078bedc340c482f54d753bb9968447d90cbaa0bc5397711634a21b57eeeb411305b518a1b66e3fa8078dc458bd48d81844f24e99f79f56577849f99e6eb1dc1a8f2236e5750cdaf11b01f5a1451f7527ecc513e498a467d771cf993ff9c65addf2afe22e5e3e8d269b055ad43662853f2a0e09824264d40f1b2b968b32135733be559f2838161af794746cbf7495b2b753b4b819e8df4b16952d881ddfcc507f7217d228e28796fe95e82ac14f88f0e0a1846e36f630ab40afec592574493c929c30af449ebfb3cada7da22adc19e12a4ce074e2314415ade5a435610040326021378257de660492f77d3c0e1e567f0732d987f77e0f333b93828da46b769ff4627144388e2c3d97754f03102aab43d19215f7a9a7b000a3d8ead62614292c0cc3e274e110d33991aecb3f81042e4da67de76ce387e5a0f1d6115ed8221c4b8843064fcb255147028f148f68ceb5f5df06468ca3244ae25bb097d7a0eb9e5e3e05f53d109032b937ebcc191f6e9d95410bf13e00a51de6e9a5543b628dac003d95448a4233cc38f35dd15e96517072da8d199ff68cef852621b3b36b3884081770b9c3359ddac94776d150ef8ba28bea4114d3b87441cc60a2d746c0165d544a5b6ac830ed61e618174a79d9d078e3bd73e2b8d61879b16c344563e07b739b05f431de024ce14ef985e21f4445c5d449fda328bc2a691abbf8bc8995dc0eb398b09376c5b9bae80dad6425c6b2313a92359f36c319936c6dd672da2829599a5d3fa29fbcaa2899331f3cb7ce2cbac53b389960317576755693aef3598f7032d3895e776335027fe5716a64a601fe65e6703135674e0bd328a4c0cb7dfdf181e9a857374a151036c6253ff082a7c78b1513b48a929137cdd2a0ef7d8e131ac7e3b84aefd60b733604bad136d32117427b5d6c29dbcf893d980c37b559083c824c43756419e83136a96a4b08bc8c3f02d97af2eb15cda2a6d209f91c8f2dd2b65fa0eecf7fe57a1010dd75d984392f094ec6b667cdc94ed27ac9b40f5bf8076c7279bc66a7d30d467557fd06ed6a20d61d16187afdc0fca57d2c662bf7da6371b9224a158841567dfbf3d88299808a9115d4b288424b72fa7f64e43a609f0b8d4232afb5385ca0f5e6ba188040f99d00350fd0d9fdfdba55e876940767b6f5be507667d58331c6c33d6e3ec218219e2252c715782b785401068f0a766fdceef191fb73a7da355121b5b1033e6de8a4c0b3a6606cb16b4d5af5dd4c51f0ffcb1a11cfb478648b3e6d3eec0b318e669e6fb769438dee20f0d71d1883e369a251bd3ae57b0f06f8f75cb5d7d59492ac72577408d8a19e13e85a99058b1e39a45ae95bf65a455c21dab92bb45358c29bfdb09c87240e8b2b27832e3bfff1de482b24284a61b80871d9b9206b1940161644f722ca4386cb4030d0868f4a2dc89e5859793d75521b482002cb08479975c1a312aa6670f19dc29d848b7091cec865d89c90da38017c9c3232013ecfa954203a6dca00c9cf9d9d2ddf3de1a17b99d5a7bf0ae77ee7ce2915c7bdce6b5f206038377bb20600cbb161c00c15415366a8751076130a166198d9900908bffca0068d52576469b9facbd1b364c3f5aa5a3e2753f8723c691f72ab31110d20de0e0f5a1b654f7605a221383aef8c827ae5a43422689336e201238506f1943a2e1bd2706d05ad6908798e26dce988b8a5b183d3eec3141289a3c665e0d2b0205f59b66b79b15cc2a16181d14bd2d0f85b576296f3ef852c9f561a4c28583b7502f72844696de010cb7914ac1dda7a9e930cef0a1fd59a8235ef09bfc7d90859e9aa018a6b536bf20c163096066b9dd16a2830cdf2ac0f4b8e5659bbbd3a68a9914be5c295c74ec8158a220ef40a9c07184518e3189820524e599b79439d3cd88a58f3883adc6dbcc2aa3fb0a0e70381145b0cbb26268e56249aece38eb3e675edd5e6e54e83fcc7f6d6f9f09b650f981801d3fa97918ff11fc73f60dd6a5912c2e7dfb1ac67ad72ef16bc98616bda13600b107c6d8c9d15e8a16720d3317c4bf48fe9d6e6cff5fdeb22756a8f8b36a98a0a25fef131f9aedfa87dd940cfc80dfc23df540f8f73c92d15db4e92e79cccd23e7d2a46f3a5f0bcb15cbd9aa689be0a4bf1478c23baa67620cc194b3409cbc3c34d3a9c7f8e84fba13848c308c657c1438268d42091d8f860d5022aa97d8c97da6ada3ec9d94cd5719a71b1ba6184a6a01a25a9536851e3790e679bf8a2980c383bef4f2267e0574be9fad3e7106901fa51b0a5090b2386cc01610e29d9a28a2710b45a6e486b476c01aa2795603df27e7a92ee020050f7147293194981d46b36f4e731e6e47712f6f4e14d5f093c4835b31ba2b504ad8637ff4defb33247bbbc349db6bae23e723b6a07bd8684e8cb628ca3462df9cf1ce2b5b679f656195666d3992d3b0d0c280e9d91246b4e31088446e5d5f5f22cf2380d2160d12794f61580ff150854a636b96110a90e6eb97e7d99f0aed4239d1ab060899e1be8c172c0dc3fb3816522299f13c07cf3d05666ba00e42a0774a43c687b66de87972f3c98582c290f8b60056f5815c0f3fb4b2380aba142019c9db1f4eca7a91d5bb185fb73fddb12ade8de6065ee00ebc441b0350ab4b78345f77bafd51a28498ac8c494b78bb725c7bc0337939c3f378091cb9a853c0b6ac3625ed077d2d7ef40b57f78f11338684591e60809ebb1a57575641802d0d6d75492d9983fe01c38d811fa02d120248e0980dd8445da35e17ba62f4cca830d4c028a3d0f955049fb86e641ba19b421a7e5bd7139eb9c65ff7e16707bcad13993d5f8168992d67c2a42ce8b85d2f042ce09bf2878c6c121052b505e66db01a366b5fe72109e488b7e5165a071921ec6185e961042f4ca47f56d77d03bbbd8da52ffc39600eb080db81f64354cb2b1e6a9ecc355e0d2b157744205b052d6bf47a097ce67f4f8a2d48b89fd9e7cbb681f311c1086d38b872953a0538365541dc7d9f8fb37af08016e4e0e40f08b09f759a3afdb67943ae82ff5029ffd950e0f34a109e33d1c7eb1a343163ceb7491013126fae524b38765ecddaf7ad8b6e9d2c415c2b3398eba5d9625e318ad19d336b72c430fe7c3e837f1c7c15e6d170be7e53e8cfea00e9fdef1d1b772b845d3ba3cba83f1171f3d7c294b8223da334cdb1c5976d51ffae12027602ff8dc5771e840139a1adae1e8171f1d662953d2115b32a69e5cd96476ee794629076938daa75bae345a1f9d45165ee0958031b19d0e87f137b8bb3699c61177cfff08ba77ddbfd01ddacc99ea92b4db35d57c632849bc902be751f80954a12a089ee1764120a6b10b82c8a48a502cd95278ce317ef7f50a9c0d8c5bf06110e8c2b55ecf0ab8f33099027e1f365bc8cbe36505f8f1f0bc2077e3715d9515e0a13fba9dbee0143078f340ef4d1ddb5741d629c0caba006b69908a851f18469fbede83c5980031f4ca0db5732e6441e97105ff5183620d28d061111a3d526a771431f00b7b960f00ac15b869956596086e01173f3c2be0f4785901171f372fe4f5b059215f1e972dc0cfe37305797dbcac80cfc3b2851c7cf8ac803bfd981d0ec4421b28e9ca300799b6c2fc51306803818ade8f96c03bfc8e192596ee8ad9a9912f704bb86981a389772a744bd879a123093617704bf4b9a07bc226054e12362d7463e227054e249ea9c03dd14e850e6c22f1db9f4a85abbcd77cd5e44a5ec7c8b75653676b9b98d6dc414b06d579421ef3f0032160fa26e21b9b4e8e75eb6cf05ccaa0bd57ed033f1c6417ec0d5f7d2986293451d1e33f0c7ea3a3439767c81ca34dcedccda1b1a79301427fea611d9baac2800fe2c7e8f8c0922c12c76c993337b9a5874493a4e44d459ce23c93e57c05d087a9a084be41c66d6d725f85bb2b5538be10847b5b53d83390eb5477ec38c570e83ffc02628bb90f9a9e275d040c696664fbaa50137c5695bf1816988e59ad6a7520dac572eb2a15b70a42cc3a2c35fab17815579f309d7346e308fefed00182204c230f180e2d2c68a9cdee244d07eb85cfa777fb50ea457adeea231fc58db9be4c5164b2e65de09fee03f4424888ea42606b86699b23cbc54e291f981310c6472c8b267a797487f13f3e3e7beecf2493dcb1108f32e55714c931a40856ddc7c82ec28dbc736ac2d5607fb85db6239a923f1a0d1f63edc8dd692eba98f91fd2f134d1af72e5235380c291677f3b93dbd4cebcf0a05c36c7ab1d7f18ccaba16cb047a6d4937ccb603722bb338c735bac5899dec2f5c67a4250104789ee61531508951c144703fad0b2ef1bd64a1fba7827d135da59de7188a0b54d8fa3a51f5be9a75286c66596602b17f66d9ce7189138c5543f9560e02b322c62daedf3aa5ec90de0e3fd4dee0e12b0342497fc61ce7acc8ea40a1a8e4a98b5ee756b62be512b882b087c4b28cbd941b2ef6345f233988d7c12b2e8e79c6bda78a82a6a8d13b734be368ba59c304473ff98bcb11b71d11f664e08d842d75eac8c626ba5d9be6a54c959ff3163e99d5806b8d585de0cb901e8065568692794db49494ab575cf14d527a3880b5ae96de57db32d3821a1c115ca10a1670402321d46feb8a9d728be712b55ba1bc61a5b5e18c77626581469cb39f6852262ee70eb8d7109736b45bb3033a41640645f12bddb58dada14ee3365d1ed7587d08268c3e8ec1a7925031526e354930cba87e743addd7b2c12db91297d101bfd32ddf323287c66c8dddb54dfecbcf3eb8ef539d51531273031043da9fc1499a585189845cb785c4cc716da011b9c1b7df8c7c98732d4f35162716d48d6a02860672a867633d5c1c0390a6b8f99b8d45c453277c286fda485f18eaf4644c7fcb9e64b410d15ea3b808dad73a5f14fbffb5716d57a9a9ee7212773a450447af359be47c542066bfbd02049f3f8859cca7418ccfb2b7e5a77020012985568394001cfab5b676fc6a77e75646b2540c0785611c208d34961854c3d05b98f3a427d74deaecfe0c974571293abd3c664bca25b468933cd6c911bc74fa941cbbd61dfb3fda41a7c185ea2a9ae0ced20fa8d8fcfb6fb3165480427a041beb12713cd09ad4d0d0aed8efb290e9abb89c082d3c3673b1db9b39055d2b12d79a646eec3f774724ab1f591b327614a0cf134652bb43f58a3da71dcc6df9fc94a081af4c994f22447df8b450dd6b7625f99073f6956d2df96523946e25dc73337dc73df38384a7a18d6ec1d4e7e80d097a6aec0da1b937ff45a0192b1dea662c840863d18b50fa12baca18ec9de0af07a11ff5095c22f85d64789004c1002a36487992e9aa0fcbf96123e91bdba7961d9a4c7d94b5ed53e198a514492ffe6d70e5e4f0d2162c1bcd8db7777e1d94a5dc1c69b88df85b9098607d8be341e81bf34004190ad03c72e5d71bc2531965e7a118e50f7d7d7bf2a28eb3f57eefc30fec447e195f2fd064ba19d477ed311bfed41c6d602dbf87a284109c7327f0571c811805a1685464433a512d01f7d1cd03b88f839fcf75d1b6c68ec407176370ff7516ebb1cdc7680c488dc9da51c73bd28706e2dd961f3b518c48fba22f833ca66febec093383a92e3ea68eb262dcf9b28a3965e47d62a601d0312a17b6b1b930c1449e623582e502bbb35742a3046379a56ee9801853e9d7d22c3e4231b5d1396024f987f8f1aab62d15ca784fb3be9c20b9d8581399873360657e38d096c6c17eb3c60b6a32c959b4243bbfc8d1a6a23f91d5b0874d94c00034901b8b6cc7b34b510ebb1c06dde6e7930086120fe5ff631503b725e94ea6904512f22b54b10d90cc605ba71d9427e4a5545b44fa829b84498507c37f147361580b06c7a2dc4db54f33a2a81df17a21d4ac03d1b83935170665f295a6e7f303d90b123e40ff948dadcf4a650cc06b4b7c1abcacbecf984b520971130370dd3a2ee9a15b885821e012647757165138f999ba428a83c0bdd96273404c6924e6257e3c4ad9074506b38e3780dbf7fe88100c31fbfa4fee412115d0477063fc3841f2a860ff7734504dc8fc23c51e7eb0c12e312493eb0c157e98aa2d6bc908865b80970f1d1236eebab4fc6aa7ca95ae65a4b8e35d069a2ab60734b24147f350c3e99b36c90f1a55027f07a12ddd3443ca6d8d6d5083fe00e4699183482a84b30ec6bce0759c02aa247945424fe9e92f494aada27b53e768bd6357d5364ebc5e63312ecd442742942e0790d5efc3f13eb84270e996d9e046085627a15cc09368fcf7805b75d09a9ccac6ccbca7359bce9cbc982b4c5bf3290283865dc8b846d03041f01d82139b577d71bffcf5d405e867235d148a14c515ef09fe31b63628cb3726a3089983685dcbf50d676fe388355e1149db754106a7a607775548fd4453e4868c81be0a0db001920a8f30274524b030c65336d80c0f2a189f6f01040205b59fb6d1919669b919c565aa67dc34d3f0697643868188d50d544c93761b47844455715cf2a71e343380d6ab14f26eeee7411bb9a33b9e00947994aaca5bda2a044818613e59dc0c736a117486d50b4820542db60ee14af25742a5b189d853cd2177d08784b0d4955089be5e948f123ec3b644a19a8a44ec37cdfc1868aa144173f0d4977b99e3bc65a5ba4dbcda8159a1b2f7182676a0b1521deebf0a6bad3bff3e1f5bbd87a318d2e9e04f6c5b82daa5c75134c18d7d1a7a2ea72e0ee3094c60597620c3f5abcd14339cc977df4fe140e8d358403f22384a4b42d7cae70b8cc7d4de217696683450a4794047a226aa35b7189503c57432959015d4bb037a086edc15ab436e4b91e7bcf2cd939a34811c7954d9592f9b559cc245268b818b4d0be34e132bdd0204bb433b19769421a33da0cfc128c3a24c9ae1a9822281748fd8cbef769c51d1314e8ff2741079b2071109c459f248ed963dfa936ea91f2df35bc50bf706ea6f0ecc7f5dd40de220a1bdf61de4a2c50fee4bdafd18be2d6f2737ee6b52240304d54443dea93a65c270cfe8644b23f27a95ddc3a7acefb9a739f304195b29416df7339bbf27c38bcfdd5a3687d0c2322e77889d0115f59a5f07eb6cc564b59e1447f2b51947d2d617226c17db21d3f62753556c02f07dd70bb387c44e0714ff39780a007ea4130ae15884995228410d1ec5a0c832f17ec4aeb822e45317ec239e22389cd26ee4eb06b28ff749dbe524d5c723675c39de37e70e4cfc16a11ba4fa07b1b4c72ec5f29afcbdfab2d7a60608c634521937479eaefb44d790383ab7cf4c01fd00152234a2c6b617c347f8e88ed33e3938c4c3af3a38340adb2c0bdaa136e66ef971a1c5130873845c5bea0aaecc2f5df4809da0ed81e8fe7169f728dafcce8fb13dd87373e281537eaf5907d60d3efed9bd41cea8f42fcddea97016aa728355e6a13cf16e18c033f3b62144d19e1f54c9389dc745fdad0de857a7e52fee7e395afa165b0557fe086e699f82ae677e7aedae872f3c9a151ec8696db992c5b47f4cc7b8c35ebe9346a6d06a2eb517ce6c138c383d5c0e220c5d0325b72498c5084a8f800aee28dc796c921b767b52a8a6ff1b6af4edd6f642396488dc80ad8f8f18f1d94858d23a62de18b1d458a5fc0e096c03ca9c6e7f61b05819d2912295108ee339563b64ba74c9da0146df8c9529763b0e56339272393b37fe2b85138b4f7bd45ca4dbc32e02d1fb9c40a25350d4c27d424e01544d7bc8ffff116e699987d1109bd9d5bad51c5ffcc0ba20a7b9fa1d7cb32a422175dc6d11250cc5df35d4bca2946a8914175e5ede89fa2587d6e28fa34ccdef308beeddc275fb76bbd1ebcfcf9dc45d4c6302b7829e4786b33ae2a6c6dd1c2ecf0d4b17ab99d220c4e04d03c3e5a6fd347fb56b1ee492047c2b4ece1b93d351a12813c84990fdf7e728c9719f86c9b5658f8e505b93c4dd935267f39564b353957ffdd6a2c8478c9b4ac2916fa9a7faf83a59eb8864f200cf56d022e8907ca61a8273d580ec67e070823ce250668a11529d9617ccacb082b5dd4ff04dba28270222beca58405fc8f808600834b8c09a975662daf0531cb504d85ddf7217d3f6f694d104547292a92917b5ed76baa4517369950efa15efc1c5e06433cf0f44dfe105f8c80304097250c7b80a8d4085d67df1fca07fd6c469e572aedd2e83db7114559e9eeaccf12558801be1770057f90ca46af1a8cc21a760cc65b0845b4835248502781e1237fc3a0e12cb85ca757fc3f00ed11c7729fca6ee0dcd8b08e9ab8dddbc370b821eb5819156da44802223814448d8fc1cb82d090c9a03078c540376d2da4c7ff7af92ab9d6b253686e44e82bd8a8066e5697f61ae79a04c43dd9de4510e850238d1d15f7331ee25ecbd1b2d0a5dc3e9561b8eee12cb051d2956cdac188b44ded4efbd6ce027b50eadc148a82b6ec05e724a84dccf6057473aa18d58744ded2095f29df1125ac8c6c0cd5884a11f6faf01cf7616029b98d52e5fec5b23bcdea6150f96350d67c6e5ba7bea65ce719cb8b510751658f336c58012f50d6d80366c56fcd1b6050c7c24b9818e6570dc12bb96350ac2982fb16126912d37847d00dc6e92a02627b0ca8e4a5f6bc14e9f380287080d0de28311d647dfaf3716677dc09979eab1601d33ef90e2810c9652c58be515714ef6b0c6c8ab4fa9ce7bde3217f8e09a37bb2502f1415aeabb7d2d67be5a8cdd73bfc2a528de71dd19a31e38ca5144eb17c02faab909dfa968fd08125d638f340e8bf022cf4c000d163ace5c93e34f84f8800573eec5db3f364a497261be45614c409620a6a60ac6326a9239189b00589de08109630de3bff70111c5bdfb6fee5469028fe168d4c587af6326159f156ed5b8d85395a5be15fedff8088c572a1bc066188771edbb03ea4935e64d0a924986e1f81e5ca358419464112a0321000dfa41c74ad8fdb2f03e8f4de3ad2756e4eb508ceb1d1065971fded747896b3535d317a79a60f049a848ab8f12fd28bea3b2c6b87da832761f3cd1b52bc97f1f0d6985e7fc3b69153109e74e66f4a19a6c58608003109283289278e1f38a0498cf3872e8c72f7673815a7c0d48218e0a51873ca55e434915be3dc3a1241c449c2c589d498189c93f561353e33b5c5ef99acb48268b2af16858a1854eb4e92ec6abe9536ce05130bc0687c30bd02c6127308b76362f48105874d0633f47b5f4d728d6ecc2411da64012576d63adc72e84d906384aa76207f01bfd5ae83d062f4c62df8f4197f2fb4860550af1b88753c32139cf7ccf2b40cfd750b2f92a1e6897e8a45cf50eb1d326cd72dd4ae20eafe344c5209ac7c4847b541f45b190e3110bc89fa2412d7a75327ebfee611fc71781c08469ee0b47db161b8b066a4500d2454eb888b43fca8e89d9ce0368fb08c427534b652f6672df636d8ede10efb7c17ebb203f92c04e206187395a198ff04c5f1e2229427e365bd8da7986aa82db5d60ada27c8096e0fb6bf1e734226c17134d1f95b18f2ab1b110821420fa15a58b729d383d4e2d006b5d1d48a7a4d6c7b3e76c006001c9cc571dd84091c7dc6c17d5bfda754dffcc456f982ca80403c4e767b1a2ecb4473fc93f5c825af0f6db85f47090b6fb28e9aff60b4ca4e5c306727c1ec03e02dab69ef760ee08f4b7ac45061fa9a7377d67c99de6c236aec7514ec85cb9b5d19544b43d01f8fc2c9a759b176df04ea1cdce14e681c3b45f65ac2885811a9da8d36ead0f196c01a577ba58220c354d897dca64df74e604a4bf539e3bc952b07d683b94148ff924dc06553136ab2fccefaaec382e462cf18c9c57519b900d57b1c2211a80b45ed3eaaa71093f160b80dab7d5e5bd3f77def393854800476b096462dc2ab523dc8009f2563b061db5589058bcd0e7558508246f92b9a48c5f796cc7f293004682175379331a23686f2d7218043f648ac412bdbb6b21692aa13bce3789c6a1b2bcbf1582accc822c489fc33613319745226085d8f45e08bfa7956d362dce8c49de01590cfe0e98758d9fc703bf5813031dc56ea12aef5c0fc0d7320213aafcc8f90a1ba478f0dad4d912fadba5ee42be8f5f01551d3daeba55b85e59216d53c47f943d4690fdde0351e9ffa362072cb5e9e178aa359fb3d49ab315e793dbe01cda341fcd545b36cc259c82e15018b89d7fd130da50f5daca11e2f373071d79ecda85b37b8ab000420fabfe089bb3bb5c921f28bf43707fc47396aaa631e5c92f7771e76df3806331444a5bd0de59435348df44033b0dc1a65a12bb63da79ebbc2ecb610b37dfe54c53aa961368356e1fdd94007670fccb07d36e20d3cb594b4011da1a930eb699bb35ee14993ea2abbd48191aecb188cee065f6565a83689a361f6fafb1f0ac299fa38e264d489b0abb83d4859ddc796d18be15cf7ab1858fac442ec12527e8ad5c0ae809f47c523c79b83fdb462424b03811cc639cf7cf61cdda0b53c85273a52d5f5de5ab30ad309139027e9adc4e1b504c4365780aaf5a1fe1f5ac8bc16f6506a0e1788e009a0ce44dac66a05f159c19a850669f9f4f69614172f4092d0355183014bd4fafd270b91546dd710cef167011a15cea157e1b70c50e2713861f67d0456765b26644e408b87eb900be827bb9435013f2aa0bc82bfcb933cdb8279570ad8824bf696492d2b6acb744a5476f35082a1c8874be923550d47342ffa8621020eeb445f53814b4cb41230b4a5f3fae7a17ac6b45f1703ea0119ec909c673285774d809e43233f604e44e4cef5eb4283e581d3318779ddc9fc004d68c7d85b893dc0e13066b871658cc8318e27ab95d4fe8314efcf803fcf4353238b155c32008a41ad590b89aaa3c8668d1ac44eec5bedd9388e198bc842f78dd3def36f59364c79c5f449b7e09efe6f9b118023a5ce7fa419eff46bf8700a54b73d3e084ed2ca1bc49a3d72c5722a1601ad453a77ee1a2d4b80764d6b163d41855f6c90b12b87c4863ce0cc0b4a2974f493d32a7978bbd6f378b4ae529300a550de2df3eee87cae633ab084f6c50b2232339d37ec8dfb73f3f9cdccb13101b7bac739b6f06d83faa111a56b57efe543814a5f1a5ac7f1ab68bf5365b6f62f582ac1cc5517d8cc5810b6d4e278a19008358f04ddc3895a5f0545a16e40cb401ded72fb8c636457123a73b27112e8b1458bbc19506608c967c1f5457f85efff5dedfc87fd08c9c3b56e19d0bf3bb0080fdb27997f116979d7c13b808a664f77921fb2d3fb644e1be04504be85ae8ac5c6cc1a46c8536b62aa9a1874f4f768c5b1048115b5482a606100f7903714191408f995d38296a74dde3b94a5dc4dae57c0708eef38ce68e23c5fa3c7cbefa3d7dd43ee5bf388b26bf76708c6d3d42ac35721cbe32a88d00fdfee73f0ec422c9c754e177382007cdaf92687777da00098185ea655dd67d753f3ef793bd559b5a7e6d4df53a3674bad9deeb83a29cf996b98156ca5f64c430d82ea4c13da8aa5bd8fc4f60ec2dbfbf86eba7f019bdd74efbf6f8a723eca6c4eed15ad9d931ed991134d1023cd0738c3fe29cfcbe2cbab5f5f6ea5921a6c1ab57f6f86728fd35653c1fbe84eee13a2bb7a94d83fc3dd9d4a3270d01d1bd98727e6bfc39beb14a30e762e6233c3878f5cd9aa3481e87a5812f343b9e8b09386410832b2bb028d21b6a281e271ccdee5d126ccdc5b24d8d6c8fecd19073ddd2071870a33b2ab5c578091fd9f705f8fe27feda5cdb0872eb2e70e926eb6c8ae69854413965676e8d26539ed8321b12708bead3b76875406d1c6b193a149470826218e0c2237e292f02131ab417262d0c57e13299fbdafd8e1fd10f951faee815fb1373fdd1d2c760d2b4f789bc836f65b1935f0d32f898e53033b78a2d933bfc31e0ed8e03087c6e6d0fe59629488e178634424875eabd8c8111448c0c615e56f502885aa47c75414ce85ed82f04354a7143c818c51a3f7c0976d22e093bd7503e217e5abec14e016808da0268b5ad88f39849f36c63e938f0a15a961778621fce9b57f95c9f94200e21ec23d823cd78026aab75f301c1e473716cedc684232c84b520c3877035ed1fa302d3225a62ee07527361b219c03fed9df58631d8113e813a473cbb8af822fdea10a0d7cb96ed6eb27b7ed0b970aa678875568f0f0ba59af3fb9b7c581760a3178990a15bcc1e5f4317b94d0ab9d120da286e78a46a387cd8b3f97664ab0442d274dac649caa0e0ea9469d391d87d8dfb788efe810914a25d972a8831962ea3d47f8cac206ba8821a3d6f469efef8179e3c187c2d23c563a1becc2183944c9dbefe1ad55f653854cd308605538284d9b0f37b9bf05db5d3edd4a560d340c691e54702302351a4174f9844c014e020de8722f379e8f9d2bbbe6b50e73985fa06f27deec5d3c0d0e1db2c33decbc08c6f62fe3d27fba56825019c5771b39d1134235215cf4aa12f2c63ab46d93a115958dd4efd14622e01677431fddbb811118a80258414d712bce4f15688737ee43f188e85fff6dc50fbf235f5b75bf295a606c1e5c5c1a27238e2ec47a9176bd224307f5f83ad0b7656702526b31b5b0b53f80386dba0d9752daad9c9463b879ccd95d5c9805348096e71c8380a3bb8c752f3cc018f3fc514b2733c8c42a4d10c31daab39f2299cc5ab2eccee352c2e2219313d635de6ca097e55abac101f5c837ea35afc14be0a2ba8147d6a020a46064b62ba1e71ae4cfdcc6241e584cca15be7d3de6900c9d33c0886363e23398bea6baf811e5a4a3265cbb2a420a19e84cb6965c8a86cfde1edaa3be46f2cd8a6919cdd3ce41b1b0cdbfdd0ec011e0f8e9106ed91182b0de76a3f1e927b25d2af2db80a7f903861f8ea4720e5caf8acd433b4da6a0bcabde2e0035ece75366020dce04b242c7a938ad28c86a4df64383b5d416c07ca796d8486275fde3f6d5f43e08462966cbb5f6149f551f8ea4d5424226cfbf39a971abad5db2ddeb7045b8acec8b2fd09161780accb6e1b493cc2273b4ae1575dc0737cdbb7f088d94ee646d0575da0f58bcccccc4d48e2f955d2c22e335873900c30a25102462e75f31ee1ae0e9d9ba5b3a0b19bbb4a1c97d19ec92128f535ee9701622a3feb6f9ebc3cba32f8568bff1566fc5c30f9c234a3a2796baae0bc07cf15ee4c59f1cf8216983b89cf181da34dcc259c52691e9bc5f4f5980b41cde45217cdfad365f467d8239ac9fed6dfe272274aa1ff7232501f365d27cc969829aaaaf9d7d6d069d971e8bb59438be2a11f8cb549531a85eacc0fe86fa262b912a9887d2d706946168323a9b55a759813a0548d61c570e7c5c1e3dacbbba9e0ed59de3515db708c1a77e4155a571d803a5df0d4a11d8b2af4d40311e67c125aca1674674d989aa180d6a9dd893408d181450709b648902afc72942418794d2a8de30c95c8e0254903b72d5c19d2f645341bec4c321454c961e695c90e932e92ff2006a596e8cc6f93688b2cec34f351649599633460554d237488c3d0e990b1dac2eb867cf019859c3b5a71609fab6cf0a518ad323e3bf8360238a4f468a8b0a18c296b822ef6457e363c2167ad10266d5f43019351f4a0a940095af37ea484b933880c9c5466ec56f42b3f0476bbd2a13c88418ea5415f2fab9935911535f099b202acbd8f18d7865f0479d9cae9b49dec7fd9d14f0ed778c7182f7e48cdc78303b41b6de9b8d41e42c66fef9c5114bc8ae8131119cdecae67a14a7b8bc07f30e3a2fa3c1c1b6dfc57af48f475782828f4721957c2ece8f36577711e414531f0e85589095f96e52bd4fa05c1b01e916c8ce13e7c1005920f47962894b280901c053f9cdf651f9a8406c552cdd4336fd3f13dbbb08be326945909750218b8a5eb0e52a661d0834096280bd6cac20b9316584882eaecc519a2f3144b18667ce1c781ceb7787738718ae318822707e549342b400485249ac643d89ddefcc7b274408ead4e7ec414016b8976e98c20ed3370c4569a606a01bba7b789091d767c5cff85b7330589ef06c6afaf145d8f711fa247b61baa8c9beebffb183ad07e395c656ebfddf6a21c26adfa9c5f70bf95f87e2f00796512aba8d8fb606eb27c72e60057c04242b069c4f6be8dc8ba594a415f8f99fc49eac49a27a3039699afff0492cd61c29fd7dfa2dff317d3d2297796ee81e00a1b318b235561ba7285b26e92aef878bcbf2210e01652cb4d3b8f59104bbc2ea59ac3a06f72457d8e4958af0948bd2acf72e34453d1af57f0fcb63cbe323d850582c19b4097d62e6a142a5819de40a505dec4ad96f39cf5538114fc8498b087ea11b47a4161633207428ef71591228ae7bfc582bd2b82477574cef99576f9f6764f02119d4454ce73ce0ed0f744d89bc4c1e13a1c5cd3fcac8fae4f96f9c01313e0fd73435808778905491161311429f681164d8a1ee4c9008f42fb251112b4da69946d5101616f0584a93c5df5d11c247d6a06012a3a329023d313050223010693e6a2885c878e284260c16c7543639da98c22614d9beccaea5c3fcd0f36b0b403bf7fcadc222017afd7b2a370138a097c6d5c625c541338cb0387ddb7def6da841cf8bfc630638f240add3ed1e9ed9414eaac7288200b18a91f2f02fd128dd00c3c88c1c2123dd296bd0f8bf165f8d2d2630bf0dcf87dad2032111c626fa5be91bdf2775dd0577bb76f29e7ee48e76e7b4055322bfa96ce90f7ccf70425be306913c80c264a98147a1000601486595917dd73d55e48e93bd54337adcca48b586bdefec81bb2c29acfd4a35b9e1aa9dcbc99424d2b964d1d0010a72e39fdf04be1be4da33b23514076fa793e2f38045a4eee06aab94b67f0c407f643d0b83edf4244020ce33e9391832327d2003cc7150a1d5301a6caa2df3c32ddb0a4333d607f8eb5549e9af950c4ae31b485dc162c082a9476651bf77e3bd5eb867b5b1145a8dd7bce7591de455f97fdb856b82e5feb1a1930174bf9e67eaf3d7d58fcfae8729662100dd240ee26c149969500c6f0491f38925b61e86367091db9e02cb25b09f5efdeac98cfd8406dbce4796d06342e52b36753483cd9bd80e049aeac45816d9c82598d566bedf3d848353a9e69f69947f193c7182f6e1c6943927bf57ac89fdfd89340b1c0894045fd43e21551a24419263c8227fec85ba24587d0103752903883600547e15a0cdb980a32aa21bd3e76f7bf961d1a35ded09333865c0d42abec804dd5b0897239efd9c5e35f7e7673586c8307332935b6497e6658fa8850921b844ef16a274841ebadd02866e353146e0c2046ca0e92dee1794eb035a276ba3541058c96a3aa836e327e31ef19c000c842612956a9caeca95c1a3ba5bec5ce91e951f21b8a0c518d17e50e7330ca59b537bc8b18bcae6de513666a39eca7ffce8c7afb7e2191e32e3a916eb467637b9a5942949193b0a490af70953f52fb70a8e5f5d1c1e8e577934f223cdf610f7f3c0c2765e44f262ac09c78d51ba1575e82035bfcafc6081ad89b15e7a3adfa74af20311221996cfa48776b5478364c21f14e1f951355fde0c449c83cd114f48391a12c2f155723c112219c6e17d00c7e3f86e262142248b112192e1d85e9521224432a10fe0f0a2e88f62be131c378aee9777e21ae003e92fc69863128ed1c7077b127a441303e851c210ee18d3b0f051300568dad21fc432b40bbed34cdb1ce83805793dfcee85e66ff3c47a4538fa1c9726498ef7cf81a4718ea7343b7e3e0d12faf3e94f1d4035524aa943870e1d34d26d64fdada2b0d9e605f434407c1faa76d87451e5f578e8af9f977f737df01e6277293feecd11df9f2b85583747ccd2e28469fee63b4f75e7c2435591167a76dc1cf10ea61ba6d0cff3fccfa548de52eaaf9bdd7c77736fbc1d4dfdeda81991ab7761f52d3c843d3c2bee7730c19daee197cf33b41ba4d0cb8f99cd3648e2e77c8e97733ebe134c919c74e236559f6d649d0ceea858dffdb062f36ac8710311dbc03adfc954576b174c12b18ed739dd6e8b119e276b53b3d962e3647306fcd1813f38f0a759888f65de052f3f46c97c0b5e666194ccf778d9f278328f7a185faa5ad7d343d3f3307cd0bf5ec807f9165a787d0eaed7cf07f91eafca9027f2425e55dbf15435165435dcdf05dd183ee7d22c5d9a21c01d8bad8de1875c211f746972ae0a0ff16892845c1a19cdcc5b6d234bc2fd42a3d96226889c17a2629d1712c39018bc26307836b3215e131a1c7e7ec80ff19a10f17e84d0cc10714e846216138a5084f124e44428c8e7958d2cb51272a3200014fa79221a4dea4fc875219d9b818a732e9017725b6886e62f1b99b7fa6bb533b49c1b31908be3290e204fbf8d0cceccd8d024b8937321fef9c6f903b8ca8f4791bcd5412e4dceffe47841e4e0f3423e07d7ffbc907f09f19ab07ebc26b6ab71f99ccf969632c2fd3af7464a7f38c7bb29a2d91b39399e8dac0636e6b38dec850fea8e0bfcc9f9feaea9abb54be7fbbb2dfd1af2fd9d52bb5cf8fe6ed6c9e04fcfcf7cbec1313e62be98564cccc35ea1e214cee7c62a2a91a75ff53c37c8133b619e221179f87a225e93efa12a080b5bd81922d6792310bf90f33ccfc2bb70e7903babe896fc162eecefc7773c77e742dce3cd22b8c3738910c9300b37e29d3ba5e84fbe8efc1cf93f2e0ffe9a5ac09694f2bbc0f25b5eae41c0f23f8f490c2cdf870767e4afe0c957c1938f4305770f2fc6b694e7e588bdf79f9742f27f6e5ec7df7870061eef5d1f6e3ee220f76bf3f742fcf7e63ac13caecd07b934f2857c907f5d1ae90501431a53c840a84990ffc04e13570845e1fff3afe731b359a78600cf9bc2661b998d52bb6a683c9b59bb20c4d24609e8d2b8f7427b0123096b33b391bd2c61e9671b590e3efff3adcac1f542be5538bcfec76bd25c5421f4fa56e110e485784d20ad8b22b02114e4839c7521ce719d601c37d31ce0fe999b6f646e9e4a31377768e09651a44c4304dcfff242cb53497525db3c612a81f38c0d520a671b5990fbba9d17425055437cead14809a43f9b9e179c6d68b86d643e38745e08f978af0b85ba987a2921a245924cc8e505792bc885421d928ed30b7da18021a309331823bbc2104b13a840a209303041139c3041134e7046138a9ad0cdee96ffb9777bb7e73802767777d30e04b15d4e3aa5a45b9515e53b6ee08f2d69fb8e1bf067fe905750d057eb3dad1b9d5ed08cf0474a2e764141fdc9a0a89242d7b9fb6c772925908bd3dee7eedeeedddd32fdc1efce818694dd4dd978a1aa54f73c3c86991caa43715d979a2877771e5c92e145664db6c7a4a4a464d409bfe9a81b37dfe09c54feaa4b171ba22007bbd50b55a5bacbc14c0ed5a1b8ae4b71aa22b30328187883c65c9cf84eb81ecf7186bdf09b77491793d200f6ef4e46a29efbcac16f150485dfe432505130f6aa7a13eecc2252a5d4d3cd0423b44c7f7f4e928accf0011667a4b164021770bf450277cf6e26a400bbbbf0f98be55b94b0f4c10a54d8ccfa7a581d5291f6347e8b1433ce39ad8f81e7ff97051b44a03d52a6e41294c012062cb12c010b962d4f60f93e83a8041de00904d63c7a7919da25f17c52c6a684212846b01829010a099e94c004133f58827581a4841814fde088050423d40b7a2ceb1fe7d3d9605c40042e3a68821374d085e90a98cc93a51862b9c20433505c31c588a6a837af73aab130b3dd49c1f2dbc848b705fe44f88383e5774eede2e203db4edeacd16cee6a5d4d0b2cbf0ba72060293b31b0944a70a707169b3b25b0fcae08eec82b3514e6af49a32f9f45f3d7ac39ccf60ace35357f754ede922f7dd66e9842d4eb6ade5249b1fdb9ab61d915b54bbe7c9aa259a5abdbb6d55a2b7c1666367271f35abf9b5bacf56badb5d6ca7973bb74a8d6aff5bdd65ae753ad0e63c5fcfa3048b5bf46982040982e7085d102d75ae9cf59ddea56b7ba552965e52a57396ee3b6cd6fd7c56b12eeb977ae56ce721fd4b30b4787faab8ff2e6537ff55535b030b5d675b973776f776f2f6360087b3724a57cd91788385d6615c52bbee58011d705a3ba218ce29e300a95ba51a0bebbddf7f5817b0e1861bf999b8ff23ad4fbe5bccc0123ec5fb33f7dcf1c30c29b97e9f464a511da9184657956b584e57a3ab151d8650db8b3a586bcb57da706e4d99afae412792e25ead6f6a3c23d9e6059f389e5dea4970ef5b77ddfd9854b049a6a409eedb735e0cef6d5cb330a787b2afd6db1fe8ee0ce369ffaebd2049042b4c85b3f6636774f30480d2345bbb8df1e2609a60af803f1f63062c01ff95bf75484e7cfd94637bad1cd09104620e63e49c4156256add6426751dab7ebd2dfd65ec7c2ccfaf6dd97a6df09ae1b85e16d8361da845084fd530f85e00f0ef667f28310dd6a0536f53eb58802c367fd64c22895ea55aa5984550f3fa82deb27cf12e0f9530908f480c8e33f7f4a9946aa9fb5d54f2f6761b7cf73ca349a526611dce97f40049a4a409e7e9f25804840ad3e75395cbbcf1d0da77ededcd1f06c32049e1c908227078cf0e440119e292f636008cfdc0de149239e4537cf1aa6f3d24f5d20e27cee53577583cacbf3299e9cc764d6f0f674fba0f62a2fbfbd7caa5f2fef5f5f6e4db7bce27a2166ed48c2fee0f5d09fab96b0fef24e25b8d3bf2aaa3ca8d5d3d0d284b58881297d99a794522333a61943b3301862d17205670bf8ee04eec8018cee113fae95c5eaa494124277e936a0bd70f7c056ff0bf0f5403480f23d42b31eb8d3dfe31ea54b9772a27ca2ea265da2ea466710a4141b74d46f70db36d443196bba72357a4c4bb499cce7bb40faab3a38c090fe82d742031cb5a8a91161afc9b03f4b0b142742dd23e8a9a284a71b68e932c61353aa0988e8d233bb94042e3bc470610d2d9220c3f2e78e21481d240c614102166c6580661cad1185065c1841250742b22e81a594584a29e511ae602035220d0d0d8d60095356a81e74c1831f3f805cc8ca500196f26b194d58be6595c1042cff8d60032cff332304587e4f0fb07c1f690614b0c4d2840596478882e512135a24131c5ccb182a41ac8c121861648b190b082bb082cb7f5f6031d433d4458f920b2c5ab2701a184af33bcf4200f1f17ac6b001ab219b83eb3ec7f5211f32b034a2261c5b80212d04329cc5494585cd3e84c3f371c5c61d57768be7c94ef8c38323158aef317fe1f8f88ee449ac8f3336922a08245b0d17b1fd45fbaccf01e675bc5c1a1c5e7ec7eff09a746838c9845ede89104be6469ba7e3b693b7e2b36e3fbdec17144abdeacafe5232edcab5bf94ccdb76e5f8b9939abc7cd39aacbe97bc157b668384cde1d174ef81d5df7899c7e1d1749e0d44ab7f790fbc7ccccf3c93144e3db4fd759e4d122ce76521228781886d2060f537be550878f9980ff255ed58b638bb912be1f833dfcb5c9af843b049a8678b30a024110323569045e8c6e71e1c619310148af18e780bc6c68742f276acbfe83592d75254d12f282464a3c5e68e61d91637823338732338732338732338732338cb700667b20a37824b1d7443e544d52f4bc8b0d3a0c6844c88e7c9b2e00f0fde84fc3f9d54cc7e61b3ccca0c9b927921f0cd4ea86009853ad8752a4b2b4e9264b0610138c67fc2f2e38560e0b96199c2d3cb426026292ce58b8c0854bcea06ade839b70937a89af18ba6d0880a8d8842512669646df506074e683dba7b7577f76fffa0943c5070d323a59c1142086972680fb46a4a17dbe5a45bb52c944fcf0a23a028aeab3a33540daa74ca82dc721c97a2421bf7af88d4d64776a4421b84843455b4ffc18b8a6a4542faf00d2e1491789c202255a488b47a912f2e6459d2b94ab72026151a92153f5a0890ea4cca28b0e0454a2951aa2254ce10c440f0033c777c11046799274b5880e00458fec69a73eec093fe9c73ce392995a9c1218388f664092c69434f60f9f3fbe69c733a610c3c5578ce39e79c32ae2964c192076db0004b1b4e60421ce23346e7851490bc58e3c91a56ac81461720b03183290c4d6104587ebf38c51fc346e749066a8c00cb67a9a105cbff4f0a44a4bc783a2209246dcc9ea69400c9149c60a6d81b1862d9010f3e0cb1ec60c99d707c2ed0a9084e70867f01275129c95f6ed4bfb444a3cd663219ce363ad96ca3936dcfa5f4179f690519e89048ef804f1a4f32504a297d02d11a4db03104050cc410a2750598521c98524a29a552862155520241430a2144c1882894118526a431c352fac4109c31851ed4e09081b30e102c873c418a27b0018530b8d8000d29676c8182881b4e58158658b448eac1104b10acf8d052b380fe1f4d10dbe0af9e9e207f4def0aef0afbd463720a834527a5132585227891c41152c00222cc30966ab02d514125890b8ab0851345465f84515530c676041b582c677c71861216ddc42d65ad1e4d12fffa46fc6b93592375da1f79c0fd17461d7410a7fa8b52244b7fd67659191655cd57fdbc56e565d5d75a3bbeeab95a3fc851b5d2f9716e8442a15e5e5ede487737cb2658f52f2a959320f27413e6eaa36eedefd7e8204e5ec90b8576f098c2ca7719967ff794d28eb60a21298d51a8d6dfb64df59dcaa349d2af7a23fdaa7782519daaeb54ea72ef04735e3442e4a952dc7c952d2060411337d8c108a2d862cae4428d2352be002307568a38821735054f2011e1193b8867a800bf6088e50c249e20194722902541892c9eb002055c9c306a82043ca8520411b21350349e0793ce4921f0831e5c71c414626461c3a88b0dcc202376840b981082f9c5925ff23bbc921d1c0d61253f86152946b0fe3cca0e576ca66075f81d7cfcc0660906f6f7010327104182d1aef85d13bc82828d4a98947872a9043606f915a2b10e0f554574f0246d872b673fa0606bb0d89481450b5cec58431e35e01df0eec25be0fd7b4abb5a78ff366a57cffbb79476f1bcb3f0fe1da55d3bef3a9ff33fde5bef1f9fdaf5bdfb785fe15d85f78fb5a676f578ffc8d42eeffdb6ebdf6fde3fd2dac5e3fde39576d9bc7f9cb5ab86e677bceb78ff98d42ed67b8e771cef3331ef37de61a4c422d5a7de25f0fe43f82b02ef6f83bf20f0fe47fcf580e7e1336462c067e8d4c3e7964de07327e5cf4df3e173cf10f0590e1de0b377c9e1b33f19e0b373c1e1b33b15e0b36f21c067afddf0d99b6cf8ec4c03f8ec5904f0d9976af8ec60d0f0d96909f8ec57927cf659003ebb1292cf2e3bf2d9ab30f2d99366f8ec5214f9ec48327c762b00f8ec31a1cf4e057ff6a3a0cf3e2586cf6e04c3679742e4b3170df9ec51803e3bece7b31309f9ec433e9fbbcbeb733fb93e3797209fdb0988eb85cf5dabc0e766a2c0e75e62f2b9a5f40b4a2aed0474f9b84b2bd8b3945260ff2ab0cb5e8044593e71f5809ffb021eaa8a28e083da879425eff02ab99907ace4e30de27f0022880bc485b0f5c3dde1a3ef7097bc922bc4c44b962c5125bfc34395921d963cbc3e4a96fc5c1297f830cad22db94b68962cd9c1dddddddd5d72c1fe4b76804b2e845ed0921bdfa362659676d5f04a9e0689005ec90be095bc0eef2f97daa5c3bb92f7974ced5ac0fb775dfc1f6e1327423f2cd1e177f82517f6b783a7c34f550d40bc9297aa0128f9580508ef0351a83004fb831012800fbd2a3f7c90ff70a350f24a76f0b213bcc3c79be30e4b3e46d930c60dc7254a94e8b0c30562c34abe554574f84e87abc385f00854f2539544c9435511251fd43d6b1794755c6769577dff66eaa6aef516275fd2e14a2c97325cc24e64e302ae02ae925b7bdaf5bd005d39f0f347c075e10f705bf81c6ecf1be0f23c0e97852fc0dd79025c9dbfe1e6bc0df7c70fe0b65e00f77b1aee0a9fe4e27c006e8f4772bd3f72ef1bb9ff33dc9b2f7279bc0cd7e601706b5ee8d23cbe3b3ee8eaf8182eeb61b8399ec8c5f143eecc035d99ffb9312fe4de789f0bf3af6bdf755f3ec85d3d90abfa176eea2b7025f014b811f87c19f03e5c077c0f97879fc06dc033b910780bdc077c02ae118855f81aae11887d7c025e3ec9914416b809b882fd112087b0ff01ba60ff1c0c808313f62fc016ec4f801b9ab0bf0d03c882fd05e05fc3f5a7e1c2fe925cff005cd81f12ff2355607f23d77f86eb5f44062bd81f0017f62774fdf185fd055dff18609082fd890c018261ff1f22ec2f6408fbfb5cd8dfebc2fe5c17f617e4fa03b9b0bf172eecaf024cd89f024bd89fc9f521d3b0ff0492b07f0fee04ecd1545cb87d0477fc5bb8dd739be7360bb7776eebdcceb9fde3f6507ffead1b3f2e373add887363ad3fff1e3736f5e7efddfb372ef1b89116afd4dc4873e38e1b65feac1b73dc88e3c6197f19ff981ba9f4e7374ee9cfdffabfdc28a5bf5814a3dc28811b87bce51f813b84b7fc21701f708f78cbdf01b7076ff937e032e0d6506173cd91fe6840c0027878407ffe36633cc13e10e34487fe238c11c509433acc38630a38435995d9ef97490d864dd6fffde503e1f432931aec1e1b2f83b21ec0581dca291386b4584af9d0a753ff5cfda7bf0fd3691482393e5c5ff573d70bbb61adacfecd9393c9d7b021841feeb71d04f9794ecf87e941b1fe5169a33438c1d40b42cab6022a204714eb7f9f587f19973c76951e31ece8b2ba5debad1add7dc28d763115bb63bbfb3c72ff1b176225398bb42c5814cd700b15d52a13387e74f5cb22f61d7aaff206ec85d82b8e2a7f4e62f9830a9bbaf17126e68ff84b87c762b172588b63f55dd7415512e983a45988c9753f6f1763335eae3962bccc822d988f1e0e0f6605f3376e76883b3ae5ecb974238b232c627471c538d4c972cc82634b98141cf10b7a8dc0e8f9f4272511b83387c09d2938572c7728110567ab040c67c0b13fe1fc71d9b033b19056d42431a41531e1ecf33e43e08fff7c22f0073a0ea6e460b30fc647da952da415c9f0fc21787ecfef3d290d3c3d4b121a78c28c43f32750ba6d946e1ba5ae2024a6af78446f10154f8f68d0707fcd67895d538dc4120e9c65a211dd10f605faab7a2bd2873edf7a2bd29eb4bbbdfc03f71baa7e906f57043984a07ce0a134d99ef3aab7e2530f0a842d87d65b30d383b00528342f14eaa7f1cff227a594524a9f081f21ea7dc049953040200518a4e0054fbe10f2a0482f8af668dc4be2a98800618c4334000c5a124a4680886d6cdf3737c1f32bdcd9c93deec19638de44b8c75f40071960e37b96f77798020fca1b1e3635343b74b072e0989189b901635f56aa54c7a1ea46a7f46e29a3d04929259d28cb92927b29b3521a597deadd3dd81fef95fd79db07797785682fc592ef24e83ce5533ef52cd3524cf26805478f5e66126bfd1991d5ff8606367eab8cc878ac88a44a515495f865e5452bfdd1fcf5641feccfdd0c6570162dab57519611ffc9ba177f58b1eed1daa0f39ba5aa489681fdf99d926ed56f9e568aa88c483c63708793aa549752a954125bb8e337a78a58231257b8f30293f22ba5941d8451c5a8607f7332f9b09433ef373f8e9a1c39628e39938ac19192c9919a51b166582c964ccc0d160bc6b258ac17d64ac562b17c70324ec671d1598fb13b469793a6aa1bf951fc1c28ec9c54d22a534738fe262363594cb6d69aaab19a1ad6d758395074100a5bbfeb4ed5982b67735445dcddfe2a950385952f352fef2f2a2335d85a18981beedbe6eefe5ea6cfc4a3478a84a3076fa2f0c30aecaecba162a30d30e2f41775308c1e9216a6898185dfb90f841042086184d055333894a24ab9f22dba6ae507b5138f5104394d74394d44f76eaf3a55d98588284a141ca54c29383ae1259030cc02439a942db8cb69c2e6b88494b3029beb15dc5fafb4cb91ba1e51919fc23de4ad4e7995d65b521f531f531fe9775b58dab4297dd95fa4aa14379fd2344d7b493e1cbfaa284d511a3b1447fbe977ddb4e3a7babbcf123792f849beaef374fa6b8fbb352d65a3c8b090ce2ab290b284bb772933dcbd4ba9e1ee71ba2d6cfc5afd5f1585ca62e9c7cfd1c2e60f6f342a1a9597e4c35ebf6bedbc0d5e096f85c1d91ea5d3037fb8df9c30f7b905bcfdd72ecea349f261550dfe1d7769b897df79349c97e4c3dc739ca75373dd838e2203679b27e836601b43da115ca20f6d818ec4239431acc2f17bda055f3045b4238e9ebee888295243518c700662717487d5231c83ba6d96b01d63ecc15b311a4518f8fde4f16b357e0f317f35cc5bf01b478f4849b0a2d905edd8ddb5c6eebb7d74824c8704b675486023091a771bbc8e6daeb02e858a1fb5aba778015f92c95bfe4d833ff47d32012bf2273035e6d0194f5aca98b11b3fc6dc47d113621eb52b6257ad9ea4f9cba374cbfdd53d9042833ec4841a4567c0b40c95819d69084ed8db0b3f9acdae4c5aa57254abf8d41a76083609d1e4e0011ad701b38f9d988067d1594dd8ccc239c2f6ac6b27e02ba2503030b98f706036f711dc890d9382fbb728dd43a8c934842e889643f21b534ae79431d76ed29f4f9f3eddbd5d763b8c31ca15843bddb0a18c4f9f3e5d464229e58aaa8a00113755912e821042082774afa3f417bb1be6c4c5060ba408649d9c4bbffac869105f8de471c64ef530fecbf791bf9c4bb7e2df88f63a52e489df48fe721ac496d73012545e76233453e76527028e4b282ffb174ddbbcec3ec03d9b5ef62cb0752f3b1548eed45f7c1b14dc2b6c10d2a8615cfa156bdea23010411646e8a1284f44a9f80df357c38a1afe10863d9cdc88312de85cc0e202172f38c1c697170cb11082d2b619a103666b1da0e33814c775b0e3d68010c215a8d8a89c1042082184f5f316747777dfa6808ab25dc64a1b61f74d16856393addf64b76fb2f49becfc262b5337e8a44c94843bf02b8410be854d57c8c184104a192b13e32367e7104208218412fa48296b0f8639451173184a5fe10690260987fd23ce18c0eeb565819d334aa3e6bc1dc7d5212ac7753a6038f98473ef5dc721319f882ac449a74c35c7f4e260836f44d02b43100d47e4101f227c2447040c32bd19638c1d0a725d773fee7f8a875d6a25654febea40c9309141d58d4e8e8b52373a259cc90d1523aa6e7422019d1491decb4c118e27505ce54620a7dc2a8aa342deeaca71dd7316eeb435a2680e015bfd5feb2f4b878571025f91297ab10916133016a1fa47e27064ca8962e3fb1351855254e2094542713ca14828f4369e502494882514ea0111dbc013500111710f70a7ffc50429217e80324407e9df246ef7a0d0bc318382e16eec18c25b1f8b86803b9d52a95236f81902eec42eb5f291b1332a1f7fa56074dc56cf2b22d98a0473311ce23b2f57cecb1615999022135264426262f281734462f2d70660abff494d04b85f095cc40c5c4e14db0dc161d410f0074219e287803fb068e6a8917ae00bf51f0bf690bf545e0402fcfa51d01569dc1de207181dadfb2307eccf65000504c7db370cfcb6f9dd94b2a5945246c8ad24852fa228e8135185502e23ef0210f0001e1ccb4bc3111c36950d3e4486a07981f4f47073e55e9444b084a260d243c3209372407684cdada8176fc8b933bf4325b9698ec5e2e04f9c7066557d6450a5892d2621c34466c6c75fa89eaff597a5c3c2e038045bfdb108f88ab49804134de08e553027035b372aa723d1fca34d8bb448eb700f6d99919dec0fce20cdd84005d6c90afa7bce21f8e32f918688e28c5ebf44823f36516018d290680143da08a6604be384edfa1c2d6e9fb9272f34b8e3f3c9cb10dce15e30b2f3890c8d76648fb29cc18b1741c4f9047b86b48a6990c4974970f601474f3ac1433d8720163cbd2c672077cc1c4915c406cac0472365346ad7942bd80643ec5dc89c260225ec3e9d6822d0824202838663d81fcad8aa1aa2c71444231a9b88eed65de2e84c61a7f7e9d86f667334c2d148290819f4807eb5a00cfd925d5ca7a6464626c3ff7a7c20cd0818604833c2087b7c181c83fd6bf42011364bdc9149081dc49191bec929e7f4640f0fe0e10436d62aadf55714016c350f4c3c4c96c93259261f261f261f2658ab081bbbb819116511ae5c2583ce85606f013b6dc1d7c32b38aafc0a7e7fc513c02370160316193a7db52ca7680477fa5b300b270e2a8ccb1740c0f225134c0638a92a36d7236ab5c2c4c402b6d51d18c92c68b488222ce159c002904551b720ced5d28a70929244548200c88a6830268c02804512978419236c0d8658c288bd30c4124693b5b422a6501dc4705ae38c229e30858bda441a2388a143d8a8448881e54f4b2995c21946a4a0249105142f2c1a48c444170d11975a8960f0b05a2bc3aa99330a99832116252b6ec870b4a919f9ab93926450b320867d8f756b98c0726da5bf08838558c86086fd1b897f23f11abaef006a7fdcdbfe389ea78a94c47d9c538e585082f522cf61fdc50f57b08c8c97650c4b22fc74e386979d09cf92c0b444ab025beb6537c25cf06a15db4296846405a71a8697d270aa3135618ef37294e1380557f88493c2a0cd64944ea31edcc3e9581c0ebb2bf278ed2417f0c302bf2881005b2e99d0904588aa0038c1532ac9198e8ea14a05f0e55380cff209027c9651dcf0594e6143dff87e5703be7818c067f944009fa5086af82c95a0a16382005f1196e4b38b1180cf7e04249f9d0947dace00be229291cf8e83193e3b16453e7b0f646898eff723e02b2e01e0b32f21f4d95590fd89a05f6981af1e8ae1739f01c3e75683c8677f32a45fb480af8ee5eee2e7730721b7183e9f5b09fea4be9bf6fadc2e707dee19e4c6019056b1015ffdf4c2e786d247b4f0b997e8e921c0971f79cb732c8385cf51cbcee77806d6e9eeebd845d422e773b402f7c729e0cb67de729ce802dcd109dc5109f872a7fa8deaf139468182211b100ddc4f01e835b00c1e48e04bca98c01ff8b33dcd67a84424c80e96d101831b85fb55802f89037fe00b55fbf6f1572d80972b01bc1bbc7c231b8097ab00bc6c6bf068804c28a3245eae01f0b245e2a18cb0ff112fdf5427235eae3378d9164172c2fe3278f9c6eb0c005eae425eb6d80bf2f28dd7a318bc5c61f0b225e265d6102fdf6c4f405eae3f5eb642bccc7ac2fe3e319abf36dacbcbd5e5651b840684e65f5ff072b52e782df478f9066f433c5eae76e74867e64efea248def2cff172fde165dbf2326b09fb7fb3eac3cbd5aee065ab028e976f30854999bf26530f2fdf785e665da337baf172e5216536522665d86792a36afe9a345ebe61b1ac95358797ab9df164bc7c8333ea868f675939454e8941c9c858a2a1664f4f31b7f10d9411ea062824540c75e4603818304ea8249e9090e04e6c6c6fe36e5567350995a92e559a47f128f5e88a24a458ed76977aaa4cd4a20aab434a4a3427d49896faa88f54b73b2eb66db1c9b6a40da94b17b813b7a1ad89cd682bda604b4bb7715d4ac3a9c624851428242c3359522c8a4570a77147298c56a147b4680b13b8c3d4c693530dce8ec09dc69d4c0a833693651c8c037722aa0677505a38980cb82731355067e45c61fbe56dfc42d586c047bd30fc944c0d0e104a16791c48f42a18367ed7549397990c407a1d7560fb6bd86b3e2899d3b037eae9051c3fa3b2e8419961670a08fc69225ac02e30cc41308c28336cfc1be20298682460c2b6bdbddd614e0eac0d3dce62c1242fb87fd63d7cc14ec9cf8ea5c36fc26fbee0f9451a1349b8b62b3b1810621d2ce34b272ccd0c930837104e30ec0ff6e761875970ae38eb60f974e2e9e550613d3ab984b1bb5dd5f352150d4e66f8566d2528c2f33b2881f820a05f4260ded2aab5acfc5fcfcb8bbc35ffe53dd62e27dd2a8aeb52aad58b8591e12e466606470e968e1d3435363c6efe6effb8eb918a323cbf870a490bc72afa9b1d4ec44979acd6580c09a993a2bb1552426e47f3d67c98db2d49918484b452410c9b3b58076b57947e09f989047f1c1be998dab5f2aac4264aa90a4fe98fa79452fa4ee93fa594de3ca5de534ae97d4a6b9e524a699e521e4f29a5364f29eb29a534c753bae329a554c7538ae329a554e629a594c62c34e629a5947e4ca2f129a594caa794524a29f7f4c6d3d4534a29a59452fbaba794524ab7a7f4e529a52fa32a427156f87abc0a3ff3c38ad6e7630515703ca41e5eccdfc4bcbfe1ddbf81a949d1cc9a6635339a99e561a35443f3c2cac1cab1da61850e225a63d572d45438aaccac89ce66b3d4ccd631a596a15d8c157588058371d10a3b51ac5ae5b85a6dd65467747603954aa51a2b35ee6c5dd9dac16c1ba54adb6d6c6bcb15ed7a4b3bf9cbe7e7d3da42aec3bcd5317d7c49a55fbeaa49b17a79d42e9c5fbd5401049244adcfb209bc7a3905fe7cbf7a39d42f0983c164d1ca871a104852717df6359ee0cf4d967ec958ac29b625c665a95d3c820081a45290cf1e0618f0e726f54bce66b2d96c350308249b807cf61ae0d53b12fca1f9556d55732aabf7a276edf8d5fb11106812bdf0d993c0abf728f0c7e65f13067b82f910cc615bda55a305024d2a2e7cee25d4e04f8e2bfd9ab11818b12cb1a659bb70680181a6520b9f7b0b25f8a323d6af399b21cd9256df54dac5fad5b713106836f57cee16e055ccafbe89fa356bb58e526b293d05afbe87da55d980409488e7736ca30bfc9961ea1785c16a30a795cc102010a5c2c2e75804bcfab8047fe8af682ca614bb120303affc571fa780405429f2cc9dcff10678f5510af873e3571f8dfa456747b3d8aa7b9dcfb10478f5510908449b84e817ad5d8870149cb37a0a40a08d28feea7f6817f7ab67027fb69a65b2a15f1bcc5bf3c7ea9140a08d4ae4991f1b9281e0d5ebf46b8bd1d02ed4affe08fc91bffaaf5d2fbfc2671f78f52befb3e3d5ab008136251cf8d3bffadaaf6df6b25d30bffa0881b6a6c833edaf7e055591950c4b98bf3a246fcdef21612a4849250275565c52c6fcd575b2939d944a11a88b059172e62f8e939ce4a46c8a405d0744ca9abfb85a67b2461481b8a61788f0e46231580c16835189409c920b54f0e460b1584c290271545a509af90b55abcd6a73367f3645208ea86736e1899a796b7eadb359238a40a8261e2298bf50b1182c068bc1a8442094120b54f044c16031582ca6148150547694f0acb5daac36abcdf07cda148150443ab409cfa7357fd5d98cd666b436a335a208549b7288f0acb1182c068bc1a844a0aab4c5fc556b6cfea614812a95b882129ebfcdfcb5d56ab7da36c3f3716ed7d414812a51e49992c8db9af06796d73c4984e7fbf7e203a9b3b282a78287d3c3f3565e6705cf57e166c50316eb60f371aeea6b1dd14d8d47add611e1f93d68523b664d34b31db359139eefd974355c4c89868bede0625cece6e07070308e4a0e0e868383ad38184705cf7f1d28568d23ca51c3515bd538223cff26a6d65953cc6c35abb3263c9fc7cc26838a29c5a0622b54ac7a31253cdf865247c1a8a06028180a4605cfafb931bb5a4311a5862acbd61a8a08cfa7a9c24a9c7da9ce9aea6ce6b3263cbfe368b61853aa3556635e63782ae1f91d6a87f49f1f69fdcdc61546e5330b8627153c7f63b16c57a4a35b69ab55a208c4cd22cf7c16cb6eb54a84a712cced9a6adc2c027565409ef97388e51d9319706772333c9fa59aa1b1f5727260656398c4e459da156d4c6a17ea5faf9f1f95e22c5ef95212e6adf9b26330d854cdd01d537ff3eded9a3a98bfba2ddd9adfc5765cf084754c70677ee39c1cd81457c3f3b91ab785736ad7f6f33b24871d12ec9090f0fc98d430c8ae95f25891bf3a26e845b55acb62fd841b0c22629efe8ec95f1d6cf5cf8e09311d9391c7dbcb95372621c16edf3f57af9f8f62b2b9ca2b7a41c858454e4a76ccb63ba62e477ac79cb95279fdd4df7c9fdb5dba31e7a1e6bf6eaca25bf3ebfc9ebfcdce8929b3de65c76464c33edfbf65aac8858e45f0c003543a8b0a3a4b672122e285e6d38f41e2cf5b3b48fc0e8cec5670ff4b0a6c841746e9cec4ad6088a5eb58a4310e4e843bf1a5089036ac7faefdd50a1d62271d03a2c6a8b4cc76554d512a433300200082028315000028100e0704e2904824ced34c9a3e14800d779248725c1a0b646990e420c48c41c810400801002000223233430300447929521f7f27b62e95cbc23d10808e96da37eadfff254b85e6c8881868b72b352108cef9e0430bfa35f15529cd0bedc00162a45470e37f2266518a1da101933a57083c29fbd1b89a837fb5c00f885993da36e08b0d96ee0f9b355e525721719e0397b6f81f105f926a82d01e2a20c648911bf7f77f37a4c2cc1adc640edc9b0d907f540c0138cc85b39f196691d58dd8e04b8e5a5ecc5dac0ce1324a9a3ae2cdb81c6b495914c102b57dc5e8928616516ed198f6ef69c2b03f227a10d328e1e54ecf786ae08c93c0096f12584861b62474bae4b86459c42b77748ec474d15af93638391a93c6394862a1491d456602e2d334ec51613daf555ddcb02ecab44436e56e0ac291c8ddef2277657c3d9b23f9cff8e29e38564220ba6a0a882eaf5ec7fda44b1e2d68e00f79eaa9a7b63d63d740593ef0fbbce3fdbbde8f7e80a08c12097e343678fd9aeecf8807a20cae2c05a520b8e790bbee01900a36744137c0afffd17d89d8f537ba0f1f9f602b1142e98db0f6ad2ecf6a90eef3f6c99106edc6819713aabaff5b9969d2fed3d41c20bc0725869a2848aed29e7b71ada6030826425269c510fa1edd5f8b960b398aff5d3647119b73ac2f3c3db4a51e9ca75ebf89d9e9c88d550eea9eac45a1fd41a359104e1b093ee65b6d22c3b728f33192eeddafa4311a9451d052fbaa4bf1cb2e1ab8037ba2b9fa8c7bae8ee12aac05c2e03840fc00ea68f280bcee2cedb2bdd3e602409fa4d042b024e23ccaa207fd6645a0df15b95614944b542843ea89a190b67d0905c30683d5cf0039c8ab5b6a009f4a503ab7b177fd4e0b738bc6080cb4b3f71aba8aa2aba2d5d0f6a0747183b04631ad60222daed93c927f3eea8450573dca223c2cb706d64bd9f550767e81a12c58817a1e2a32e6c6be1cc8657b28edd5dd275ecbc28f5a120bcaed84eb0dba87fa5ee7478d9abf025a7f4ea68ce674a5ce4d99ea487b915edcc268dc0eb5337e043f5130f713d020abb7f3bef0c7c586f91e1cd21754a06cd396f26acdb1347ff9472dcad96e582e4332a008c7111c0e6e832c18d27337a6f8b94528431378dbddcfe7680f184de73510ee0c938ee62817a1a859464d1e5e98cd973fd881c2bb2351260aa7beb11730d54bb3d69a75fe6a339c8eb49dc7a4e6d2ee09c641cee9bbb96bec40064bd92500747db9750dbf341ee8b96dc5d8425146a6331dbb0c6debb75f8795e3d5e09a31875e4b4bc2db2c165a6ebd81cf392052479714e4256db8ee05f23270a4abba8bd823230a4f9fa6c3b4cec57660a392043a84a009f4f039400e7269faadb09e5e76e3e5358f3d4ee831de461f9779f0c1cf6023010707cffe3291720cc5164daf13e01d4570f6b02a78f0acd20daa196d56ddb0a8afd402eb21a500a6588fc0fcf45876f12700803a39b7d5cfda5019131e09cfc2e47898a19d26250cc60497667c1848fa3228ea73dbaae4d3513754e46201f5f00feb777bc5cbbc00d800cf1c89c17778a91779cb1b789319241e9698ac6ac9dda63eab7dc3f695ca94b5b5aa635f8b01af6de65fb5d024b80ed8798768ffe5f728d9a0bea15054e9b88c9655ecaeb05548f8f596d58fc634a9b6325b5799ebbcb13ad7134ffadef6f1445f7196906e7fcd4b8eb1aa2ed036ee704b2ef065e8e22733b1179b55b59c78906a921250c08af0623880d7ee62f1a7466b0134502a9c1c97216bd09e1e124640b7ed881f993954402fcc98a5608c59cf0795a165976749759e6625b57d2c4f5d8b35d7ef163566be4f35d0a556afd354df6534ac82ced5bb23263c088be9675aadd5b07fb72bba10d6c83425c15274ac219b5bca74eaa55515a220240f7f83b3ee0b2db4edfb590ddbf454709fd96931b5bbd635d718b477b453d25b8a2cb4baa80e61b90efd830e883f9703b1b1563b4be51012d79eaea03352074633ef34c3e1363bd7c121d3f9462a06343c01b37beefe8738b6628cf315ca89b48f2391226ae5d95345d9d879645143971e9feef12a73ee12b41ac22f96d9abde959a36e1b5020c3db78a472d28e8ce9295739ec2eda7ef1e2e7444a3363b5944cf09bcc3785c86cb4f93827f95f3af728d6c0f7da15ea2f11ad3f0a9c61221349dcdd903c4e6090f40cbdb09ba3b3a1fda0f02b01b8302a3cfc4fc0cf75b0b3f4dcfe350617b8844cba399b0a345a759145030f45a6433e62938e5917cd4661d485dac55a644421e25004975c4e2c1acd132ee382057cb204ee9e824ac7ce4bd0c3868e1936e9528f4d19b38d4b3b43bac5340327755ddbbab49749db97f7c59252221bb3a0c823b9682074c446773d8752bfb44bb10d977b452018ef7451bd1ec879e40f6ddf2672d07cc87aaba06615a41a55fb95a5de6270a021154f8840e647064a69f02977599a660003e7c9a702c118ad5ace0d5718e68c597e0bc3b9ee9fa1ce634bf7d397ee92e57bae504ffbc698c4c46cb03479469d86832e7e8eec860861a02e0bb1fb33b2180cc887a4b02b05fb201a7a609ea2ff3fca5813293602ac33fce79c57393d8f0a38caedf63ad84d5b44ec5eb82b6c883e344ffd7f0d27a2f35c262fa6197f6951d3a6f985fb2a2fc98fdc5912e281be77998ecd8f7dcdd7902b18f77bd098059a4fb1ef9cdc09bda7c1c20c4d42d83c68d166f3336335aea0a535ce41670e69569a2f5a675f06c7a4e7698ff9c0af32428ae65dc39b6202f47407838912ccdd213327b0b4be4a194c2a9f514ce9b9edb099c4125f83dbe1c39d433affe12cf2dbfb3865553dc0702f583c192591113a09ce4219990ae23337565b6be0cd695c9ba325a57a6aea3e6cb2c31838820221d99d629d37a655ab70ceb9769dd32f4faec64c8437d062935179e8e9967d205bf688f949618d70e49b11ca41c8e007e6cd12c842b0b6255b25103f252b06516e71a161bc479cec759e7f19d566ae42c8bb71f81fad738fd2e91177a300004c21f7f4d2d33de9ded19676d9ea626f4a8160f03444d146018a21f4d9cc34d4d17279f74806adee168687ddf3908fe2951cd301086e1dca383ce07d178fe41e71dfc7bd40ed504ad86a7a4735fd7700eee3fd9d9d1988bb76dd150ec67569be5fc8f193b8672eaf6aad328f9e74e3309ff1e5f872a8774c353d2b1af2b3307f79f6ceb68ccc5dbb66828f6335bcc72fec75c1d4339757bd56994fc737d99847f8fa9439543bae129e9d8d7f59683fb4ff6743486f2a7d011967750ea0c461da24723792295fc172cefe4efb974f85cbcf95e1f8d4e65e23e33bf53195b73b2d1b1d1ab0e719d26c344597e7768eb18da9c57d3d4943739f00126dee1c47759f939a73946bd44c94104e06e3b5cc060512f4eab910bfec66978a524a699360e922999d9b16110e251cc904fe29e433caef3348b8975198b3f3e28f5ab84c05e1eeffde3c3531a2c0044def4bd5fdb292e50e49ab28333fdc5058c8c51f674c847b8c09103ca1c9f000cdc0ac99b0e90e41ded9dbf201501941c4473cc81308a544bdeb10026ef7bef820f490ad0e4e0de93d00493023899dc7b17964952802707f79ec4ad989400cae46e58a3bbdfe8740351de8033c36a390829efffe822d5ebac932371fde3fee2ee9abc4fb0363dc48118f726fc93bd0e507d31d998c34bd9f4698eadc84959a47bc128776546469d9b960c19f04ae82a0f2c2ebbea7b3571e6cf7dd04ebe239f906d9f4699ceb1a11e26847f89aaf4e357c9758ddcb7934fc0008f55da8f72320120a9f728d0998e32e916223d7658255fce347772d06930a18c38d8615fc3d08c40b836d643049a86181c18021e3c722c92ae68d083eb6a85c1c8d4091b21da879af72e4c689477c011bc138747e5825a575360b9f797e9ab3e485968d0baabe891a9013a18fc667a51750a86e9c2cc26f1357041c9dba172df91536405018094251a37db2d6a81b790062e8a3396fd43b9230c42947cb966c186aeb8f30158845be7ca32a22521b86163e980aeef4550ccbfce7c2a1d725b112c91c0d0b8143e0eeb6e169a9167dcc365a50a042feef1ebda2f4d614740ae5fa503067e3197150120796e0bc9b8732814a2d47d82cf72d5651cf01f1da0c5a6f183315716e6b625b4eda0127debca257605dbfb128b84aef7e8a4aff80471ec830d2d9a6a0cd502900622f9fdacea7543977e1c1dc121e77cd2ee78e297cce9a17444d38b58fe90d77010dab24d1b79f51cfda30f2a81dd6cbe2791e555f58ca0de6ea3b1f83548e94f6f5c936e83af0f13909b6355028c126e6763b05b9b5cdf4e2b03061e264f4b5d31a5e222a19481886e2f31dde144348307cfdae847283f58e497a6f5d9b415416149226faa597333ec6a2c7a708de30da89aef5846affdb49ae240454ee05058cb1b280863cc607be510916ab6dcf316d6666e77db32ed1b5c5d23dfe078aa73aee3290e37c3ef5a73bf161cfcd5d136f9c756c25ca36ae4f3262f1845126a988a23f2dc09decd3e72a8f4f0496333814e1e434f1c69a579b179d0459ece56beadc25dcecc98b4321e884a69c0bbd4611ea8c7e7a32cff59abdaf6409ff14c1bc9b637c6b42d393842ef62d39d01a5e38b19879a689410af1860d018cf61d7842ef8e8e1ff13ff873843686f4121dc4ab1fbbb87c7ca3893162c3120e4c525247584c2ff2c600b335b8bfc0cce086179b042fe87a8c7b121a7ffa6d11547ebb530d6a2f4ade20060c5814c48364dd389fa114eaf2f0c32543044a7abdcc36e08e96a8157e80e9541b8dde76148fcf17110bb513ea09b67b0ef0f3c9dc9c374ff2ce23ae21478805c9014cd1002103a801e9c15f3d882e71677ac34319809dcfbdffa5c616d9280fa33c3c514ea723e02a7bd1ad27daf24975ee5779f99f561f69d3116dbd41884bb32ee9f8331ea21ba049078e5394576688ebb29219c3ae91eb59b3cebc408ed6f223d7bc59a0aabab79e2936eee84a7d7ba35f613f52695efd8bb0cdfbcc5a02b4b566be8e5fb0c42d4bf656ba9376bfdac366dbb8af9d4a97e4422f9e9285a3ce35bf45889b16394c0ba321588c35991b5ebf7182618d9e9326a42247a463c238fc471270a3c49e237272dafa765d54bd162e0973229fa3f44a0a5cd8b52719ad4d926c8e2ce68c0b7fb5a786cb2d980c0bdcee2e1feebcfa2696a98428333308cb74f8427c4e64bf7903cd4beac08fe245ead3dfc5d7ba67832da6dff2b97f9c810ddea13191bbbedd5f3b9a2c4a1bc105f12ef520ab5ba74229edaf9307d065a5e77f60cf65e15b16628c9d6552c10c319ddacbbe1ce47b36f3e0b244aac7b3e21c195b4f838d9a003ec4576c381da2a9d3204fa5ea7eb449ec2a2c990b6fa709006138cde7a7bc5a14fafeb7af40cef19c6f3f06cd90d409b800c59976e080b3954681a4a977c395bc7ec9099efd0aa3dbd153e77fc85b7f117ab1e0f859368dd59b27e07a867ccad1eab16fe292726461fee293cc7129ea6f3d267d7d0b34a60f20dd153555de45279a42786960ab89e488359933cf2b50602164f8bc2606c7af3a59348730b319ab04d59b6d986f70e51d099b0401adc954aea8424f065ec951f97ad216b5695a382f670955351090b1c6661c7b32f63130f3e4d9b51232e258778e98fb7813fb79d17945cd8cf45119328b414a66f1332672e27bc7fc1922102e00da7b507daa71c2f2e2ca41a652117993e2373bca933677fe540a08bde23b81f995436ab2b0ce92d75849170274e317b01bcb1b0ee1ef0e47251c6929b15812eb654694cd139f0e949348400f5372c073d91c35abf96868e686efb68f0b59c881d5bbf155daad5923e67c4fe2005ddaf20cdc3bc4b026a789a9cef8b0281ff01f3e3428998609cc8f07ddd772058da115f9a1d4994fc3307bb37eeeae47622df88fd7f7e6023f79462dfe864cb3ad7b56648c0c3a0f61d70b5493c0afd1c70f413d4bf4a3b30282d84d8a8120cbe7c3569d62109aa4ef6be30ee61e7dbc71bd79437092c99b640f117cb839f5bc2ce5c19841a09dae03f637fb1dca612af8e3743a03d061721501e8485ad9195203c1ac96ef611b265742313508a209e1148104104398c2f86b2b523fc911f6a714c2b6c30250c7fc25706b5c139c006d1836b709d7377997d50828c7520ea93c82f5789014b29a11c28a9f09e7959f168b94d0b42a200d3f496884d33514aaa4f793f831e0b9be92ff8d6cc7faa0a0f1c314aa6c4d2cd376c724f0aae2495859cbc1db93f78ebb2dab1ccf9d7f7e04e2fe6b47e562729423281279e259b12d42fd4c22ff28554398b57fd78ecc74c5aa9aacec82c6548a2e7b596533bfb57b36750a1aaaac0375d7527913a2151b63dd47408b669b2270d6c0964ead2e4eca3ff6940137081a1c8b80abe4034bdb994e34f0a24944e12328dc0e7efe2d936ade32dda2427b9f4fb644899c21514b516ddf9965a945b316505766c356e4dfc43bc0d408d6f4d8dd94030e5979797a2e183ddafcce9512ea553760238c614ddc5d581ae2555a1ac5cba07d77371d9950c590b21883eb76764ac2c909d3471bfc8eb4bf4f681c0ae671d09122fc9755bfd7c397fd75b4a60bd88b67af2fdcd1849655aab4d47d9f6dc808ca302e7d160e47577e6ec396b861fb9982adb679264c6b70de038e4b5c87004d88fd59b336e70e5ed533b5f485186ee13311436b521604f231c242fd6d4de001d85dcfa61a711eec5a19e4ac6f363a15156c1e2e0e38b5e590776658b97459c5c77b7395fb6578723999a26f53c3d0588ac363e6074f08279702915711f117fdf5b4951ed7bce3c16c401b00a991ce13f4fb152d781e1a11c3ff1c47947d50ed731881e5485cadf2a14683c2bb7142e876cbf72d449b028d566fa14911acb4f1f2038128ca29c243293447bcf2b11848e904b8a8ca9d966216a37228950485cb2e428182be048ca6eec6ebbf25d145b7ab2190dda64330dc3420bb7466e65b36846dd1572fd26390f343899b0e69e29e7f94724f6ac1cf8e5fed3816239b00f62fb7a20a4cf2be292609e0a929cf45a6dc971b98ffb89e641ba93200a534003a5880668c6f6cf1755518f31435886f548f5437ed11f23486c9707382d4988e16050a3267dd58f73edde690915b970da8a25dca7ed9dd60828824156e7075351c13fe5e093f1682aaf92d27cacdbfaf7930837b36ea1f6defb121d583ab2ff6c8e0537cfdb1d14095dceb732f446ee17b48805b338e639afb48832f3688e184f9c181ef3c8c6b138c45d069050ddad80c2f11e874fa5e64fe5d803207addc5adf8308f7d2859eec69aea0d7fa87c4513345134a7886b65408740e33981905577d5e202dad4e546a077c8b5377944a7a74825fbe20a8a3439f1957696dec7cabb59781fcbde67f57bac7d97a5efb2f25d96becbc2f759fa2e0bdf67e9bbacf567fd3e903962137c94b404933a9c2e411bbf0e7347a71207272a316e45530160ed985ea60b43f6c3e756a87fe73472a0e034b74e5cda45954872fbdcd4b4fd012c473958fc182b03f43c60273ba7b7137122361d7c6f6fd16a22d176c94a416a2f8cf66a42bef945ff9f42a73d7d05d00f0998c5d1d6ebeec506f3bd34acd9443db81ee0690b49a79874ad70accece50e1a252ec2f0cb797e14f84d3215a1b1d4d22966a6aaa282b4b357caab901180424c522632a216d727835dd420e43a263084e9c3230368a1755090f1c52389bcd46fdd448117a1bf7edc2e05d4f1d6a89432d7a15dc13febc8cb419459696cc829682168396a4960f77540cd3cc09cf495e7adc69acf950c069fa3a2c753cd529b15b1fcf253298ddb7836389d77601d4d64b835aba3f8bcbe00896268111c33bc23d6b8567f50549007fb59c6872b3d68846a7910ac27e9c12c54493b68476fa3bec344bc6754e35dffb639256cfd84ea46b4e0c4223323a0842917f0fb9022d37ea97d6af849f4020d77fa3f0e2ca9e574e484061b551155884a2c3c6aa6e71abb75495a7e7481cb3b5d970186665cd2bfa8fcc5afc005a293637fec4908a5850dd4f831ca244790002928484246ad5259ddad0f2c6c447f0628e06e6f5f83896e4a9cb07799d6c5001f2492c295c1fc9f6cf06eeb75063351cc5ead4927474ba6a07e852b2fbad9c478a2a4cb00e420dd38a97d816b14e28aa77a766bd3270cb3bbfd394d3baf62bb60776404a04f4a900ab353e8be294d6a9fd6c4e3dcebb0ac8dfdd009141802e6289aaebcdde61cca6918d7b4cca64d1c7b77390568a2e30025b03beb82a1461e1808a5048e5aa1622de71daa9f31768106feceaf1f00bd205d80f1e24a79ea5dc4bd0da17ef129ecde24ab85f1b708ab5bd54e3b1fd1cb3560a2030f7a7c08926ed9fc5cd106e6b5715083b1c48f28af5131116dd4ae46c8ecc12b19953f3a835129deb96fe61302b976832cb02d6b8dc71be556b0f86ed27aecb4ab7d19b105e6c666ed3f7c3707a581152a773cc31c6ed86aa30a80b8ef39a38160c58b34df427c0fdcb2d2ddf61fa9cc52fa0566c821c903839d11dd36df67e4f1b57f44f64516d5376442b904f7e2b3317ebee7ee06bbcb21f61886a38dd512030c4aff30c8a9160439168cdb1a358c155178580cd6ef4335c142dd8fa88c892fd116015c09ea031306c9821f62aafaae96a7fabbbd8600a17c2814b67bc01fa1ac50d4767ce290cfcdfd713111c414078b7526f5993a3cafdf66453680fd8cb4f393f32f8a1e7d33a0c3ec657ca5862a5badf9c925b9bada08f81f4b1bdbb792b29fb3ec3e239cb959a42140750ff064385ada9da70513a3b005d84c5bfe004c961a881a28a9c32ddb1a4c0df1ac67581f4b2992a615f819ea3b5828ff9a5dee9a0230cab6c967a4f0088e9ae5b72c2b203ba75ce1ca7e95ca7749c8fcee050f4a8ca6e6cb8d453cb68626a0665867811d3376b623a6f45743cd9f1c55e8d9b934af202ffadeb17389e2252b0f82406cbed18a138b150b2cbf1d7f6f6b66f4c1f02b6d9f6b60597ed90f676db066cd423e4f111dc03b764867e6f3b28189c094bae9b533f67e22e958b51078d7071808abe2d1cbe79f80057528ecbd0f3856c4ab5933ee243a4d328fc8906dff4dd42d0eb85c7eac78d1c7cebd88997e1b1a67d392f94665968d8f7f04bad88abb52330cbf5eab44529ae58055dcd6fef82d7ab124dfcddd2da7c805927a48e73732863a05d9f2cd8078e1adfca11f93b53562296ea67aa420235940e008da02c77d83802fb9f11cba499114f1fcf5de9d720b6f1b0123d1c9ac24c78d1d906b76049e8306bf500133d3567136049e9913ebc4892c35024e47588156b0ae429da2da846a40b65123be1482a8ca9f89067f5b5b34894f8edea425924b0dbc7c263ab5ce2190554cb822b76a6dbc963ff06cbd072ec62b888206bfe617d44b5c18c5d569e2c2140b9cdbd9fc858aabbf446c0d58a682d6bed6095170ebe9cde1a2dfc8dc6c809aff550ab107411a9b0d0730c435f7094ed5dc8fb83bc27a1b901d4941524054bd801d28eb3aa992c750226ca2b4f53dba10fec8295273fddd966221853bcbe708bab255882ec3769e85685f81295b4094d1c2d31c45c582da5af1d4bd90723d6f93473552e19c16bc1f7fe27963953739d88cd6998c9c71c519c8971a737427c06f3f94956d78b882333ba8680b4f2580230fc092520d7832b3d23113329829b82891b15543126829e2a322243e756e18632aeaae2327970d07acc3600e8b9fa2b38a241210d0c1f2b7563983ffbb754361078adefd2773a0520b5032abbc56851e8f587b36e9f3198aba8d03c51ee48cef544363394050afdc2b53fe7d6755170bc4b86caf45f462024e6493eef1947a7420c5cce593a45c8fff3300c4f7cf876688a7967356c6c4f0d1f4f12fae5212eca4c742ea3bbb5297a894458c222047014bc8640d6f7ceafeba977b5a1c28b50c4e6f1943ac253e7615f89be934719f66129f7c3d5042dcc38c989c7469eee0477a0dff55f0a833764e5f722eec1f40e45445fa477194e01644b5fe15bd4ac59981fea4813468b025e516d541e3c48c5cfa36bef4a6de498adfca0363175f63bcb8fdd713e4236bd8f5f16f5075ad82dbff6f73ede5f6e355437e4b99717e58021b34989b86a65c10a1eb24f0fc89eaf733e77ef24f80c1827a92b72652966c3cf6c95884e5039d2c26fd2b069c905c362e2ddd4e2f6adbbc99ba7a024e34ca1c25f141ce20a3f846dc0c3f07ae7e13ca9305f7fddb5c50684eab03e930fd9ce35464bf468e086865a51b0bd36f545e3487cc9602b69da7b2c26dda052847937aaa53d552a318734c88fc5d4b70a13d52c653dcdd28b5c02f1454a3d6b6e04606150ed77bca8614c80c8e933c2cf4d52e8690d5664155cc147cf3dca49bfad8a0ca10ae969b720439732784257089ebda73979c03356b36e0f26d872f1c16cf2b82ded0b7ca23c52c00f61a5f650df4188fad2f2abe1972ed6492fc7edf935c311f9962ad4e1f31384d495ea9072ef86307003c0502357f95b39fb3398d9c962f50053e4c32a9f950b035ee5d206231e0183a415b424596846480d4ce72339205224b25e25a558235ee86887866805c2f48b5de450eae680a7a47d167895a0c84a5510dd68312a1270395263faed76a3149c6fd66a36b870e94f6ae0d5be3afe884e8537f044a74a3c8750f5cbeead4bd550be86ec887b27298bc02914ee8755dd334ee5b65524f95817a9b15b7e7d2f4efc7ccecb61649738dfb0e7a837916996af01420e0094e971f1f7f2ee9e326037ba5a10edd2e886839a2b984631aaff8a5a344ed46ef48f5f93d30eeb6336d0a673290b5e8a907c9274ae55dfe6d6bbe4d804a098a9d30389a824e18ca6db729107275e6b51ab16dac451c7b9f699a203c6d055d138312e20be1baf021375deeacf7248bf34b57803dcd8022480d5841ebd3d1bf3c086153d6ea9248944f199fdc6e24cf923570df05452c7bb66dbe7afb67a38b9e3ece5d3abcf0e3088478f631cfd94dc783471202577019361aec8180e1ae821ab6c23ccada2139d787b9e620900829287d9ade7960196318143e11a44ef4ddbde0f88b96bebdd77edfa81bc8cc77104b8c4cc069d2e0db9a1bcd24d5405cf5de7f74e29128217ff5c20ec06a7eb5cd7e9ede30f9cd40e656e159fef2b502270590c6a49a3e75c6914f6c0b647f8514827e6229fca02ed5d8e857e9ac5db74e92c3e777214512a0945b9665a29e937d4bfaa8d312046e733345c88217247aa21d3291f169e2adb2f2543772ca79a275263497aeb07a37aefcd339f48f7c33c4a37dc25a830e050c16f131d7b2641cf0cd70e0abb4e359b740ceffaf1b202f5eb2d49e7178f8e0e61e9a64825e2dac5a1a0ed333073a99b9613305a8f456ec41780f4d5021db01d52a9a7254f81cd00a3cc8694c367c0052c981744e26a13770db82b906103d79f7be8535a57412555914c188b0f2f828f4f5e40b1a8d618b37f3034fbd4f2998bad8b0190aa5191da84a58b1d5c30688bf797c6cceb4145eb0d358d29cdf4af616f8900d3ff2deac9dea2604a5ad20686ace4294ecef0f9ab47863a51ca7503e3c5cb880d21c54c1fe3c917e10d39733ef02373aa44259bee775de0d42639390ec298dbb97ce5015330c070d2b0c69f49390173b4483f6661696ed53884ad91c5faf1e894cff19ea6849be1df0336559704598e89282d7b28e8eb10fbff8e73a78817ebee5fa3169e9efaf02840cc9df0de1786a52ab778d66ed3eac92c4319dec4ecac6ab7a28eaec6fd82a750c1ee2cd9047987d43536282f27a2272507009904c99d83977824d3f07ff448d15cb7f707a8d8ef3c6610b33e8a380a5ea718138974ae80485218e754a21f34e9dc977c7cbabb28e243e1bf957bca2334278a68f06ccdb1043c99965f26691a8c8ecc98f25e50ab9399ebfdc3716840e036ce027cc3e9685c9cdf2c9ee51de72aafaf513631eb98b50f8063a36f60cff1c5e1387be53d17894c5351ebdea86af28954a50d8bf59ebfca75825a594f031168efc0afed18e19c85ad81e588b9799fafe904d1356026bbbd59f56ee00dc6842e362cad2d27e1bbc956f747cfa01bbc36f8bec609b178aa962d86b7c54455a3c4e65ad8d52aa45d06c41329f8d082349e088eebfef028d7205ba08c5233c32ad66823c339c7532fc487ccf229b4df3f79c870c6b9fae56d583e7a9ef213bb06b531d8a5f9a86eb4dab4bd911fe805a2b9552d15953bb6bedc43dde253282ff7781bf030863ead0fa2a3ed54b96d317ae44949efb03e8e33f82a19b6b5d3c91a8f9aaef3336af892c312abbbb73469c67b161e7292c5978948f84a4f20d1e42a19960b30c3cc1eaf1d691867772987370d987a46236206da0e6f497a935d205fb4a1d41ba07fdf0fd58fe0ea9360bdccb84858186b1482fe2217a6680a45373845af1be69ee2c86d0b62ee9491abc5cf5ca62bd365bb86fd12fc4cfeff7df7c987f6c1276d2fd81fbe26439fb9203dbf1611f7bf7dfbc047f7e98b307b17b311cc42f26b9ec0d8c548cf0f8de04590948c6ebf301af01ccfe5c0d1c5fe40eb603422133af4fe6178f8476e8cc8720e5829e33ff6e9339ff9e8e9dcadc3b6b099fc38e7f168677f94f4851212c7b7f3e03511d353d2abd88a515d9167019573e84abbddc804dad78582d760c9cb208871dd1ced7392dce3fc5c978a5c3bed683bd76848677643201233110c09c3be38e0cba435c215c2878f13b45dcf282c5c63ad91c8a965a87c2f17ea077f59330ea1ca456250750f896894b0b441bc2a1aad0170af4e984fc79f8b14e830e4a618f80632c262b2b744201ebcee0fb8c8ff6d1447a90c0cfe8ef97f48d035a1abf58cba7cae71afc712f74d5f4c5826b17489713fc4b21844147282c61eb12e6609603d7b26efffdb4134505a707c23cc1e064a432242cf34184a73519e222aac47425bd79a3bd934339736ad59c7455d9673ff8e7b28519bf9332e054e42e7fbc7aa66f6ae8dd39cbdf015b6ede186a76d3c9f86716a283506393275f4512e9fb15443f70d37fd1e7f0b3660517027c152590921a7ed9b8671322b102ee19e6256db2a22a5acc0b83e946e4ef6a007f570970048c717546ec8a751efa61ad66693f208f932f48279440650021ecf1f04913fdcf88a20ca25c3cf17edb7adcf79a512fc2af74aecd8ea7bd03cb788e41b86a4e7d7c10ea40021eaa6be8c512fe13f26623735430bccfbb7e0bc98c5481078cdc6b73bd72e8c3d7fd26fed2c61a6c47b923df00c66a23e0faa3d49e88580eec8a5d4fc34772468903cb8786776850ad020805571b3a8b3ca371a3c5c12f8a059880219391f5742c2c0ff7c13fae74ef3ecfd5f1dae6dde5175a00e157cb14f1259bfcb466d5e979fcb3527cab04aea88148044e75e07b78c18689e81a7cf2dd2caf878c02a4be02280fa8c8d61d0e1f2401595662556e99148f1d9a57a96477ff8aa6a685aa51d27350cf4ac39d7e40748152c9340d53dd75e4420a6bb399da81dce0e08dd4e91c698044d5cac1faf2e06571a605998b7df3af0d5fd842f792f817eb8c60cb573def5c1dee760eea0138152724e7f9e373d3a592cd3a4ec0d8a1c6333ba8da2f2d4fdf40b7e5c689e09142eb8662ed3479a8ee20c4580f85eaa1aa9ae538529b3dc95ea7bc0ce66679b2bcf921912e8c95ce56db759c29a3cb758320c299f86f74e30336dd3a617ca441b567405e39e19e7f985b08b8581f7946a63341c33d78b459a014e443c0a718d5859bd51f7387399d70c03dd9a30df9a8a5ba960d81b54be24831ed71059728b810cbdb8f90a024877dac226888f430abb2a59637be1202be834f14ca9e3c339a9c277d6af09471382f6708153a4eeed1f2c821c287ca702ce61473619752cc772be0dfe14f703dce621d5e479284b114afd4bf15f4a746a7a8c669008c0fb38a1b3f3aba46d532595061510016c912c3828f060b5808d4cff583484af6f534a0fadc656d54389bc6843b1345a20c8aec6686b36df2ff87e51e127f3fd3ab7e4aa50809825298b9fa3e06ce4e4c9a21f5207259ee29d9045250dbd07dbf4e5af9122490fd5a130dc4cf78463c6f315a84f2786cb5530ebf02102fb0bb30103d49f38bfa40fce497b3c08bdb5b44c6073b25ea61ed8a8467b6ea385e6a7e90a695d55967ea4acc3a8eb405cad7224406731c361a2d1b3ffb1afcffaec637f901864a9923fee4877e466915e87bc505633c1692948385f0059bd290f2c785c5e0bb1a8009d507365eb5e1b2a219bb109a59af833c9a009621d174c9b6229d2a06d5a43088a182120026539ec986950fa53b4ebe090247c2ab1ccae74739a7d9ecadb675bebbd4ffe8b781ee4beaf41eac7d8b072e38efb010172187eb1f483378720c1d82564fb322634b23d35798874ec10988e7851f6fd8dbd45c9f5114b1edae0bf6f0b88cde1475e8877c4e4894c8526b64617dc1162a135a08019fa00ffed1fdca347b9a6da90b99da0079bed41a883272a8c0abaf4ef988bbf2347ae6b823a6d03d65a54d0e3555e0a134c6e499d71cfcbf630ff4419872b685cf7f657680efd10582c751f3a1e1f270873e8177217490fddc4e69910c71b17c64f0976aed206a68e595adb5d7810db0c2252c6b1810000681d9059be4a8896e392e922f7cba7d270b9ef05f8a30633b70a834d8cb9b6860110641ff06065af4e70e490b209da80d7366b5f999f49ec6558010165b7127a85f2edfe33e5cd814e4823903be2475d489c56f87fd7c53d59e211c8d8a3010f823e400cda642123118cbfd8a2f3b0b650671287029684051d28527346086d2d072e8ca5f6b14dbeb9f9c4f80f66875db8a467d6824e1f0557e01cf0b0313b6657474f12af743bc0cfc776162a4d3433e67cfced89b897bda0f233f26c4276c0735745dd03fb847abcd079c7cac4cd873b724408c0216359f292280155c1b5709b49689731d1d895aad28c8a18fb1c857a86c65b8ce77ee6b99030e1ff88c1290c9faaabc870e330e11def1f2f33e3c862c3d3240fe56d1647706b3c7d903b1a10a83724f1bc7105c44002dcec783d6c7151611406929b551c9177c27efe178551788c5de29d33ee0c6edf37752dc12da05a48a20bb226d121c514279de45add3c151bbd04192db74884a9ab48a9b1791a02ebac02f0703fe80f6e065253a1ed43b7d2686cdb626796605c9fc0961718e8cfac2e612d5d0a0bb0e4294f9918fddcc25225b24a6477630ac4a6adb06c2a659c17e40f5a0902e3036977ca800f3c95462de81284ca4ea3de78beda931d21abc61ad86d53c9c442c5cbfe39da2d37bb3841204f030ad834edaa7f53945340d8bd947e558113d7eec970d76ee5a752e87aae594e9702ea64d390a739a33fd5e9b0264250b396da6f853b41197940d5d308db7984db9283f1671d334e2b84a6c5c59a6b0f3ce48b9cc13217bc7b4ef2836fd5c2a727877785e37b14d8c5a2d1938dfe9f56eec04751c9223ca0b5cb4bf4b14d7b4006c9f889e54896282c2d21ec40e840ecfdec458835f8bd9f42a2b181b356580dec7c385667130aa0928f07ce70a03efb0f7ea68654698a05d3a7fce1a38a92714f2bb7f40f80c73aef96c09fcc2fb1589eb9a250cb7054c4d0bb6dbb13080a7c1bd106af8042dda52c651fe314fab1dbb00b57a5719f90c0f3cd00b82fd0c36a823912d0ceca41a0a87761ca6d2e4ccc01d0ad324dc2e7df0cffc74d85276b2ace8c12342d8b942434b7839862b0b240c74fd7642bb0bc02a5bd887af78914acd07a52cc5bae8757c6657949cd7d3aa8ac45d35d306c482fc54b375b6d293046391dab7a30ec446c69309c68f4b7a708c520f2c5478cb7a734d496bbc242a547969e2fe5df8661e604ac4c6e43f0fe7ffae186abd2d8793203fca7f555348b47333b731d9e5f700204fa2ad2cada192d142cff6f77d37367b801183537ea065847420fa80e6b14050df2a87bbeb10b119d30cc87134edb89a13a9b200a05dcecc351e0b049b862490b845fb0d14cad0837d40347a3b2808a3ec1b6f93d0870d47f76a83b7dda7c9ca3d825c461e7fdf068d8bd2a5d7a759fca1ef30c06293664a73a48e2a0ca82867c18349475e679efe0c9dc1bfdfc99cb8a58e8cfa6e4130957edcd89107959f995929864811dea36ae6772e34a61346695cecad7ea371f37c0290ced62c62a4a3918e0ff0a9c2e140ef566be2103d4953a5228818871107ba2cc5a4437cd254938c0c410f129516deae3b03df04d3e60a1e86887031ff42c9c090d478a812c5148c054ef8aab9d34f558264848a7bd2f7796a3cc0d7f04d851c3bffd0acf10a4a0b853e73a3d5509fc3822067742a26a86f2c9e7aed7092cdf74c8d2cb2abb13e3c72f4dc6911b596604887ff689e671b0e6d887a80a0002ada40a62e6b49246fd548e1bc8ee9f5d4d00ae602998eefb71d6ed72178b08c8f3d897dfa4ecd7505f6cd1970b29a0f2a4af3073b10a0b9a6543059fa180b6f91e05e8d413a0f6257d24a255acfe1d955e18f35b1722ae951b9528959fa386fc35af67032652bf0ca0e788877f422306deb687a1a4877da9544c41343004d40a9d422c0b2cc7101ba1b0e3adbd0a8a6227de26bb9210c126c43bdda2edb480ebcbd4186c4976ad4e90011877be0435870a757703deb1caa3b4ddfd783377ac50f8594ae7af3942419501364b9182e640dfcdf577b74e499a1d11c5fffe376b033dc4dd22b03b34601c42b6c253d873396d9c421134701350be45363002cbecc1b7ffeab911211e5092c1082efc824b51b2a1a1a7b1260d44f59a9ba05ed6d39d883f2043ef15413c7cf58844b60ce94409816367b540f0ee4f656a94e666d63b3417d72836576f671f427a4f867ee8cd115b9f770288cfbc832d6f3cf882ff0cc9bb4e85cc40c26d4478e34d500fba9554b82e2351200b362509bfa8eaedaaf03a8236cb1f6648301bebaa430a90121b4cee88570838d2b9cda10b425179c4ac12435f1533c2db071472566d09b88c473b33bb9da65bd777738adc03b81a0d49798e773bdf03b1251802457b5625886bb292e2eab56582bc37aa503057746fd4bb3459f889bcf03313855d0367f95ff445fc07fa669f4b30ea66713077b10292e6a67b03dbef099a8aee53dd056cb62c76550e9b70110790651aa1b0b5297b5feb35cf38c3126b317c98d17031606fef1a1d9e8e117f2549e99a78677268b49c592b03c298c376705a5207ab4094afc44e6ef6036a1588bb593fb7f00de2d308002ae6447713d2693b9dfad5b8296802181a864f14ebe4ddb622373169d373bac601652ada8544a40be0c9ba37af1daf10a47459249c587eda54e22f8a0a4791c1bfe80fbc92d50d5941a38a19ed1e24fc2b56196c2466fe0ae8d2f0c1790a509a21019da3497b9ea518e05e1bd796d0dd2b95d2d2d28d37482cd0f5709507016035554d2d2394732a14e2e444dcf00ec968bdee5b9a4ea2ccb24ca051262ea65ecb9ab36b9bba1ebf3ee99070074f3fadc0f234faaeeab49af5ef48ff743daf30eeb58dd6297d82db734ab4ac460986015b6eac5616681ac9c15ef527e74ab294c0fa921cffd0b3ae004772523303d22fa433cde708a07200c466d7932af550dbe01794daf2075b0e931d7b94d552500415361b41e602c20a00051b27183f43fad79fa3454eedaaf213e18e0d60f5ae1c0c774fc0085cc9597ee03a4538fcc0fe23af43c836b2bd69583f3badd99d9684d63ea38d24c37e862d57830ff7c655d544dc40fa155a01cef294ccd4186ac986a9351f8a13fbeb2308e8407b50e49e14a11dd211852bb823773f77ed595c660883898ad87f194f0541125ad59281cce757775dac1c5810478071766decf58302369c320da8f612572f1b74780c6b6ea6401660bf1b091e89bfa0a3b1cbda740fb4d8e8586030f20f1d208f37d103688f4ae672b9e255e95c16906a0a7261f47632b7254991df1b3f46293cf2ca1f19609db578942c1935309ad9b7af388558a1cf85aec93e1fb6e67d1dd1ee0400bf6f708378d149faebb74ccdfa95e9fee0c17bf24cea5ff57af112af9a2e3ca44b0d1a8722b42c886a7f06d5aed343fc0b275c0e6aef3b0b296f2f4a00e22357c6c1078770fd1498db930d1c18a0e7e46eb23d8148c05a68f36f739cb263a5f1daa0161feeb87a03bea9c9d72de5b6a4435ca8c7e0b591532b8c6300abeb927b9d2a91ab4686aa6f5a072a0858a575aa5285430473462808e0146c6d2081a7a0d9c46d8f4b4be14ae701a9b8923f1ea6320f9396a350b07ec405dcdba2119c310528bdeef162dc1e7c58cb5688c778cc01c76f7f94ea5936f3606ad2e5d2f8a640b89d684c520724e9d8290537b59471b3e02b0d887ce7864c94fe7ff32205588b7cc611d1410511f87b3e85ee315515448e75f3002ac93cb765f6611c4507795e59f666106e1ba7da4419e5a4a7e4ab21dc94282c036a1473e526bcd913bc400b1faa9af4f70af1951c19a81650a727e24c66af06a53a4abd6cbc05706b50046c5075064a5fefee33b4d638c9faddc22bcc9c4c43e80d8a4fe7ef92362f46e650e739c6afcb29b0a5d11d203e224625ce2353ea48a76a842e12dfe5150ef678da9cd22d6c2443a25c9b0781ae10fb7fd187ae65891eb9fe8917063e0f4f0f96dd7aeb14b1834e077ab3a1c15c7966abf8505ee78c526ea68cd05b2d52c1fb39b46875947a026b1a89cce6953d2f60370f6285bbaf775949075d01b8b1f05d0054384bf9e85d4a305293a610a2a345e95dacad1782f9465c5f5b6b7589a8619348a5cc5cc7772432aa79abe8a9b8caee515b4837e75f8724e0706a658a38390027e5500fc60c2f1ef884f77208b3a19a9b47c16e21ad7d922dfee062be5044336871de4c985a943dc2085b2ebef9453ee91738247aaa4c2e2c3081f0398b88b57ae7b6fae4c8a71e21b7fe6b75ed5803b339a8c5477f952d046222e08bc2855d911eba73620d1775a327fd1498e0de1f6f81bb892f0fd16a10c578bf8547c17ac7300d4d73a8715e70b9c5fc87a8f604a54e0cee58cd5168ac7fb0575c44d75706b922740095d1dae1164ee799529a232ebb98772643a0b559dffe3a75200abaeaad936f54fff8650b711f5b1e14ffc1d06dcca2c70cbbc26404acccf153f53d335603f87c99013f7bf1879dd6ce30f83d39917526d79b79bb6c47346a604f850fa201233bcac232575ffa5a91cbb68d9db1bc15d7f60c30782e87d10d60cec840080c7a85816f08d1354fb264a98864e5240b9c1982a830835f4c17bd1f7fa5ddd772ab9821cf192431b79579a6bd33efe612d5300670d27c12863b9fbe17d9afb58bb79a71f3b010c30f57c5549df74186345be1f6e95f55b41ae1dfeb663e56c2eb81f908c7f812dc3fd1e3da7a358a42710c300810cf71465e9d03e2524c9ec418b2fb71a36c0301c6b8aa1980084ce9a21f747f5668d069cf0bcd7f3f7ba5546865db599d5ece889f4ad453ba0efea6faf9ec59f66f46a40e0b6d2cc4637bca5ef2f9547ecf2dd2988bea43b3628cc4fabb196193e80c696227b554244400c843e0d7ce986f8c7bc08108dccba03b581924406bcace1500229abe52395dca8d6379274dbe0fbe765207d9a4da6351fdfa5677996ab1ecf7ad18e6dfd5afc39899317d76f26aa7f31c9ebd8d15f2792cb72c44e85f2e8b43b64098a78ed399d1880ef32b88e5be629de0b3079e5f331193413d6f39cf00842f57810f34e4e42e4919290a58a29b34cf73f559614cee23a7b41d56fc77e7f1dc82d65b38676eff03a5e51970637e4f6c258069a8dd9029b7b6ce73e71a31e53ffd51b8f5e896ba3bb706605d7981485984aaea9f358452209b109663824a4e785c60285493f9fd1b134275a7b8ee3459e1fb70404af731fc9f1d63e00de31c1677b28ead6229ae72c04977af6af4a49e21c07c83a4a8c0a939872ca846b149a2fbc494f9abd82fb9ff68e27f69b9b8b939f819f613dc1b8b6ebae864381573ca0afa1d8dc0d4f71b3c6efb3048dd6b14e99253079b9e0892741531993e2e34a8b1e93f50d4b5fba3601ce9c1ea3a8bead477a77366ff03a0e9b2bb994ab70e36faddd3edd6ef02bc8fbcce4d16c6bcca063152814f2ea4365e8c46876791b2104d9de964e18c7be25cd572a4315504d8cd9b1f5e986ad9e2eb059169ce0402e0af316cc4fd6caf322401f0b8396928866bbd13750362194cc7d659a312695057bda66019b60f872752b99387647376e9d368060cb0ce8ce1cfb0ffcb88f9a15fca45d7d31ae15279163513de85d1d724c716196fa561de9a7ae22953f49876311234c15e6904c4af0687a9192270211e4367c27d5551c0b2f2f51a2a64294d8998f36f84f6417541b72626615e1b159c714541078081fbe2fcb87d156be6ddd45d9d227c9d4b52a31995e4265a8337e547af430081f74f6dbd549928f36bc59756fa0a8d35568b6fab4933ca9a6c19a08c87713eaf2fd3cbf7a49f31fcd1d9f9d19b2fdf7efefac95b4f5f7ffdf5ad674fbf7ffeeac9974fbf7ffee6a9b7cfbf7ffaeab9d7cfbf3ff50eb89d84a4843059c2b0d78ea2dad6aab556f34fb03d5641673d2708a9bc916edd8d122ff2a39662943f2f8a24fda3471372785778b910b27f86837d7bd9aab555d88c38d5429a511a5b33398ac8c5a72082dec3103141d6af797711c23ba44a2359fdf86ac9478456f273b4fc7edf495e7510941dd8879474193c84e5982b9fbec1952eee33e585c26003d5f54256271f728fd8d86f321b53568830fcf298ca9872770d87f4660893ceb6ee069337903f8c5b8cbd51b78f4500cb8b2995795ef0744ddede846edc0b5277e3f65863125297e93e895f76beffe29ad745c215a3f27eb7707ddbeb00f8399da1faf168c2e124d97a03260ed63475f1d220a216d73a56d58e3db9dad0ea19af42a4b2add9bc8f16f96567feecba01774bad856fb2e027d7f5a11d3f04dd50f46614ec02978eb0afe35f10c3a9895a9844c1505264a0a0917fc1036933d4383966232aec8604b87945d8fda8d221403bbc21c589923fd53240f5f9bb0f09a370d5fb5088c761c701bc818ba5a77adedbbe5c4673cbb47a67bd1d45bb6fabad644253167311653468a7b341830e096ab010750a3f7acb2cb814286243eb389ad272d205710f85a1901d9a50c92d2b582d1ec47dcd4af7f4756832e34676c69e52c20fb62c0db6b2ef516d6adc8b871020423eba3853ec7a58a385cb51dd7e1b52528bbb509ba112eb03bf8bac4ce7fb22b20e9d61973c8170797d7b48a1ab73647593b4ce06e42be7ec72cd020e8f5405290c7a3fc9e4c023e04feaf7f0b0b570cd2e08a604159e0da83ca10a8ce5ffbfd5958f7542c57ace6e87e339fbb2eb1e96f1a64df20c9021f9f040fe5101121610be13c8fb7b5213a47adc887aabba27fbfbc2630639a1d55912642e6ce64ddfa74592cf32f7f100695941c8e214853a5b85a60f38c74e483d0f1a280b531c48cafbf712c1cbf954277026a52d9041371e3063da71bb4c483641dd7764c1e4bd2010e44e5eda836949782e559d6b86e79a6067278e1f960fe9fd1389c901e9c992d2ef6bbc735ae30b3d95a91759efb156c83725b7fb54eb972b677781a850e31a43bfcd037080ef0aac175d50aafdaac5c5177d27b7be32965aa80493f593c448695356a3f49c2cc649f18d3b8ee2ea5a71df60376fe615fbe91222e762292508b98f5fa32c1789aad4fa6d0b112ce1b22629f47c10ec11aaa8a72c190c36aa46a50f7353c4c194d18d4a4a09a927c022e7e9be763d8730a93ab73901ce1bcc381b1b047946ca28e7dd9e1a81fe60e32c1a5af45c8244770afcfd0a7ef4074bade1cb2f73ebaf58271d0b6128274b4d92cdfb6a1c28cb3a20d4726c458176d3523c7d6fd3f47ced0db8e5da370616050f6291b9cc1e928c97968f4c66e6531ab89a94688df6c9503d8f960f7f80bdeb107e114aa8e09a8f99c7e58a748d0959f2d10df7df0426c124de734f0ce9b72e91d0dc265fee3c17f88d4e350668a841ce51043fd39f860088e9047e3f1622f9000ea1f72b465adfe6b3bcff8e10ae54b8a4e2aaee0c42802c5e875900b0f9887f4503175607b51471816bf3f4636e1eede6077061ec57543e1a0227c9e62ee2c5cf5ccc201afdf15214187084308dc0992c37bc680ddb083159ddf37161a175d285d0107aa378ba84460cae3e88866634109db3e82442dc7ed5f38f37c8e680a5dec6575de0341a7cadfe9bef33b9bd6e4e70d08c34d8a6edc452d4019b00bd118a56e9d55fa650834f6a9e83502b88d9184053ed7bcbcf436332025ee00120ab3e3c2f317cb02654abb7817f16f0aaf7df6edb5c7b39b93b1f32c19192d61f2daea4c59fa15483f143467a9b5c907434bf177d0649ad8b1369dc8b12f9290aa26ac8944024b52c38f377ac207f702c1b955a6c933fa54dbef5d609a181ed91c4d91bc00af598fc45492cf8ce7e94f790647501730fe6f2842e7b637df61865f08d926be7a95c93d778502a705abf9e474e9967d59074c92e9a5855941d7b91e88d1921c100256ccc3a7e01fd06c35556eaed5aedc544fde5d9532184d11919a8c149c25fd37a54ec808f6c30b6ef3a067efd6076d8ff0bcf7b2b385277119e0e64cf8d92970f8964e36e7caf1b1721cbf09c61cb4364f559582ed162d684e8f2d6a23b68146aa0bdc0038f89b6c05dc604547b527530bce3731a4587803f77daacecca574ebf59a2a45e8623fd55efc920ab93356a83c5eeb0add0925b405c3faff045af70e3d635fa86bb3bf8820e653254ca12009976d36d99fe2a1e6bd09930e040a49beb41e6f93e157f4e32a63e95741a262892be9cac7d9dd98d0c18d89541cae5e2d49efb083802a568c337345888f05cb2cedf61cdbcaf947e5f75caa9159bb14eb23ec9af1f2e6cd31eb33e4fd6cb40f1cab32383c0020a8875a12cf7a14165f9d26b72ca37b30b329aed9ac6a6c113071cc3b554964a90f51147ff99bd0f9ad7e2733556bffcbce549f9b61487d50a037eb0525dc79880ce62760ce9c04d8818530a21b66de3adf4cead08105dcc240e3d8177de4f2f3de529c011cc1009dda8da100fea22121e1cedb67a529c6ce9e622d09f5a57f21827e2d11a9a867338a89290fb7920f5a1c6b74689100a72a7041f70b908f2b710f91d8c51d10c2c32f618b29add0633b1ce1df9ccb68f96bbe7a165117062059aab5952149ed5d761f19a49e042cde7e1c5c463f4207c59923883d3da4f6c54e157a3e2a726408a879d8e55cb6d98866abad44ae46537f054806f37f9f9c9e52a182a840e963545a58c73af0d448a751a70a35d55a768c62abc547cfad18dfe5a308717d5395c111df3bbb801c304817fa84005b4a8892691bdabbb6398e23d7aaa55ab5a075eabfadd4c6d496558e074cdd8287a05386c1749168efa3635ac18e425923593eece5c682cb8443e36c2f9bb5b1e725801803ec047313585f852d90ca56e64a10602b615f130c6e742dedf2143907b8c2307a04dc209745e06043ea4e75bb579cfee16cfc33c08d8055f3f17422e6043ac447183e185080426e60a62da5daf6952b7ecf543ea7ce80353161204d316e62d84192c0b891e883c11e8e7b5a4bbb3d413cd834814f448daea0cb52c00b62ce0b0e46455b2b988e5ab8c3ee9aa407f26e52d2ce1d54806cecbac63ec1d581a2bae749426665292a4f9d53800ff36ec2f666e65136250d3f3fa4d660b03d407e37e13ab743ebcf86b9092323ba166549af3831312a0fd00d4e3e2f83ea90ff058e117987342f04320cc214608b7388f87747d9404bf444f05e2e2c5d498e68720bffa60d37358f42b4d854e7ebc473012f9fe0a19a41c2fb044a22693c7fea0f8cdd293cc63688cf0c1514d18e80ec0f53519ef68ba6076419a91613e00a19acaf9df9645da054393604c5d59b63da98e6277c8334fe8217b8048043a09aabd0be35a08be89306fda4063f728fe7006373cfedc4a5b8a614d24841d1c93d430d8b24ee870b2978f136672d727b00c3ef7a05f820491185ad04ebbf821aeb462e13a44a7a9ca6c393c4ed974156f5ed96d16ef5b2e79d91fbbeff1079be77108b80e0b1e31b0740dbfb2bcc1411400e1b8b700de76fc254d428103b98fd735fe6f4a6f3bf3611afd04efcaef00be5eb588def6900f9e483e44e2dafd47ae708917b7583727ee7c342dc676e9e67934b59f73eabfc7366055e1b5a527a9e7990858297fe55cdfc7e9eaa9e4b8ed4eedcb3aa136e6dabeb48512b92dcf1e6e0e06683bc5d4e416a46cbebb4c74101116b758865cc3eb3219012b5cd4215392684e6641e6e762ca927713aa1bfd436067c86cad18aa58f1e9e44668de47cd79fd50ef642548d747c0a79aa7e3113ed0ad8be2bf42dc6d233634c6d441ab18a917036e27b1429e29d00a76da945dd3900c31f2e4652d6a51165852e73662291117dcf036f50d1cce5f82beea3b80105c65537beb6b0f834c64c865b0cbaaf01f8802434779f79d84560c644630948dec8364725fbb63102128df2dba9558562b30f79e46416c54dc93d9a6415f9fab5907093b2bbccfea6fe1862f7c9e940d246bd8548b93ffb46e7de30d53c1fe50864ffd896d909083176852c4f1c0099c476cf89aee3e88fae4b3fa69bf777c4bd0605ee919408eae02fe8c6c3efc053c615542a6fb5616d18f3cf4896d30d45b25f7124d0f74cbf779eac403f641b410d3f0b9198363e70c4aad5f1af783224da30e3eea2da2f3b960a2863214cbf153ac2231f4aebe8318be1018149182f8f7005cf605eed0112141deef6818308c6ac919185c27a7f45e8ab42ac4afcb6455a5c22cd81b56e5ba88ec869de9a077690928ddfc56f14f2e68a96b173c45cd8d046d3e811eeadfa7dc72fbf35cf918ee9a6c4485a1e1d92feada9ab9fb4c6efac9e4aef6a78ea6865f5d40eae70604289ab4c0e3f143b8d41d7d2447ec6e5577c9c8b95d3a77a7b8cb4943a06bdd79fc620c3aaf98b55fd2409fde2e8021bada2cbbfcc2168da135628ad5ec0a3c3b76acea8581a6424824c0a2925f26e3d1d5db183794fd1198087651af2021b4f3614cf33b59c94bfb5cc4263eb3317e683e3231a534bd8aa53b66f3134df5e94dcf2f0bd80d65f7fcdda6ee70765f9703b9d2b0963cdd5dc0c01483c4d4a3e82e46e904ea1c70715380904dc34a13fa7ae3aa552f84121bcc86bf3afda8a96291788676c70b05340eeccc07bbac466e85808f9a9f4ae05fefe96baeb378a72d30678ab91c770371ba0681ce04724b955ae91e489cfbecdbaac35171091c5de3a40178148c0feaacb77b5b5abf0d9b74a6237df56c08a8d3c1869a3aeaf5272ac6207c00f1db896fca4e9ca71b4f68a231b3a574f3813581188070cd5e8ef59ae404915a7c1cac34ad09b9ea2504011472224e14cfff1b9b370ec5206e366c0b058d775167dfaed0f0968c56dd0041502525c8f4481470c20379c853974ca6d27f70d53906268c3a18dd02ecb2a61ca84bd66dec431a0a7f40460e3ec33fe341e25307abeac09bde500c34c09996f7800c5d69db11f3c199da0097f9fcc41b0b54a0f1db3996b7baa01b6a6db5a9bcd08b5bd46a185a27e01ec0456db6b78439fdf6a079593ec7deac894bdfd15b17aa24ede2b7ef6b7f94b7fad9e9d443649f7c5313f40215026debdcea02a0770d0b1eba148dc68a3eacfe9627b369666284e3753e34ffba6148b538a768c8bce6deff039058c4b780718d9b0751efe8d69813c5203273a7a8a71cd7500e90cdecb951c18f6704eeb78306db3ed9671513a7f97b5932b98c5579c35e059f2552c26196dd29eb820fbd2b6b82a0771c5e78db6f4c1e64df47a42bba135812f89a41414cbdde04984c6ca7c04fca517f934db422b0576c3c390fced62c5bc4d1040b54d9d6907a8136c47884df89ae7730eb52d839206dbc00416a089be98ddb4b091c1117bc83c5f299adf5bff6156b2077b24f6cb0812d19775b68703fc50bc2684a08bdd21056d21fd4cfabd9ad627af37e7c547179226c1b34d871538d98f04023ff7101c0aa463bfa5dd7189467a480b069e016fc25a407ced5e4de819fbf075824754ad966241742b82a2d242140e1a7b86a847b916d0707bcf077b2ccf784f3e7c91d80bcdddb4223663702269bf70a761afe7f97f267ae67dde60a470dcab61bc1f74aa9d10c528df1593ce139970160ea05d07f4d487364460c10e4c0e3fad55fb178d236f8e1b4a4b22bd181d8415b78bce9770e0c313c2bbbc51090c46d158b85412b83b704581a0bef6f5cb49978c155fb08a3589f68328d3a4164ac1d61c4d1e85e4f5fdd05d6dc31490c13f01bb8fce72502f5ab4b18d25ab5fcbc7662a8ce19bf101c19aff7fe8be562f2e526543173b60554394bd38c02597f4645504c5b2ae33edcc2bb5e7a5c6b2822767fe1397408bfbb92687e04daf8983efe6ffac060c37c195ed46c780312dcf9ecc0d07bc162702a9a7502a6c097cb8d142115bdcaa8b3c917e89ab6a11903714e9c85821e6310b8fafa990a2c60d7baec57e73a95061d1c9b4b40d0ba5f9d12c628a9e597ee02551d63ce70481aa0c11ad2be2f53a84a4bd0ff9d13d65ec256bc477305f3a80f9112dd962cc6b14c2a502373ab02efa11961018a23a78d08a9170be4c7f7acdcae0373eb3da43f9b10511090e51194d066901288b7893eee3ae1774b845d3696f48fe82309f633b0722827d0161ea96559364fe7a2771fc893f1b089238b58f2c1aebdfa5f27cdf014ad089775c0d1ca96fd8ea98007a74c8a6d65600a628de4aa9a44806a750a681194ac0b19f3fb9c0d289d64f0f4cb44ef6c1596a3e50d28ae0867b19eadc545dec5d27c3029b3e7d5ded7712d3171282dc2800f27bd00208782516064b467aa0b70be80119e33491b16883b5d28414de6a5c9e2bbc3842b8135247347d7cbdd9decb2c649e896701d2aac742829c4dca12cb6ae35100e265579f926f92298212f3de820f9100b0c1ad67ef6000486c11447411ee1606f947c2788cb813a1f215f264f74ee5767bcb72de8c34cbc82ee19f552f0c13f189ffff63e18b9c9772a38c9fde04b8a3dc3cc11f51912671d06e50356b98488716f9f104632de5cb9a5fefd597de6ff9c9209266033f1c5c4a9d6a0311d2f28ce29a9cc9791af062c6eed17e9fd0e6a66261e6e7538eda6688e7a162b45dcfa4bc8b83337581db8d4459897f5c12ff2d61478fda498bd8f9ba35c926c79129b2329421ea098974efb35dcb76b8ed687a9653b7482c7efb0918c998e41818250b31650836950a6a38c83305a4590ac83aeb1b30fe109502738d31afc8c7250f8a908c9166cc000f2469559548a2a2801845c8e4c1fd0f83d3236d3f6bb209ab5297ba3de59e83e429709862e9947824dee24a88317934b7cffb232a34df9f47eddf68336c037d7255497191b232e5fd00f07e382c7b9fe127c5a9502a812c6454257e491d505aaa916d228f1b2179f85ed2cb91b759d50de3aee59c76b3bbac2da26cdfa35676538b49948da2122a3ff95fbe1671b29dc90e94b40fd732d74f04a2aa1f1061d5058cdedb74a92544fb708fd96f87c83b13be20ff95a1b27ef2c8c9893c10c76ec730f3e3f5699604483d10f64700d92580e64e1c67411db0c73e2f3d242a708fd860808d5940178ba9938c86e184a4e33d3eb5a73dc5272de8d2c2d25a1406964e969bb1cda417671a9b7dcc0f9933b41e2703bdfa018b3b882a0499cf8af620e9bf369f05aff1f76191c3518832ce7a9993e96febd2ed07c91b4d0717aa11d4b5d673f180a8e68596ccd7e3061f92e0ac60e1eb923c6f254aabda92f89c73b612f296359769c75692647ba4dd51465e29c55f655cfa628d27a99c332cd049f072fd8288f218f4ace7727302a36a9be6036110ec2d818748e2be60b34a5294e33f31cbff4448c577c5ba9aaa42674f3c6622f2acf5f6c95333f154b72c828b42281094a282fd526f9b7135cf46563d80ebc6f82eececde787bd7f0ec4a89d0ac406b6f711302f47306fdf073466d6070a37fd469eda76f408f269e1e7ae4089801042f4f936483e08c6ef8e5d2841811626386a18de7cecd95f0fe3b87c00ebd0ce9b0fee891ee9a2901ee27559af687b7ee59a1a30bcdfbc203a0311db8fb715829f7c58e55062f3a30f5e7c4656560feec83fc20b411fda1247e4b59ec6c1494094d85ac19c85689307cd79e35a554ff9ca824d2e938e076c3831a8119e0fba4f1cdfe50effb9ebd1c5462f467d02222fb439d8993408a936df018c2b123be8486e209c43c5038253cc8c7a94d63a6a7a24f9d88655c368f50a2cda8147002757e010b0c9b33291d8742e1925cd0887b0d695b4af7083239734f9cb1e04f033607f3476c11e20b182d5cbc349690d84773c0bc5421f1a5482502ad89a3606ee5f025a50493917673b0d30892c933458d725e89339fc18d463712c7c54252748880fd47637d65851a8c782e23634ca10b0e080901b70a9dc81fb4b6c7154af8ae450269cf96295375c2988f4bc425f8683b4cd7f07939d2adc5e3b9fe686f3d104bfb38f1eea9bd0048f16cd7ab5f97d3ba0969d3d5d70baea9c9561f0c1f37fab02ab0ab5441d82a19c30e853ac971d590328d1f2754e5ea30c703d1f68cec40853f6321887fda5c53f90a551cd41644d1daf2a6b5a2ae46b38d8ee2a735e740fd983410717f67f6251d0512afa4be51a05c49d8377cd0b33fdfe799a4dc13010fc795ee0c3b153aaf10a7a55917e4a29dace858d5d3e8dea32676900135fe2971924f0768ee509fc8dab5a812c0e77c8072f6e01d7d20cc2638da1bb02fabbc7c13e47cd5453612163a14c34c186a22520e6771ca0111e462173eb2ab4e24d592e2e60b04a34f55d9d20122e885df83ac3b711f7742db72103c6c8b0fac5ab04b76259025618fb2ac754d3f7c9ce9b894c591822b87b76246e8b7619ca039443a741ddb06ce07dab0e09cc8cd2a2461e42633f5127526a94f47c13e547cd24453a32d44d103f097a603d642011394e75adb4962b05104d230c3295c021644db6d1b950652103d9f34a670a7ea89c634b77764f33527a67fee52b8cde45145ede39e2a5bd983c26d04b390b97cd586f0840c80445cbd6aede0e022d5e0490b97effe21c51eb5ca0439a98eb76fd6d8d80008ab805fb798682f412d940ba865b4428cce5990a33c389651a2fc702b47475ab81590323f7f57ad9780401b68371388503ee31d5675f92a0dce7333a5f5fb7bcfa4177bf8e8094121825b65f8d564c9fb18dab1f9e29cd8110e0a40b2eb7874d3f8a732bb1edbe42d44c848458c1386f6a3eeb7848f38ab606ec09f7c545d6d4641c1bcaa8531983952be6bd92eaa15c6df44d8f72cc25efb3306cad65aed98c7fa399f8d84b1770958422e241fe4b87c83934dcb21df0a4198678c02967f114b433ec9903cb728946a0cabaf8df0552982ed022a6a3cf3f5df233c1d8c574181d752ce295ad05a7cf93113c84e38c698b60e438f4dfc3c29ffa171e65fa7455533966f24af9fbc238d7e6d4f9cfcdcefc3596738f4ae47c8077d3c4411eaada747c08220bd3e3d7243b65758b4f7786c9938358c4e594d9292331ca90a1c385bd7d1c03250b44e9825fb6c969f0d2cc064aa1cffa6584e034fb3df279a360a0b352ab1622744cfb7fbd819851241df2fc6b3e59eaf07f1f6729660da49de3cb6c93f4a7d7c91cca883d47dac072f7579125756c026731dd7777fbc3485e51477944ddf581e59d896d252547269ca72d490260e7cc24e0393caebc2a5897b000df1366db7e51e152cfd5cdb8666630c3ba055804e2f2fc09cb1e3a521cd19c97d21bf7f3e10abebb73c2077910dd913736fc916725e4d12f2e8425be0c9483c9bfe28de951445fb3f6b5d1ed0f74e4e22c7396b98468379e12e4c67cf10b3b620f82e5d27aa6c90a580e729174df8bcfcbd24c5d62005b85874df179dc98a4bf80e65dd708f9dbc99367c0ceda0f5f600e005b6a3923e81f2a2f6bf02f389a6e6e9ab2a4131c5294706fe2d5aecb0a0a9234f8859143eb3f0a900fc2804cf6e6b80be17a86ea366a571c337facec17a57ab3764f90b50b3fd6ae7fc5aac4ee7e9aad4808faf8117f0e59c5ac27d8795a89fd0a25b8d2d9ef2fc06c9f8e773a2d09bdb39a85966d2037ae37165af1b21d8e831049c03cfe659c05116d458991cbbda61bcfae8c22f14aee7445a7504fc2afdf55dc4d13bd5a6fa5f47eb487358f1d1e16005ef43771ec35f982b7ecbd80075b1a16523ccb30047400f4ed00ec064ae2560a1a067c6564c95f4ec56e05c27ee3b1b0ba8d3465f9065a650470b8f95b9bd8f92a4355e48e3587588c2eb3d61900ebc2515daad207c7d1ec1a22d59ab20ba423bcd5031ade32945c30903a4e072a04cccf4a6966c3b818a3e8f92178c3f5f88fde680c75d4ed499390a4d9747286b761b541205580aca20f210fdc3f296aae4188a21c82a6344f41ef11e364f3e61a981df13edc32daf993bdb4b677b72de596522699029d08cb091709f6e518ad53dbc67c2e1ed9af45b62319c8617b42ea7ba750eb9ddf9c331082ec7340ba5f70c15734beddda240bc826348e7c05740fa7d9e505a1e19e6bcdf47796ef7b279369e3e8e8339f510ecccfe8b3d6cc425d6f646b74e4984553fc8f0bc7cf74431db733bdff78df8b7841ba373d67ea28e54e3f3ee7b98db77389dea7f1a3f832ff5b388a33e19509430ff371618bfbf16e9bf74750d97bef2ebef7de3bbbdfbdf773eebdab7bf11d69beddb790dbdddb9dbef3be0babb49e901bbec03d6d4f883f3741fa1c583729da97b2481acd3c6f71de333a0e9913627af7259d82e9efc5f8fbb6edbb79e98e27f0386341b4753a7b8f8a014296bd7720609900b96540fc64eafd49a3941ee5edb96dfbae28df9ff34ddf2f64b1d24397fb1e8226f93b11bc1f1e1ae94218b890c7b66dbf85b248c6e312e12e3705e8347012f9744d1afbaf2944dfd8afe08c02ce20179fb48dfd0d9c407ee44844640be647693665ad33f3cfbcfd75fbdb6f3179d33add6fa73f82e23a2e94de8f24e4f6bc97de4b0fd5469ef724b88a7baf47f63e01ed7d4eeb74ef799ee779de7b9e870a3d211ecaf3501e0abf45fd18f32811bc203161129951efcd66cc84a7a3dcdddddddddd3d8777829f40103c21defd94f7a3001bd879babb7bcb7d82e009f1e9be6d81fd29e84706e85c829e4de00972d61b16c9e105a16112996b08c30b48d36960dfbd70dd6c7d983b40e747b3185db2bd7dda7215f73dc13fd97e3b6d92edcf32dafe0cd33addafa46c9ee12ab73209cc778467976cbb233fc3e3e9af6d2fc8294c22332a1cef9ffe9e401964f6c217b62d781c612421fb17d95e0a8db7ff82ab52f7fe0d65942018bc841a6fbf0379b44dce5cd50869844132432f0420a1b2a552b6efade4a2ed0e1160023be36cdb9663db36d376c3f41b104bb2aa93e0bcf733819d73a45276366788c31342dfe70d2f051a524aa54ccaa44c26bb187fb325655bc8e574ed16a98647c6914dbaf9b66df79adec324f2c76692f9f722e5d1d9e7e105a1c9b268471198e3d169a0ec6981147c018401890c3857a06dec53a06facfce62a069ff9ecfb680d2fc80c2590fc71bfb247539f3992ec415fc479117f339fdd997b7dc685a31f696192f007a5bcea4cdad3009b35c19975aec9b4e53101734ed98c39c6a921f3e3cf7cf0f313d03d3acf1e79fe07e08c398d7d1b60bbd310f136b37533e66d3f7fec661657713f697841ba3089cc5e3876329e90edb93089f4998b76f4996cdbe286a3bf29dcb62d09cdf78fa0f27dcd14dea2a3eff215cd2011e7c5ef71bbfdf93da7e95bc80c4d53f2e4d9e9001283f4914d7810912335192a9a4bae5dd0077b426698444aa02019248364d0fc51fbf948a8ed6ced7b5e10ade372ba695ffc8f7d311cdbc1027c39e96a1d9974f284dcf7205745912ecf4be1beffe8cd7b319eb9cfe40f2881c06ec662b1fbdd8ffe5d287d8270a004923f3b9ec045bcd53addd99754fb9cd691607f469494a595edcb9e23587fcea68527bf5ba786334bdbd89fe08cb9687f071669dfbdb4ce7dfb7fe3464cccec1b8e31e8c8a92434fb1f4165bf7fffe6f1feec467feca2e987116c0a8d70a10472d1ca9f6d0b1ea54fb6d2c755a9973fd6ca5cb4394ee30fbb312c9bd9d98c878b5606e7f874bc2cdbe77874a6fa13a44f039c42df2f8833577ee434f6c1b4b5d66e333c4efbf6fd680b32431c76a0cc5d4f57e3e1fc08131c7dcff37ee6297a7892e57b2ccfdf01058fb8b725b249a632783f5cc8c56ec714d85b481f7daf4ee42ad3d3f9ba361c3176c105eb2e17bb1fe70bd63a528a2c3dcf01f3bd97ab529a17a634ed354d0b3b300728c3db2b7f350d7dd8161b6f18f1ffdc79f497d4431fe2da6facc0e39cf9d2cc5547bec2ae1a292c6baf7de758ab5bd23ade075ae784fad3e93b70449d3c5066d3c934875c149a2f2b4ef3b233a96da8b5324cba17e3f9cd7ff1e8bb7c45ee599ca80a3cce574efa15fe99e4af23f28c2c05ff6bfa6bbed83758f038936490793e99b309f4e3aa7102654a8196260d7dbb19e17102a5fa490a1de44bf3d6d01be605a1c96e25b5044642b39c3f99bed31f17e9bc021b992fc37cb9329d3323fe2f2bae226a9d2badd3bdf75df71a387ae128a7cb45dad98efed315c4a7509221181e3d2953fa54c85529772b43ae5292afbd9c497226c99924a71557cda3d7f1e57021777927b87daa94c5c5271268f3975b71988b94c869e8d3a3eff2156569c5002bb96a26b54e7dfa138cd6f137325fd2e71c3c82caaf65d7a6e84128cbf7973f933c0153f5e7a76a485f3a7d8e3a954047aeaa99e2fff7614ce7cbd7c2d4a43f93647772f7ed86456865f92fc9ee0ef09fe13ca23d063f6001b6468e68a1911962f7ef6850e7dfc3f71787d4c51bb8c65ff1fd48a881d6a00af41b04e2620d7fd4a74feb0f9e5ce9bcd19a6680a6d24aafa952a6948e3899e2b848bfbbe9534a29ad956a9aec075806a2c237efc5b8418bb72eb4222e760dc32f6e1da9d3fdd7fbe509b8d6c13940cc946bd34ffbdcc86530a6a7a690bae83c441d8ed38231e4640ce4e4114836d2effd08247b395c8d1fdb48c7f4352600593eea246b745f5328c3b1bf7b1f33061c0790ed6310059eda28869cff72b858bfa6b22080205a0787d669227d6a1359e629bde7fcceb2894a9396b88a888b55fcff3e1a253ca6fae5cf9f61aa654f520040c872264b1e59de64f9ea9bfaa34d96a3b7bdc9e459869f04457822877c49f99aa0b4d2f31b94aff9f425ef9523d8804d9f9a8f846e1ea6e60c4517ebd70dac71b1ca7cf59e769a6bbfc11107c7c3cc998512650b33759c17842653ef046badb5d3a5363529ad0ccef151510b7fc8dcf3ebdb2afb31caa33862166a6d3035bfbb3376953fca637ca89aa66955abf5ab875a0d02d75a6bad55d3ea089472cb7c00e3d455e34f2cb7ec0796dd9354bac4f12cdb666b1afa94be53fab76de8ef7882e9370f1d3934d32742e53cd51a36d09d5382c1f58e2570cb7c78e596f9309439efbe2e9d85f636458fdb61991cefc323a775c838a345a9a46966f26492e5f454505a4a72f1032f8d0c220c1811292101164354a03c41bdc0c1dfbf0eb1c66a55d32ad601fe20b7cc6565071847164b18476e99eb0b17ce14b547d3344d6b2da66c4ef444f1f38dc48fd6aac2897c004193b70205b74a4c192e28c10e540c5d21c309ede8c3628a165f5c451031744ff0840c3045e84006463011c62a2140513e4551ab10496c096201d29208907e68c2fa40850939c45e375072420c1098200c9103b020b9b0c860882d624c68b3245a0879a14589a12ead12ece089ecab0b0e40af1a80c0041bc85e5f4ca8c1d26b4904b12a3c3431a10b2015b84c6085167826b7ac8b5717433580e5f1717eee66abb52e6b65aaed2285b55d5ed0035017303c78f1ea42860f4b5cec00e342081d8020c1b8484207203700e2e2091e807831a406af9bdc3221a0d30d335bb55a654e6639c8da444749333ff8e69639b9411e711054e0ff2101891e234cd025064d58a94225ac0007ca0f0a8ea0444b8af8699915556a706ce49661e1419eb9655638218301e49621312577b9654364f9b4aa69758b171df4ecf045139f9f7ac596987605148a3f2c8331920fb58a2d39e061cba38c4c0b594c6e99162ac836b74c8b57163159708ddc32295d84b0965b262507d80b0621b74c8a183486c0a9dc32570f3584302ab7cc356548144b34b72c0ad7c52eef4b0fca09265d17b293143b705e2c7958ec6002ea94b80062eb6288530a4304174966ca8107fbe5b539f141b3c1eb0afd541b20591810465bf0d0801b18d5c049036cd092896146100df0c225db819311340069e95bc1494b1605073215b4668a789c80f2e5cb95227ee84189a1d90315cd022524b01265298c1e88a8cc18100991456c054147566810c612da92971f986859e10227ba68c2e4656700861158e48004508aa67841c4a30409bef8c20352bef0008cd0c745c933d1344dd3b67c69c2e384043d3003882f5410450a585a8b0b914c9433961c600b12378a40c0858c2f8b3058491633c0b10515980b0ebc2d53d0303112e3f4431326272646583ea8c203415c8664946cf183238ba01b4c5c00c6c7c40634b2a8828d66334c8ac4004149092bd011022545430050b265875416586ebcc0071b379051e38612b6d82200215062e444c701b450e22b0281163ddc506294824ec9164b7c4cb41823c5a4288bb0a8898e2c70f840c90b90985112562e4636946c198386122c5c8cf05620450894e412645faa907d11e24b131e3954ad56adf24cb9011058ec20c6183ae8c1fca26ae18982871e0cf1a089104956d01e33b898d9b0c5880c2aa828217b410f13498684908c8b0eb229b78c0bd2152c332aacc822092996701ddd24aafcbf9488ee8f1f07470bb667f8ab04a9835c727da6c48a1b6411c4911322aa70dd028fdcb227b1217e72cb8488e516e4963d09937f89a5ac7d6ba112cb5a2883e70d7c799ca1d56a65193e55b747b5afe1a8d5d0487dcf9daf7deda9083dc0f6b5d048fd998d6832aed2b42c61943580f31111b2fcb1861f76d1592dc0e3f5d1315ffe93d73d4ed06627cdfdd3d4e48fd3ccf9a6cf3873bce1bb4743e913058f3208f7e7dd90c35de8004a337d39ca1bd2cdb523fa6340ac6db5de9f2bc0f0d98119b4522c4843d75a0d8c102c80d24312d554820e4b62ac2dbc74dbbc13e6735cdb7cf9077d1b8332b260ed98be06ef0104605853e6f3b0b80d3481ed9d70ad168e35e0a48898c094691e915afd4dc897e597e5d3af616894b2d91961edc77ba4edac317ca6bb793ae09a43f54873ad3b591a91c185f4d370d4004f6b61df79ab944b604ebaa00c1e72e82fa6a8e04c44703497dc300450ac052c8eca1f58e0828245c1fe620a8bab578e382288c5695984a088c5d98e080c122c6171b7932209252cee76154c11a93b8dc516222d7674cc8055abd8e24b128b823a66e0ee617060f2f439732b432e36a17d7ee9c5fd72b9bb7cadb5d0a384ee4a1870a56cc465c8a3c9ea0412da3252f270dc365a8bdfb7064ff185b6d1de876bbf030b1e71d6dee7476a6f240316e81e2ec89bd6b9af3dd6341f382ed064f9bd2306523ed5ea532c83680feacfa7df55ce39e72cc27386dd0bbe6aef1d50707b42eaec4ebaee50962f19e434f2fdc845293f01b97f34803c81decdee9a34f2b57fd93a6e18ea70b1861368fab89834573ea090500485e3cd419bf664c1e34c8acd2557554b655a51eb6c5ab48e897b93e93570e4c251d2d7465fd7c2acb8982dc541e6ae1dd2176c4efcbd58f3e590c9b267ae682cb6430adc547ef773d58577bc4628cc456985c6e8ab87be640e998ebeb27cfaa2548af752ba972b2fac9994f6b48d34e234e6aa2cae9ad6483f7df9340c2da375b637fdd6bffd05475318769e03faa9d63f97bc20fdf4fd9358f0a5a54d5c94613e572438b44e0e72459328ed610459d222b2b768d25c512c4d235f4b96cfc54bedc9f23b8fc6da46fe8e20ea4fd2532117a5ec3f32f3fca51aa6ba73b1d5e4757c39b04cc7e375fc742f6539fa2c4b2995602e4a1924024df9da6d3571556f30c03d85d436d2affd0591d06cffe650128529177255bba4f384222e1ed1dc4ddf01f4bda9f7fc59ad911ab16d3acc7131c8cdfd31d89c64661c3c96f019356296f33bd0ebeebedf55d30b9574bfc3c3eade87746183dd8156a6dfbd0fbfe08d0bb0f41153e3e2fd9aa2afbd092eca57a5683846eb5ca06d965aa7d61e792363681dfa36749730774918ed992f6b4f960d933ac090d0fe22cb26372ec0e31d3b4bee95cfee5c29e169c1827c7a9678c08616eb033c32727e07f6a459752fbf2bd8b97b09990a4ed3df320f470723270fe73bbae05a87959a6c39e94b972ee7eae3708460846516d9633ea53fc12b6fe6d350b682be1c8c93e928664a5d9cb2556ba52edbc6fd65b565670839be9f93a1af92dcddddddddddddddddddddddddfd5f58c9a449635fce4eea694d1c37be0fdec6cb1999af41e3e5e3b82a06a3fef4f27d9800448521445c75eb4a04151512827be79d8b3269aec2b7612887c070c8e7430bc23b08b405dc3211b8f2975b26029fccd16e0343780a6e2f02f85f412922fc05eb6be07d13a83d079abe034fa79898af51e3674099b761e33ff083bf017e8f03bcf113c4f129707e0eb07308efc3656e5339ec3f28959cc6e6105ecaa6086f5fe608672a1cf1f470bcb44b8121bccc384230c2a3944bdbe7f8711281f0e3bc9253360728330829104000e173a400428ecff11e662ef1f98384665fc2730543d3f8272db55260d3ab751b58730ef004ff1428955cc401ca1ba07ff63fb06f03f419d07d6440af013a903f71d17e0ce841a04701dde542a00fb968bf03bd097fb915d0612e5a0b7a16a729babe348d5cb57da6b7a39986b47e6fa61a1a714adddda9d322bf5df091fb92085a3220926aed524b60eba1e012944a760b93e4a2955fe73571b2b3a560eade0de491e3628cdc1105965fed731dd3c9a49cd43a3f128c5ee14983c7c8d6866cedb59e8381c13d0f71680cf732c9554a4badb37db3591193a9b559a1f9f40c524ab9c477a494d765fbbd57debf2ede1f5ebe2f1bfcda467c01a699cf01a9a467e8004fabe8fd2b99649fabef62143c7bf779a59a77399917aed034fe1d172fd987f4d2b5bd34c3b65dab3d09b2a2f96cb37abbbabbbbbbbb57a9c574940face00ac6656d1d1964fee10968551df297095af4c7df73725bdbf85713e070e83557f762fc852fdef08269585d33ac411e15dc2ebafc5ae5c72b08df577ee22aea9a567d7ee69d330026f86a6bc9489c2c25095f96defdb28b5cec0c4628f7cbed8cec46dcf470d34387f2bcd309851231bedf7d3937558eb8a9226537c7c90dc04c301e9860b3c33e31aac12fd8c391d684c1212c008ca4de62aef26dc9b4642bda9236a42dcc66b41d6dce0975ee9aabc65e16a7f12da6d51081e96fdbb66dd2b308b0cd374b388b965e7103681df99b7dbb34575cb8c5b67044e52d3657d68b4dda62bdc8fe1636697c8b5998bb62631b2d657f2e0abc116d587a8bb58efcbc64dfb8e46c5aa8fc61f3166e60c02de6a23f8d173c755a8701dbfbe7c8b0472fa055d5cb5b38d62859fa3cd9b972249ac6bf04d9bf4a769762dc622723579dbab8ea6eb12d6632559ffa5381dac60a2cc9cba153dd01c8feeef2267cb5b57c0950f6865109ca0ee4c16054aee4d185bca7ade4e164bb9f2467414f28df2ce102380e1ee0f1cb2e9764186f59d85ccd206965c95a6b6d6e0976163428cadcec245212102329b4922ca20ee632cbe4ac5933c828d967481ececf8e9f76f3051ee5926de2256cef6f7b5a8b81000de4d2bb19f986520bae911bb6842b6f60fd719a272e06d528ae98201a7cb2d397fe6a43ab4e482d361625c8fd74288333d93b41fe0e26f0789ad500e17133ca8e82f7a3db663c1965d736fbd44361bef65c6b9b918bcef9c625fb5329e9a5f43473d1a987c266349bb3a1142edc64e1464329db09e9843457a6b7ada3790d41ab6a00a96a04d1a2d780229babef5e8c371990ab2a0e2dcaf7af1144952c7fac0125fb9694fd6de4f1840425fbd75a6be5218f35924e48d9bf46d25c7d509ac6fffffb30ae31d43592b28703c8e307e58365ff5ed99df5b5e6bd187fadec47e419b2e7490af73ebcbd08388b0b776a606d4f5fb582d507ac3f3e82a454320dc74d36c1fbc950660c4086471757a05f3f02f33da421a4610a117f1ae6d330d5375adfe1f7e5662d9961d294262a80f202a19e18105a6eaedc5cb9b97273e5e6cacd959b2b37576eaedc5cb9b97273a505259686904ad09382a0ac819b8fa0945a5062c94bf6d3f3a5880f287846a6333703f6b08209188b93dd0c2867008b818b25033a97315832e04e6f118225f333e04e0d2f66603163d9d8a941ce58325f430844b066de06b85383f7704613ac29618d91c6d7789e1cf33ca8e7c9a7e7c9def3e4ee7932f73cd9f43c797b9e7c9f27dbe7c9daf3e4fa3c993e4fae610b438935f31c0ab0ff0cd8f908f269c4c8e609f43ace1425cfdface4f9162ca2a00c48bbdab011f60ca6e15507ae5b9092454ea72de032ab2fbd39ebd349bfd6494323f43d07f10f6b5fb391fa32557efb701a7ed2a50c63045cb74082a321a02117093d20982c259635450334cbf716acd5fa020b4e83c00cb6afe1ed7e5a3ae989527a9af629c8bdfcc1c9aebd13acac3fe98972940af9e7e67dcf4b00ae18d5b8b53f85e3f6d745cdc8f6dd778a09719fb8af6f7aeeaf7782f71ceda414ffeb5f9fd340fb0e12711acd6d38c4458d07f8cad0878bdac90793d0d413727f8e32597b0fccd17daf4374713e76d5164a79b37c9b8dc8af32d42ed5feb3a67daee12866196aae9decb0d0217fcc2cbfc1115ab543bc616fc49d2bcab0d34a33ecd890e77338e011e33c9f85b94a11b17fff863b6dc3d410fb3cac5b64ae7858468c913929b6cd7c4eb60c7f784ea279be0b45797e1157e1e859c91f925e7055cba210e5f93dc24ead2346905babd487abc62e02e2aa51cc33d5f44d70d5b592672ccb503e1131c66d53b303f6f062cfb21b1eb0e7fe5815af2a5c373ce099db4e1f2ee79c523b2242015a8043982248b62e6cce39430664d8b951057f9ee1bd28d09f4052f45180471c7c0293c3bfef86072c6f78c03777928bf2539f75d488d9e70c533f349cb6f1f7ecc37df80d1b3410b31b034eca218c86d841fe523f4c228644c82d23424683981091831a542528080dca120ac82d0bd262976419941d48c82d8312ebdb397143888d466e5911b310a698d36b1b7f2260d9cb2d33a24977113ac89fcc08275266842b7f2eca546e991144f973518232236410e343a769ad1cdf99ee6f17ecee77b793b2bbdd6f9dea7ed7c9ea9dd009917fdf878fdb731d785db4767bbbbd0fffbe2edcc2eb5e90fb32ec17524386e41b666018c2f17af58143d22fc3cdf2ed7b9eb90dd0d3b2a75e2073f5cd19e6ea7ebd3ff762fc7d5bb5328a05a5cfac3ff87eb57ef52b4feb9bab66d5ce224e4eae0dcb15c8a4a9ac1b6660e992412ed6f782c0f2471924879ec89ebc64417441e28723f99cf7dd3df71130bd17d2e0bd8906d477e17d2ea4a1fbfba690862944babf4f83e9f274ef3d91eebda7ef853b5368e8bd09f55dd8754fbfebfebe27d336da775df75d58030d3e7026cdfc147d1e160a4cd1100991ee51f7e3bc47f1b01e9f42ec2368dfbdf7238e07ba70c8a499f34768716f7a9087650279582fc1146696ad9d229366be299c33c0f0806671a36c99b8076590f409020af201bb37819e7c09d630893ce2128664a7fb2e945ed879ef7db3ba1fe5fc0e6cc96a4f86336da37db3c618b61ad222b2ea7789db3aaae37cf4471c1e4b3895820846e67fee3015244b198a4ff3f85987bfabe61652e4fa63bb14b91b968df453f7ae942ee05e8cbfef5f1467c3165087e8e64cdddb7597eed65dabddd46bb1ff7abbbbec3893778f70e771759359862814aa6ad7b30c4fa793e6597bb30c3d2ffb6bd5d4d96b157591e6d6e91144c9ed9d3acee49d382efbcbe7a6181ca577ce09e6f9d9c89c94522ae794f539da9586b4a911355a82454f006ec8a628409001050f4c808458020b1ca0d0c2a0a2c68c8c97e82229bc145df1822e3aa031d07870c585590e5a98002305e80a14d810104de0882c443f44f1812853dc2a34b832c3206a01b6b9b5384901137c736b7172827facc58994ac25293520294c03c058e235788063726b71028412f8945b8b939e2270aa56aba64505528089a208d7171ea051e61500182919028a15656c112233779a4bc86a2ed9a39337b5d6aa2d691bffad49d67ab29492d33acf3544e0da84d7a12a247bd4da5557db60c163956d5f65d548f3d1a27b6c699d1a46f6a0a1f681bed1d22acd8616fd5b1e6a37b4cdd45a5af07cbfffb1f0fc5ae7a6a04647f61bd9eb8f29570593dc7cb9efb6a15f61d361d9c3d1c1bee6f2ffee7bef2d73b7249b7ed45c1ee82ccd3557f762cde5a61f67a4f73c719d94ee276becb2dc2e787da2b828b921ec054629a59cbb908bad8129cc293e6839234b694dc100fdfa354cc1004d210599e9a3d039c4400af5ab04533845b8befc1645988c6b1d630d1890ab9cc668118d42612f9a25bb8b36a1c96478881005a241340a75513af3d122c610fcb58e4b693df99af7c3cb9fbcd12baf62a9601046eab23605ede5ff6490f23803bfbf8ce53ca475ecd00abdba256c5c64df7e6c19bc8e34da448ae9b76dfb9ecb32bd1005da803617f5a13d2e7a0d9bf60ca1409b6b13a240148802d58069de41518730fe54c8e13235b55699c74aa552691df936725fa103a53cbebb1b49af86b76d3ce31a140563ac6943459a0c75840292fae165dc5d4305835a14551f8e42a15028d4a3c42ef058674452f022cbaf1d53ea58134a5c24aeb49aab75ae4be6d294f86b3670a1e93794d69289a9b3b9ba17e33a73c0e93b14ea04b68b1e15fc79c4a8130a0814b85df434ad045a15cda5b950272018a34e60fd012b507d02561358a3b888da26e66216a4b60193e4a20ce5de4fcbee4a24b6972b8e4b49d334fe202ce1ee3ce3297814efcb8a288aa81ca618818915d59c7f4ee8e7eade278252daf70d50444e8ea6c9b42e5a6b9ad46477b78efc0640ee17c1eed15d3c573b0b6802ca10ac1a342118b0ee4f7141177c047f8e52da69dd3d651fc0111e71708e56adfd017a62a1e0ba852a805e3e2fd913495e603e806c2d135aa965ddc55621598daa91ac6182ce770f8513173c7e96262071a3eee1cf0013dcfed85cacfd6a02101f52e0f1c1b13a89926feb483a6be552bd542f2a18d5c86a30988f7bedcf932017371c9ca04b4fa55a152414cc320d65f09caa91c70e641a4eadc52ab3a6695f3b5b0b8edad7fab48646e8d36c84be0c7dece2e7a258051e51f3042ed81fd53af2270e3664d6e52ab746f6c836e16f856ccc76b12f4b743bae0a3c4aa2eccf7d3b60f93554f0edb82ef4442ee64bca826e50c1575b9a34fef762fc7daf2dfdcd122eb246ae2a41af646b47f6b7437696345734fec382a5a86291b25b97a624fdea5801d6945c74ef7958f7f444f09f5efb53b833450b89a0de0b77683cea7762b42d4cad703abdf7db1612f1be46b8d3bdf73bdc9f9e87459f06d8a20c70880a4fa117f6a4b921126d33030596990060a8b5b6860aa64040eae33dc059b4e70398005f70b3b40fa4c0a3a6942d50f6d7f10496da52cf5c591feb7a6b447be6aa5941403e990a65b7ae49e3456009735105772b645f3554b07c93c9ba800469da130b694f0d46eb4a9500dfef3c6a43d675b10e156029d3945c950a22ff6619e2048179accb457fb10b1ee72b48fbfb5a98d2e1e2e5bee3c2ba46fb7a3f72713687402993a10e2b5806f5ac28bf8ab29c6932d024945e694341ad23b52de00d0bc66e79b4aed1bab016cbae25bb36c4c5ceb2dbe087e71b5a24eb6a1b7f1106587a80c99537c8960f38730564c62a2d32573404d709a6507aac2bfb77afce8aabac7559976bb321db3c55064e9231d9922f294a9252ca2abb674584faa6b2bfd4d22b2dfb7de9454b2e791699ab796735496099c77b5bc7efbd354960c7315305a75670968c65291bc23847349149e09b455ac1e34b59ec35cb4358846c7ff36f2f35c4fbce4402104425d6629960982b1bee4c21727ffb1dfbdd7bb8b3fd7ddfd9e9c254fd49d34ba670e786a9154eef7daa86444e210d532891ae69d8def45c6a05fbf789d8b0befd66d190c816160953351cb2d54d5683c0e317699dea2af9b1b691609c02bd92588a8ab264212281041e35353266050c96b01712786c1896cfbd922c90342ebd18bd20fbbb20d636fed26128e2a2e704a640d3f87b21823146111a8bc5700db86e01074529ed5b0b1f956b641d14ae78424b699a88f3028f9a58971c4d76b7b33850b2b40e4cd5bfcfc3ea9e87c5b50fbf59c2a348845392830c8c58dcfb982b8ec8fd2edc11abb0ee0399ab7b939de59fe31f1698f36e1cd99ad65af5ba4d60cea409d2463542d833f53c1460f710e7457923087c6ae10d6c96067a6eef041c1d707db94ab3e2aa2a8aa2b5da0fd01354e5d17cfb028331213341134c0cdd60d63120436658424405a40e63d4f5e38a640fefc95e46124eebf837573813d724813fc823cebd17e7a35afb4b2f058b1464c4401fe523f38c33720cf48dc4d047b9befc18be8618e8a3d815c3288f6de4375f60a7b118903abb54dcbe970557f5bce0c2926c82a494fd6fb380e4480dadbc99befbee46abb6f6eedd3b81666e33853c5cc4c10177366aa1022c486002f70ba55a6b723340d3f8ff35326241e6202b5756659d93e30306194c20a1c1c46a8eacd7d61c51f0fcf1e79cab6b6ff8f35bc7c32ff09813cb929393a3c93423b86e61054a278039d1738220a4eeeeeeee1988f0c8c1e9dcdddddda7114769350a40d723ad4b5dd29a54a4aa94fd6957a32d2d0332697ce6fbbfb9b2f1fdcf2a9a9c8d4ce995f2658f2d7f36c04ac1bad436fe3360459a34b34953c7689a2411341b6a4880671eeb120dd05931a064d9a06003992bdc348e42e5b1633bf203365c318535f35ccd1158fe0cb863e367be8f6a40fd6004ab7d84b1c16437daa94176d1c39a62fa1a709cdc3003d614fbd54bcffc08e66ad43a60f6b7015630b2572fb2e370816dfccc8f45e6cc88082cb5a7a00c355b1be0ce14223131bf63e377a61091f9987027e6657e87c6d7f829a7e4d9b10156d971a76fd6cece1422367e26dc99791b610a7fb3766c7c0a03a1e4a74ee10a92e7e5a363810b53355c41ce30c1237dc61972db5ec8cc47669ee19105c8b000f6a47130ade35ff46af6acc4f03226114bc67944f64722b3d72cb367ae6c194de3ff011f1db2df1f228cc848e4388f30b26170fc8067586535f6041ec5ec3eff82154c4daa4693c69f0628632a128e16a6e110202eba37041ec5202d6f7080c76a6442328171d5ac46d5a86aa0ccb5fafcccb4504e2387d6ac080c3e4f62f07fa1f5d3491d669b15c9c9992bba75dc166e81c79ebd00c3f6feb2a7752619d9c3c8eee406f96b1857e5cc55e768b55babf61380256796b3dda206a8742d5586452b15d50000009000b3140000301008074462b1683c98679aa63e14800a85a2466e4c1d8bd32487711032c818020c01801002000010012222a30477a81c02289dc2c21340938b65c1c8513a0ab36633fdcd2eacd4ed8a14049942bc417836b9cb2ebefdae51d58aedc14fee08d04028b42f8920e247559246d425512767c73f9ab4ec0f1d6d0c9c112ccbc835cbe46818c7603ed2d60856c288a8cc91d0c754c07b7ec405dae1703e965760ffe64111cdde3577b21be48945f9f1208884ec84a20b1d242a902e6231cfafc0639481f3ea30e440f016f5ba80826fd01b4316289c0ab7ffeda3fa26de71cae6ebdac627ae980038343a7936f53a5f178780c2051fc2c77774668f86ecc2bdac394d8003379358804d4502f2d8cd47c325029df2c89bc7e2063c6d250e60292db7ebb3b41a752cb993c72e07f808bda210705a8059ef5375bbfd2d2250bf2662b07b526436ec668cae820d6603dc5fbd69d8e8b548cfa259cd15bc9712be3fdf91570234d6de2377fc56cb107acd55afe4cdafd79179898d9ec43bb1f5ca42f7dee5687750cb47b2cd3c68e878fdac94ac1dbfb1494155fe1f837bc1fc5914f53b6e16fb71f3268a1561c6c59609920cebb22b051efdb6a827076d83cfab7f173619118e192d5d3316f84d83ffe5fe9e99fa4bba3741f7b0f48fb9965dac39cc08b6b72653a1fd610d62442cb1b294719f3d4332b030fdd12eaf0c0308ecc2ce6fc8d24c5a3b7554ba95625b3583deeaef4653051c73fa9ce6145006445eac5ed31092cd88dca22528ed0ac96aa8fb18e1956054dfae8629bf8797057a37ba4d606eda92d14d2d088b4da1588d3e781b9bdccf4491702bf57934d767313874f415d38771453cc207f4d975ca62e4f7b7452487b8422f4267baacad9ccee75a3a956311bed3a6c6101c6895d75d6bbfec9bb075a32ebcb6980cac33c02d9cdf2f3362f62f55e75074e523fd7609a77749483b2f99a352ddcbbea4f7fc1e2e3f983921677d17eaceff09a2f79376e9c3635c180020f7e937c67526e8477ad491b6f0a85e36abec7c8981760e7c4988b184713ba3e3c63e2a9d806b9f761d53c7dd12930ac0b3e6885c172f971231bbe823b9684d3ab183bea5c07f31c7f43bae1bd1a2e65e8d9ec47fd1ef25e2c5eeaa359cb46dcd81ca85e312cb11b1e88262cbd19e637b6e29801b4c9ddff035b27116350aa5e88a0dcb5d657c0aef59f04a7d4068afc6f718d469b022e3b3696508398274c17be9370f4dc24dbfc173e8cf2e643af888ee20bbf694977e7a094db64b3f060216b083bba5dec948844ca98e40fb2ec70ccd8a6ee761a40744efabaf0590c875d9330b031a16589f54ac974b10491b6d0ac6cb07e386c0d925b52521dc9bdd458cc3f2780ab56792983b98213ceb4ad8d8feeee7cf2a1c161d6ceb155b948cbbf69ae2973953df9cb71c4513e84739446e934d225781b2ca4a84468a0f5fc2f042a7af4c3795e2309be58d710df69bc17df3ff6b6ba0f0d176c40a1db2319ba09b4c0cb4c34585c454b96a53291357802c487707f9373db26498d598698b998e4b98459e5c3578ace592301290e411823e0b3ba4e5dd37c192ac447ad8aec7534ffcab8926554b15d71f076fcd38e6b0e28ef468ff9706ba0ab0cf16af4dfd4a431eebf37c9959da5ac69b140594678021641d2e86c00ca406f608f57314b98646f8f65a71b1bf9e1cb60a588f627ca5c5698dc212f118879629698d1a702b8bb823e16300b30d8fd3e587fe207c127b8e10519f8849a51daf6bcee26bef4741517c0075fe39a5b479855fcd251d6b2ef6f0b4c8f367edc4ec260e1907430b5c91fd424b2b3de536e61c7c7b6e2c9f9328f06cd93105ef50200624e3ed2353b69de34ea89d4d4996a77a85174c6afa46e085ba3d8662408ad717cd19812f35e2bcf1d2148cd39ad53c1e573c0af8b65d3ad4d32af4acc51242ffeca5c35c53a4bfbe1832d92709004094182b090ee02b3b95ea8be22a28c824147cb6088cba3cc6d5c9d665b6a6d50d980866067b25c08c70fc2bc8b0acf660ad62e01c3dd328bd365a4529286d10e4795b20a0f72301f1d4129ad2de1580fc9be08b93cbcc8663dd14d6e8fdce93fad0d794eb019522b559306bd873d53283246a3ec594a6a50c62179bda52930ba537d834ca2df7f5a93789b80f5438553b68959e638c72c821843665959c5fe375dc2fa698c8831c8a94e66297679bc97932827a76bdc22e053303be19161a2468ef45c950a984eabdd87d74e975fa79d7bd53616082e05247c46ff8a8f81a8bb4d6fbd79d742a2cec02ea3270e2e3a169143b275887a61b12b0f88c857c2e71ec8c8a5d2c2253339bcd6c9481fa4613646e4a304f24838d1f91beffd1448ac58d00594fbaf24f9501e59b5a5b877b0cce5e567b13c46fd21b2f687b9b34678e081102751d7abb81c781f463e60c03885092946880e2f3ea7c60f440a997296b0fd30a79e7c9da912087f1ab330c92a479ab920c5ce3df35169108e4e810fd680804b9160f64d5d43a9555928640efb44c4e4b166a2f329cbcd11930593f0e4416dae72a7ac3c04f2c8d5c75904906abcf5d99a158ca82bab9298260c381e9a54d239f97f2b1fcc93cd404c35f74167cc06f277a48aa99997c4b6b2632093546ffaece213492408f28b3ad3a0072d443d4adde8e491aa4a591278c17fba36b12ef39dba8d333ea62af41e7a8cbeb78c9eeca1d5253706f3e0d404ec1b917fc8d6605789d4fbf1046bd5646a601e1204d5d19e8963dc04a8bc62b0a055268da983945005cdad4bdb123ca2891d60e84de085392d6b8e81a43261bdab170e21a1c3b71d277d2a2a4deb9cc68903ff24b7e1f2300e091325e5f27244ca719c47aa0131b5ca51517fbde1dee3ca31f3e5e68a5c1af03198693271d4881aeddec7d588eecf6520a79b1fa42c3bba5efe8966c52842bbec5e3be9a0156c8860e70c72d0b0fff3aa5c89049c88deb9f2429b608497668015bc6e018008fe2901daaeb651e0953c9a6bb21a9db9d114b865afb6399501f90ee885772f829cc1e3b257044e26950932166c994155e3876ea1e9e066c3cb4446786261698370df3b6eaf40b1f23fb04b3ba906726cfee1c0dcd12a94d2eb8f3477709b5391598bbacbff24f1db5f192b1e1c26a705cebc317709a6a3e83931826cd15d33f6daa70fed407e9bc450e30580117dc27f9ef66c1610bc879753119f49ffed43e501498bbe3179372dacb73b6e560663aecd84fc1b9614a7ae62d95e230e05b8e118658ce53449cbbadf7820e08520e82fa7338ba036509853342dcc6ae49ada822673dad5edadbef0d2ceb671f1e3856fd641cb3f8481acbd147743d97131f10af14c7729713715171ede75886037933f7de6560814448bd7b16ea53c1dc7bc01a5c6cfe80a9c9673656d0c106da6f36319436d339af17168f883a47238168d9dd1219301e5fd54c5b257b9c98fc20c61467a51f0f0592ed03ae857c7fcef7aef7ea57efc5b56fe6ac211c282bea9edc07c3c0560d6daab3e2b8d4707ce5907234de7b041b11f2fe45e1d25d304a233f2971f0a35c7d5f108961acc09eba64b740e4833d34c5065b84dd71545213bb1fe6c541dacc70f5022d06b6b12e0c4f9b5f7b21242b38813f355042a6b4a6a91f59d6f94f8a4f31561646522fbffcbf599b9f6a44c38d5315b84ce9cc6ed5e729b37dec39800b4bb89476a87e3e3b4587258107081e0473511a3665cf97c6cebdb761ea803a44940428f68b90ad3b2491132a8519281eda997c63751369db05309ee792335222df662feff5014edde5b018c02b19649c038331f6802a267c73df309251d87b901fe03e446b3a642156791a0684b42dc0826280eafbb4c280be12717c1c8344491f394e2e85111e666c86a815536c9eb9a7c45c5082a1abae5a69046724cb74f3533ab71434d682648e390a99022424c8af05c13612c7a613d812e9851295f6d2b93a073e9fc0bf224f053e888f510a8a51c4b1e1764da09fc63bcfbefbe45fe6839e7b1227b25a12b6a2ac94fb4b59fbd9b61d9c6af1e952265d1b3d9f45b6bd3e041c4e195c9295591444c6c88001afb8db547fd07e46fb70d0e6c3c290d628f3def35529515e0d4d0035508a08d2eb057aa61e497aa868f0ed26e2f71f37c15f5c6eb08ff70cf2a6db0b8f467ba402755d757b86385a0245c1683273ee75d7c675a3666da51a52879e581a3e067b48148b3ec1880852995e7e0ff8ffc9f4c0e4ded58b0a846ee57c0af6c4b6bdef33b629c7fc310ddb286825544f07c44f4e2bab4ce4aed7703314d2380f6a340a877213a51a7d0d6f72f416107797f07c981054ff33a0ea7320b210f9cbc0647aecff2542977873f44cad258e7709fcc05c1264a94bbe48a77e066a00355c5ecd27ea7449361c1460862b708214309068da836afd61ac811b7e1e1be2e3447cef4f35b41103144440c6dcd30094c0dabd61b863b4aae7a4d25a5aab0806f40798aada7bad09ff7b4a3b015214c72c4802849c01c6bab3ff303a97505dcb4101f19bf7db83b7770f319a171a4f58c597e9167f03bbb8604d370b32a36f12af8d2b8d13e53fcc98fd7fcdb02a98a5f6451b39e0dd2035dabf0eccbc03d660aa1e8ac085e5f2014dfa14d94bbb2c2339c15fd25130cd633893d6975b0cfb4ef32b95e2c57be0af4b9bea5960cf81882dd1d2a6a8d1b2b6962f5cc9541d3c6715b501c6401082278742fe106e468d96e7fcae039c778fe6553f52e3c54cdd9a0daa504ee170550caa53e9defd1b640268d822b891f048a4acd57aa13264b137d92f487e711c57e45a286304d2fc155a17e36cd9ec22b98364a27b2ae1a88d0a786b376ccb4cff14e0ae280a2c55e7232b74294f896db7a8b1fd15839f1a46458ced836ef8fafc90abc47ff571b9b92f68b80a2cc3905b13ceaa5fa17ce0d86cb33bdde1aa336db9e36b8c86f735f5574347c3a4c75f86b24520dce5b9c2df9f3b56bea99ad2bb8449aae782b025947f52548eabc02f0010fd69c23eac8302057d8f217f6d3942dfb81713f0fb70e93ca1517692e421230562e02c2c32d932ed7007a0046757d070ee50168d0e4af096e1d01ff9f2939d3eead6d84bdbecda5d37ca1b1c76c2e7388521f1801e1dd6985b876e99592398686b0fba22dad5078f4dfe00ede07e4a0c58d1cc3b82c9f41234a70d1f9a957053c1eabd0af06ec8fa0848f17582feb3737ad27f8a7593421ee9d0bb2aede4bb603c138107407f8b458fd1d0a503718d6d3559c59c77ec224df53fa7e9bd512640df750c583b6a73b77b8efe088ecc1ed064aa8c5db0a445f010c20d42bd0f544d712e550112e99640e2d697d6299c1943163e3276ff27c878c89768fec34e96863092e3ed797fdd5977fea236003fd52848c211242c71f2fac1542c0f49e980e30f1fe74d4576d627a7aab4edd333b5b45c56943fb69f8c277f52cfa351ee1702cc7549f1d21b0df2a283dcaffa2f3aa69645499a76c036ab51c35024ae839f848c927e91c0d9f67f1f0c77c1b9986619fc52d8a6c9812d7430c5a0bd3163383f48ba9c79969a29581a7e60c658d5a32a3b34c9ec4224cf6cc28346ea420234920a5e064a628e9f67ae25825e2ee304f0c6ff818324c2c1e274072b8948a843262c2efa6a1a9a1ffaed2b119af8f1dfafb700853ae3ab9e54fbab2ff79e0707e8a0a72deca34570b0a7f7a38e16022785a24aaa6ccf99dfb25f28b5bdf6489c79c2c413fb805818124d5382cca1cce2be47a6deb13ec947a382b2ad18e08a6544efb1132c309d239ab73618ce7862e73de06d8ff1af02603f700f216f5267e6eeb6df670ed5d2a4f86a5defd053932078c8bb2f278080b8128a01c7dd0136cd26433e26d19e5cfdfb984ad26ba6bdf0f14e13128ba53b6ac54d94e2e0a67386158962b21270fa4a4574567482eca61a6215803dcf4b8605a0b3ee36436e09b563f5bb65f3578828540fedc837cacc354315078199e7869d214fa66e32b25476fcd7aa667195186b2602ad6c5b807c1fcd6ca3ea16a9991f9fb0b429a01f6b5f2e2d42ea8441c384c82e08fd3a271fab6471e9b0a04a6bd969fe7222cb1a2f512cac4a7a1eea9e594c2f94642ea06c7c4c17d10e44d82d4a66fa6bb39e5c5d1c9335d55d60c115d398e91f6c8f149510014cdf6c5d2da8169eadfd6fd15e72ae902769e9d3d7218fc3a7e47ea85f6792d0965a8b17f784e24df7b3d501c99186e1063a14df7561f9eb3813d14ca21a07714e32915347d635e6d7c34bdaf2bbd604e3eec5b431b06c6969cffa5d00fad51ca3c6240d3ad97d3fd8db09ee466d1d809768b98bda39377f0131887c9d439cf196408f5066cd0329fd822a19c16432e2222ae144b76e33d0335082c147566966e5c4e8d62d08e760dbc8808665ad275f9ff2042ca21bde98cb4f815f9c0f644b8219cb9bdf6e2ae6811805a548e65644025885773aa783a861ab7ec761cd112051d1a68c0bcf9aba5b908fc947d64151baad001a72e7c39078272d8b6ab0ef2c8d0309e56ec101fc4db0a25b67bb0f57a9c6515a0f8679dcbf30e25c6f48e0c0c9f6bc9c7f04e5b764609396a1677d5847a422f1dcc6214f72a45d2d10726744e568cbe44a8713ac12df123f4fbca2fd62a781a9e5c941fb42857db45955ea3ed7bf739b408d97271d23fc462e15d73aa73231b382b339cf838606df8dd3662a067d5dc011fc2da95f4dedecfe06b5c0c48cad1d5c8a804b37969b979e6d6d3b847dc8573bb43ad67c1d9848a8e2f1362b9635d7a945e91bdf5eb3c0e88cf794367969da579099a13cde543ab3dc7e0667d1dd05570fcea0906efbe6af40446e2a19011063cfec211ff67e66957b13fcc45818572263006bb6fee47e1e24b418bf7be446a28a8c9e85c1659bb3590719f24d574ac3a02604f214d03fcb828856deb051b980c7dfab1d31d52aeb33b3b002f5ab0f2f68fff746093ce2187734d70a81629358628551d9dc99c323670c17f6656201ec8a8343f072d4d5a58253622fc7d61625bce644912aefcd56d1e0683cb9f5992e907fc0b836d41a78a8d37cb708510a4d11026db6815991d7416f172bdefe88f231485c2f58cacda2324d9e8d3a6a508849688b8f5d6ac6f449aa397c13e63cff40732d61b9397f0ac2ee030705c26b6ac5b27c707c059b3c1d2b934f4aa61cae8306e788015cc6a908561436aa58615330d2e07c0573e3d9f5470fbcd879322114f1fdf21fcaaf258b4800f02f8cedfe3a4fe3be2a95fb6b3ffe60617f89779e1660abcd171c0bf1e019341655bcebc7959d266af11a03045ec871a29debec967ccf23dbb2a3491bdc436fb358b334dd1deefd349bcd028e696a5b9d2a894f134205da346784b9fcb0a881442cc51e4d06b01d92ce1cfa57d106ceaf877b84d8f713b7caa0eb0e273ef755c36789c0d10e2a42d419c807941c74cafecfae17c6675df1ce1cf3569e954eb1cc9cbcc45d3bea49c8b0586157748d04e8c6c138f82465b4a733885bf1e81a8d35fd58ba8ccd1e68d0e6ce65cb55de00892ac14efadd415b50564c9f2e1268667abcf5cfd582e9e4ce923dac1a16854ee2dbada72da3a2440bc494158eecbd52aa57aa573995b2a3013c13b40742e051055dbe48c2e0ede899b7c402e59013dcf1c7001baaf2e6904ccedb063d9e7fba096701459f8326089eead8b9c3943dffa008b624b691f2eb360ab7bfa9eaf3e9a3f7fed8ed592a1be6d0c0034a10149c4416ea066b787e91f7e80235d25b3ef3967b60b584c67933b858c999c98c9a1fc30dcec7f846c3d36346e3b7caacc18677419c19613a076d5331b2f3130a9f4d2aff612e904571e042743ed964fef1ed8cad3c13380601f69d1da3d32d33f10430f34999896a343b40caac604213d18025b31f5ab68d35a980b72f226593f85bf466c9fae612f0d8c0bcceebe1d14a780946ebe7115d04e3a0018c456ef8c9f9e99fbae35fe71884e3425cb7c83bfb004f42843c484a52e892e2694428d4c1c57d3f6ca6e329ce6a62a03ecc46c72bc7269b7a39549bc2e8281fa677eeb0a3608437f6a071c3869837d85899b86c0a89f69c19ee02f45bbac0e5a6f644c81529776900b610b078e8621198087a42008d3afcbf35a6de13b56b7677bcfc42e9ee5895568db84744229cbc01fc2e84144cf7eb71ce36a7d458946b003b1f19824e841beaaf35581070c0a106395782b711469abb3c166a94a63ec3654ed673723a1bf3d406e53df787392985f4ee1ed5966611e0ca3c4d59029a6222f3977f8b3c30475a51fe7fc4245d5ba057f379bcbbf709d7f3e7cb116ea722669abee0b822dfedd05c6552b265a7a2a6f57e9e97797e4727adcc54d0a8bf90d1ced33476020441e2eeb1e2ddba413d1e7b526a0563bc04792a7de82755f6f9bffed1a9966f465701988134189ffe13e6fc58daed97205e23015a3f0218638eb824d7fc0a020b05ad63fb418dff931b0f156003d45b386291d1cdb60ef4b3d2d000bdd0609741bee945925508499b894ba6a6418804774966a1f03494a0f7ec9e1edf1d21865d2f25d3d1d841bfc66bac5d568cf5c8b411e87309de56be08fade1fff95f41f98d78798e3b5728db5617dba7f7378ffb1e053c5929e192e9a5ab703b482a26908aa76b1e4b159f64e8dddcbfd43773fe3d52d4732aa50768070d16966276ea1b95a4eb49e6a1169af5aeb9c1f6d9a5dd97e787f7ae3a5aa83bb97de5de643f6af3493bd0f89e01aff995e4c2a98c8c9a6dffd138a31945618caa5d20107426eb8e3c2c2ad331dd81af212790e7a2f4a2f90ae01752592457872387b702d0593c38b531bf0eae6cd72af1cc6f4befbaebc86e0cc0b0d4aef148f6cbf93df0a7e53fdb6f190a2e9b69f08fb8251f99a5894cfb22dbf8be6511104dc16b44e953bf384210cc0be85a2c74ffaf03c988dcefcaf943265425940d6e722dd235c8543bffee0ac3b09b59241f5e0be019253dee8099350ed93550407f9fea247046b70d748df5da8d6f6c0ca8838584d7a5e885c890049d08ac95df13b2087e7ff0c0f19c13c2c42cce4b0e5ed3229471562809a63a2c0099e48d62532a69b2c0d34b8d748f1477c3a321ca2accad291e07227378160fda0624bbbd48f97411ec49a107aad88d159abfac1880615c365905edd26529ac89dd50b744d5767abb254808be809adb66f38472876f4a060c563f0d4603dd882405cd5ae4e7d0611f737817b8c83874ab6c5177e641b149d61700bf1859b5825d7bb320177d2d8f36fad8e287b3f66189ec006d0fe19b59d3d7a945d1680e423556eb490becc354643be52aac3a5a88c202632f5cc1fd00661289438a6bcbc58842daf60b837f3901a86caa61f5af5be7ae6b8db5ecadd40787cf66d1d2a45523f01a3c2af085dbb2a3f9dd97a93329ac5bcea595b75fd9b4ddf75de9816f311d99cd166db42246afdd29c7f77c3e3d0d1dc0e91fd76cce51fbe499b4a933c21e882b32765e8ffc87158a277138eca73a213d1820f5ac7b7264696c06cd941ceeba21bbb547c7ddd8e3c313d396403a650c4d17b3853e6befbf61d79aecbfe7720692e75daa67ca7daedea95d96ecdf16ad351c7d37e04e36b893ea40a35dbbf08217acb2aa831d4a1658fa8652414b5059c6223df8c0f0f3bf650f91d3d73268101e0fcfbed7292a40a80b13768c14952443538944a70888b6d085e08c2b68991c8f0ef2091c815bc9b055d17880489912e41176415106a41cec9b8332537b9f4223f7b9ad8a937c4cedba8c6a9318cae7b5a1cc0ed6f06385d51750e78f2c7a9f8afd944b14d63aec3d9177f9e28028562e09a31551d2386b5e782861951280814984bb0549a2285e89558d844764317531495b261a2684f77a717a01a7b8048974546c4d1cabacca30a7bb27961ad977dd46a713da3f969b54ae75e6832b1a5590ad32e52c6ad2d6a8613d8c6d8c35f33f3df66389741f2db4cecf50c121173f084e651ebdd5679dc06b2c69000d7b183fe26439e117804b289ff081a37306690beb955ddcb6c6547cc3b4fbc790d152c07408767bb8cc0ca65ee1f74cf33bea57b9ed0c42bf13753408efdd6be2a5f3ab08ac3119751889fa0f77ca7a4898d5d84c928984c24bb95ca05b8a0cfc8392ace8875ea343b903e99716dc9ceb6bc036550988581828a2272adc6a1cc3562dff671f7b3fb8754ce818439e61eeac9d3b7ee85b9c616656b03cd08c87812ca0c4b0dcd0a14bad61dcbc27e157d015a1443f2970e0bdf2189bb2c0a8b95b8807544b5976770d42fa306b1efddd238fe4e92f80c1d234a7aa9fafcab0d31cbcca5cd08f89dbf29d336d77e9fe7d20b4a69ce3ab1aa5c0f1f49d408d328157ac0811881937c46e45641ee18e62e1d9a47507d7dc382408237a2866d601adfe75e9806565e98f9bc8b9e0aac97044c448035441ececd2315787107b8536aa4cbdb3156b080c1efe3731b02a6420739480a8463a9eb0d041399d40d3751f6ea40c484700c5635da2cfdac77a792d31f1f45036e198284e787125c17c720c557fd97fb26d015270b95a593e7e3b4d97cfece714a90e3ceb01b1f856fda6df419cf9070c1e62ad3a7d4bbf4cecd50f0ad531519a6ba21dc3cad65575ed6881b66a2ebc90261c82eb72e031b430b6fbdc9636cea7a4f500cbfd760c1d11b6af7850a9a3fa2c5c71979325bcb4e390e2dca889126c20ad508652c851f83fd4927acc365bdb5850080fd57ae1d4395b63cc69584f5faeeadd5b1c1049159a03b1636166422acd6b7c5588cb1cb4491f16eba5709270ac7dc7961c92105bbc183ebe9feab7833f6856208dfa22ab8bcd2309932f9f3b542558e7fc65703435136766d63893e34036b083be5c1e87f65b6eb3d8488579540af29f4c798d7e4af71109a41b1a097203491931283dd81b881b569702d809ef13066fd8cd9936d96bbedffc242b8f1f190f51a3ba205c1fdf670877d39b1ba50438536fc66d44b9fc98aa8f342991b959079caecec1f8521d97f2a2acc6bec08697842dba4f2c4554091d6c4a3b332ee042c8971020fce78f52680b1db09a971a780a9869a6ebb639d9f5f2fa4443e6a0e424320ba338436734930723e8051728a69c4aa530e4f6cedcda806e906a9e4597a33a34f9a988fa082fa807629b4a6f2535ccac5a5b0d560ec05344c75563ba7739325771c910ce3391ca6fa10a90524976a9a68c598cc94dfbc217550bfd3d45dac34b2920655217a42f66ba12c8e9cdc3402aef7ded87f744a8cbdd24d5699c5d346a42900e4196434b292a3787d6ae0897f75b9c1e1dd11054a515e68064831d0f389e2b2b8755219563f1e24a284a4d939cc704f34f5dd1af86d9a8d3ef52f8f22c38879070ff9252c7eae8c40f68b6aec61176226590a626e27c57e8f211de83d5c8e12bb6e57e8ddf81647571729b15577c1552940113741993ff32b880a9c50495f54109ab1125fbb7774e5a9d675e91fca57db6a2590e90b5a0036d9e129a33e7121509806b6bf9ccabe2c358ae714dd43b1e122ae2b6aefbced5390bed58f8ea18bef081386f68e85d55081c0c8b1ac81bafa9352097d5b1abfe6c04b9bc81a33c66b7b78b43d5307938447fb9553105f9ee29149b5ed534afd558ed51ddbb02cc6bd3decbd996b465ae2106ff5ed33547c4dc8b553b42ccbe3d86f41d988a73b2ab25bce244e59d45cc47938e7131134b08568aac6e2b6064d2ff2eb4330be5c20431c934c1f0a34a12ad5c3096cc42ab3c9a8ac03539246c28c50c7db7535935f2b0f20fcf4d327126290a289bc0cd0d33eaa7dba05aa86cb81884fa8fdf8a28dd250782434a23855e97a35474020d349a36774ae7e4f5d9e50ac4316978186e1a8a26c514eefa6405d30356ca514c91e98b67021850ecd645522e514ed15db612284b8587306c589d50866bb31dde524fd118154f5c49d6443ea08b21897d8d09301cef498618b078f39ae6c6fb580e6b88e404d851e57bcf9b97ee2e34a8c00c895f495de100e26337d5a92895ea729668f01c60cd2eb44ae048b0224576c12c1e5a863524ed53e9bd8e7240a55efc5faf0ce98cb855edcf27ce375f1970ee777929a9f27c35d8057c0a9dc89afe654f7aba951f1cb4ab805eb82d0ec1094a5b0864f411b0a987955e0325e2ac9480c212da8ed7cf899f32a155e284d3966c7a82499c1736619c4fde1a8c1b38112f872020cb282d637ad328624cdf56e2cb052c57fa4711674b12323a34c2fd816e9fcc819a41519ef755e87af0ec481cec63730dc8be3842f60caa9b046bef4157ccb2dff1c36185b464821ea266afa38721eaf4c0395b1c252ccc30270ce5e75b36237494b31492b5041e0f9089bb0ed49412797539f28c5dd4ea5c7694dc0027069d05ad3ffc450aa148f7849d62abd32170536ed0ace24d1baec1c34ba58827e959c64df2c7cc4d9b21c7e27a9175ce07664475a5a4d8895a2b5a422608f20b2f2d36db78f254bf02b479d207deec039f963f7716609f25eab97ed914358d07cb90192c30437e4b0d2f22407a766faf256011a944f35355d4a78086b35785e466cf16191852e587ba70103ea48a8096dd138d34a1cb6e96912db1c84e223e2948e69701ff0ec2c0b632541dbf0f14603f44127aeea05b84ef1a3d9e791deec4b587dee064cefbe532d82d1a409f33d218742a1bf75ab5df32512ce076bd399c216573e4465f2a815986fe42c755ba398d15317e9aef989ee65ca0d8bb5bea41ff88fb690632d6abfd8869a1237d4c8504e16b05655719fb414045c99754383038b926f02d840807a41a5373bcdb3958014c9b6395457fa203ac1482ee2d826fc5f9685aa135e087e8d57c7de749fefcf2070ca9a6d8da057df11dc65fa18bccf0ea908bbc4b0ee75c63c1af54814a07812bce6d8920be8050f1a543c6ef50c27fa68756f3fed970c2a6be397f87a08bc0a42ef564754af6f7dfe250613cbe444d3631c65819edb75c10f402bdbc91aa2d1e1a6ef3a834292c9455f650968c1e2af68718ff31c2e60b3e27c35c997fbfd89fcb04afb6562ab9199abb7f0b11b5a11a5b322ca083edb682fd4d5cf2ce9671c0bfad1751b0951bd6be2f2655fb1525c9cf2c973c254af2d9a5438a9076057540a6dda45932c3b383a17382658cc54a3cf911d179fd7c18adc092ca5dbfdff30268712af7a232b611a73c2178bbbe9bd305c1c0db107638d7bbaec0f0c537277164dc254fc0e7ca01b986ce011919cfb744e094c6de61f1ca8019c30e41ba8afe07a34c3c848114bafd89ab9b5b408097a8255f6cf32b9d2bc4e82312a1005145b0550ee08a27ca456bb933d88e9e31e6b7481d19f5b515d7ca916930b705db0d142056ceb9b6a61ecdb0ab770aa705f9fd1fc01ab936b210c045a8cd581151b94a05c00955f67219f96d24a21a5cf867a325c80fc6a3c522d12ddd417df57464c6929999e8ae71de8a4d73ff7464acaf94a34462cdc3235b67de0fa1659c6f97c943cfa04349c5f01cca4242d2b88206d087df0d484da0c66a6158f411cda80957925b2a02212f274a92cd1297b563f1606ef05d171f572ad3b21fdc4e68aaf2b0fd146655dec7cb4b6c73b2fe9a0d40364584c26c9f146108d526142caec7f9a8229aa42a246995f17bd30ab6130464719513ef7c0d38371d140bc2b75f6de7a2b176efaa1faca6208adb3adccebd1703da5666af23390216aeecf0b451f5845de51db600781a5a62523912d903e55bc36b0e10445664187f0833f6b45044ac3535dfd553342f49a4e31751413e58e9b205583365a2bb34453c2f0d70f9f844389786474d7c90e5f0220b48783198eb4748958460f97d45e5ed55500d99c3a214e5f985ad429c9681407c60606a536c802b83021ab380c50e68a61a2a6f6aa646e96948700072755041394c5cd41398f7550aaaeb2169edb934782f9531084fd93556e81b41f2e5ea4c31600a8e0c49c3a899541961e646640a2ca7aa79d2ccd2f13015ce3923e0f1749c6200f6ea28285e779aa77932b9f2e1964c22f66bf81cea3f68f138ab058a056e056dafdac2d87ad4f0daf33acf0b7ea02a90250c572485c3ef050ad8c2ec60ee1d32b56db80b896711bd2ee0db08a8e8260282efdd042e9a8505b6560da57f89d0693411006b8b773bd43ae11b12f750b7ecfbeef782e50a59c9401966ce035650ef9c0e2f858dd33cd344ce1e226733ea63afa949d871292049ace2c59abc1fb0ca58d5a3ce66d4299eb689172c89b28043652d680a0beaa516cbba864c7de0954008e358b7cb0f099124ff6af14c0f515cb8fcd0eb919fcc40d0054648c0c0ccdf324db24015b70063b3979b2fccd2ad9e32ea8b2af0936c21e5d44c7a164e7440ef5e03f2412eec5218ae1a269b74b8ef99cb8dcc75e4235a1442368caa86bcb0b1cfdba3932258e5ac4e8457c7de5d727c6596786d5a0bb8dbd45fec6c3b06023c3ebc1bcdc6ccb667250a9af030097a7c389debc45cb7f6e6a7970ae39be3b60df6cfcc79308d2c0209fb78936f79f9d7280562640170d62073d3fbff81ccdf18501ef0b89bf12bb9a1f6e70a05e4cc05a08900ce3d1583cbf821f03e0dd37236d5c9bf2977fdff17eb1e1447b1558e24e42480e668d8ae03b0ee4c335cb65fbb102d506af29a915cfa694c33b9b187254e520eccbab3768c771bb5f6a8f3a730209f53d5cbc91b9bf803574f0694740a148c1847a54c7b0a8d1352a645e11f22034561ba5aab7903c26e22d99fb0510916f232c62e9b3fbff444d3cf36e4901660e0647d14404ceed354d7ad48d4df57aa4229afe8342bf512440057fa304a1c695c4eb16017f94184225daef07b9771433f6fa5bcb48d82e5ad899dc6a1d39dd4a0732f4503a61240dc6dab69663ecbe6a8ad12ce0996947fd25a5e5a42673a2a36525b1cde6dcf4756b638cb6cf2e2b8aef1fa71ca33c410926f0f2071185e6642e7970353f0257d91cb16d546dfdcbe7de680b4e5a92fda68c6fda8a785211e905dc80fc7612e83d0d6215406ffd25e5f242bf25b202be96e05f728fdde40d32b4dd91fce291c50e6d0311fe45130851010f9a6220d8235605168c98f0b55e080c4dbc314e3b78ebed0ed029d375506ae0296ab4c95d6c1ab366bf05d305def6953efd40a3284fe692632745247095cd62db28b7296744c24912bb395afeb9edfca043cd71b0808eec10644423e9b3fad08aec6029ac13d00ea90a7ea493e06e234b0941b502baa6c6e256ebe24487debf14c61971a46fe7fdf61305dce3e019c7a7d975ddfed913b75a8a145aa5aa1c3d318dfe837035e84faafe53dd68858289b9661156add0648b45a2784a474094c818e19821ffc9f3d459953fb9364d548a005c656c771cab979da89a27c717374a92f7d5aa72628ce813f9db783cd9f81a4a6e6e74bab281d12e24f48b0d5bfa3a65aa483823dddfa95891cf49caab1252b253066e808904d53ce752a215ecd92583f246469a72cf3f9a680a4b72cac0491092b06eaef6e87019519550f1ecfaf239467af62ac1fd528271bb4b912534a3db5669b93cd0592b7979ddd317187f3c28351688bdcdca904335982c557ab714fe7860602f7882cc8e095728ebbd556be9555d5e5f0a07233fb4a5170136f58744998f61a2f9d506e5e7720e86e17123c5e37e8c5312a0e7d8f643377d2dfa736722387abfb7f92f32f448eb99f8722075dfa3f14437f51c07ae9caa66b9aa43f494072fa785ec95827b3a7b2e64e47b3ea5d9ee1f899b53810f1f3d0e1ed4075dcca1c668cc093181d892369c3a38e4aae2a304e1e3a159836b062c213000f701c964dec9fcb6327d031ccd0a16c8c59ce3c67114a2be6e03bc438f81886af8afbe9fb8137278958738200c667a240adb8bf720d4e776ab523942c4882dd576654ce816e16441134d213eb5a04789f8fcbd5bd4829b91bf0b222e15d5db9d047d69d551a4a8685837da044ea0f05b1952d8f8b0a2b3aac6a95cc2f857b843d110cfcd91394d8f17995da66e94b3cbc4b084e42053229fc4a7f375935bd499bf110a4c8400fbf1317617ba1c946825943ddde66f340032549fdf12074b134ea82b4565d23ac743feade49c9628544bde2779e59cc1ac27259f6e7b485d757876965a930113dac77ea3eb41b7d38fe82786c4688b8ea64fafbacfad97d912d7756d50638f0d277317a50235d2cde6ebabb4b8e73ff61ab9581632a57064b6a93a8834cf488ba1d0b4c1ca5e996980f87b8e489914338da064b7299a106bce888badd8a985206399d08298bb64249d621faaf2ff3509e5838d19d813ee7f63d0620eac9afb0f0fb482178f640da3e6602d185893a7649a2a64f7eda434f8ac76f6e56860ea43094989a34f69a0c3eeda01c1814f35123db05c88d6d427b70c0adefd084f10959df9e7241a35c9b1bba2349fca2be4c58add76721b24b0d3972a88d61715974ac4e0a0533708c72e0612d4e56ac4931b9ec640191d1e9bb780effa2a78be6b5f8e367ffa674e2c3a1be4ce7dfeddb2a33470fe1c8448cee7ba9d67aff9b784dc91dd44fd2cbd9a09139cdfa0ffce18e521aca3ca0e56576d472214a30d6d7f0d94cd366d66fa2ba269a024e28f908d86694db4704049ff0d1055a831a24260ac8ea98839d59961ff8939803d824c1136b9050a5b92a3796188429fc65109c7292a023748989f043b2abfd137c68d50d027b1ad0ac56e053fe8ed550c6e57c7a7f9a7a165c41e9d7efcda8e2ae8103bcaf73359a2baa8267969d30146067172b3497d09b151470bbd5951616336a71bba795432e9b6f074983e9808dc8af5bf4ac6d2a83d3d41a7ae3af78f1fdfe280085cfa5746dbed508c13cff71bf2776b1b9432127a19d6552c6e50e12fb16575287614f865b450b5b81522afd379d8a9f844de57a0b4d1d5d98d56fe655857b1b8958a1f49ab5495ce214a13a4f3053245c79fd1a2dae21684f81c2d55694d7f8861417a1a7e1ebc8118f7ead11e5018a0dc616795792b1a1d5013e0d5ddb45f0a1fd785e328fa8789386050044ba30ac3a0a8249b02fdb97e6e7e6c87f8c5b77cd1428c1b20d947b812ae8749217d2ea9c4e45210ff67305bb9eea1017482113dcc62d37516ae40b10e5d25361597831dc60b6af6d93713ab12a6cb4b105c692ac85a1187e85aa172d8750933d9fc294bfad541cb897b2ae90d22157b17737c3f9a24b5cd1e86482ddc24c050e87d3dcf1141e900556ecbc34723c6d3bd2a951528f60168a1fe6ecfeeed4cf35cc00dc3aaa72b15cba2dcdebb05751a05fa122b61c29c4d8cd7ba841e42203d52af93426eddee45ec4a2b242ec19f39b5828bd7701390cfeb63dabe300688993b6733e4b9d43c203f08548ac641ae2b6b00ea9e4cbb318b37dece5f9b8906c2cd16d7f7ba868042c193ebc72a108cd8b46b2ba6b6730112cf7b245e04d714a378975ff6c8f1929c89629ba27b81955d520c740110ed130a2cb88e2c15bfcb286f38f9ca6e4cae2bce083ca04d225b20fc26047730f7105ede3b7dbb9e445457e2387441ca68bce565e9db4068e7a22eff3e8e25ed52ff75c4afb3473c90ef616dfbb2372077c6a4882291d75f3d0a317dc837d50122c9a34877a1c914e28709d6817e0fb51771e1319301cd43b7574e9c9f73abefa7748a77012bdb893f0a37e268e2fd0dac77bd4eb203217554e8478b59f3a2f031432573c0cb6e1dcf43c5bef015fae73511e6a16e88b80b87f58d32ca5e3187931618cf7d59847a6aa1291c9cdda238f223a79c7e7b9047790b27e2fae2b8b39706fc84f2ffff1e05de980d40568b345daa1b3b00ad4f28a241e20d2fa93293a1f9a3a9d46470daf18e23668f4fdd78326928a196d78c444bde020d1743143cb19430ea71fc3947e957d81620ccb6eeb117933077c20668119a35fad369158ceb0d08295ea3c25b62d1a12383b5967f3849fce9e14e9095cd4281dbfb534a806ab9a16e7b410a1a0a9d3f9701262e305df99d05637a586a5d97533a62605a9e8b0cc1ea0d66c74e4a1f423ade640423bd9a8241a5003646fb584a34a48a59111c6f12a336db6914e0a2e4dc1f87d7399eddee3cab54055c84aafbec284c330cc15c1aae1406828c55047d5e5fba4f6a86e0aff9ecffaec57be51673030156b90f191d4c776610de82af0c5edfbdd925894ab1249344a92a83f93f78c1324cab8cf665325d5e2a3181eeae3c6cf9c0d6f0cb6c7b2a396753634eeb1c567787e554043c16fe2189b54ca7cc1baabe6e3ab1a5abdd5dc9be15ff08288911976d4a5bbb2407304910e9d01bb277489f7eeca0810481e6feba81ec6ec4ce2b4ed6ef32de104324302b063dc9337794915f6a1f9ed051873d04c436d642843b2bc901a8cf151f09d4162c672618bc5d88ef62b2301568d5293048b7a23e6b7bea33789dca604503d07266db3d63f193b3cd13b39c95c37db9e721c55c22adda0a98b2fe5f7533d7e40bce3eec7cdd3301a6ccc587523cdf2fa0d94c9c549bea3067ebfb2101422d928ef5eb23f109ae1ff4e380ad3b4acb6fa24195237a7a4b6c39ccbfe3922c73dca5adc602a95fc8ce3102e0378c32b1e0e163249a14bcc290933685ea8f0c3cb97ecd8370ca5fcb2850f6a9a29577ab5a5cf624fdc9b38162cc9c4529106a6ea2cbee5c27a3ae2b07c37c20b47cc3ba9f7caf91edb62f28b172875d5f05dfb6d417a936078cb46998f848f3885df5052698087b7911729e2e1ecda98df600d9761006ac6323e0238d6727e9cd7adb5a0c519405e9f48aab1259fff10a2408b381aa0902a5f2f9969dbec3e1c0c4b70435863d51a1edad606c655bdd545cdd4d65236283c4e57288e785b6086f2a917759d87ab12dfdc96a02a1bd5599f78768b45549ec09c4ccc570fa72027fef4c1197e34cfa0eb6955cf9e193d67a24cc808530ca860be391c406d9115c07178c979596038295bb8b61dcec2413a7e261cec89b4469feaa4c6a032d79b66f9a7e75f07946963a9ea36df4264cafc9174ca713a4db0d7610a16e75751d756dd86e10588124be023c80050727b8aaea87e16b0bde7154c8c39006d5f89d6620ad0225c25835bb8542941cddd8665cf6d7e26430fcacdff7cd299b5ca1fcc5a09129fafdcaf4538460b806925af4f100820f3a0d87d126ab188b70f540d7ae19f44689c7aaf0205718cd69c0b4c4e515f89efbe1aa22b32c409a832082af46f5bce56fbc48dd6338b85824530c1c0c04f86404bcb33f9337a9fe6a3da6500d51b504aa9e1cc8fdaee169176c30ba00b98e853ce8a971204cc85bf84cc21ad9392a3788cd5c83c2ca53b036a3cdfbbe6798b030ad490cc998b3913789e43ec0463b53a8436d47731cb02051daf53344c12232bed1fee7f48f877616b4ed3c3b9e9df41acea1a8b80811530ac031ad051f15d951c065402ad58b12ed8e4becb0640caa9c4dd37e3bed0b05481820aebe08e6157f89d4b5b42ab15b58de6c61050991d6b4c6071cf1452dbb14481e462554d08d4d010b192c0f6997f7f57bc1eaaae7ae4aba69bd4e232af66cd2769781cfee3dc6e2e74dc41cb91f03f93e161bac773ff5561b67fabb1fbb0db2a5fd8cef7d0ff1d56c5f4670b262f2105e6ce23a8813c1fbcc23fa8b4560aec8cfcd284e30fa9f76a96ea0c60633e04c84a9e1f3014e28b24da02c949cd4137c071666484c859610cf43199224012afbb6509bd4e7008ad10ce0f43484a93abf8081930ecc74ad05987a990f439d5f22777a77855c4244aca314653c016753d001955eac58c0b2e515812710873fc1f57df167b44ff3c24c95dfe69141fe8e24ff8b87ef629d4d99897de88efd7e2e669f43efcd5bd2c4c94368d5901ff9b8e4e45dbc315ece378bd4511c80702ba8da1728b7b5d8b81e4d552b4e6ff50e5cc75bdfeee51677eb7a93cab9a82ebb36e38a56895c5e36296f03fbee35810f05eebd5f8c996179e798f4fe2953b1e64599e0be846c96a98da0a5fc3fafc9cd3e991bf86c327cae3067223860ce4ccc7c3ccb1cd79c4228a2b283813b184fc1c32ba889c92e001f197fbcf166eb736f14a26e9c6074ac33920d538c30aa6fe8c16491d2ec2208016550c392a2d0df2673c9438a7f7ba4058c3fd34da7c026b92569f202e01cc7236bbc8ad9424ca3e196cf326c8db44902a5ea60434f16c80006572283c02f29ade7176a2d48e7eca860152fa29afd3427e7051ff88f7cd1383e448ea332f362fd5a17cc9f31d0e206a70f3357e5d591b66b2f04e34e5524135794eb3846a9921dbdf256d17fc9cc156689f78b1e8a24e36c216c3108bded63bd3aae37ee61ee6b2f72f0a1fd3e609a73575469d5321a34d49c9fc9079209af4e30c36a19472f6c0ea018828db75133d7c5294d12902f8ce313658806c2f334c7696d1ad5c7c3f3f4829636c1f574c673d8a64577f8dbf54e880f230b0c291b94a84f4061c3c97654a75c89134745aa94e62c8d87daf5c41fc981cec4f4807f96da9b8a222f25fc2ef766526f053b986aaf3e3d6e6ef955ef26c35389e04444ee956b6cc5d91e54536b75685db1bdae4b8f13daf7408b6e6c5df39a10c879396b95c66e0b1c4b825b42a16e47a0925d8401b1327c3436ab24265e33ba4e8addb5469d38b24045266f7fdea62d6579e4325aba3e82187d704f7c3b436635a87f8184b90d6263d5ee0709278734451d40f886c8d6342464eb9fa3df8326447ea5348ae8957ac6f706757803842fbd7d2e04be539811f00ab52f6a3ae662682d57f9163c05abcd0fdc8c3542fc931fc91f55530a75db1bc528357b82e682670ccfb7f1f001933d0870407ddfe9e70de13e40076d2fb6631448a58afacfbdabf244151b95a5d681f0aae1323e8eb750410b1ca2f3f970b6b2f068e0cc9652b4e1f3b68ffe7c484f9a9d2b9ce4c2a8d74734df800d95c0dda2f739add32b886efd59ab3d2961833b63a292bcc24669b39a2d47c2f119d12485ba9bf7e2baf2360c58dca10db214249b8cbb4fd430fd8bf808ec8bc92be945633a278301d0cc8e505b7eacdec1994e1d8e1ad529ed48e0331937bd635430d8fa2e2863539eb0b8c05e0ccb22d3570c72465b1f3eb44764ee0f14c057ef09f47c255494c693d663410a006cafd2800d2ee8bdc4aca745eb88428a78a0308afe2168aa5744479495ce9151e5756b62228965a707958d9cd6d4511a095c6d0095e60c84894ce9d0f05e81955d9bb0b64ab1cfe0fb0a446405efe244d6066baa8e340d55e818e53dce9693367ddab88bd48a704e4169e2cf78747b61ee784284749755c75886f7abb3d412382fbc1cf7e3a76ce713e5dd5947717e0bdb8946a15751bae729bfcac102037a19943965cd56cacc3d22415a9bc1222265f8d2dda9b2047d2e82bc664bfa257fd5c328782dbd0a6a60e988c332f7c440a649ab820c3d3ff1f1113090ec5eb30b9117d45b52e211a1f2b0edc8474d0248603cd8f96a018b5465b67db28b2d7ede5799cc534cd498a29a2d0e6cdc49646da5454cf7500dcb6355bb2d710b997bca1c403408a0ee255c80500e195da2f2a5255a998b1b6036c036a4dc3bcd367abd1550d2934cbe75c597bab1cf00cbcffa85e77548d7289de50db88b3f349c3cadce0fdb966ad7394b3593eab3486e5e50868eed4eaa636d42b3152d3040f3ff4205b0d603b5628f7219d5330ace211d8f7239da6d441c25fed0143a99c941f3af4c693090a2add1a3bd40358ebef5d0612ad9135592eeb8ec6ae567a08943cf1257397f1d4c363b56a4b66d2e9953a22e63f5adbcac15ab73a09eb2f3f4aea93e7b7cd644b0a97e3808bf9b83a8d0a7f39087ae01c23f18a03430e32186792ff6788791b7c7d361c6ef3e9e8b31ebccd6ce86306544066d888565eb2ac82bd7e38650712199e9237b062e92c8b67dba904f48c27617c4b1aa6191288d5567bb880017d5d9bc57f555cc0400549c565264df8a419e979ff041bdad00cbebaef1a8bb0b50098fa7a59def8d92675fb4f8ec09855ca5208ed6b2a49eb995b2899307c8e17b9667e007454701b2e3b2bd03979dbff8fa47440b74d25bfd4240ece1e34e32519a54a0083b57920a5900a72028a798c02835edcf698e311a67b033a79d50d20a6e2117dfbc08657eff9926f669f72aa85221f749dbc92967c7ca3a3838dcb42a6db97f28a755c93a5fd906f308088e8140883cb543a2670c46731e32133c5ec47eb4013e9f83873e7f4fdc87aad7b8fea307252ad424c2969c67ace27cc702babe0175b4ec50ca4c648d7b4b77541471ca82775a1b384f9800e55a60ce21ccd111b9739648d436655ddee32f041901cd7265df9004f33274b5bc8e9e08738c2329dc2f27a133015988c1e9f987cbc7b9c750be730df5f883ed6182eae0ecf372a5804a5071289829e97ac6a2f570859cc72187e0ec0d15a713e61097701da86449f7ace1e5308d2b2de3b8d1d542faf310e89afddf1f4ed1a937f338a1ced3c4ff747efc648c1272dd45fe6ee1eb130c609fe0fc9d7ad1d07a3dbdb1811bcfdfaffc7cce4cbc8f1a973958b22ed088221be2e30db12c8decd927848d212e12bdcc3a7c69b460396441d2712f60bd3e93a0e937a3cdb933de889e717b87d186254feedc7ec26e4282441095847f258f7932740d89acbbead72222c8ad05397ceb9c672933be35292c5a8e1c060b83097a601411c0a7b85d1f95f09ea1fa150855036f40d2504550d9151aceaeaf7bb92f38a7a87945d258def95535d1a2a43ecaadfb9abe0745c39a10b12215d7efd5d44f87a77132a513dc786bd703a34bd4537388f8575339ef0ff96546336a37b35505f59acd592f334368a45b70c22650af19d122002cea2f9024729c302cea08e1ac348ce2f4e23159b7bbd5dbac67d4945990d01bd1c716d0b4b76dcbbff8348a9041da312d0b3b56126e852f5ed85e753af80da31bfa44ad582e77c720d0c5bb5c05d97d9a6da81fe66a292ed4b7d5a9f4645512dd19e90d12d89d19fc8be1198b1cacf86193fd225b3f8b5d51980f9d1532e2ace1baad7b7690e99ce378c80873b8ee015e70ab2310c8ef61671661000b8e302b613b5c0f67213209c53af708532301b623864c4ddae168613480b94fb44be6e21158c86d064bac1d0dc9d8abb0e1434bc6c3f194971160344e8b6725d9ad2f1be20ee231f20ad9253865f468d13b280f341929738184070c993b14d60fba13ca7837c2e3ae107559a522e50dc00f7af8d7910264f67ca980779397f30be224a350fa49a13d4835e738164c2f6ed6c5d412c09cd62a0e8a96f344c4d183a8dc992871c34cc495aeee23602da6cdc18d297b4f62b8133b40aa7185854a6015f0cdeaffeb9c4b39c5f4a1ec73990f9eacdaeda935ea024c6f7b69039c1d6856ec7a51fef29f3a54f8cc52e9a2da55a9e9658c6614154152ed981a06cc57750a2c368ca1e97e2a1e002d30c2a7a757d0f8223395a870f002fd0326fc271ca0b4d15d6cd173a80d63e6e185c28151783cde227c2c787962699082ed184def0f3d2ee8b84a2449e922856700a65cd9178b605c9b24e501b88e64a0fe45cc8ee1259f606f20a04bc4dcc5b8560356400961b1e2ea4b48ecebfcf469ab71064e7098f843fd9d5692543e6d25adbc3515e93512a750afccf5654043d9ca3f927e3d1417a6fe584744044307d22292ceb8a571ca3484713bbb20efec17552605877885d8101f0b618989db59b03611be30ba555315165580b63f46d4fae3b0d92004b68d9cadd4c440fc898adca2421ef617bfbbb7e2f697a8310a31a711358afadac360fc601c48a58ed41eea0e059862b73f685a900bac7a0dc7818d02e9e4cacd4d6577b28af21451d961fba4d3c50891ac5ec5ed864bc63fef4a898b529238e01d5728dd5f144c728020a49e3fca31e4c6f93a52e619dcd0bc48a99ea3a3985f2352152bc09409308bd2fc9452cda2320a3a2eb42b17db8841bc4068386712e132658aa863d3dd5036c60458194042fba25cb4eaf0490d357f49b8b612117f4a7e5099eca480876080fb34dd481aa174133e459bd3640da3d209d79dadb1e16bca87cc2c30a966bded620f8f50564cb13b22fb49a63554c73df985e639ef5edda5b522a354eb87bac9493bc6925b702e4194f73090d12bd80a324e69437e4dfaac86703e71d63adf8604135b8875689bf8e62c23f510f1b84193069b7a7fa72bb8a908f2d0ddf653046f957ca9d42809f1dfc9405f2699b77e0579019af04be69003f13ae1d0f217e8dd96c576c35b31903e7df190e2fd12ee1718031e289ed741608231b3535793e6f268c552c298f75aaf56a9eb30597e8a3b50d455d0ed23c1c218a3618ce425c4e1f13ca09600e28afa43ed684a2b1531bc15305155ae344a6b15321fa38c87a764995f64fef7e9f54f1b06d9d909006c9bab3b64cf9a9cfab43c07a54cdc1355b1f3b727a59ce60f5eff1aa90e50e4d0c7c5f26b6b88d79beba9514a1c3b27934517e1e87e02bd101f136fc4b05c81f4b137210f10e3240e872ab9223c6948bf55a4f30450f3a0f3107ac6da2d6fa42155998234fad2b1427721a82044625f4d9575ba8585c57a190a6e2cc5ca4509a0080228e01323cad4d396f5aa635203162341854b3f651edd4e2639fd81baedcfdc5673a605553902a233c3387a1772dba60120b9f48fe6e530c94da062b7513f96388957ac0911b66b815aec711997c30d0024d9cced1f0ea5c4bf90e8b51c65ee9598c43624c375364684ad69946869da2b265acf5f80229ee6c21f77053cf2b2bf9bd353ee174e4bf4418505c30d30e292b3f397f45f3826680a9a7f7bef2051ae0b3864e05177b43a8015fafcdca811d2bb7f950851ec3af56750e5a0c38cc10c4c78751759c07e6346e16bfc1cfcdd86734f284a5a90832b351c2ddbd2395213d43fa312d551931d8dcdb4d8339df158ca650e45e6e907bf4c1a1d08199f83ae59c609a0047467b22706a9a3a9255630fdc1c8d351bd296642832d4b097b2d0b020d33ef5094fbd762700f8a2ae23991be6251b84de697a2c905f066581d781ed2cd4749dc31554a47fda0f93a289af10f35e652e87fe1163c2990f7fa3197505913459d95606ce11b4cb000b28c4214a0441f3710a516cb5032883157c1fc3941b81bed4a72602106118e8a75541ffc8c19abef1f89af29a0367a55481c76aad695463d8eaa92b9420da86206810967309198b9ab31ff900d3b49d0c1cd00e6ee261a65386ccba94b00425349b67c7cae63a6c75b9b09e0d23afcb80840966b1ef4b9e61b26b5442803523c0473799253888517ac24cbec497849be0c81cf08298ec7b8648acdb4a8190a5838e2305398d5d1ceceda7724e5e3e56f745b34470eb82ba988f9884267dc2072726c0e84029bcaf922007358d002d498a35852479aaf411e005bac6218fc2f4232384686fbc6bd76f97f62ee2d75a08128014028a22480ff2f0b40f42b9395fd2161149cc433aff79195d83e343bbee01f5a31c8eda51f2d0f48ad873d3895a91ba0dc510a4b27cb05e92de87b16a8a42ec0b22e1777c448442466f162e9da4c9f97cb03fbf7d16b411aad565258741ae49c5172d600cfd660987e0b651ce92ffc0df8bc25290d1fa64373dce021f49ed07dd0cc86e0f1fede0ec6d43432064af5ef2fa1853eaadfb20203d43b9f5e7a67f1a2a816bb3cdc62279e13bf0077ac5f0414a7d1140b900666bde6a77cc15d191dbcb7147778dd4073ef8abbe0209955676908a17d34395f0ce9690c9533929efa6aff70c83cbbe0e965f2e60a10d8d9cd8481563200d9ec6e03320839eb8194b65c637c3a590025147d1de26b22f44bd7d3f15770eb76fdb7edfb3a789094039ef383153b5ccc8d1e6691419c63931e3ece1f24d27bd8307084da24ee3390d80f7689ebc7abc4c4bfc898dba89133b32a3f63ca79a92878196ac30cf0114b46399ac3436e2032710aa3c25d3a4981122072c9ba92c2830768ee42897576e90c2e294f2c53d36390e5d3d9a52ddc39b98e6135dc1186731926552928be66896506db80258e09cb1e0fafb420b8342dbff64973767bd90b6bb3c38d855cfe6b3ae07ba04cb216c55787899c8a6d8213a82741c52474a24e40ddcbff4957d54c3d7116d3221bf0e863209e6d512547ecd7959d19b62329576873128c849cb9b3a1061f0ec448689bed43605789d4632e422bab784a097ad0b420b42a7c0df72f1034896d2265d7573c9ef7fc34c8711d920a24d2abcd99295748258cd1238278d9c487887459ad5241f3878bfe20eb6ec430dc5909c082482765b48f36000895cc5c2647896ffe5d965979766f576844cd6f3d281a0a5844c21bd875d63d7caf050369fd7a267233e706da976fb749518cfcdda598df90118bb2f3c8e80fe04332d6e8ccf88098835aa5060600a1c2cf53f1861edf98f7a9f1c5cfc4c44fb154e05b14487f9b72a790946d53579b61f94831ad848fc99a8551971fd3793cf50640e2e6a07726fea9d04a817b3a2d1c1a305678f69eaae6df246b06a2291eff857028c42b1a1a1acf2f626b0a862d2172b7767012a19689e8e6400ff7df4ae484915e6d8a5cec5dfdfd4718d1ae74cd614649edf74027b776583330e38dab308fa714c0fc57baf757eccc45ba99a88ee54067a41a4a9a0d0cf0c3e0648d5a3e46194f1ab83a4733fa3aa4594d55e3021d3d0b80e64852cee7dfdc868d4d4b5a6fc34a15edfdafac7bd63f765ab7d331c194934052fd4c5d41358a786d90a039564c041b72c4824220938e50610f540880cb6d40ae54482918a87bbaf1f02bc8f314cc242530048225a9e70009f21b33b411d4af8a2a9843ad708ad0ebc8461c8bbaf175d187c4673249e5a84b74945298e72780ce08548a3c1dd1154a167c2422a6d9ee5997f8f021933feaf87fb9ae1de42edd79a94f2df54b96227d763984723902ab65e7255e46a576b7cb0b6db105fe2d817b6a3138a29748570bedc7f524bde2143b49a29f08e567bc541b20f849608aa20081a67a568cbc6e0eb6c821fe44de8b9e76282ec83a8d0eb8c60ea4ad2de521b45cf0cc9e0f1ba1b083d935b3be0238beeb48c574f8024003badabf008c116e8d3a1e61920d38bc6276d0449575df05cdba3009d56c0254ec39639c68afbe0d2684646cda5c6bfc8eb8e4f300bcd119dc8dcc2e44e2b7402b07c33edc0d8043ccae8bc277fe95f65ecbc4ecede2eb215d51264c8073560667264c198802867b814cb8bab9a88d4b14e62756d94bd12d5a41b7488dc64f337be2b4ef49810a5509b9a3faecde68e1e4f6ad680233cd219fd303cbe33018f0d41c1bb344ec3b8b991f2581e38fa21ef4314d4ae7f0d4a1253d20cfd394419453065bec9d42823dd8239d93a33203f3135229472e359e0f14d9a5502f593cda5b59aedbee9126fb756d3353ef593966c40902e2525181d2ab858c66130963d8c5ca7ffc02f44bdc0e33f10161e285333d9cc5fdf6689dba8bb477ecb2ade55c28e4c4a976b1ee15c9bb00cc850ef4dfd5ae52af83497ed3bbe20230814afea61d61794cb2385ed55773f838be1c504b7738610d8228e6913dfd7b2b05033ce3bf54972d0560ef3cc1ee1df8f75e1111f367f6a7b965390c23efef874dfd935ec6ca240a0bbc6c527dc5693ae8f7c12405b88281b78c899355f2e61963aa9ba9d23b84dc6d80bee6483fd7c86c3d50b72098a5f66b623015904eb9da051ec87023d58351cfc8e06dac3333c510458b241989545b985fc75e6186ab2245e05ce22dcbd2fad62ae1e558c10a43d2f304f740dd44f76714a0941ae0582680293cde36f33579caaec218bc155b1ab598f437a0fb378b787691ad4045197dc240f7d7f1235484a4e8d5181489193a21fe5b208ba4ad9fe6c2d824b2c669e17fab3dbf15ca3b3a59e72382877eeeb8b5358044d1873c1a969620b1226b6df8b80855db63c108519077ff79bf7baf35818872816300b742881dc323f87e9bf9151c0dacf44abae5a6a56616ff9f7ca8654d53d31974cd682608438fdb48a9662406106fe0a769d238f9d0c824a5774560aaff126b133f4a357cc9231ff9c0e85f824f5cde24f2309e0190692c4035625afa504f5cc2f5c816cef2b9e8e911ce37600b668ae8dd97b4fbf842a7a33f30b6f7a02294cffcb08eee356ad84346f94077b5aaaabbe448b6e90291cff4d8a5ae79294228df3de416c0102883404cf81f9d63c51dd7f5a223b4f31e5cf148ca795cfe2b9eff3e6715cb887edcd2e8b06507f833babd845d3356eb460c634acef09aa31ed476505a022205a4a0f7aa770affdee047cc24d151e5c091564aa48456f6d8f21f5a91b087079d6abd260449ab13b8fd8042cd4c4cbe86058175c28b1e77aedbce2ce0d26fa6fc9fea0e2a4b4b09850b8080e679ac5110b5250d8b4cb273babc0b78360142edbf621455a7f9903a23fb33754e8c4528bc7e6d7488c03c7c7a658e34be1ed4ad81b0d07f728027ed205253384cf5576752046a63594640e90b19a054e8d772a77e310ed4561e938c6423c705175f5302284ba9ba59f93481b14bda5208ef5628afe5b7e8d7d7b96e28d9be4652cb2f433d44aa05d80699791ba057d141300476f1b54e60db105a0ecae1580a38628b2ad681907b3df955ec2778f932aca95b21c00398508702905b07038f981251b08812827ea8d6aabfbb1916f6c253e91581068369c91880dd52fb643394dbdd637a7baf68652f4d9cd5d0d535b7b43a7a1b7b888394ebfa5f4c97b01ef1bc6b8f7274c3a174dd9679c1025b08b057659bf6a66d8d0babb1563d00e7e980602526f52a145fffc6d7de8190fb68b25a00dfe8fd85b90a60f88347ed24a0fa45a2a23bdd8a68c51123429457d27f638cbb786cb0c267e665d4394cda117d13ed834c8ce2be439f8bcfc520778cc8614b69328f2f86869817fb43cf4c4efae2ddb565156ae26cdce72fcdfd620163c05042750b1937996bcbb5acbe1001215670b2f2b5176a602867d834650894c620c77198feb37e2767852578cd35471bb0e01459be2844c58ec9fee50df28fb250bf73798f341d090ada43bcd892da8d804011be2327ff579f2f4d0e0be5143913a83827a293f011c16e17150aa1aa5d8fb9036d09ea674374eb86e7ccdf28348dff28cee47bd07a5ad9540b19279879ab07ca8d21428b27b880c587bd3190d17854598c3618e7abeb1e4c1cbd1a456bfd7028da41b3b3f986ec871d86f1020c46bc0af0e0240dbb6c3c6eff190b333f58ef1512c8c76b6cf34abdc79d477ce30fe9d11783d656c212d84f4cf81213cbae413dc9ddc6d65990236e2890b290114c3d129272fce5e42c333045bc83c08551b18c88aa9c6d40c667443f77d6595777e59b7f7c3995453b3fffbba07287011338543cb92c17f3d10f9e7b022b7843a3cf7853e96a91b4fabdbaa304e0cb34398dba7f0ba1ac211b24ba03a0f95f8e8f388ff1c109a6588f9a4ff7715f9b17e18ee0111426cc85f7d877822c3ce0778e6104ca7513b4c044136a84f68957bfb1b2d07f2f6eb72e820d0bc134cb4c6767ac7d0030ab4eb12cd9ba447aa7ca0cceea0c62bd0698e2f3599d770903d8a38ce72ba20c3cba504081a11bcc5e62efd610ad18ee6675429a64f331ee7153291bb9034f2a870da93fa819a8a88db618c3b52f8b91282369315d48a703eb4be6dd9fc45534633e2745f085508d60ca27b6668129932046d93707b59114236128e835c78d39526c838122683f677b353cddfd22dc09b6b307a68452074db7d32db3815422ba5f8bb9773166c8803eb00ffcffba1a1080e57df2b12d056784e8869a7094f967d129e4394cb5a378baf7e8a910002061822e7269c772a35d39486f04fc2e20c2570a82d5166c43ff614cc8033106355c8cff7b343d1829b050b04a277b82e88d14decc6420489dcfb414c654e0bea9e7dd904ce491750b769b7bd271c94a21ea55031592564279dfc0329418ab7b0303576f310741994d433e614199c6543e7f63acf35d6d5098b22eb3336394f326b67dddc1bded7c452119deccdb99916197f2377523f00ea0eb33f7291a0c9d0cedc851466f65b0a36b66c6d13cd812a684f780d54fe43a4e4c575c6415ba2b5cb0a5eab207c53768d521aa8f99221b41097f0b498a3b004bf46bdb0e2df08573352859d81c7443467df67ceb99e847e34e5573d6c565c713e21279361f74ce436a31737b0649aa6821b9fa88fdb34e80544e65333a41637703f24140e3e82dad8486b30cb4b84322d0e4ceebe3d06ca726281ca81472739f65417d03012f6f1bf4b463822a185200aaf55f6ed5a30bd9c429b5b2051ffba7aba44de0f39f1f31872d8272e08be4d54bc86a347f594e7f0e959d22100e7276a94a18e0f6abef0da308cd78171a18e42784ce5f3296e9d3332382ef70e9d4230d5d36e60c44704f2e4a728932e171ac3e5c83b45942d75748849d29a01877bec756e4b55d3200ffba262f9fe8e6435fcad532ec7d1e254b37093d91b0858266a3c631979277caee42c396a46ac485f4c172287bbcbad5e6b270b95aa47c25ee36578a1f3da9c23cbedf7fd9d504fd27d09aa7113ed97993c4a7ddc0e769451478a79b5ae614280184bafa60fb1624553cad08b0bdac40cbe38a309602a533dfca2258a5a9a8354161df316a644ef395a30679bc9e14b7ddcc4edab072f75e47e646d3b37d4b7624072d2b542eada8908207b62e47c56a77e50b4a57278a6eaf87e92b8b6aaaaab195e5977a73c17e051018b2684cbea30bf1305012861a3827c876ab6e7155fb2351115cf7c301d448929553025d8a57da748d034d9eabc57b61c82d42dd33ac27848c4e267fce2e58d0aa4aa79eedd60ff3a88b00da972e167c3a031a1c066c5c30542a520730327d79eacfa1c1627f5d5324c6b21cb3220c49738e953617e9750262a53182e45006dfd1092918320cf442a6d6c40f00287a1fe5140fa47975d520eee10b6a0a32e583f7b51783fbc03fa234b4f7de5b4a29b74c4906af06ae06a10659a3af49929aa669da67051e4158ff0d7d0607730e9641b0044e127dcd7efa7af5197996c0bcf291f66af1061e4c99be7bb7d91fe9cb4bf4354920f8025f207d81d68216b420bd44b65902411b5e540a4eab28a592ca7b5ff831dbbdc87151ee5a554de8e40c4a50deab864fd7f29177b20e4ac7d3f974b1ee49f7ea7aeabd1883e0bf486369ac7b3106c199ae1bc7fa33040dcb778797f7d1229aa087128d77791aef2d1f4a9236d2cc3b3556cbbbbc8be8c3e55b78f0214314193517eb8edaab003ab57ef7350c41c3320dfdb5be1270bfcd6cebc92f4767bbd6fd97d0c70e4108e307a59796acd9f1d184d9e1723f0a7809fc96106647cbc33c8ce803e65bbe875247239c3e21cc8e1a4fe36b88d307667b1a5fe36142981d007898078038614498ed6b88da093563ba04b3893278097c509c3444cdd4795b6ca0ef5c35954aa5ba5497ea525d4a0bacb1ba96a3ba2478d45c3eda5e6ba97c0e37afbcbfc6f2d2472ce2d820f82b2adeabccd28a0a187aa96b4dd2f6ea56421c72c09bbc517f7bb5b460ecbd5440d05192c751a29625979790044b800529e1b06b8dde178e221e58a3f736a8c0a3c82594a50ee9b758d10745f2bc118676e3097ef9a83b978f70c8e176ad496aa1a89696bf96354b3724c1cd56bceeb5845ed25893d4dddbb57437c62dd7ef5a35ac227bfd118f9d2bfb6d6911bb56f66f75d7f059f11a2d21b5d6edd67b5b7062ceed6e97a6d280753aa15a3c37ac20ab9d54cee9e472d9f4b4cd24dd3bbd5b6bfdfa6914a9a7134a13c205766aa7161ea5eb09653c14d94577819469c93631a8f2145558a1e37446c1d1399d52a6393bd539cb3627570ae7743ab138279d9c41c6da61edc87698d4aa7365cdd7e33ac0aeb530e40796f4a3df614aa6610ecfed4deb575aa9ad1f133a6bdfd2b8a8fe48f335406a1f13ea770853c506d8dcdfdfb976a569edc58486862685d345717da66aae266c36993a33c7357b266beedce9dd56ebb66e6b92bc87817bd05f0c762a90d8aa944c66e3083c5bad4e4dd56d5d28401a38d4b26104f71c22657c22d131ae028dbc6691a9ba05388088851ba28b80db9235dd57c8187f2d2ee8a461ac7bc44776aaa66aaab64db5737dae7b5b98a1525570857a6badb5d6f9b5860bf06722f7b5251c218fe0976f8481c75be9b5563563544ee8e40c3dc071cdb5d95076b2c4deddddb5697f7748290c8f3707a1f30ca6f0d8280976dd4d7c622e7a0902fba85f86d314ce5312bc655973bf1f9c29a40082feb3bec4e18a0e8594e997a819d3e30d732b19e592dc4272479192322b6894aae74daa57a12765a35c5405961f8eaf089e736a5a6be22891905bfbc096a59439f9e96364cd69e209ea28408c94916f001e3c8229c8008f379ffca673eb90358d6a01ecbf176310fc51348174355ae858ffbd18779fa4b740159e27938bda0b321d75dac1f28774563649d8ed87b00daf94711b4b70ff783315af6c802c6154a472473dbac926e9ce100e891fc6e29743cb37f419eed119e7e373412064eb472bd62f8ab96cb656dd64773e286e32d0e6141ce051bac6de9ed3a67d074892941f008720aff8809419fbc4fde0832c432ea142c121d1ba668cfde1ee0fe7334916f620882f0d2638233dda4c9ef2a0ef219a747d668c3f3602442256e40649da6432c61f1c5d3636c81afb791db73de733492cbff2e3e57c5a93b4695b0b4f15b11512b756f6df4c3e6a232df2078963db203b90eca314633bdd8b41f1b49d681879c918ff2a6fbdad49aaa619139ba40ade96e9b6fe417c63637dd91fab44922ccabae47d293529779053c4d18a91dd2a713b457671b4575894d502b443c8fed5a2462bb34fd81f29e34282c74d969a651b8cd468b1010f929095e0f179cc2481479a041ebf7e1400b2f22c8f452098ce7c648720456e8bf09a311b0862fc32ddab526d55e53b2ef2f0875b72f9966779ebf21772a7bf10b2bf4b0bcb4a68235bf01ba46c0201be5eff2088b1b441caf8bb5abfb5f2f6c4456e834a98e50bfea110873c803f0e73c816fc4115de7e36998b9cc6b6a05e482ee44808723441303627bfc936d9ed4c29be8212458cb8b1d65ad97d31f8abd9c454f9160abdd4127649c525f4d14bc6407286c6793d4dbe25fcf8028c262526bee761c60853102526ba779796169a19d1ab84b2f4b343a12c6da173afb2b9f8a86613f9c78a0042c0238d01097d4b4b48728155dee5c7b796529a3b1595775151117da87ce87dac3c8bb5945a4ae7addb76eba4d4f32ceddafb44e9221b2f8c81c5f5052d82244d93290c54138af0282a97359d5045f8536affd5719dcaca4a284b5ad82a9a1575d8bce8ff019023edfd092047d3fa8763aac8dbb3149a09c2d62decac5a2941672db601b5a594524a298397baef3e1a1206eeb91f51e07e542177ab8ed53dd185b274044ea9613d855d6a295b18eb090e305613e417fc3b19c891944dc80a44ea89ec5dea9048ca73e1e99652b89217c2ece8de7baffb1e4a5d1752d80eefbbf7440a83e98749113f11a6df1367316335e5e69e13a9ac13e90fdee1defefc7bef953de0968bdcebb8b05bcd98aaea56432489b2644d8334aa8a5563072b22b27fc7a411605f761d4fee1bda53147522a90c456528944c65bfbbf73b6e53adbad5dcbad5f696e33e9c25392f765d074109996cccda8ef9c84f37fd45939907691994ec6fa5fcfff7bf17e37fed27c94b3333221108c6bec5d1fe4ab6df59399a6fa9385221538e6aadd4cecf7af4deef98ac198002c490ff019097057c00c97a2905b803dc2cdb185b1e72c6c3061db449bfbb630f86ff321fd9273eaab158ec5e1c95096406a5dddd578aafd2f6b49af2039e4fefbdb576d1692b685f5e02cffce1b837f75e792578ef8c66c3ed0d354d6b10cc013601d7096edb0d0a92508152a0948f1cf402f580724038a016880968053a72ed0c4b094a6126c8513db9280b89b10832060a0c9417988bec8f8d341642622730c803bc240a70093ce40aa950be3c8252a0d4174eb2312f64cd4cb1fc0882921df424fb83a490a025b286be3f088996352cc89a9bfd4143648df62a3f82b8c80eca42e21f415480a2001919414540a96f05b68823c822c662208843e288b1385e90088252a8140a94b29ded6c922a8bc562d9d9f79aa40a83853277ae28d4a366adac49b271b15c2c174b7b99bb864d924f37edecde7bef97e376db6802d390e6eed6530dc2bab7da6f8585c02e19949235dffb83563e9a818448d28bcb8fa02149b8fc0822f2bd403adb77837c2f3bbba70f257e27177da76cc99031fe3f806865213e979d81522c2cedf2dbd7938f3e938bb43c7ea63cd653febed58cf94ea813ea8402418c572fd8f7827d2f98679b602c4222e0bbb8fc8bcb9f6e09fccfe423cc04293a4d12ce42c6e031b2fff80101e3e4c5898b93132146ad26091be998d724611e2c59e128564b804330fe5ed91fa4034abd80ac8f27da18a813471be3c41b616096958fae9de5647f906a03a526e9e539ee3b97d04b7636492050caceec2c75efc41894da5c5cb67f0967e927890a41c634c541c7f4fb0b644cff185409b98ff003b0073ac84d93c84d99c85d83dce2e83510500a94e23889236590489920489914587c851ffa8a5cb8207867ba006b8030520539d2be2f5716dc6fd85903af054170db2a1555989153051743d4efca51fd76cffdb50604f6a761bd955a9bc20142d378b8fb4c1723ee5ca021067af0808235916646268147dc5a4ce341e9cd0f16f7f6b8f7e2173c6d60803ba37ca4390cdcdb4ae4f4a5ee7b7c2031cb184f112f0cdded7054392b1d17b1668cead640d6c3479e05d03939d4d02d5b5a1daf8d6c92f40129cbb751d0a49f3163936c668c8348602386c83ec83268a800ebe043ca1b60e04dec11042c4b2dc412288aa03295c6bf60172e59adc1dad0a304019b05e4b0596852b3917d95c91e382a94a70448d3eef546e10c3c52d56b6a1f8bc160af97cbd56addabfaa9335923ab4f3d52772a4f65527b6a131aab4054d5447572706a9057ab57b29fd6c4d6a1aa16a9aaa736e98d356d35b0b9c6194d55502ef3512ac7a1a0e669e2f86c02c9fe5776aa533eadfbe3edd033ed54a7f2155d0c49e264cd00b2bf3ff11f5953ef8fa093c1892e4b79a74cb2468d915d96923e353807686749c697a54309411b2cc0a348e44e5679fe9065d903274ef24b13f6b759a29d8875ca65b3283eb22e7399cbb60d47b5a302224203e9a299442767701293561451032e879c3cb16d3adde303ecdad4444c8899decb1aa7d92ec75955ceaa1aa176adb2caee9eb5e5cf5a6bad32c65ff492fd09a0657f9ae5155c08017402ae2291fbe04a070441902b882260ab152bf6c51198ceab498b67b53255934e7ba111fcee3ad4b6b7f1ed2f0e3dcb3b0c2bdd4ef69fb3399bb31933eba8d75ad01497ed9df5aac9544dd5544d9aa7759e5c752b59f33e9a14f0d2ca8742981d21960f7d0f25951066878a0a8b0b0bcbb788d307e63ecbb7bcbccbbfb4dc955f61f1b6b829a0f756563e659640292ba7d3e904da4edd296565c5bbb6a442ff0bc72e77fdea7ba3a732ce1b61e0ae43a15ee5968f6a351d61ad2629f4db4651a10e85422f55d3247577655a55d3ed561d925eb13a14ead5aa6129f00ce1e48404090c491794de7b7bfefe7d59e37792feefdf1c5f7e2b287d5993614c5d9bf6a47931a1a1a1b13e742e41f075ec580100344c442eb406829aa65120648765a7af19e35d4f53da1fa5d40919e3ef2ceaf5cf0fc747a58f68f80529630ff973a034f46bb7e7b6cd6da3c17e3fed6e59a9b672515b7777597be5a2dc9ac872571e672b259425d32479a20f26baf7d7c1fb5b6a94ea8636b9383588c0579572912c7122133c2669422163fc954c71c56cddd4a7e323eda66eeaa66abd49bdb0cc7e64139baa299dd49c335553aea5288147e98ae5da12028f34250aecdfac19a0c3c64574564609664f386396822293c6644d96325a4852a963fcb58861217930041b5b71aaaaa65c24a40ea94ba46cee55fd7442482939c975af7ad5dce77144bcb76104d3a7d408ae79bcf47ede275e2ee546381279f422d9473792fdbb8c266560c66cd1249ba921010e815190fd65bb0f4b68243411a4cfcacab9faf007fc63efc46cc7ea43e0e6fb34d472573d1b46f046432ff70753047bcf7d0d24f00d977be59a244f5c62b5f5138e0910b8619cd82f93ace99894b9f92149adc59bfa898bba73809be51ba04166c0037234e5ece5d3ab496a6d5eeedacab95c6eeb9dc91de3b64eb5e3e2e17c050910abd6da5d43cfee55af3d18eebdd7b282d8d817f6a5439bd45a6b2db7fd9d28a8b015227d7c92eefc8f3579bd74509fdb03ade59d4980d79aa4dd9412c008dca94e512134459b7e539474a29333b44c4ef83801e504847b657cc05aa61a27371b69064704ffa5dabd54abd7aa76b856add80d206b6a5702fc294760cf2161b82b1f5510b4b6de2477d68ebb1d87c2113ca6cfe9022192d61feb91ab9349df4823b47d6215e2559a9aaaa99a8ac5786c9b8b5342698f4e8e21b0d6841d14b400e3278f334fa790a3f94b3089c17e47edd30fc77daafd536d25656c1228a5140533f0fcceddbb56cda773f9d3c77b3cd633baa0d16aaf76eff4bed5dead7af26e5ce7699e37bd3dc975de97a2a5a44cef14e9c92f4c01cd78292947b27c102bcb108721cf51d15454a6b78aab5c247f06c581b809ca0ccacf8ba707c6f3e271758eaa6fb27c2dd5289f3f997e9101e982e4718166c645f2db7d4e4da355ab757ad7f639358dd26a356ba7b7d568add6debb711ac74d6fcede6de3b8aef33eedfba6f7c7759ef77d2929a00681bcc6d8eb50c86b95156d65657aafc870bcb2a5c5932e2fa00682d31b0c752a2b2b5e87234b96cfc292a3c5e5457b7999de2f3705a66f0a0c04f22882dca2c35c249f6500d66dd7eefdda2d657777df1a262b6bde276d44c1f46d44c1f54519aeefa591fddf3658154f4c1a0d4dd32ce10a9a28b79bfb70f48703d348f1831779942f005cb0898f8b288cf6e088a107fdf9913f343fe091c2261259d395e298344d03d2c8fae1d0969cf144ee1918606dd2507a53bb15c77192933dba677a1cc7719c362ff5b8b045c6f8b7cc961a462041206f90a38a0006f48deb131ba2a360a67285be95a3fa34d39792fe9423fb74863d421b943ca0023a1ac904e468be832fca89b238d29cebb6deb7716eeb3a6ebb3b52c6c5c3b5e70312b01878988260604703913e92c6a07282898c9b5a2b16a62b664ba4c05922c5cd9224b18f022603fc94eebb1f81bb57a07fbf451b9a48b4c3c74bab64ff8a520358c2ae486518ec48f6df9101ffa2739166c6f8cc09ff4c184130a0da20010711d091834397301f1fd1d760b0186afde1ca784175612530a8b3a104c679bc35769a3d40154e99527eb0ab72a12df7081bc5891fd844f944cd2df649cad088e1b19a7ca829a827b09505d995c0f5d61e2eb2b1f55e1c5575794c306255f444294afa8dbf07f195ef788aca6e3f39ea4f334564949c31de163c279a948f288e8fb4d3e954ab555516149aa6c3db2a0a02d6a1672d6734756d9541da3aa7ad72c62065fcb5a903a5379adb19c7d1d0bc94526edbb66d311cb771db0df38cbe0206832169edfce83c26ea449240ef1b48a44e404a06587fd0c8b64bf3dea554393a9c7739b07bef6bb3df4be7be5e3d701cc7711dd61cbb4ae2b31b1578a43fd4c7b5ca5ba54d9008a0ef4fc790355c23808a350931e4a80e51fd7062255271aaeaa57a7dddb66d5dbc602f8e7b75ddab7b752f6ef3ac8f0f75b95cf9be70a77cdd7ba9be2609bff0ebbbee3df1fb52c259e23e54070bd980fe63c0c60aae9e9904dc6fb55aad56ab455b2dcfe3d568ab0c4ae2e4f7ac230e43f7ffd64927571871f63ec1ec864161ffeef66e17afc43c9d4451144d51176542716e288bf25015d591f7de7b294a24ea165194524a29a5946a934e22b659c002cf3c8ab26bef798cbfc6b9c6e05c98b3229cc1c859915d83c1a23c6585ac91d9da0803cf1fb926b2b7a40c0e1e298a537139148542dda4382f05d65a2b6d576aadb5d63640646d15dd0bab3b94760f4a9ff69035607633b27f9aa6dd39653d54789432d167517ac420958209ffa093ae2a6bed63e86005ef9fd2047034345a9f5ce0666a04ae8e1703ae8dcb5a8b011eb31db318ae9c715bd7e5ac7454a128405452dab4a5943e8f704da31a95966a56a3fef36597c9a61aea6399a49454d29e99e9992470c65ec85bf6ef80f410a56105bc68db614bfe06f4dbb799daccc133112a655e09f088b3e7509e89c578d48a4ddc12275494527abdb384c9e0264b496aff6aa54d6055d6dab5259df5dc2e8fa2a6b3d96c8643e63a9ba1b3d99c5571047d76a5bc9f8c91a4dc429ed9026b333a58fb6062d8fed2ee400d36af9c52061992742b10f5072543caf8156919ed43458110540999ce9e80671a4b6369300dd3f7c2f426c274157774cf953a11a6c518bc1e4a363a668c57139d69b4d20aead134cd6a76feedd6640d2bace0b1262e72edcb028f7ff38fb74fd514914dac381ef30595c55ce42f6b5a2b82dbab358994f10f8d186b92ecaf4d2d89ac9199c6480ca0504852f8b4353e6d3d6d3d6dbd0d100c856cfa5dc99de28afc55faaa148e0b54c5cfeb059b79407a7e98c8a2c434914847d439bdf7e290247553742fdcbdb0acf962af0e266bbaecdff5f848eb66dd4ff7aa28fbd3bd94e01647ca438385b56e11a6473beb623547cbe37d614e0952c67f9a7ce436cb2e08f7d3e174aa6ed51de9b4e6663e1a3925f873ce45e194d05aa7c2dda93375dd8f05a2a2640dfdf691105e8564af2fc85e8fd85435e7327191d3bb754d54dd6bc67817bbb0fbea5ed4755fd9ab10427e86984e1585638e9bf86876af4c531ed3afcd77b95a7914f528caafecff0a1dc167b0e224ad18020b56329705e8a7d6978eca6d9576208d206ccc8cd72586b51f6724a082944590228c03faedf8e5086c9c4c0bb0cbb40077c581c3043617c4fece2fbf6211d8bf2350e78f366cf8ab521d291cd00fe753dda66c4e85529fd893c5a9337fadde98613124e9beacf94cb37ffdb1a64a46f62abb95de999469327efc746cc6f897ab2cef00cf6ed645a93299b52ea82a3a390312a2e0e095240707ab262196bbbbbbbbbb53f7588c878ef3a0f46b800c0bb6dc55ef4fb2e60594aca1ed271eef446c522341258f9f47cebf6552060b7c8231827b868de322203f31384ca04f32e3a0e2d8aa5025832e8c3dc543d6041965cc4f17902407622626260c654cfa29e627dfa97102dc2df32978f829c4f2ace828a593ce8f8227a59452ba5d90eb4e978b8697052c7097c7cb02161866cb5e8b209778044c5b6e9fdec09338fda99235da5b24dc8e8b6e20475c1229724e8a24dd102ac8a3a70449766ec79f4be2b31b420935499959af9062c810d9a50c0e9dc757f25206854c45ed002a3c82fe8cac3d0eed92359d03854ce787539b09e2ff6bbd49a9b838d94acb356654abb5d65ae5bdb36728bff6c497c8955f4d514a2bad3417e6cac9f7df46114cbb6b58838cd93c3efd701009d56a43ce81731102a7d150e8c386908c651abc1fef836cccd42063b99f7bfa4de0b6387ae26b3de41a4a71d4be1373e01250f28294006f3fd2a484561cb53c1209556a6d288503f37470ee409a66adb5b4d2f95aa85d0d671befb1ae6aaab54d02e89d20ab56f59146552b93c9643299cecc8c480482a2998e0111478e50f2c14302c6e0c28cd2cbf330938202468989ee5f4426ac7d2213c4be7c4d39f8aaaaa2dddddedb30049287a7274b180fcf8d2c613c37b95f777adf6255c99ba969c6e4405da40213b83ecedbb8ac6255b1ac5c7bc44542f0d82dd1b25cf402fda842b660e83a06abacbf43322cba4615f28b8ae3adf9135fcaac5253f59a6cde8bb14c5465a2faa3a8fdde0fc708001d7ad8c9ddc391dca2fd6a5fab9aa67d875d6badf5d6dfe1e55d7e07f05bbe869f45526e6d6a79f07770f997dfa1e5c1af219853fc83e9b7c0cbbbbc05c06ff9fe605a94e1e55dbe8a3bc06fa93c1f7595947117cec055664f40fad4a6da377c096b903c3546f060e5b25f9ff48d8b5226df5a50f5a7ca5c247dbea840dae4222732c6bf8a51c9f8b13f6655d6a62aabd464325daaba49e1d8155781081bc09ad2eea63db5d6eaa294525a2ba594525a002623d8b5e3ee57d16e1dadf53bee7620052948c17bc10b5eb06e6205c16b2f8afba67871a56eeaaaeeebf6dc9c8b735b97c95ddd9652ce56ab355b2d516bb674b00f39058c08224a3e78f02024297dcf030c114bf8e009253a445e95ac91d9bb295913622796c8fe294de0f11ed1c973c7455be0e992b317c646cdd624dd8b3108fe8b44ad992e70f73b789ff23b7cdf3d8c0cdea7bc05be0e7287ac6e6a871d52defb1dbaff9e4a29a54d1258ca929761644879ef2dd0fda7438a7853239dd94802b738de237446837dedfd79c8d7d53d671569a0e298812c5b4a29bb069f97a8e44d77e6bc54440200000553150000200c0a86c30181583c2ed8f5561f14000d6a7a4e7a543c9a08c4418ec3280819648c31060063000122404244580dd11854192e065812155c86e2898cf2be5eb7a861768fca1b921183262839a67f06ed84b55bba4c20ded11a4daf6915b3445f91d245332dedf8d9493a83b74192da0ceeef6f8c2e0e40018993c695061d05ee1878ac108aaccea168f9fe238a4ea040eae9589c700d94b50e074ada848ca330aab7548905f2c22a166cc728b43fd05c8c5549771ade9f25dc2adb7f3326784d2baf77e71415afe5da8f71bb023284fe716a7f66d2697edee2a33822559b3776d4f1a2314dc6d0db3f1b85b2dc5dcc8a750a0daec1b5c0101de58fbc7d5e5466d615cfb2082f2adad8458c685416dbf685d1e55376ed5d7313fcf0a1466559d9d05adfaf6352d94675c959149509b00e2515c6404dcb00ebbd2fabaf474663866ed1cc2fbb2b1a77879dcec73afacba6053575defdaaa03badfe5f13e0864bb06190b5f58c1850dc1109abe87f7808f78f048527879025a49e9aecd812c0fcebe8b7a1645529071792024a50373b00b438d60395fa2418ab4544911a099acae2f489223a8a10e784407822290e0a02460b671c61a23c9e0655922e86f59d51c66e87d46864f5c3946cd529ff5d64b02c719adeaef807097556fce86a0391d5d37729a6afd0f00705e8025a5bfb3ff8ee1c3a8afe6685b4b4a8c1277bd3bdf7993a488cf1edd22f7ce700ca5706e2ed4bec8445377954a459ed82516cb3c927d439c28dacc02b0003d0ffaa1597e4cfb11b2cd22a3061274689bbd6311783a0ad87653082bb5a61b2ca489ca719104aa37ce83e734fe56ef022e84177135aed2f571e13d545cd8dbd13633d8da4e143a027a20f05487300a92619e2aba581ccaef2a32efb1801940759bf37d0a59b873b36eddde748d17b4bcac58c99ccd33caa951efb09c43418486546e27b613a8f81ca161f4544b94cb9e0eb51e13c0b492afe20c9ae1788ffe4ba00cbce52544b5e8a40b42ab7682a1eba60497b2d60fd39c275bf4351c85f251c049300715935dbaf6020f7c34b54f5f6a69fc4bb487a3cad46ff6edadb6551bf158e014049ba60fb41c2ed43c8a23a00a0f51eafa742386513a5406f0661aba0701e445276aca3dcc1c8909c3d5f45ace69742c7e63dae687cb70a77b987448809ebc81532ca02c13b3dbfcdf0f06b1edd6f74bc38a5108820db3889ea97df0a944d47fcc9de2e1de092e1596540cda9d82321baa465ccc8c59ae3e31ac198cc70728fa6a3677935af05d5b52a1e16d6dc6a75d4d0f9b931b24669670f0ae584ae4ec22271c6580d750a753c872bca436286d6c4dabd1829671985467a40e95a8bef86297a212a200f5df2c2c4632e603d63e22308c96c80946186ec17895c6211ec2889aa3264b28ec2f901069dcd5144c102a0e607cd2453acac5c60caac4c872c74ca26a7e3111b7e0adcd91271270f6380be9317d76fa0e79ef125d938c64a51de532efb3d67b05278dc6472b3a66c262ee9aab3163827ae7a8cd43c5e8d0680fee026878cbe0786f1fb27fcd1a0a34ccc19454be78eac2f9695bc44ff44caf9a7a5aa4d4be062a0a04cc6daec271eedd40e2fedfa91f23fb8bb6d7a9bb7d12a3875833c4c3dbdc3c58e8a279d186c18e36e24ec2eb756e6c92eb771338624688cc66ae541d2b5f0bed208d2070e0bacf3c296a50e923cc8fd5e0b4d0724a4d00a879b890fd4ac1baac59d583d9925bbcec3dada29c965be0a3e7dca6a853ff98abfe933dd524d20bec2a6763dc875fe44dd1a4b5cdadfa0344c8047cfc0bfa8daee5de6a4359742000733c01e92a070504b2414e2e60d8b9c76dd968056e0d011c0b6cdd37bef8fdb5dee15b4edc008fdb800203cb1889069e3a121707eadccc97b511ca68c1125ad31622adac513c5b899721b234e62c631828fc33b468cbd028f1183f414b5bac97ae8a6ffbfb494022537f529f0e48db53646848a5dc0458e11c02fbd1e39aef1eb1988bee33ade4c13e98aaf2e227eb60143111be4e0907d4b0eb14841cc601d02dd2fcca2cc4b20071406ad5c40847e14ea860420f8394c1bdbbbfd87cc06d27fd1ff272c8b9e8ccd078dfe24b67852f0973d49c01e5647d0e0b46d35ae9ca28a0455fe2112045b0dc5d3ee2db748e40c415f3cd6796a78e8708b49dc391230301fe1c1832cbbc60fb6ae18b294172f3fecefca7502e41d0b9c78ac2a762c7e9ce134c7e92e5791a6d2e966f971cf966a141aa30cc726339e4032584af567458d0c7a8d4fe7712031387ff4456fd1f1cd2e11f94fedfaa3101037bda7ef93b92a21bc852a436c3457caeb14f496535a5db91db2e0ea404d210505bb849b9de5b17709a0a6851e40190145f55ce8de443277322be4e974eb29418dce240b29a1789dbc2d860d037ee4714c2b5e8720b7327a781dfa9c10f6c434850488474f156c82ce6d01b5369d3a8df03af9b32ab058aa0bf4f3cd3246f1cd8c9a636eb8be87df066df4318803e9c49e15ecb6d6b114e1752cb2dd0ad809258329105e47ec119ddf4ff6eeaba503862c9fdac105d5f850b939e62e92defe69439eecba01883076094bd65490f84cee4e7cbdeb987cf3ae4360bcebf0a93b0997e420394ea1e075cea90afd3266a84f9776a2678cb0bec0f1191505aaf549a09f76f7ac7b3f94dafcfa9910b4309b3be4dc31c36e9de0bd76cedd690196ee7e80bb7ed10182a7dc2cba36fbe134923e2bbf20b92a2a1228b7adec47e9d3c7acc9d5d8284ab191af961cb45dee2edb8e94c951c179831a57e86fcbf5ea17da32f319002843644bc904032085d0824d995ff6ee0462e6734768fde2e3ce806cd2542f4e914d6d81fb83fa0e9db38ba4c39e57e3bca8b7a8d30b7906e658f5f4c4d0eb99d04c0c7522df2d6d16a9f8d59fa7f883ff0998ee903f4a8b01cea1a68e5c6435ba5098b7a7320404b9cbfb62d365a7021b50e5f28499ee111bba9d4d463c792b2eab1c756757761f7bddee0e28db85fd1f8ef6ce17a21f6075ce673f33c38ec3a0673c612b1640d06825335963961654873415bcb59c513d4d9ce43570127675720147781592351b58ff23970fd83b08277905aa28578206070e63c58d48092564f9e40cc662f8b59b8261c22c47a7113119a00cd572900e98ab40252fc1c138f638ed207d449a849c71369ce3dcfe3266cf9e319506e947bb07b933feab710eb38e0f648e82e6250efe7306fae682dac33d0fa8141636031110580bdd94a4be6d12d3284e42208694af78df07a03e44d91626d99d44d9c40515ab8115838f03690949812194d9bf1a527882c741160da055456dc7ad0c1ca724b3ac96a605b9b74069006a168198ac0d917c84231e3f9bb82f108ad9e1a1188f0a8d275b18207de7f68a4e5b2e31436c00318a392ab2091c2046cd9c33e79456d4e107c53f19bb2579f806c6c1567e6f2fc43ecbe51422730562803b2598194518fa5887209d0245e94ecbd25f48c0f87c336f932f90908722e1d77462a3e06d6d87efabd0a3cdb047287be7b16cbbfb1b7aa4b012199b7dbcda0b69057e276898b358cb951d66bd20660824f0891301a0c5745a0f055abfc0ed4507ae07be98fe11972d51d8a84bd5fecac087ec0c1150b61dd2735fcc0d63e1c1357b0797a4a121c809d4cec23f4c306ac1960939a0f0bd3c05c053bea40ad9783f315806b64f14aebe356a20345b8d40bab234a39fbd78e1ea21ebb0ead0a34b1ba3499c6eafdd674e17e4db55c0f6378f11707975163904226c127602c23ea039dac4ed7fb04af0e1cb2ad15acd43e5d9939b098f06932efc583e87befb47f40f1d790aea4cdca5c3a27b0ac848fc414966ea51188b484dd86bd7c30ffc6a5d5bf2107332d4b67ac8657ad4c1e349838a318986d6d814954ead3926ed584edd09ac1bbbcc2340cf76c565796458a955c29539bfd380a5fda0efac3120a769ec38352a0c0075ffbe9b76319d8278e5e1ecd80689950471e00be029f39a5d790ab082b3fecefba30c483f8844cf799539df4ca97cb82a0544c5de42e647bd30ee4605be9cca88a1b0bc02885d5e29f05057e6eb5aae98c07b14195fb606ab5d1cb02b92e187c8adcf83eca710cfe8bceadec5bdb23159d744dda2644d549a57242ed112d5db15d28c3e36262ad4be1e574a0ffe8da5335fc04ba100865293405e640bb75d7b712eca2ed1f9e3a4aef662fa204afd533e33eaf1ef0152b43a2145299852dd7a878fa0e048369c16f28ce35d2b301295000d26415d550e2a49ddd6796a5876b3c30735022f0adb42a67bfc397deb62304488e3b7dd78258ee1e7dd3ad2b689467c19d2acf0b20fa8975f1bbfea28af6a38d04997fc1e0f2d72e6d37f609532ceb74ba1ce0dbb47602ff6d687e19a642bd45c5800253322500b66794f7cb5fda4143e743e6577ab15f7ab0f2b511229ccb37612f9e56b4b19e1f2a1b3f57e288700942eb75b21d6e8d3fd8ece3845bbbb65d911da1f7bd5e3c8ebd5f11540bbfd5f145fb93d7afec53c5c3b60d1d8dc37e84a2fc8bde68b96d05a0a87c00c5fe784bd14d7269ef2e4eedcebe5160d15990de0a9e4d2df62ddf476596a4d2a81035d1b712883ef80bc2854df6f9454eac87e477fb03d7e39ce29005553ad85a6dfa5d940853f5376c16fc07c05a1f502124f5ad446d84b73715982516d0f1c99742ada41531fd3a0423f22b8884babc6cd45087264e0c6af3b0f8601dad4a772e97d1095951e1f7f18a1fab4de84657e18e432ff4af13a3957ebbe693327c10de0c21320ea17dab109cbd421028f6110140ca75f2830349fa809c1c1b1d2c89f9fa55e9a8961ddb7e1a3e83a62630203f7282897f35e895cb9cc147b6eb6a944d7f7431795db8579a9a089b5b264d9819b22727c6f26062322735b0b45f5b9668b5c09c915da4b529bf0881c281bb998eca05af6397a42306228dc8c394c2087568e54a9e001f5417cdf6abd6d8605d097a9b13feee2d8c5433318072d4a37ad2630f22644f2b47ac2ef370f5a044f4e5992a9228f378cd7b7e389d395033a4417403461410f738d91144df98272766536b868d7f9c160c163e5d2b13d7f58318d1eace18733a15b173c56183c7f6c54af96b3dcb43a33f43b9f83ccfb1807ba7c9a4cf2c9255f02f58433a40f11b8d8d2954cdca2708b2913e380a6e52fbabad94d11d657fbbc2961eddf29f3925cf0474bd596bc13795244030a5f83abc3e83fc93ac3fc3fa049359735aa5432996cd6f7df5cb241f4711c7a671a2978ad411dc5e6c9ce4d20595f59f72dfc4aa293c0f3831ec448140583caada00f0cb10411840a673ebc846c16ad78bc86b40640fb12d03b43a088a2105ca9dba1c40c17fd78012ff35990fd53966b1d56d6f1cdfe9bb548614e2c51a5eaca7afe603a95bd8ce572722f231741b75fd025b53746f7574cad687a3368f619a1f542c412303320f28f203ada00e72a4f66e760a1a8a9cc6356dec10960dd0bcf1dc57059bf2d06b4f866a9625d903d40e01ca2655ca6c3c71272dff6eb8c33de79c389eb6c1269ceea770fb11a769483d58d9d84bed749d7720f4ad128f2f52ba4966b77114daed9b9f82fec6321c010059b3215c4e7e2b02cf9fabca74c5c484e111c4adda63b59762804b3f8a4a8a9f500d295729a56444219da83b2b4dbeb7c744f3a88d0c931ef2142bf2b98920869718e50f69921aa49501eb547766483c21f98eff135a8780d62d4acc8bb1ca9ff343fd3d1cae03042736f8107ec4270fb87e0c65d2670308a648614c2e618b930cb8ff7ca7de4bd0db22186787e6a2d26a91c344883330a26d0c7cde771bf26505f8548a4ef6ed713310e7e058f28d19ec8c5087c9de3fed29ce8ec248d902355e2d30dc52235a2c298fc6e6f9006f72f0e988b7b94f5801e89e8d8c8272285553fc64f09293500c25dcf71e58f04d7bc4ebd74d4603dcdfb0b5e75f68fa03ef3766290929436130e34d2e0831087e17fbd49e6b534e17f3522c394e88b4583b391ac0b5d49f75823be4868531e0dbe73d00810d6497d8a6ae6e8dcba84147d4920dfa9382bb0d6cfe6e66c164300bc39adc8aa110fe318d305124489d2d13d85c44aad90d85960d9a52671058da088cbbfbbc92921a0fd1fe98c091ab1fb8fb684b04208715a1e2bfbbcd24ad45b2507c676a72bf33443ab6e3ec8fc430f2b61eb115f0999cec92fc1808918f52b1925c72c6fa2714f684be8d518b47fe18f0260721c4a2370d66a86b4887f2d3885ccca3ca38f984c14de32c0454ce63561e327911c232695b8527a94e40c268e88e911680763f4f8188433b1487614a6cfaf491379bf2955493f394555676e8354a3e580bb997c5787813000fc6f218312726d3f661d2b8384ca9d2318a1b23f8aa8838daa1e3b24157133564f1693edb591fc87624d5944238ea00dcf34973f52e4a81a326a6f3dd3ca675c3ce33043a3c9940553eb0a460aa0b8e82c1b2c367553595447393a8d8bf6a4b26063c6c41dc4548c685cebcd0f33bfa92c4b479700b794651a435cf47bfe884bbe2c30075cefb8309b3c3beed3986357cf227bda18ded69accdc0d876bb42fb8f5d397035a2cda5b81d95546bbb0556a26b137102ecb025a409267b1cfdacb9a490d0b3326ee4793e3deb9f3ad41c96a3e33d9e4d03980021e39df42e3fb42d5ec5a37f9889424944f0f36026cae6a979099500f5840716bec771b5f7c3db33f7bb9ab22db33c20f524555327cd3ff909d290d4151037bd69f43bd9aef1587206b125c8da143a850df22c7448a81bb19fee69cdd746d0ae2174d11d43a60960d3cd37a6ec60e208fabef7068007e5387433215ec979d8b14b62828ca34ae667d4d603406a9f008ad9ea0c43e2422b2a77e75a9a9def30f1ea248cba57029346999cf524d153efb79f3fed3493ca81a741ef72b918b2b089e97177670338df8c397226fa04c6994f05e327efb7368e4277572bbf21d595dca851736222cc6300745f0229a650a634a779060ee711035fc558c2cfb95b22ac9fca7845cd3518b8d2206bbff04940ca1d3a8666a55cdcfa0badec4843fafb99c67bb9449a5e5da51634d3fa695770b71274301013cc4f7c3a0101dd2ea993199fb8ad16d881817c54abb60757e2a4430858666cbbb944ed093be87aea4c8fe5af8d105993ed0156e4e4e3829e59130337842a8d53f60ba2a07b29a69225a72a28a95109fcf85b4274a126f3d8718557da6fc216079a2c48b261a03df6a8391220b64cdf2c4c12c868c1ab9cd604f723b6da80f1581b710dec06c8cf4d09fa421a4215f9c3af3a5546276a8e3e7806613b913d57afa1ba9663778af9007c6105e3b09736c42d7e362c053f0063a04e84f01be6c068cea958119b0e2c24806b8310da8afd5330d6499fc6c7c9a458b24225fb7cac2532c04855b4fd57373adc72025961e4aeb0c1ae39c1d1b6c3b640a22a85126547e981006d77d7901f64a996a91984d60ec27ec667b19cc8d6def8730df384c5103bad2b33db5012692a18fa28df52365b5423e499f0fbd991886a03a6705d8970b623a93d969721ad5f38594941e8c80cdbefb62a2f7202274ad6514f223e6322d19c8ef0fce6f8af3c723e3afbc15d2dccb396d3c533bf746229b7239295d898f59791855fb570247cdf63800a9389a1b7939cb3281c0355aeac26c8e878da19cb534db3d095c16befd4d752f16101bea2081d2bd8f24f6082b23a69822d424be2b450bbd25b005d65a82279ed69b5d0991edae15979232ff277fcaa1911a31fc1c9e5810a8d0bf935915473795b8e4d00ceadbf74725f6c8944ea37de630e7488889d9e49aa23228a4bd5ee0a8197c2be3165f0c81b1e856df32338ef9c6cafb28877229b88edf166302dd5c838fbc5eb5a96df3cc636fc5473b3bc1f3071d93e070b6454a221a6acffc955cfd647ea288bb68d0303d1bf0a13ad149d8426528c2d16f59012414c1538f14214b2ca90f92493f59b877e60d4cda032d8005032dffeb9f6e14e75571e4736eca3da1eead67f1072a9caecc86224aa7356c0e500b6dc80b1560088339f1b2b9ed6e77e0926310239f32d0839613bdb0c58df6794e8b7ddd5d234cf6badafbcfa84f45686531734058f5578c8e1978823da195361a521af561ce7b4a30a8c1a8ccbe31c77e17e90710acc4fc1aa2f04094ff1bcac2842cac70568c41cd86f6f39e8a1b17342cda0c54525a530eec471f9985e58343c33249398f59364827e3a07686f4323c453382ec12a3129d4268a6bc0de30a55665c43656d8242211c11a45e124afe712be862463ba194e49d5cfe26e9b286fdb2d4988254d2a08d2c68ab4ae21cdfb28559f84a429dcfc40f9e65a2e896f93e8b23b371ecb1f499fe237041c615ec84a8425a2ea304b737697c8bbf338c799bafa0950f6b9f139441198a5ea8bbe5427bab2b5b8a2cd7746cfe038d61ff047cc19e803b72239434a5fdce9d8b8e93e183694587f31176d554e07a1e2fbbac1279524e349b4f82fad43647f03238c2d241babbb4fcd2a008a11d6acc5594fa0127947a92bbba510e34a6f38798eb7d5c6c88df7b35bf34429d7aadfc50643e2b0cd2ac7b9e89ec78d32a0496628747adc871611ce460a247145750adc6409ccfc438a41aa13b4b6b374c75b1c4493071ccb3768e23f15816e523e29f225bb178a5933525ce7c1cef306528b09cdc5fc5a9393a16d84702c301b85030201d64dba859f1aec68c34614fb0c0dec0b744ff284e41c514db02c728d052f1fdda8792f9bdb8f6efec31a43753011fb8f8920e4f2b372d5ed0aa7b9a9262df40ffe1095b662a08ab95df0d8d9beaa01152233276d419c37ba8ff521ae1bf2a854fb834d5b8f7352ac4b73c632f457dad6edd4eb5d90e1eb0fe9e6b01221daa16440860054732c1c052035b0388606f6270a1d4bef140113c5c4318ad09d8dc0cfcc4446225e5b795b3adde3b20164f253c74a2d978b773ca4ced6161c5d69b03826e1ce89df84b20e3e32cdd13a4821d878367fc8557db1f0086792a0297a46e7ea6f3586be1a9530b4f462f406df4e9a1324cbbf1024045c7a8fced8249bb9a7b63f4e9a95ec3538e408a74b636be6b9c52b0d982de74c3f417fd065f5c705b30715c042d220e815ecfa06f354fbe931f3454d8affe75c50d77e9e3844733e9dd8d734956f9b47dbaeea509082e395a32d739a287798959fbb796b8ae26585bf62f5ce5ec750508367b780498b4f3e13df38ba15f9c38e77acffe4755a1a4fc0ad8e6a95b4b3bacc69b7b2e3a84b738060a5be9efd65080722dcd20c17eb83e112b449ea7747b3fffa85ea3209fc27601bf856734e1c337f2034e057c72ea6df929eb8982c724155b700d3ef50f5fbd3c2f7f68f21f827ab95847506d65786e211781ad689caf25ac22baa045faa833100f5b8855f30f0d6926a8ffcbe920e2e63280db8448fd0af1a09f1dbbe4533b8763c5ccfa9e9d6d4ddbd3e476e79e0fb2f805848fb9d847d87a1d9dc56206264fd723ffd31c47ad29f2d06fa19d02aa65a32a44a7ee814b41368d49a1a422ee5b6c3a1c8733a101f9c5e6309ca5f36470e94d3253c9abb473362900e656114650d9b8abef5d9f8eb110a991261385a0b031eb1eee5f24da6a0396a4aa59a126406ce917da3f1d12cea13a14eeffc1bb7f81a02c0991172b2e9f78c966f9c45621187b304a5f2305707a34440a7d25c6e8e173e87729c4d01dfae91ac9a1c63f2c769a297fa732c88dcb10344b39e07099aa96fac061636033422568f4cf7cd8ba1d2636a4b3d7ad8071fc9838456bd66065904c5f46372cdc7f628593c5c6a080e320b43dc0cb1142779fbed0ff0595b161ca03c8067912f009ef8b7a7ec38653420e729d8e66343588a0abed2b895b055ad663aba6c2f7b9cb617a70138b8e251173521ebeef7cf43bd04e5479b645f6b5890dbfd27a7a726405a622c7fd01e601292931230d4f2bdfe4ba1d9818bb05f9f7b2b17a9126139bc82cf8b1f92bcb387f1e7dfbd7bf49e0becb263b0f9c0d7ad60057c260a2e058dd52b05e5396b3e4f5e223d2ac9c461810d8ba1f58f0c2324668139a030d75ac4c0a52115111979620252c6c4c2b3c72f0325a8a4ca41128f144dcea183ff2393fccfb20cb012b2990f6d68e42590bb8569d8c1b971aec1110b030465bbe090a365281f5dc542552d117f03c51c072b4a8325662b3f40ce5006e52c8140030f4ddfc1e8b0e5a09d87d60e81599d0b737176b3b5afba144c808472f6eb45037f7e7686d1b72167acf74d9738efbaff8b60a2151f2792368969ee54558263027c0bbeb0b8a1ee85fcf1ea06803ae389eac1249cab2bffc459b4ea206e6b72015c59daf0180187a2321c0b1b6ede650b5668aa922c52488cbc9c823cdb4eedeb446e82d961745b9e4b34cbe287d11ffad66f25f566cb98d53d9ff1701e452fdf081f92a3d57222506740f9ce2322459afec26f265b11ed3612000f75638633fa873902e6eb1ce4157c0247d350a37b0604a5a6a2e80bd3a0eaa1b05128455c9b4b3aa36383700dccd6702e6c3dd8773ba4497359e4cffe915c7416bd13f3f863ccaa48d73e0132cd6a48059d84374792628dc37164396e775876d1e812726cbe2791c2b2c9b803046181ecf1df559146567ea04533e47715d83bf45803091d096a1b65628cbb0f6891e47a003aadb52766b0bfa37fa28f74aed14c0bdb2543c57222150db0287e3687f5d1d127169104b11b862c329ae44289e6e38cb996c2f3eb3316c80ed66d96a95fb0c831abb2504ee221d9e25a8be02d8f799a72896ac0098d892ecc698f75619eff77854a48099084bedffcd6d48a210ded20c743512ad033b46ad38a33a47e104f314f36a2436703f20d95a73c0c8e04bc2caed8ac068b5865cb65047caa127c67934b0003a79748ac8074249ee9d56ae7c12dcde0200a28107a08cd52f68c9ebc8dad4d8c22a1f5624f46537c7c3702dd6d821a6392b74892ff7d41e0743cd98c7484d6fe79ecb79f98827d873e06a928fe064717fb434c791e5b71e01895509a8458e13ff779334ff45b0de52d1865d091abca1f198bf9ad91adccdac70ea51db7bad5707073d4ebde87919ad32092d866d141c2e616943063e02443163806b45e06bae9d369a53cce10838213ad8921cbb29744996a33c12271ea4c7de9df81e5504e4cf65dd9fd10835534467e4de8f2e4759421bf12981272676ebb521c94d41fc5bc4a4b0891de0ac26e578285f64c21f79c35f6d152dd361d8deb098d8a8a9c5c80c3c1d40a6b760e2bac0a170a0a0c24abb6886add8cb11954d89b97ac6bd29b8287fc2677d71fd020e8aa20729a7fea5ef792c65cb624376638a5ad17c420f7a1d012ba5293016c69df989f45ac4656387fa177f83b69c56a4ddb6288415dc18c1052a7b933c644e1949e1cdbfa12d36a10f32b95e9e016588263ac5d104f4f814b3c32387b0f8f9532ee4e9ba8972180f8dd101bc79984245c9e70e305ad80d6b673802812cc733885363c8855f8c6fef576a0c6c02eb3706f711abef2131bb152151bc94839259f15abfa285da4470346058f3d12a206bf1db2a00f105160b57e132d5d6d39631e1610b5bc438aec2923078e5e40063ec73e0cb9ab63cf84cdbf8e54a314cae08f9db0a6e46b17d3fa7bbcb0e09433b122e8d2b64c67315014395c86159b133c5642a22b27a9e4ae902828e1037bde26888cf2f2c79126e5adc4300173c45a886ea72c856e465172689322cdcea2e26a41858c558aa4f11125feaa03e4b8784f1ba86ad375f9dad605d180c20a238ac8471a1e924d47737887e4ff96b6ae3a0250d0e59a5c46ef85852c9b07925013b46186780a650438f1d2924dff3d00d0025bf1e308d22a21740adaee6f310888abe663efa54966c73f5d453c5651a0909fde6051caee34ec235b0f2ef8fedbec5470c23c1d28b77caf928ef260309d3b637c1628ad7daa380413cbeca90cbf9d80cde80fa0763dbaa91f0329f2d3cf8377b26cbbafb404ff75cac79364488c1fb9a7c77de8be18ca797cb49952c0feb693ea1a625e3997a1352375ede97e74a756a47b37f957f3641d00f71377de8b8a715b001db20210eebb97f11a5e430f130761d65ef02f9d893325426213121eba871d0c124368a9bf784688966c1384ea38557c2d265401a09f78afadebe6dc93be54f5cd41c959fe9242fa1c14aa28e8ca82ef2440758152701b46746090c5476dbae33f43d87381b975ff3bad4b0f05d187f3fbf7e25e00576c46efd3aa2c3923afa0a20da1125f1924f01609dbf4f4ed4304d714531cdf0569c5766b604e86dafccdcc2b8318b805cb52851113345fc582aa777eb6111a1340aea2f5dd885bbfa58181520f5e2bc8594fb43d42d635c2a5c8d31b049507db14583aeffc0ad3bba67cd003b093bc91b2f41198c601496ed4721cd1f595c9bf15061614d4e0e3d68aec51115170966d56cf0a7f92adb2a725de541b35f18b77e8f073ea2b0bb94c99e2e29834f4ca6cbfac02f14f3f6a1b02aff861dd4af1b68c56ef2378a39fdd45d025849893604fb58470507f7ed66f7d795f0137553abb10d1705d6b734dd85504faf9d96ea04c48af631e1357dc56be490e0dd00f6157ba3b823a0437924e34ae1c2bf81a62a619bce860984aa64458c1e8a9953506966c83059ac5f29451380753c2e9fe3b7029f58a56b25698d5db089e189a56fcf19f157dd9bb328bc7a29bbf200287474db6c957d2442865e6d01b8f4513442e12706de8a8910b1f9009a17e7792ef70a695f5b08b0b229ea8e4a0c4ed0daff006fc3893293cfb48aa87a6b177aec21020c247d46cd8b4a8c37b930bba23cb410b5527bcc6b38dd3681ef26b15619c849bdf3342bf1934ac1e99a39395851be6d9dbfe867fe7a7e5f3ac5ce79544cc57a8fdf674e9bc5f59b1f85247d2ec06a12c5def23fcd99629a55c3992fad2f3ed7ac33f8059378e949a66c56008551e4e9830e50efebe4b4dcee95dacb3630824b2daa92173b8d53e0a8422cef7267df3a2c1270b2e8317553768d8b2011041ae23276b146eb1f09781e942c1fb8204b2722633b0f60878c4c745b204a52802af754c1a218fea2a9f962d98d300d1be7a9b3aca6deac92da5fc01cdad056bfaa8a337672f6fdb9b3237aba8d2dd5762a797d69197752078984fdd8c7fd904183c44cc6ee1fe0906b495de59072f902c8eab58c29b1a7df8da2eeef5dfdfb3446e8af7bab6148e2ef48b56db9ceb1b4e50df762f3e86305a468ef2639df8daf03677fa0f4256c3943fcd1e79ff81ab22bfa10d492355fe16dcda9ae2e482c1625bba09a02c10dd8056dbca529642de4c2d0fcf3403b1cd06e8ab23496d05c04667850658cab20f6186916f4ae306be0f1720df4fa7ee577adb58703885a06cc50e42902ebe223e6a757674c16f1e0cabcb0f8b263f45458f41a645ead985574b45ca3760b4a416bb8984794aafadf70915e0259b7838b5bca47409968da640db43e2454728e1d38b023cdaa0f5a1e020b167549427603188cbc273daf57497ac40c51b17d881172fd24382394583974b264a22dda0d479b061eb1826da8f97385905263469f1ad86d52f6e8b18201f6e1238f050338a4d9330ca4ec7bc834df6eeff83de48d7ef2987a7d1fe6563a6a2b86fba9261285baccbe3fee51e7308e307dfb85c7e910d08dccf8a797459f8d95a0ef2115ab11fe49f5a55f812e96e38338d8518a8f97e82647167c70c0c59457ff8952f05da6adc887d8428e9a6d85e277a0a14d32ce10fe44789af2c7d42ab1a9054a9c7009d22b344e5a3f79d52eda1ec9741968b67ece52acc8cfc872ea719def49c98066c4adb80eb36dd9f18cd72809e1016bb37554f7440a873ad34fdae50b971e79e80ee82f3574ff2093cdd40c0d214ab8b9233600253b4665ac969e9a9515db952d657d427778795f21b7e761b3162c7d1852ca5ed7e8159910eb5217648ac6da4e4fa9540652df52c3c8bfad80b06625b6229f682f03c4050b5021809f11af1584b4424935b112dcbd63ad092d2b07ed4b3b0e1a65050291278d14629ee98641e4b6611067e3e74980963435071cb1636e28c6a34258e211368db7458ea724027c65510d3ba8caa0bb686398e974d0b69ecb266b15b30795227c0c832e659e2fff3ade844eac1c96311b42ca6a58d6d65206951ed7c7d9d16ca3a023dbfb1af1c3aff0119d759674356156c8728f2bea9f18d803aed69f4c05f6428a397b9a0a5124c48c3a90283ea5983ab2f1d09400c50ea72dc3aae6891208c5a399566bd0126cf984427900f61ab85d552837aec72ff74c48adae454a21f7891a889b7cf4a594c09f31f66123265e54ce90cfc1e7612d4c84a74200979083c78f656cd86b3b97b9bbeb4e4a688afc997ff30844d0d43549f11caf68db999f9107dc42f79d3fbe9a5153ef941781a1ba5d44e189b4d14ad083f1d09089f39ca8c472df1e2286add6a06cb426cbd38a50a219c9f5af57a0f3b835de6d7323beec522e315e005316004bb45345dd2d6251ccdd441be8478a802c5881dbe9256179fc6831f9807baaaeda15132d6b97da68b4e8b95215e8b5321c1ee2379f0ab9409b21bfbfc5a5bdb4bf35d0397a55707d87d634f26cda178202d9cc24992502d18bfc359cc1e459ae44c2444cc4fda283f0c84406e23c0bb6019378e54a019a97046e2f20ff2788f4114a7134cc464e1c60429e4cfc0d95f5e578685e21c38a321e1c1b62d5914cfb07b8136d2e61a022abb2ea93675516ba82c1890c86a61d85e427ff29d3f2643d5044781e54aa051e6ec3befbdf83ba550b241830c2440bdfe5f7314fba541ea5a5bba2f2ad9d8e9b1e9b97029a218c46738798a2b7260d3f8f3903f0bde0401943b28418a2106636b0aefde864f1b52bcc095d9517cfab77285f83e515f34673987b3050e17c3d88c228ba901fdc986c7123983ea6a5b9accc0515975b8aa1c565b011942f70062c1c0016f07c1673bf8138048133febb58eb6a36a7b78769cbb84edacdc9118122241241245c4c71955ee2738dc457d13d380b24f2e1064cb090c414ae343d0f23c835517c25714b0bc29c076affbf7a9250bd48b9dcc36e0e87e2ecae5006753c6106c630eb94f4c91037a833b31a020aad472ead40521474952404e962ac426393a538aeaeb8f70878bd74de715c2fa304e230cf0b8e2d019130c3a7ce59f4f07d2b54a87edb484a922899fa0c2671cdd1d33c4ce95c931f35e667d498ca6868d916a0105d505656c81b204bd05faa3adb4bfadca01de2464f2790438c950f99ca8d4db3f297f9fdd74f77f49964151d54b3ef3f84fd6197b07d26f5dd9663c9fc4f9f49e92e08102e965d5ee8c2af544c3285e3397891187a7f1f285f4be82d34b43f7f59808fae53cfeea077dbfafa03690825799f5665e2b5a6639c0debec3d472b1a3ca12165e729d4b1de835a5896440d3366a549bc0876e3e5b478313e81f381d4c26834221d5875f53264b7d8921efe5e930192e8f5d7e62d095b20241716f0785b433adc84d8f3abe02a333274f1b8071d5fae9cde078de76b27f0ad0984554bc8f814821efacd8a49db0bcb51e1de495f1a5cce76c0b580b1d5499dd1057c55270ee2f10584d3e6c0fe65736af4140ffe0d2b557681a32172e36ba27821a6b3e215164383d5aab81e2463ce1f42ff0bd17ded8ca22f8721603c4c0b18e92b906c0612bd68a8929c556804915289b1f22642fca26a44386dbc5c4e98fa4593db0ae1f9827c9b91bde77d1b459045d1dd4b58eec510b76aa5c8df7d56568022b6d6eea4937d229cce6a14c2c5d3d88d190ff2c8caf766b63f3284853177e5322b2d73d82d49235c5e0640bb95090acc15c4539d3dad4193211a21b3bbae7b682869ca07b855c3639b1d63f857370cb11c3b381cccd027c165bd972f787da50aa2c020e0f52ebd10f66274769c880ce0243bf113cb45111273a9086b06ac311cf650dc824104685799a0390085db8380da6b8417030938760701a028e238917e40e1f546b43c8fd45f906838c3c3d240f17147f6f7d1b083b7df5fa0e4510d27eee25e0d69c0f7aa0a45b3122141818b54f43c16de2904064e76e41df5fc3c76ee926aa80b8cf3e63b38b24b60628a558a4374ad7f6284247153da6ae7d11a0fe6b321f7729feffab455003b20d808c76abb0919dfe02114949480bd98b3fc88e0b55ed6ab5901688704108a7b9f4eddb0f3fde570601af781833ea0ea07163e04dfb07f58c84079511744387b4c3a2bc78f9b74241efd1ea7999f16de4e4a2edd6bdd6c1d80a82d1617c27204a09557d7c31c13f086bf08f22bf3f01546fa5016776cb5702c20b3a36e2b7d279269d770471134b60a3845608d4e86d3b0a0a58e298a9b1da3f0f90cce87495feb23b74cf42d5355aa5ece936ba1d9383970e632e7682630d1f2358cd274b4d1f6262281fa527c2fa363ab415d56e44a438a8b4d89c1e226e49b6e7ff1fdfafc83cd50407c08ef9632312db6a42aadf2616a6594756854d7c38e887b8b8a9e3bc34d204c2e71e39593191a68e0fdcbc66d524316919ec27bc8a72a213bbfd09ce5e1552c53121e740bcbc8429430b9275cd5c81875435b8cf57434b9e8413a930222d84cf9288c3c622c4ffe0245ff868078170969faf7a7a6889b001ade13d70a14dc3122a50615a70b457f00d2206bef5a6c37106cbdb6e014f02bcb380ecf602dfb7ee4c1e0df1b5950b5cd6ec59bba7a9cce23b1d59099daeedfce37c55105c948975a036c5aff42ed2c04b991f45198de1d507dc3df99edbb6c693ffb43aa3f050118c8d878d686cc607be75c10f02cba2b77e8a4b13dff3500a158eda48ce5fa9e9948e35b048c37ecd89211c71f6d492b4b2f9a9c4befac0cc03d6cc6fa67e4a704790d8948a17b710ee85ce85eb5397227909fb27df3ea9af00f59237cab8c656865804d9b52f344bc5f4777eb0e9170a6a0fb390093baa0e96241bf5011487b66efc5498b6e2454b1f58c0e206eec4e098428b8987ea4eb1096141eb961818f716ce955ba53d3f8b57aa2f9e5d90a839cc9aaa57d9aedfd62aad7f662373cdc33648151873ece8df0b7e8b726cdc8b11f687041003f4e82aef2f28d4804ed097f37432e5346b32c5871c6fb31357d0c96b11362ea35db73e4204c541d73f51e85d38e404ba646648d976ea26374beb7fa3faccac86d6033a1c50816af238c84cc571ca5594475932ac8618f1596116e2e7e3e127329878a25cf8b44cbf005c203782c239e6fac4080022291e122cfe178ce8434fcd3ac14ee8949d0e223ad1cef8a6dda2ee788460422a63baaddfe950638051a3d9b92772bdd1db101d0eff330291a68a278224ac93a545b4b846d529b49495837ce2d48540e3549223594033c4be592924d9ca318d4cd5026803d1d7eb65f95a33528842564309883d85f82dbf83097b58a57512c500a90ae2396344c2e7d3c77fa461f34158a79ae8678ab443791cfb793b67694005bdd7159ca6fbeb84b43b7e2023143b2bc3cdb401252466d50b1522ef92fb7197b6cb1c771cf3d9ba71ffae3c679a2129d82d2b611589f27ec36bf0d3a516e972910a89e5ab9d13f8d2a3b182df152ed0b2a6152e0a7bae0c7829b8c4398109e12e65407b1b6a0e0537fe00fbf052a1c11f002116ba82823865136901df2b7340363cf5cccc2632b7647c53aa474557cb64afa76f96a684eab4bce356ab7a46f0626226da8bc46ae0db1130edb4c1d7f6b13f0150650f1f543b9b84a294e9b4b6ba674401907f69c8364e8a9fd418f89d10d53d2ce5b409bf556a3e49f43b10f28e196bb9245158eef8f1220abb059c8c566c5f8dd89d49d9ae1fb435134280990720de1e9866fa39321eff354dc1ad1a9ecf5773640628c390d775482d5cde2818e77d2e1ee9056e00f72f26363a3d0e2ab8ed3879d29e52150b31361e0d3833ad9ae60cf08865face30dde73d2a0035ca8edf5a1d334a7b7cbac2985206d6a1ad296ad8dace65baf8069f3dab2f2692825a6cd4f52a82a3558fe8d7219ff607e887c3e8fc20d9c7e85effa38fa2179e11074df17c8fb8e2b5813fe38cf8b6078575b51263cdd1ddf102726e531696e7fc08023c8f7c712a8dd286c1d303592c1fd6a7593c3a7ce1a787c045b00a7262c686dc2ef9f60093492a7523d5cfc1c07acb5a771f9997972b1bc86851e1e419b9ddd06ac746fb94f51fa56cefb0ec5874bdb93302dd0ede7b91a640f3483c70eb4320edae09409c5331d34a22b78e1a47eb38d77e119a9c7a5d3adc185ead6c8096ac9899f23f1411a0b53ba393156db157c3f27b88511dda9acd811fc75851c4e2aa00e14544c55e0d581de74e8b575a0414c21c944e9381d9049a0e831c065fcb3f84790d09b111598838d9770047b03a6edd27464f6430c3d1d6619415a801facc6ad3b149061582102f570018195c6c39e4a9c9bd3619cb3f230894a5d28bac23104b7092e8e74e99aa99e1fc3c9a15d216a682925d0cc266d6fbe018284b288df27e888b554fedc7f10c8177f7c0bf0023678386f5ce97343e4f1e15e3ac21e3a86bd81e2f865e2469f38a32d2e31011c0aa4d78434ebbb3e0724f846bd5787d98d3bb6046491ed518d9b331a3a2ccca4de4a68b25ef9f73f5617e9fc2e723f8efb66836a893cd53f2dd129e43a268e404518402679eea779b5599e549ebfc8a9abe1d51c653663d7361a10f4602876c97f1ce99ed3a6df9db039e5d284f7b08d9da26db8d1840a2c52cf74bbf45e82752eb926ff3f8041438ad6bf196c9bef2b70091d7e04f193c84a4d8987ba5fcad8f14840e10768f62e5eb3af38c564d0c48befc7d2e6f14a4ba0fb307e9b76f7f2a35049349282ebd7475588c2d96d231b8efd01785f646faa50765eed27d6e38b4a2fd9592a5a7add68d1b3c2baff2b13ab40af698707709b4fde083dd7c38fc70bee15a1086e94f8716f4d032d87c1f6da0c7fd92f1ed205fc5ee40fdd569ee1977ffb8a081a76b78283ac716b691cb8381ae49a97b067359b6f019a4fd53bd04e541379b02b43a3360157e3615e031ab915d1be38817389d376eadd7e52a6562b5c301eb609b59ff55983386ef1536906a5552ff3bf0de0c364a8fbf26f494387af142d2f6e256a0b5cf993759fd1141f649a0586696703f16837fc530017f967d0fdf1e3f00bfb5c82661dff24340786285c896b618a316f2f740a2d0b0b9da15a3918e9e735ee651f5ed814c40332e2823f1916dbc236ba76b4d0ff1fa05fccd902ff48f0aa8111f807155723477fabb7578ad3e85ff8ac018831039249e433c4d06c9c2f45933960fb7d24987b97cba4a3862baf98481efa7beea19c96d0de80dbc5cbd9a87100581b54405f6f53f5da9b254bca8bf1a4bd2b12502b2c91ec01000748102c021a1050c043a10b48781b289834bce6703ca95b7ebe5449c0f1c47547ab79d212e901032adb6eb83a894d98e37158571dc0d614099397c223407fee07b0e76970a01274a8ecb97830c2ed83241f6077b72a8a5ba9854b31390479d73b4ebb4aa8a0c439e686036074b87f6329290b4df0d1e434abb22306a963a31ffa63551e1324ba90739034a3fc39c7d748c451bfbc5107bd97d144d255abf79207572ee6b3e1433195936286a10d1e11ec80381701b7a282674bd66a50d1d283331a27802bdd57e17382866e4c7a7c3e58d212f8529e9f46209573f75fa5cf4e6dd35907f53ab5b21b1ddb208c84107b4ee40005c1cbe28c183fe894c68112dacebe47d325042099eb05453ad6a8e599342944a8f508dea18e3e55ca8c18095596ad82e9451a4170cc8cff887b03dbcb18ca6877a6de60a37f5560788e978099d24672192fba8038269eaa3253e4d70db6a44484b6817b76193b01e6e371439d76d116095a104563adae18e001e86be4980a3bfa0354dc6bf7ffd76b383e77913f00e0bb84cfa71da11545f33f23b9d97e138d6e65e2bd7d3bbaa7a87baa1c05e05816210252ea7240e858b5de33140279d9bf024d099cf2579727afda8c9a2343fc4ac3f56a737d8f076c70b9733f7e1b91980474b03cba91bc7a60d7c05dc4fedd5aaca7507092bd206d57fc97231a2c7a9b019851bebc4032062b1b5dffe29cc6db9b145f5701ad439d3dbc801f13bc0603101e34644abf16ec80479c638e77968e6418edec371932e153ecb6bd7cece0c901b36a9d792799f4425f192a09da8e1e19215d76671bfbcad556dd7c726ea78f7c44908a3a030968e749a51a3d2dacc3b5eea44124b398a5863cab73969ae3a9588db6418f3c6c4d76a166bd76cdf37a059ff4bac3f87cb6ddbbfc231cc5017101bb0024a27afd992652bf059f33bf2f3037acfc206c88721b9dddf3181190c3ac812567c107eef18afe71fedb1c508911a9994a791156ce161721a1837eec75661f21bb2484686b19991b46845fdd73b7bce70ae30a463ff58c989200dbbae1f5fad014a54cc109bf58f42267551a1f695e56c6997f058dfa0b495483ee723e01159c0b3a46d1c219286e66add52e22bbd506c75881676fa8cdb3d7322dd7b9e84d93dd7025e535eec3410b1dec4081cc7cd3cb9174d18a5d415d9f9991396f2fb1493e951ff5fca0bf8a718b1500d14e6c3670413e0ea21841682934702f4ddacbe402cb99b63da8f7cc387d47ecf8c35e3239cca022f9e80c22119979de0e6066dec1a33dddcfebd439b337b66b70c016d54c318b7211949bc77fe212b237c324e53d73a1faf76862818e7cc089d6fa9cb71c13ce7bfa48706a8e1054efa38cbafb7c392dca69fe3ed39aeca2f08f1aaf5d6f09bf093439803ea5aa6c8b0105a7aeaf507707992560b570d2cc9d102667397ba95662e976cf06e694bd5ae70d96159b51ccad0225e9aa46fc37d028eea7232ca21bdef4c7849db546799ffa1f2f58d33088d2e9a7176d4589746865541206864309d4e090ee14b533d2250b2a7fd9d84f9b184e16a653cf93ff028f2ce805822f0863a191122d5488912bd026d4b72db630305906b2ff0b734e35a724b9a2cade11196f03b610b0fe9227864805ccb7cc50d3dfda4591092ce1b205c5496a58ab6d933dabb6cddad9cd0ca848ba681849b7670b01f0225cdb36d10a8b05d8721ab44ed4e89d67b0f965d8aaf843952381de82d8b2e2c86d5fbafadbf6164065d9a9edc2cfca84c6bd3cc16182045493072c06c7228b90bbd4b453a22df9a6cbc1ca27e1bc4a59c1163d921f604017bc24db500a05b6ae801595f776a11f67ffeff2aa206a28ff673510e1aafcf19cffaf5c56c35e4745a807ead146313cf15b694a06c66e8fc139b1acfd3fcbb87d3e9fcf0dff959d9da6331ad6854f7f85622cc6a6e8cc4c9436ae8b1ad9151520351b51f1e36abd864995264acbbe008b580424150cad6f96770f3889386ad32515ef592a1eeb5aafa3688bc386672314e341e4ccb932a9806db33ad5a1227f86e0ebfe02ad3310cb31efa66a0f65649e47a6f7a337965178c0baacf6d01f0b93697bf4e6354569eb77e7d57cfc3e980bc35bd7b6447e4da09289fd5fd7e0b84147e5436b05d82e03edb3d69e5da6a4842f7739e76197bea9ed1638155b8120d50d5bbbef6b53fdf855aab51748586a1ff3ad0a569c699147b8c1b86ccbba510873b179ac7de9544db382c48e2a462b56080da9cd9b58241b23dbd7c1eceada3c539b35fb42c5e26f7ec0aca91bdac522af7befe090b77d3966be3551e3b20ec02235970413698a51fdb20e68a86882746b8cf69e8c08cabf9d4471e06c7e10df808768b64c45972ed623196c0144348fbdc6178a3a04a5e25e23a2a7523aa143236c1428fabdced1c5856e9a2ba97fa408c3ba88b3158f2669c23fa6a5d679d4b2c3177502c189ddeca20c28c15bb3c3ce3bb35255f3b4dce981f47254680d1b5e4eb20f83c1dd605ccce8188dfa24bb9c2e477df4eb99394afd2a13c194213e04952c9995475300d90d973d215be11650e90e0083d78f6fbdd641b48e731f8e14e6d1efef19be7eb8d0648a9cb8ce543920ec9803bfc73ed522509d224d14a380e04e4d6930a91f03e05be548c8ab993f596a358553670550ec2d0b2e5ed0d3be32bc21b4d4ae5d2c79d21ef9c02ef1371737991d1631e417d133efc8e77ac6ae52a9f3ee2713fe428dc412682c081de0d28417e4544ba214395330c70fffe10db494148198b2989fbd5c446be9e9a20a6382c0709cc30ded770ae60053cca092d7849380c241248df3f18d8fdf16b039b4da5c51d2049a3f24c6e294192fc8e8223a8c47bda9a3c2fa82f1a91b7b8eafb2167fd72c60ad6b4e4cab354467bdec2fc8fd68d9c8c3bd6b1f4b1b616166f75e843f7ce4603066fc47d0f857a6bffb5d50ce0c87ca7f240e7fe0a07d2b9fde735a0315165bd30b7234646fb70e1083a2ad44d2421c89f5bc0962133a20dfa7a284054fce7ae90f0e1caa7a72794fe3d66f974b884f7cec4355eb6a45e0873a81598a135c76965fbdc3dca786c13f09c7e37b883e6af2913595a7de62be1f2b7c158a71910a3928d49343d73659602d7228f90329f05483f70e662ad8b570b40f3e689fce7a5041197e6a9289931dd8187ddb39aaf28616f1fd12f040730635a17257537f01efd23e58cd66c5dce2ee1210b408af507b6fff26fdd94b39f01606b9ab98447fbae447bd7e79850b8ef79a323ee52e91d9d156da52f7a71cd369171b86556f2803636c7465110390f732d05f0cf95a241ca69b7b5009ff8587912cda3278513dd0e1ba50d9192e564bb174ccf3fa750c6bca63fd5cafc73ec1c0173e4c330409ac20fee7163694553686b6e5b6959fbb546bd2e031a7a42ea8bceb5eb35889b98cbaa0a4bc0159f65c2faaa1b9ca50f9420a3574085cc800c99b6b18a8969e53e25c22f39b0fa5af7dd7e0c5c9cf6414c73d2f99c08cde61e608fa3834a7fd02c9fb90c501792faeed5e4a3451efc66e4eefd1010e620644a1fec0a8e8db653d714292e55d3f95b08da2bd491efa61dee3217aa9cdf761593a7fbed3bdfd9b22ac7f4a2f8b773ab168cda079cf9dd59446cde1471334a69eb5b2bfdb4fe451381757f4d9f94485ee72b22caca81fdd800bd8be9bdc0ecd93f0e2f0eb574217b11933102b38808729045fc49149054870db8aec1eba49284a5074100d6d368e00eaac315438374c8344dcea87f5eff4b39c33900baf8357252825a8aa8c5ef9e8819ff7e98cee1bc4484197532306d7a9b93fc1561a6a5c103d586935c34396b53987cf20c11dd53796a33b8348aaf13733a9896a9fc95edb7909bf35c8f17fc64268d6200fff085defecb7aecea8ebccea14d5765141d607536f83aa21c52d018dd6335c6fd097e66610baf6bd4cc0cdcc636d697d7256cfc00ec492823c427ba8811ea858722592df5504a972fadab716206abb2ca84c760dcf8b09000ee9288e452989ee2c81baa9d8111976b149d8a5a0cdadc7b2e3433ea881b2010176a23c825d98b4f6a8a7eb91d79285ba1cca826afe8788bc0539c5e205a17fc167743787d242d7ab94b7b5b86bd7b5221abe9fd3576a3792b32a8cd601af4ed1fddd5838be36c04c105d896b5c4812ac29b67abddab5861ec581bba146d581e812161bdeb80842bd29431d3165d41153454cf7d0bdff9a2b384e916856ae62e888df06b953ef7e1de0a9281e0d8431cfef4ac4607c33b065eeb28d186d736b61e5892d1814eeae0aecd8fc8fb13acba860fe99aa2a6619f804f4bbb0e457647b5e0378bf481d84840f3d1838a18b1f8c32637114c612876a52ec13c376a8972c966a37436832b5de134544a0cbdd9a53f980e3a937a9ceedd93e3f27497d1c7f3ec150d43bf2f9e92e105fa791447944de02d23976907e1cf1dab5c4e8e4616a17b6362b731aa857e4fd86b17367a11a37e30839a90513e51228ebcd350cd4dc166f96a58fe0f7d5d30d395c23e279f52fefc49e8c260566ea772025a1822951de00478adaaa4c888e79f4f7de646d8969d635bab9b070c2ac151c7f4d2583457e347e8448052775a25558d94035af8337932a17c1994e42262dfb27c8fbb8a4b74a0eadd60f4a5985f77f4f785ed86506ae720ed2d7ee6a489bda47a93d233ef4b5c4fa33926e185139f98bbd3c458d0c1efe1ed92e756d9f56d78c73593e6200a5fd2e5597373f3b2b501dae3d1fbfd5f679dff9d06dc31dd0c50fc2707316d94713be63536b0f790c176e535982cfb827f0748f722b5e9645f0a92fe86078c55356a65cb17c6e62fca189589d929e226dd7f94245e7dc655c7575e231bd812b2f2bda0ed976006b161a07905e0498484ac96e5351d12b91087c2e88906e6a8f887359356fcdf973b567e74156de347694bdcfc49ed9a6e9994e3ae781d74fd8d63b79304d63bcef33f0d0a33e7e02cac014c932f43f4eed26cd1aa80ffd4c039a13f3998e8e957fa69b1742dfdc3fc95ccd7a0228d4e10fa62a079431c1304a889ec8fedc4298494667789e44c65e7f982501ee35ea2e58c069badcdc6c16cf924fc8eb89b4d3d62bb2f02f8261cdae6c305b39480f9aa9f12fe7d6a62996af1ad532ad52508bd75e1e3085aea2751a08ea28aefb5af9e8b2584ffcf36fe1787da95c814389e17976e3124118a11599a77c70609470de56b3f56e14522ca2befcbe1d439d094b886fc1f5798b5b8817cdbc14b89bf8ee9f5552e1585463db320567e41349cb7ae4f196a88f997a5aa86000cdca9f5cecde1b6d7205b350217acb33c1bcce62327b58e68d850e7ae43616e018fb66cd2d63e66e26f82f11b53ae10dff27d2f29eff7a5d04b2dc7f9cc3030da91c39ae2764078ff5dbe221a8d6e2bdcf60d619c05701f9dc9f5fa8f62299251a11a2853790d7e1cfb452edff732ad00f923cfa84034c9614294111aa3b5e602dc05227fd2b553340117663792dee3ee603664e9eca631c6610fad3910e87c488a41f27e704895047292d8952a8d725c0b6010f936cf493d6be002997cb0c99cd2cb23f73d31f3e991a611fbabb7019c7b1fd61bdb68b5b18fbfafedb1818377c37ee9f984ba5b20653454dccffd78ec70bc27afd70c27a2dd52b854fcb794f11263ae1208d4163c03c08298d3e454fc161336bf50bdddd593750731fdb111c556d055f598923fd45737de6dbc32366a85082c33c339974a994138d8ee9e22945a6d01d4ff36ba275439bfc75c530cd9427affdb7f88f9a358ea9af1d8001586b5ca500f39b48dcc2809cbd281fccc804ba23e319fafa76b23aaaee0e3de7c64219027e0a2508a257d27b3032cbe3141e086a96e3df950b8ef032c322b343905d39eb49cad6022767904f920dd4ab42dbd587de305c56485e00f57bb0828e80f12296860139074e20d5cebd69c58fb3872c5634c7f3fce6064074425ddca5ea2928beaa3e37a1fd7a465b4cc2630ad78c65c09f6515a23017d25809230b21390e1cb8a2043ea240d2f0b2a33e7fbc4a7df8f294f81c31a50927c889b02167b35e372ab49c00026e62bae049150cf216180a18ad05a3a0a1d943073b72793264c937d01bc31ed961007e4ddcb38e1c947c4827795d2b3d2cce731395c66699abc829b15952ee0e558479ddfc761bc99331c28a8ea8a2e3a167020240a73310628c7168fc327c7f0f0444345b2190f662729f2a385608bbd72b752f636ee3b75a9498fb8d58abc6eeddfa81029111b056284287cf897c038f53ac5b5e129c8dd734f6a174adc4f81f0c7ed6894f00bba4189ced47f111451ecd1b66fff3bfa31a91d9953bc9fa70e8656176cdeee30d36e0de408a3280270d42f410d16a160fa8e48a79895624f29dfece2ee6ae1fe5c0f04c63c0322706905d43ae7fd5cbaa85db946e074f27f6f5dc095a8eb16a975262e6ce0221569d0ac54c09a692275c8a6c8f05db52a574f4e1f8aa28c7c3b22228a5b7f46b86da89512a337ff9041f1f50f49ea1ac41a687598532630bb2cfc9c71ff141e09ec2fd66d5aa60628afba6361a375cb2a5758acd8fdec892b1b9657bf627ce9efefa3f55761d6a429ef0b1efacdc06a3027d1aa082e8c3b9fed27c44803dc5abae1b962eaa1e92267fc4a86604e97da56649a8e05e7f53f770ed5d9d58f47ffd32a15907573e4442d49bcd99375267eb38d00deeef8a40223cc991e4cfdf9c456affcd49c4aa87cee39e4738f7cdf77ccdd4c71bc9267562f96489dcf5f86480176ef536c2750400508e3d8c6eed97079ade0ed269a79ef64c2f86d07c92245e4808b344c47976798faa3dfd9cd4f6010d51ac979f63cf36f5c2c7efd3d17883e8d09c00e933e3a60d0e09e34477fe5a13ea9f523c3380f2af1feb3da7be6b0f1a2152999592a2b54670be03f4f2cb26be1bbdfe30ce0b90f11652ba7803fcde7f611f2dcda813edcdc2b4dd5e1fe9f2bea0078bb074bece6e238c46b5464775792680b42913d74e893278a304f54d743e8bb4b2b76d020a6d35d18d40691fbc37d5a292208d87244b93475529dcfa4f32090384e1534935889050bbe93adf17fa04d420219d0aefd7211fc353d88b5c349f100b1f74772ae248e2352339f96007693d4bb09eba6fb972965e24dac95d6ec39e379a338fb1335a55d022358b55da75a6f34c319476c2d1de53e220c3f68c4474ebacaa506b54cc817905285c992309bcd922af8f0259e660e6cf2e3516216ce0deb900c3485d3a309d193ae8b218bc363179a4c9ea971404b0ba2a0bcfddeab4d34b8518c44f8c274bae5bef670d0f8f63921a18c03bf89e66b212623932235f8a15247c24154b1092aa0aa466aaf634ea4533a9a4dbb7083d33a1a2598ca0d7919fcd3ddd5907ebab98c02e0de75ab43139b80b4c4009bd4061647f6ebd4099505a5308f742cd358c6fa40cb55e42b6f9ed50ba45b53d7617abfe1824d99260afad00b62fe2fd080266dc7fb2d5d3035e605177202fcd3959018458d89fd50274552a3fcbf7519f6b496a0ee79b127e965634686014736883914ba039004ed014d30b96f62b4f80d35b77af9e6d7d53e9342293ad6f06b77d674db6bb408dc53bc94d6f600c2aa2de52b93066769cfae7e4dd86a4e7168002eb98d6ac2a5a55abe4a25443915531bbfe2bfaa682552355b96d5faeb9cdff21b81a5124d73bc7f315f0218cfdb181062064d3e4ec2a4012f2dd659c9c6f15cc863da4eb1698c18b61a61a36e47292a4e673f95868d2a21d3f0236b767d725ac4c66186fb896082bccc3ebdaefb8beb157d72f68b8b297348f799a3c8c2b6b9db39e4076d9c4fb819cf3aec84147b32bc312aad2c3cbdfb4e768f03c71bb6ea0bb8ce606f30aa7b436f37b5205f6f7ebba02990c6fe36f9eee50c70bcf463e27345b06bb36251e34f961b29b01552075f497e7165d6693440bd948630c2815b71069337dddba19955bf08bb19c4275c71da9c8ed21eaab3d1bd1e86b45ca30d8efeb83028635bc2f374ec6c68534e5c069cb38f00c0410033ae7c5635c301816a7036902b06c327b48b2adfd54d2124995e030aaaa252c33661150b117f7744f3263896aeaf441c3ce6f8f11c579f5c3003800eb0709e0081e88729994dffd066331e4b97c9ae5ae784c401af11f85b4bdf7de524a29654a322b062f061506b8f678141c9a5e3ee37d6732994623ea52f1ac1cec61b57e8e247141824b43c51829a5af97bfa28d9b96c0a155c5bb24fbbb929c6f0b91e14ab13ed97fe6e09055cd6f287b12584811159d60ad1aca42d1569ab60a40f679f5b89ab462731f39f09aa2e02b63563e037afb78ff781fc9fe55a8d6ae5181bc1f7d5a9cbd9466527a3754f048e499543e7cef08631189e4fdc797e9dac0d1c35dd596cf683370ab55afb64d63f9b47e801c6ccd3930622ad5ead5a4c7c503e44c9ce53fddddddfde33e8f1852c28bd30ed7428b9311181cba022df43513a638b54bd28060830d36d860837de3dd8819041d741074d0c18ed17a314ed59c2a9fe9d7c7eaf19948452b4af69703b9cb99385581f94ca82285ca4b85c91439546992fda98a14d1a66b989faf5c45a9ab5ce52a3a677bf47956ef2b57a9bc84545e2bcff2fd85352ca576c99856791291a6877ce52f49b3f2282fd23321b9a67a37b4aef3de8677147f425bd648224db7b8771faa29d9af94ae5d4ec41887999f237a96cf19b13c2ead744bcbef741fe3459df7ed78bff23bddb3fc5df996d7c1f2e93092837ff43a545ef4315ec7caa703ffe85bbe3f7a30f3d341f42c8f4394d3f22bbfa3f231708c7ee5dbc1dfa2f22b25fc54657b992387f57d20e5edb992ab9c2563dc5d0efacb93f81127fc4793f68ab81a492be91b5891dba562f90cfd99a97295ab6a55896d3c36279d0003cdf9d3b139a76c9229d5316c2f12f448d59cb4afee4e030a51aed9bdfeb9d3b77f1c20ec12c4f842d848e4ee915fa3741cf428707fddb30208701863a42f6253a628c5409b169492dcbdbd2448fd99b6b8f7436c495c6f5ff7cb0753ee9fd4e551a033934422d5cab3a26e7f50aa5d4dbbda9d01064c7f381bfd2534c741d769d40c078ea99b6775a748a5542e263c403e5a49563e5b2ad53454e0ee05c1f175c40c5904310619ba86471bda29120cbfb2e328b4f950a560904a6202f38b1e52339b966a3a8358a33f884a915b86548af3a2055274e0b4a3b680e7c9edeebe30d88d93ee271a484a89b1e72288b7043888a720fbf711220d5845a4f921dab8106d3cba07402830813404dc5bc4d06b2013e0b061edde23fb93dea6f661c3e4508461dbd140024bd06130873dccd6b7216f29661f4098fe59e7d7b0172918e7c6c1380404077ec080068688c0b06b5bc352344d8b5adc9e466ee9ae2b6ddc9783f49938c345f03aa812417f98d479c92fd9df036317d2051e8c3aba79d6a1a37bef75887ef430fd3930fded30fa445f8dbc8182c31a124cc3f40783d37929905a80018056b71bf4edc3b47b9393bc4030d475dd48d48d4428c0318754e814d6c8bff6b59a58d3755dd775df8d442351c7d5785ec76d9acf0fd091ce2f165ce421093eaf98929e17501537b0f686442299482692c94432914c4d60961c9aacb5d69a5e883437c04c2653f69c83e326ca3c014b0f0701340187b7051bc1414f60b01b9e550fcb1a818b317291abe1441e27f26a644d4d0d1874d619d74277f09a7d8da331ea4363523ac7717f1de444decb18bfd633b7755d0febc54468e37102c762ab5b4041508ec47e604f5c1d2f89445ac9349ac09734e7e448349ac04e1c0c7210d6249b9ec061b7aeab8344d067b19e28e3da13970ee090348a02eeffc9dd32c61fdbafb57d303a5818ef5b4f3450b45f8e50f6686a255e572a9976801d7461082c5a62f296d4beee569cb57a6070a2777f2e502bce3640844ee5008f565d7bad09c7b65a2d4deb612589f9b06d6d8ca0f6349c66fba8440752849c7654d511a7ad946b0b0c734ea939e8312a9145fb0430c321680740801a2f4bc11b84527a2dbdb6852608a133bb331db41833dd18b3314629a5f4da6b5b70029d3a3c2bbbe40704417009fe21ca3808fa100cd32fa819b2e7a69c70b0b1001b82a24dcb6439ee6d7191071f4841e24212e301c2fce15a6608963924a5d0341b20c5d9263304d3214370cb0cc13537d93a924824e284883316cc70648cffbd18af208214f0b050a7ad0614cdb484d521020e63cc9bc1021cc69857039c68234494f10dfc9019c145cc4227080e4df2fa0c73319be9509ed91e0954f7de78a397db5ddb3c66c30a1c7e88811c15b080ec82e1324aa9d52b0428d6a48a24539a44616d5bcf74bab76ddbb66d3b821409c40edf2b0509f66e6c52aa2022917a96450e3b7563f349a303035d43fbdceb1d94ce6c06eeefb5b84382527a2dbd5608b34d9bdd999d6ddab5d75edb02eddaaa5d5b6db5754a4aed16d6ce74a6a4d65a6bdd6a9dd564d74af406ba63a8439a04451343486d449b9ee098bb90546323a0d32a21b821b2ffe66510647f8e7e397a70b0861485ec94d6f6e03535b6dedbc3b2ee3d018323499829992df951222382508db576ed28fa38c22a2b2c2da2afc5646201c1957f151209637c6f1439e91314a2789d424c8704e1244272d3c9faf3831ead8b2a2c7824ba9134f1848275b9bfae8bb1f362f47bebe671310526e4189282bf8611b00b4cc02e5081ef8b9480ef5baf06cf75094c818851667e1017b0b9d6bf80cdf3b5978105ce80acef7d5f0e1fccfcb62fc7c18dcb61b338d4cdf5b3396c96a8e6e0e4207b256305d63e6cd6f65a69cb5be902bd654d86105cf34d926740a4e919c418ff1933565821460caf07be5fa8b98262f303386cd79c0ece6863a7124a3ec083ecdf4a228de66ad76c97d7002afafa4d1ca87703fd60401a204b4640bb60fa4b22932907bf5152bfb06132ccae87ca45838df86a57a53cab1ea1ce4944d4994e8d398c33edab76b94dbbdca6dd5a6baddad56ea53ad6ae7aaadb2438c16145c9862c0124afd5349f578c91467a2975a104783a28e3e28cb0c65ced7ac95a0a13a124411d05d64ea6cba552a97e3cd9a9240d0e1587a3d61abc8c23a6458fccc5c47466826404ae7949010edbe592342e6f060b707b37bc1ae0b05533238932fe341d83b6a28583259d842bca9882e017cec1f94148774b97cb45e90605888a4ad6b21e5af9b4948ee229ef99798f8a423941499a9b9be8d3fdb07c6efa8b680333bde845441f22f613b2bfa905d886985330b900d79f72136d7cdbb6267003e8bfb4709764645aeaee6da151865a3605ae5fd8ae8cc37c151c2491b620cd66783b68df0ead92311ebd1b20aa652d6b998fd3b80fef42d62859a3648d6a59a35ad62859a35a26539179ea895050cb82a4ac51b23ac567a84c26b376a5aa421663d4a286425d7c3baec348e0fbf23dafc92e1f3f02c2a83d5aa59a16a4ddcd49adb3e9cbc4c2e11d1aba52eecc67e45b296bb52a70860b8220cbe72ed134dc4e648060db40702703046b39c440300622030453192078ca00c12e0304cb0e63af1fabe00ec7190cf18523b00b45e0fba2170ee3ac57ae7ac4d2936481f2b3ec965d188e05cdcde5074f10d1050a750ac11444b299f6611cfaa1f36edc194ac09c77e305223822208c9b1b84b2b0424785a383d383e1e8dc6bcf893a1920b8e6106b0f53011f483940e56d0b796cdf7d1b5e3d0670dff6f7a77783f6db17daacbdf470b8dfcc04d36e8b35fac08fabba510013188e1081ab4b04034c85603958a96d5f03d7a520055fd22b684684fd07791f14148bc160af5705aa401186baa96813ed1013a7c14d9ca64ff1898d526596c781ac4ad2c45392233f3ed9f6c07275d519cafb3a4a059a5f585f7504d8052af0cca1f6a829077d5455e5a92b1f075b0ea2fae505ed011c5221cab3528d820819c275b9f0053ae24a12052ef25083d70b62304821e1c2ee7598c77c267bb8131f72f7208792fd6f6cef1d068339cc610e3bd15802cb68733fec1229d31cba4c460afc0e138a3631dfa7e91e1ec569763cc66310820346b0d6bbf0295e90a4e1429f7dd15fe36c2bc5ce7b0d3c9d3c2669bc177259a93deebd6f8716e18828a71dd2c9c9487d1e27ae3bef064ee77d366e704a385e39dab0026ce231073d96c2121c7abc42688819271d43e71d07dd648497c36036dc6b408492e7bb77438818e34fcad1d381dd273e63613098a6f95821511615440089547d80e7d740226512c9da554f125c05b13bc618e34f4e46598a3f6b2c0104414a691114cacde39fee99b058d45a25bd21de4a5ff8811373b0fda6f667dc3808a3f3e69ea0043c28c1870634088a45931780ec830c0888b10825be3c9349e4d2f200001de4bae770e6646812ea183dcff33cdb719c271279224fe495228053b2d152fa90e4397c97926b1d26c00d1134c9b870cda21b654cde47ef6588bef0738ca7a228a3c9f8e8674ac918d1674add307284f550cc97482f58854a9a96efef5836fd8f2909f8c7c736bfe87d22d19c9d272a5d4086217a1bbec2f23ed8a021079c9c128e120422e0a083dcfbb79474827821cac2e0c214b3c8563c6c05256aaa4ed1cb1a821b2c19caa94f60b01bad879544f30d0a43b44bd06a0105f94c3cf2e3307fe22e2af2be2ba5eec9fb767ac96947cfe0347a5169a7837e0005d869470f81450874701acd562bdac000146da4057f060b87de72a0d38b46eeba0f01d01fb6803f4efa5ed725072ab94bc6f8cfcea7f01a647f571261f0ef1215d9df6f106bc418e3edac3f91524e9cecda579b776354dae9d3e839973430ddde779f8eeebdc79f0e2339dd7b3abcffa0f4cfc8456d9672b8c0a1ab248df7ddcfcf7954de771ff610ab87f7dd0deaad2fbc5bcb6136a4262573b2f34acdc4633ca69b0651a6c6e8ba0a61c066cc67fa836e141d83a37e601802f7d3067270c614dcdf2fca030833bffb78e2e1650c8e88964699ba8c7157cf869ac5801ce6375bad1cc7b96b611a5a51c68bc418534c4ca9f4f2a282bdc504f66a796b8626f199d96ab56adddc0509edb56227eeb1bb3f76b479f1a649814472130a70d028b70a382f4248d6ae7aee084ef0d30314638c96dbc028e39cc45e0e41b063d034ed8216a40105c639043b6e0329d844d3f470879096740ca0e81612c9c07beae170c3bdbdea540d411a50e051ae5ad594acbac7b768b590702a0a6ca7b4b6c129a990adf7b270939521989ccd1ed2891ccafeb28994c92932688bdd9d4aa552a9d44f2a95ea940edcaa87498f4cff0a1d3c0972dad9212385c8a9fb1dd2074a9c3c98142b62b4a1d94bb06853e2220b98ac53cd72181c06aebbeb7354cba189e3b86de3b66dd356600e46e9803e5dca6d2ab904026ce4be8bde7bc992e9d712bd6f83d5fe76af46dd080683c136587bf4a352052660ee0bdba753d166fb311269e811609d82750ad629584ac26c1421bc75ed03a6ffc2ee07d3a14f396c04236f24ee46d204a5242cdfa0fbc4673409933009e338d68bc9ca90e4b1b98c1ecd9aa939532a17131e201fad242b8b73a37bb469cbe331eec3c16863550f8831d81fb1460cb68858c36d6c08a28c7f68b91cc65813075f4c665370a0c030f7086b5529985e604282a7b525203a57cb93ad8f9bad0592fded8f58c30370050e3b35e59c499a4ecd24ca6762af82606abf4b2c39e3441bd7aa774375e9263c4167bc839e5ad1982058eb124cbfb0066daffd0e9b66aa4035a654a5e99f2e225559a9ca4a5556aab2529595aaac542a657d58954d15b1299a5aa4d27b6308118931ca285128140a85aaa8966779966f87e55bbeaaaaab1437dab6e7a44ba7a9acacc450c11cc7719aa6715a0d365c4ab96d52cadb45cf86edebc75282516929c168a352e799455224ba176312298785e5b5524e4bcb732e31845c5ec6c728c19062c888f1315c3a203ec6afaca8689ca6a2c550c11cc7715a8c185ebd7f6d48fad8ebb652c86d5d4b299e58fcb2b0b0b0b0b05816161616db29541e2a0e0020b58260ad344c297c8161eac75b4b24156a6654f73a89cfa0b94010d4583ed5075d17a39472f42a4f430c098843c83dbf26dad09aa19a4e89adb7ae9ab4f8d32504a152e88c0e757428a42d158c9fc6682c4692c1ab71022679dfaa063f2e511e96cf650283126564515edc0c5e1c277aefc3aaed3898e25e3433578ad9d3e13e93cf399170534c1054dd1fbb86fc2e9140d527e0804424e28ce59e2392fdca6b356e9be9749d83b01713e674423239a594e46181c3f4b2c59d5f88e79cd286b1027aefa41f4be1cd34663c09bc55e6f95f8c4f6be5590dd518638cb55d860c982170f859fecf70bfd6d4d4b07cba5891524a29a59452ca8e31ca643227a61c584692bd0c63197369a400733a58f8586ba544a204c684f7981479e06348c86967870a5f18e124fa1d9dc5134ea30f71f66206a79004452a892557b429612b5880612f94c3e02ff2915dbaa28ce148f4bbeeb1a4197d47ca368726ecb59452761804e5b65a02010ea54bd218a11286034bdebebeadd3d2b741d2ed6d69fbd671d0419a439309cfdc5ff8ee6ddf4854ca116dea37dd22c6f81b31e2021724f124398c32e992ae1f078160303a8c5ef439a22f27ca18ed83a91f5eaf7e3841d45c1bc044ea4fdee783c77c3988e0f0660ac418ff1cd275a9cbe5b2b6674588f31838c04d030a2ca1c00e05b6392469a4196966fa5e46dc0e50f175e5ffdab7eceedb311265ccb7d37d221b5c8103d5fcf0d2800253ef26fe56ba9ffd6c485b92f5068dde0d5b9e6fc303f7f6edaf3398050e6dca89546aa65ce5a0bda92a84043b2b6a96d692ab9ec0617b81c38e75a59da9c409ac9872dad152b84ed16190ffb2021cda94bc76fe8b1338b43345a23685c304873d6453d18600d1664a1a5ca4635648cc26615f36e62a0e8542a15028144ad2dc3b438160afb489642a99b0e972e190a24430fde588bea3288ac225d28c2870e82c1084e9c7cfe3d495ba177d4ef7a2ef17a93cfe7644aff23b31462598f64a30fd2dcfe3c4cd145679eff23b8c1ebbb0bc37572f1f4f2ee349bf83cbbf7c3a44eff2a4cfe575c478191f4f261850d88bb161c23c4e3c529c0f9f894a3c9002ab9234555687648c75e16779d77c491a19afb13cf67e144386cb37bd29f0fc4d54c2997103ccf2f8abe58931ab7210002cdcfa71d075f0bee573bc6f61f1be7e39a3b721bdae144fe164e11c010755463d5054385134d79f434e57794ad9aa0ded405312b41143b82e87bd4a22d2d81f76e5c53cb3a4434419fffad1e99a61030c633f0e0d8536d55af8402691431e5982114b4dd54ccd5414d3355d93073307d306b55f887481ef8755166de644f9ccf53a7727b23fea0898aaa95a53d68725c090e5a128eb635b16c826b137e533b407f5315d9c3321faaee4759fba27d1f7d8543c797655efe72a077db0a9a020fe07959e85f855d0342a9f7596b3f04731fa5e20fab0b320bb00724851dd57801c52148e4dc9989e3d78bebc63d3758516b11dc4991e744c08143543a166a83b9bd15981240d2de2fe480295a2a8554f2a956225710112f1d8dc003d34d513673da82c8793fb736b0e2fa5d40996ffff4f3b0715387c49678e5a8d18cd7be7fce8de5ee700094582608f535e4a59140a8d947647fbd6ebb84dbbd6ebb80d04afe908a694526a499f23edcd64d2e847ba74d6cab35251d75c40298d34ded46e19340ef1848f5fc42e602aa5979f4974de5856ad3880e812105e0ee266ebb7f4cac0cdddf5ef6b4166aba5c9081c73fd82e81ca30c13a93f9966b831e260f9485164c85aab594db3f1b5c8452e7231c678638c97e39ee32217b9c8c518e3b5deb9f65dd234ab69d63ea769d6daf0db5ab7a6b5d67db5bff2ca2bafbcf7729f6d2002ac70488201b17c5a3f40a91c49ad1f1289f4d3020628a57c69f98cbf04bd407939f2f2f3027b79f2e27a6192fde57469b9b45c5a2ead9683fee3a0d7dad2fa5a801cbc02c3b8fce41597560b4a5a60f27d91fdbfa192121694b08024ce5c165e5c92ec2b74116dfa5f90bc148934541563fc1b89172091a6a5155b5adcd7052ebd70d8d262a1489c19f5b0d08a36dc6809167ef288c80a41d146fb91128a449bed5f5ad1c6a240db8e90bdc5489c21755b5a3e431590024fdd872d4f64af39751f722d3f5186ee7d4e2fe69c73ce39e79c734ec1f3399756e8d2a2cfd9aef3eac5f6726921f1e2f299d0a5f57284fef80c5deb05c8a545bb518fcfb4b0464bc49916222dad119196d948092e2d15215be97e0bab85c86ccd0f5b6653cba14b2bfb5d218b96960a45662ab45c5a2f2ded5b2148f5d2a24f55d1469b6f4bb724512b28c81eb938828ae3d97e442540740ea94a7bbdb4b23f0b2e672189cfd097d64beba505c65a888c846a6d82d29a6cca72ae2005cb196b2ab576d53312ea828b3c2899726475e4075584bafa49f1d499cecfbd37e6164449fc28c204846bf65ef43a4810a902074d3889b8799dde6bbd0bc4f641dccc44f7d867362a4a8267ae76bd1a649a803d0371810e23d715b2fd98483d2d31d17d0e91d77d3b1ed39f0e0d3ef3836d2082c0dcb5dc0d2f4d887024f7dbd96fe7a5e146ace17f4b364419b7b1f3f2056e948e83de11f455e94cc7ae7a443a3031791a18fff4bf97698c9146dac4490d3243709dd1bf6b6a35011763d46452b4201c9aa6699aed1b2da84f2b2fdaaa86bb07470f39248d8b8ca0580b4ad69552ccd12a0747a4767db8d539e0dc3818a4dd6df369a9386784524a614ba802c86967870a3c5c9cbcdfe1413c3819e9c774721c95b1ce9bfd0b826a7fb7edbb12a6947a05faa536df86aad99ff7524a67ad3c2b152542dbd0944da88738e7947405388c423dc8198890b27dc41864e81aa85e55179d6d11c8d1c34b68b3abd5a609f5c071429e1426b8f75e6de33aaed336aeeb388ff3bece9837d1ebe0bef35e94237a8f13792621745cf7c94be7bddaac019d8aa8a48dd8f5346404000000000153150000180c060482c1804834269aad637b14000c6382487258381a49844910c43808a218638c32001003000106102022212b00f8db66f517e275de0fa85bb501ababf2bf914dc96a985aa021a7f1f1773ae5f5b44ee3cc4fbbca2bb3ac5218c6c5a2e5b7c0f3387a0e61046969588408ec2ccf244a3d08ef5604a6607153fec40e09fc441c8cbe66d44c4aa8169a134a6608f72a4da0834cd6ecead735a951038200eff1bae794b652e54efc0093619a8e5f9cf9599702d5e5a884dff8ab7a726d709e8e062c6ecd2964fbcab98b061927900ad8b4b73df82719247e41a19b87bbad6b7e1d6ea8ce61a140766811aba6c1df25f4eae688a6a78d7490a260b4f233812512a2ba939d3798b9fcc9b98f6a731d33558953505dda82266d237d12e33e5ef06ac1c1d07ae2d34971d3d77c9c62dbff74c5a1162596cf52334432c4f0080557a1a51aa233433e8b07194e584589568203c027aa2183b767dfd8ab41271a24aa06734e34bc17245938d18112b15ea7fce7563c0009a058b674c28cde843511a25f5759008292fa5024180a7e7e626bce7f640cdbe0611ddb4fed141e3b71250adb2689a2f2f8e498c00729fa019df1bcd8b5ad50245d46fe38f63160a2ff3efbb1cf82cebf15d11a7d1716df020394a3f07561ddc3f36ee6997b661a1e7079adfc0b8aef01c77d70df4fdd1651040f1e4c80db5e6fa323d56b494d45031c99a7d45b80ec310e970606df99c42ade7b4c9b7601b8ca0614f6a743795ada26b22ec2b1700bc7340698b7554e4262900a7565bfe8ee9a2cbec9910d2487494a9f9d16075666f2687d193ef5853a484ebd22a30cd2325ad7145e095e30853b0499f5d1c54760d6e4b16d1f3583f2b52e03d361bfc1539128719fb379b715c5747ea35213d986d224e6cc96655bc0d18a3b8975635d9cf34227eec70c553a4cf870ac9763a897e95446e5b33f9751b5f63240c6d8943cd3671ac11f654ce490f2fbb16370fed3bdaf2cd44036ccaae75a4017389ff4550439273dac59ff4bff3ed1cbbc87b6a82d09b3a4fd53d1f7f1ef2424eba3d06112409ae68cb03c43bcd961af3aebd2b87c74760e2563e6375e87a0267bd9b22ae2a1012b4cb07c1243411989f2ccc2563dc9a871489a6812e30e78310b50414211c51f9f59c178e9894ba3551d18b2481dbfd40cea30dbb374ff1b120c50c15c511c6528db4f3c499bd0a6561129abc0d6712b7c52d1c406efdd223c8c5111014016a64bfa858e8ed3a93e2d1fc48b62c5c1b19a16fa329e622e2143d6f1b3907f7f280662dd9d53de8268a229c436dcc748962c7556e71461768f945594a5358d14787d2e766a0f83f5b3015d28802e60c9017e1a58c9e741601949692c45fba41811b1f4058d202ab7f33907231c7d434c94f1728793854b5ba348eb49142f0494191992789304dcf6cfeb2ba8cc334cb43a6eba47e67121155c9817fbade0cd21896ec920a137b2194874024e824fa122416bbdb53163b0f4749c42c49d52a9c99d9c04264f4e49d6da8848f8ff14985ab02099131253cefa908355c26ff022c06d2e503cd79101acac70815c3145c3c96de0c4695c0598e2f0cc611b8e7e7a7b7da8d7c77abd289cda7250cf4e65eb41b9a730a94a4f94e361962f3a7b54bbe5622ef1adbee4de2b818056c6ae3282ef48d921bb0403192adcf0bb657febca626129ab5c16440753129f45176659e0c6e626a9edcf5706c4e38493c4109504fb1edc2963979952fe746dcdfa9b74af9c598d572f5ed070fd94934650402ba6b06901a8d60a4284294a8f00a1d687153a396bdfbe1a4ef51e864a232dcc60c7504aa8017bedc8dd48e01f88db8f5a55454702626aab3deaf14d8283f5075c01ff0ca84844a873ce9588010e8f46470ceca4051465b560444a68984a84b94ec4f42bed39ae68127cf74f3bdcff22282dbf25cc18a1f4d0596cb0116f8206905916ca8159719b5a7ab4a170eeee387cba37af6686c91845a35bcd72c2fb6ef80b47de2821bdce5a33b174fd5afc75c611093bcf6f1a838869d06af7129ab28646cd3ff95590e12bf0809bd35cbc6d284988046848eb902c18f2ea6be567167f3c372e05013a6b184fb9763d4d2d8ebe825dd65f2d52847179618a14a9288b8b0308eb49ed996ccc1835bd9e9ae588282fc369ac23733587c232ad1c5280a822df94719d5c139c1b4a2dc4fb4dfd93e2e1fbd11366cd8892108eba2c79c057e454823df1d4ee60c16230f20f7082016e778f05c6e33580a66265277e0bcee17696881c748383b1b7126483c373b4da5ffef28f853c662f8e6aed2313efe334777a1f2125690c38d1b289b5e4035a995c92f228ba9fa994f78e8d4351ab6a6cba9062b952992b5d09c3996c661cedba4baaa4bb977bf7569720aca4d3474346bd335adecd993cfc389e5a85b776076164fc0f5948d3720f97be0ae81c24f163e51121fd3ed9f75c16453ac2391f104a30b536085d1e852d12f465811a832d32877e9d1a41047b7dfad91e412f85f1503b0c161bbc63d804406315292e5eb1a136adcdd6fd146262e4b8d9bd337e70803e1cb16cfefcb5682420ab940b0e97131ed8d0289ac5e8fe34fe238902deda4011a8c995ea54c328f21040d7d5d2484d5e3aede24d336ea0a44f09546f94d693e3df91662cf03f6d083b2a36401b75571cd70d548230c274aab6f16adfb0ddacd29f24ef8aff70cb087fe03c29a4609b21a8db022aed55201b72a562f0c8702d61d597dddda25d38c2bc920534f5099c35a854fcc3e70882ccb80026cde794b78daef72bb3ccdec1806c2e86e010227cd67388f142d8ec6bc1aa6506956f6b8beb9df63c8d8b6baeacb9f20885da7f811247d11923ab1304f5b404d8b9591693aea9e3ca7d555bc62fdd8e7098f19b6fde2883f6b1610ca351a4a33e31c4f1ff0ac28c4a5088a6f8d90ce1385cbd44666d946426fd9c36486437b1a3f2c65ee69edb8aa9232ca0008cfd3e4c3cd5b1e7a5a4fa85cb029e17da3e89af16b5ac52184d6f834f52f79f4e6ce4e820d6f57465a5d4e7e69708c1f0f4cf3d36274827c1a4ec316838305644594440b8c48a3ec820a645ac8483feb9c3b8c8ffd5d8a9724a46a1b5518f846f9880565b7e06bbd2ca84e24f3d8719e2db14e4a9a8dc222ed0c342b75f3a1ad0ea58bd081d29d7f3860bcc846e85ac13dbd22c04bd7a978327461c8ee19fb3ac58f51ea88e05c25444f3e8dc9a083dde4e33265bf0a82f967f596cf410f3a7d5f828bc03222d5305f1f4c5254ba83036f5410b175f4dd4dead643daf4838311601fc42b6138fe7d7c63966faeb9865504823b4068e4311d34c9bd1941424052b50192888fe761019933d85b9c0cb0d76e4f1978c204a750ef40aea9c06de2441dc9f8baea3e7b353b3853f003973571ebacac136e2b6f0072c94f220266faccd1fcb942638a7e2a04174c77352e12b90b762d89fd164c6210c27dc18b6c5fa23beec364a74f876207088e0e9d33ab45da61faf8ef1a2c7424ebbe84a889b74f79a741020254511db2f494e343e758d6501025c7a473cbd794320cc4b12a30051cc43db3cc136f0c1d81fe8745260078447a2dad8ba6454234230ea3bd83991a1cf82437321955b4acd4d964fd7235a51bae5e9115db53916d3ac3c6c49678e6c30a11b762e6d114306adbd692b6fcdb2d79b14547f50fc813e056fac38170dffa1696705e0d980c7d9cef8ce6e28892672eab810eadbe2678da5bcb7e4c95b032315750c230ac56f8b358940228e5f47db34b9e25b458b10e8d56b15c688b433d5d1e861fc0237a18e766a46531ac8cecc1f9ad502832ed4d1c04a44262b04c940c91a3ec20bdc2a91cab45f051d8341bac453521b663840e8b04914dff0729ebcc0148369524b735bcc1824f800b7cfa20974f82b45d8bc1f4807fc70c1e0cd0904394fc838c600e173573cbbf165ba56b2e4079168cf86a1a16c4f9bc9b8706ffbdcd6c7794bb93264b179cc8bfb7a1e03a4be79cb8602755bf63a8b1809dacb43190f48dafdb3815733d368e9242958a40b05ed424c9f56b8490300c960daea976092bddfc14a0ba0b76b429f9fd5aaa70835b10ee2b67064f8a1ecfa70f296e27252bfba5cf7ce7c56fdd27d179edc1ea9133d7238ef3658b679cfe548dcefb9c60c0d1164d01e9c3fe8540b7a0d4a4c746ab70a7662caac1b155ac9be195bd7d4befbe377d2aa4aa16dc69006472dd54e2e0518e5fa13e7d0d22281db0722b1a7ff7825f442d7eacd4023acc8d2a3c41c6406e05d96d440dac2f37e71b8604d336a93b78da71cdbfe332333ad6cf787201a992d1652c6829d73e7dc2ed8b64829343d010437e9a8d77b09d964006074d05091b9d00c9ec88d1e76655261923c360a38352b1e2201b01c051aa396601846c2dedf75b81087c5f931629cdb30827df3cb37dd5a82a61702dbf6a369f60877f4a0412a4a65d9fed683aec87d648065df1edd3a1637f4f3a77b6cf55702cab2095d43d5aba9811f85a87b2c2861ef57cc1296914c4b8857fc272e053d170f2a7a18b7692cec7bc30a73eba8d4885253d651ad7035c05db9cbe591b58285b5245546909c6f44ffd1e9c5a0edf55a512c7b9e6d9ae878263046d10e4006c9b7f052cde72aec7a7cfce951bb246e94113a99730238ab39771ce0b10f1cd90c5e03ee8eb1873b7491a78893231e531c5097eb0d28657d36c4c0e306d9755ca910d914143af090145dc18b36adc58e665619195a2bf1e0080e17148871fa197edd54caebb7a890dc38508d256e846b4850a3af575e3637b4123f75ad1cd73cabb4c316e08bc54d3f44b6c0269b3da7f2c803a03ea495b8b152bee0f64261e42bec02884d691cb3ab71fcce489736e0a3a6a5fea1d81bed35a07b01a3713320abbd6da22fc14e05d8085e08f67c0b200db8ca1a879f06b2af3771c1f3182e745d687ef01538c00ff67cc95f097a6f6def45120cdf1d8e0c2623b54220a3037974236ce1a2801862ea3560e4efd8b9e7ad6b80208d7690721a7577fb0e7c8c15d17e576d3c483662b884039d2b33f69e69be2c09da1b63ef4a514bb61ecbaa3b855fdd40ad9bdd4782c048b84163ba6487df6213225fd281443dfc90b5814966a1e0ef37a859a36f57bac006396b698d39283f1acd46bc1c0e2076fd7e17d49acee585bf66e6a59485bb13935d52b66590c8d90aa91014ea0b3b10bdb74996833b8d3c6daea5e8850f96c088b1794aea42256669457b9de71e7ba93eb48e56fcf14068cd71d2e99f9134826f0e704bb72ab9f7f7925cb6f902cad25e1c7dcb01b0329f0b5ca3d3d37cdb37654e39256a0d4bee0691e60762e00f638a86c2c71f0df949b0aa085eefd5f63c6b43a412c972a0bc81aa6ce2153a1047f6e7820dca1ac2215048c79f55d990eb6111a970c800a6d3ee2e5abf3ad38f79dede029154047ecdb3a9a6248b04b54919785dd0f817154e682e7ac2569eeac23258aec681946b90aa5abd92d25bdf0bce9f4371e1d03e53aec2b7dc8f9507e42a6ee7939e9bacb654b972ddf3831d7d9bd7d10dae1a098721701e90c66fc1368015683658706f241663390499bfa39d00e481a1ed9787601b464049f1c184000fa9ca38d26c47f92ec9110b1fa22ee648156782ee236cc961d239f2c85277310c91734a2315356fe8600a95b68e44ffc926b3f5d2fb75938077bbdce4dd15bc8afa00a7331f61f50fd7b3290290b826d97a02ea8fa06e8008cd8c9e6c7d354b0b0895809be6f9f5bd622687c633001eb5e25eaf6c7c56d9b5147522fa99a92fd04d636b334fa24f799108a04fc95ecc9c82919799741fd7811427484d2aeb295a4aa9aa13caeb632a379153e01c065368abe87db73e007a4988862402cf5866261130505244a00374f1e445ba7edea980c686c2acc2127f03393da3893e2005de8bfce2a196421ee43b88a2c43b515e420b97de14718277724229921003669eb297f92dd619f94b741eb057fb4afc325fccfcbb13631fa30ad10778695dedc548bec6e4ee441235d1b393037ac70925355ad5d8deb24aa79d938b10e4f058f336ff55806a72d2b1f0324b01003953382d053deeb3429ed24077721cd0602f782e108ed89d0aa23d28e606c3a0cae7bf2a71f0b40afb0183fe57a0446e968d47c822dc051ba24c046ae5fb5fe1e151f61700f502fa15f948a55d7e860e0e907046d70db062cbed9a12114d6d7007150cff9ac0f624c740bcd7d94789df5281ba9a5e215e83d08a790bb7e5bfba9109c387ebe0b1d1ed9e3f018d533064b0dae350dcb6c63fe13d770cf43a5d87bfdb1d3742d2236a7122f3708edd578b338885b00e7fdca039b046782bd5d790aed9d3a6d70476a3a91eaacdf7488e630081728b257ad89f5643d0e505daf59750e8d520524c9625851f3ddeb65205914754db9239427a4298de2092a432322090b2fc0d140c2b8e6e3fb5655450e9cdef6b140d6eacac1d16160d32897489f2c1bb1f24fb8082c88105a523e0b0532279aff4b1cd16b3e12c877a7e612a55019ebda6d0936794da5c1fa044c5e4bd908d64c2153f36a16ecd99debd4c419c5b979db19fe751fd17a2f3c25496c5e722da074144a7f7ae782a3d2811c83835ac5f1f3f88cba80f84389a93e26f40796e2cd7e597f8c78fba710c5c57110b103e074fd430a075e3268482e84c04f3d608cfe5ef70b73a5855eb085a89f8466969ac21a67a24f933d9ca83a7eeb60ec52b95e33ef875efac605cbd21fdcd95c43068acdb12846aad52e3fd641d89e0cd65cdb5858be74860c9c13a1c99ec1223ee2ec685f2b52f43388936508897422c08653f9dbe41d4e8a03068902a132cf0ba7bc33b4813e10d9c38e54e3f08d79386bf66704363c8604b23c82a9367630602916fc9bd448a76d2a9af1769ebf50d0deae613a3c2ed98221da945d2e9864b43efa54b5751780d678896bd04893ecc59112686791dcc8f444e58333e84f1821905880b3dc3cda28209846b90e959c09932017595711848c8751ac16a607c4e679b30c10d1559cfa4242d0ba7085c21a1b49e8056fc2a815cccdd39f20a8ad8d97a091fa0c39f35243bf2be3e46ee0326e208677048af5c39b68bb344533208bbd0768c3060cb9568825cbb11f1b510525eb80c1d2d5ed997dbfa2d236efe51b1de7a40f5396f8f1e65b7024f161efbbad25ef3a39e613c863ec321db0c634954851c135626d033ff997fe5a50b1d3be8de36669d6232865f3bfbe32e3d49eb8bd0643cab4d543787c55a63a84851550e8fc130ff41f77b6866c2e8a13e8d0701829c2e143845659c8ade5c1328b75058c6420d4c15c6e6d9d2e84441a88d04d6ca48c77d332feabb87dac70be96340887a904caef261dc10af17a20bfa21f540a4b3b23a26c93b9711ccf02f2760886d4d0483235161b563ee0c505dbd49ab55bf3340016f4df921153f39bb9bdfe8ebf5a300148fa008e8a4b4fcaf85b2786e736d1cc6a8c7330fa6a6c7bf30fe4546791d46cd53a16cd93abcc74bd99c4dd9c62d1ff9306f693e932fecf7dd1f883c2ae876535a2b359f7945c8afd8cadb09d5d1718539479b89920e0fd2831bbb0ea8053fdc44887cbcdcbf75aec1b6bef5594fb8a28f8e8a745812ac56feb13572fee5511fc8708d3ae431e19aea9ae30902abc60355bfb143f906eadfdacd4adea2faf69e45929ba538274b74d8de9ea240a499db28a309a575a9ad7c0f300c661d096a1ecc13af40bc6bc946881368c161b0cf981a007a30ba42a7c284c8ebe6f0dac298315a0f7fdf32c19651c2f51081e841e79b8b73a0f5a9e70aa235302a1fe15f9bf244b0534cecdc8f32849e2ec49d4e736118860b849de499004e6f3763ad6e1320047cc356770cc0dbee805442bc8fa842391521221db5453a63f681a04dae1787060a88b2a2ddb83d60c9cc121541e483df0ba8fe7ea4d7e32ab7da20313a77c4537a64f3d055cb1df6c22d602eb7b95c61e5c1a3f238873e63206715be3b336e29a05aff00aa16cab88087fc754a44c68ffaedb9e55ba605ad0eb0d7d217eee1b164e68cc7745415430463389bf8ded6818b0825c89c206295d281025c7fd689fc066c5ae927c3093a3f659bd00fafbf9acbec3e4cc2fc9af6896e74052d354f924b3c395ecc8aea596c569c54ef37b500e4cad4448042ae521427f9021a8724e719adc1f100e917ae0af25c6f5b2bbbd762523b47acefd00afbf6b59d4af65baed563e3040d48fd804b5d20c27f0fa2b482de4972b4511d9f6565720a8890e2ff2d3b162b253c0203d84fd2896eacc4a8228ac68df89e472b517410cf3320aa3c2eb82a43fc1c0658a2fd5970730d47dce2d8f1f703041e52e88f4b4863b719932bc9de5385920c60e519cfde5c0ae2a83f9799cbac4aaf0e3ed277519605647187aab9ea0562a93a6b52c4e5b1c70d9d546a3880d05fdf18125c660e81ac524afe3a7b900360cc0d88a5a0278704f2bf67f0242843819e089b903a72f74f4816b357525ec4352c7d876620141d3c51df926b7234eecd6cc1d4d94e9aa82382d1c2bff801d216481e375f803ecb44129b17711feae76491227f02f14495d70e91f42051c79a804c83e14ed3c8a3cdcd055db24e304d8525301c6c310b6a0d063775653e9ff6db306b0f22ac50f72ed81b32e4db0aaf933a6bb6d06b92f76c59d249d783086b3d5346680d9f240d44f6b6f5b57be63b7b118ec62c3ecc6cc2ce311c41493753928b115505fbc51ad958f2fa5406301f0103c834b821ae0b7e439ffe2ae2285c3eda8e25adea25c337a3ce86428189df40f08d1fb0f3a4edd15cbdf51aca859030d368f836e123d496261667c31587981c93f6230c8148e3088b13232d8d9311c41b2c0451c6a905e1ac3e2bd3bac0c1e3da31962a0ea82356a8070c450830d2c847f7a0c3572ce34dde872cdf0f3163f35e4193818b5b174de618ec26bb621c9b2f2b95bf873cee97840ad30e7cc23207d59a866c8aabfed617ea753cc448ab9b598c2a592671bfe16859c3170b8c45c9e78a7796cb0e0f600404b8d160befa94a2e27aa082559a5e46a53a9407da5870070db0a37557e9e9a7c17dfc21789b6e00f1ded2c0198012add2ca69dab5ce91cd1beb4eb6c2221c650578a2dc205ef6f78133bbfb88f6d8a811dbba1e329dc06696a8437375ed8ee242b402e5e3f30167463f80e54a5dc1be3641af95b055975bdc843c8137a75ac42098caf236e0d72433d9009b6794c48cddd7c7b8685a444043acb2a6c2e3e76510ee9f92235de43e7523e80a2ecd2819d77d0d75cf5a9b2c0a484a9c361b72c5bd6730d53f0b3b1462f75b90f59009855bb52d13d75923330c6b6290c891b5f7a758c2afa6dd93a74b28a0b99c4e24a990e4e47ae6a532cb173af7312b063a915248ad09ea9bbf84f33a16e929323c11b91bec202e36d386f82d2b87d6da1dd42ba9ccfc775004d7f12efcb2ec9894be3bd51ea3a693883e94ec844e8b9e1b98540f37d489e49ae22589f33eb856f7776e700f6fe85377ffa7cafe15932a6758d9827b10b98912c397c9b89ca79d192dfeac8b2da954aa51084b37700f9c7a782df7abf2d82953b3c059e1194df19c9193f7f6d25652e8cf2cf70079129e00a73acd2d5ff41eb2badeccf31e5ce794e1a3c0cff568311077f153451f4832345b24e2b527789337619fd40f0c6a750e2bd5b9c715d73d6666b37fe06309560fdb37279a1346903ea1cfd6f804146c10870a5c386a7af93490e9acd5cf17d322004c240fd5e4c65256a833275289946f1d28144a3cfb183e69760cadc8c45cb100449a1ddd92633af45e4c9321f38bfa41931c25657681860e0b463e864bba7b675b9ffb852cfda7c4d627f7f9d0de29b805c7ca891c64c8fd21c568a78c48179ea0d6fdfe139e250674ad49ece5709993857a78c86db2ef615e6aab5423730e0d1135d8809c3cde7b84a3e648fe68f2913d8860182363a38cedcc382a572159a64d3d8af4271a9907ff2bd0ded025f9a1f3dc90d826b1493a519037eca4a5c838079fe1fc3dee6e66b72649a763ddb0d8a676d7c13995b80ba97e26eaddb07968b9520038eca2e0b07e596083ab2de21657f2c1fc6374c709dabdf67eac2377424076871ebd46f058dfbbd0fb317ba46cf50ff1170b4e7e14a545549db8029b99a29d4a3e51e045bb5f7179d739c37628e664713e5bc8ff7eac18880185efc40c600ccc15462e2e5a22d405e9c2fd7e93c847671f9eb71f1340942267ac2dba3bffec83c58fc19ffdb9f4d7e1f0133c1aa39ad26dd27ee081f758145e4209e6a3bcea2c1844e28598cef15fa6f37e16960f601d1fd3256c013116bfa061e7b43447c99aa0442d96416b7755d249a4dc71540f1c3b85c2982b6445c567e579175dc0fb57bfc8ff3f8d20103418d840184f37e978276f12a24816a007c02a7e319b59717d7899ff2dc9a2e5401a89828da32222cec7a57aea1bf39c702ffba1feb14e40dd6d8a82fc1fa2c0785134aca93bbda13b4fb256929417002f460252e1b2f4bd0eb96245175f365d5095e5a3a362a00f8ff5bd89b24c299a63467a493192f24ef4ac3af87c79916c5154b0f572a2d3b6e0c2b77d6bbd456185e2205ce7f4a120ab1510cbceb614b00605e2d28e0d3fa09865dcd5e54eb411d3344c6db63470be653d3f156e3786d0b2692c84788b52a566d06b8400438e58c94e0685ba13650b4e020481e396cb6f58047ed9998650ec8f6fe8a97ba82c0d11d13ae20e7af01f6397acced2a1d9786d223a2b010e080b6307fbb2617824fba9ce83da9eccf9756cd6e0dd12f21e7c05f3479721947dc5ae4dcb07c68ed51e7061fc64c986db9b99773011aade0cc38695bbdf008509fe76b6009e298a9fc758639fc5311bd6538c5613018a0c84a9953d67158e64449b5c466269129adac176e964070dfb619807e0610087c2d486448078e648c9c9b91e6b29adc11699bf5fd626b618d76481de787f62497d7cae8fb2a016ec46039667cd194cb7cbf4355961e1b52e9401b0308c7f547eaa33d06364e53070d108a2664e4d664c63ff23ffa3cec049ce8f45864cf50d80e0610221e16570e55641806f2ce3218df6ea3ecb216830f367861aa73a6c58ffa986a4d59e05292310423a63ffbaba6b8a066e57b96da86c089794b661da777297b5ef531a15338e7c712eed920687ea7a9a9506d65f7940c0e4861369d67a418bec68b2fd14386cdba522152030af3335c3022ff4e49943bdb1dcf3161c929c610e62523377eef338cd0c20973842a91189c2e08780578224fef7637235d3469671d121439001dc7239a11055f4ed62d2d1bb44912c61c17d225e9ef26fc5a37d1f0b96ede60289a716709680780a5012b79fb5f83b26990e23487f84db12eab607fd0a832d0ac757ed0c61099b85d28a2af18a779f98fa87a20911ed753cd20ccc30c0d56307722f5d2ee0b3b9fd8b3cb5ab4852fc20498d5cbd6ac13b3df4b68b4e378ec5965a445c0c8d240a3a7d33461701511c98f6fd770371d40d0302ae70ded9e2ee7ac7a880c1adeb26be1ab8c8d380b355978fa9207eb0793d2306f1feba0f3857752142f69609aaa944eabba4ee10a47c24403e6cc482d71b78a027879ff5061eff7cd2628f0fbace12989cd8b156163a775153caf124f40d4249dffcace3bb2f501ce333c94d8964af0bf6cc9b23e2d4bdfc9a26f6986d639b3991ed2536060d3c96b25e4188cef8f7dae6c0459242f8afcb4068fc227b0f7265dacc7378f2fd9515b040cb2a86d7b7b7149d2a06ef2dd010a244290b8e877499d05656fdb45e4108be6ec9555b5d37221f70ccb9340266433eab91e3c791a32470715e508fa29eec7feb00145477a9a3d28d773daf3c5a14ce00e351af50ca4a135db0faff082fe4b05864c1c4665639236bf4c9204aa83855f8f65717c2020043655e84ed373e0903046acb3df7fabf5ed4f4164dd523b7da0f64e5bde8bba38ed94bed492f060fad25b65b9de8b874ad1fb1640d8db7c37a5bd0a91664525fd55d7470306e355aacba7477335f3b0597cc61dc8d30221860516fae9c81c2d2d18a590e493246e636ed21f51aa54b5ecf2feb8d8c52577172dfac5e3ad943e0042bb6f81f67e24e5c4ab5e6ee9932cb976c1f7bd081a13bdfd9a5c2136bdc16e448e483d42c0bea896d4e543375a6144863f0184d0ff7b6b735277ada1a1f22b66a33c793c6dc98abdfdfdd2429e9ae0eea616c9670f4de0d4d45ad52aedbdbb4f12b99c70f31d3d5a7b0158087aa5b9976541d3a32d3b6e311f37aea4417a97610aee8e0be9edaf46cdb0e716284123ed0eec435adbf911a6c3323d5cb0ce05fd679a435d7444ce24b0657817c17548bc540f6a425a8a98753df559738b204ae10177cefaead21ddcf78b666885cae10b54808e59f61eff806680b327a9663dc1208b67d5440c05eb95c513af42e117a1b72f60231f4be5d2e648017b407872cdac22ee49a02b7cc42b6619085f8c23e80a33b92976c6c39321c621a9e7e6c1a0c190d8712200672bdf9bfd05d5c87784e9fb0d7388736d7b69ba6c357dab6865b0882bbe1fbd035d99196640f5bb9a9bab08a240aae6114c678eecbda0ab51845d0e7c3eea5998bd090e74f4e3435e750b7a4bdcc03969bc8dfe633c7da268c895706fd0d272cec0af072b4fe16040ad7b96d72c597240813c111aed4cd5e7c746044bde83738c374c8c3334b6bd6d56369ae4b892276a25645c24cad62f2102ec92100f1c3a8ab28a8a4df05937c308a4ee198c0259fa13f4ba70ecfd4486c984bbed89c7ca688d7e41258b0bd6b8cb7ed62b302a705d9e0777f138717a18484f1a04d8de201e245785400a2821a66e9c09e40970b7c3d71b5395dc1c79a73cce75bd1b902ae220b2e3c13f9464d605c1f676412ec4de8e19f12cb63856504a1eee0f05e135a719e84858ce522aecf98121c89d502abbbd69ea749f2f1f17bf51bcb8d61909448198455c8a4de8f7e25d5d48311a4a073c587097c6fea734d53d8ee2a2f51fd8ca2c21bbc2603a33eebc0e144fa1409aae58b25d0ab751a48b909924b24d4b9ea5b0430999065625de1745e98b228fe40829a72ac21182f67a8c3455c856af2e6e98bc8c89790d0ad0694cc14a70278fc3b6e8cbe05743625a3dd6136dcdb78a765a34ed7d9c3130df6f8af1ebfdf53cef22e050287cc902aa066c033abf3a0aad92480f90da344f312ae7597240d71caa8e7ca758ef25213a31692413928cd7956a7b2d6480e4f6db33d8d6e0181f4b93f9b7b62797bc957f8cca6231ef8408134c1b6056421a032cf108c8995fad5289b2512a7326ae2cf0b6cdaeaceb2322a4b50dbfc50270800d224fb89c3f4d1b68065a20c5d725c3683aa1b3d9f895011c0ad249948ad74153f036deec843111f6e8882cfdb95da6a1f6ee8549d81b3dd75013b283283946040d9514d705fc83cdc22c488a9f9295eb2b5de5cc38958e7fcc267c5f40916949356a804858107f3a951c4cd2471734c78171e92e5229200fe14081291dc5539c010413fe4e59b7d33062a5e0a6b6121aad3d9fd29f4a04a4fdee94baae9a4145eb05291406f109a87e558c07a995e8a0b7766f88e615e597c9f0a25f72846ed28026491c751931675f359ed1ac338583dcdde04a7134609545cb4dacd1ba24d66b0f146018ec989a3459e0b516e3a35970c2831cdd4419040a77b9046a3be3f711fb5d6bea1f912f30112030483b323b1a010e1d06f723b9a2784575bfc0515fa2c1b35a0c768fe8564ed523180705b3b3c84484f3291a0b10b4edcbf632ee09cf873574ec39a8b0eaf6c7bc80e8fc8963693b62ef3856003414b532accee84f739d0324b058cdb4f6e9820139147b2165882d90feebe3d8dcf5f09d68d2bcc14f4b45f30e32100276438b8fc2d0e67f4870742092ef6028d35a7a35704be93af56c96434ca49f276ef87f211762ec00e62216136c4a98f922c898dcdf8798fb3a205338492dbce32f4801521a30867f4fdab33b2f448adbf02b31e31bd44cfcb68e677385477ad8f57a0cfa4a3b22489df3f12a6d90bd14baf8cca415015cbf8e9522a99132f5d243f1dbc0748acf6f1e95b3f649cc6bf16217813f42e1ecef0f193ab2b00868ad4ab4286902bd9812a686124c838cc63f70e7474f814c3024ca80d90bce29187d8f39fee56bb153e083541593f601261aafe81a404a17e95fc5eb9119c3de1d013fd6baf85cd0e2235651e9e1ec4c8017fe482287132343d78bd334f18294e26043b772d70a205b6ecee377b4dfe6f0c18466f1458df11f270697ffc490f88442a42241a8ea760624e858b28df513d5d9349f75efe227f87c2e8b9c6a5a61dd8e88c0cf123be50893bc87060ffbdd7a06ae711c94484deabb0ddea99e24c233b7e86ba832f872ae4564d26bce524364e16b9ff060a8818bbe799f6dfe025c1aa3eb4e255f06b233ed39038105e47e163d2c3c270a448942496e2fee29363eabb1e1cc8ef16c0b7b226472e6c8101a66e922296a90c235d4a8f83b5f02c91abbd2d19f9ffc10a0aab19b47c39e5d94a5b20422bb84272caaf000161b837650bc99d1f45ef9f235218911105807dc5d679cdd0b2fd24c5e320778bf9d087ffe5da6c2607ad16a5445030405edb5be0304c2fa89e4982f04712b506aa2ae8f1cc4b5a653891b176fa4792b8c85e81b4764606078577085ed5b18f9e97a48808e915214232321ad53dddf1f2ef9801920e394581977be4de63ad5d9b401078255b012dd84f1047aa299a07155d8934f633f7578ff79988fd550ff9d9a50605df42da5fe9456dcf1e2a3bbd2105157093a6fe3e9859410db65ff81112f517f68ae0f93f451cc02bc932d6d80bf343d29a098c507aa16c0002496c966615dc4f5558d764c1c04aed62726db50269240d5e1d023ccbcfaa1905d10d48e32507104dee0b3bcc37ca155669fb1dd18c351be270bbc5aa0583affd6509e0c7f472f371315786aa887ed8240995c404d500f60934af82fa65cf441fc85fb02c8ab486370d71a2c1550f9f4a772923532d4381e181e29da02c5a0f35f804de6718ebe717c3e5b38189c60e22eb531ce01e010b0276832de96d40c38985754cd6195e10a2a20bf4239ade85616cf49c0ce280f907a748b028e933549615d5b02c690372c8baa97f461b8b06e7f7ebfd5eb1b780df5ac3ec26b6038e452907d696caf621512596f402c9621812caf3307d1c48b95696248b44b39024f42d504d3022456ea120bc5d95441a9f951af873093248cfb46119cac967b45b11ced2d98acbd9cad87f93080b50f55a0f7bef4b9f59ce935b5521b902276494fcf4afe532a67902124849b51f9e552bceb4cd52089d43b50cc9305180c427c6ac7b0435e765b8d65401a46cb0ae98cb335bd6145ec2aecfc7148e132aa88b6e42b14acacc4ec8cb65431f951a930614b884fba2d0c3a88b2cf6902830242dc7c466ac367629965014fe3f10897b0628630b0133f84d5edcc03f1fb4415a7d9ebd0607a674d18a3b0118d0874d4e0e15c54274363cab0a13604291bf3cb4d1ffbe08f2ab0cf706f7ee8af5b66b4cf19801dc66a6f471ac72ec59747ee6daacd1e61d4014d4fbeda2aa00941daeb1d4892450b18cc76f302fec0d4dac79a82372d8e82fb551e7c9c908dae2726ccd851c530b81ac1bdd5fb8239428e56e40734fadf033f1f48f49545d6c6e448075f58f0381dc01158ac2279db4bda7b748662285836561833137318ceb319af41e3a9b49fff59549971c1512684c3a6409e5c4d525860d0b4a3b46e16616a12df47c94d64c8ec8c29925b49da50c79e022d7c87f6695c1cf507d9e35e7f12ca84c7c8292da8f10e5ac4d8825066a3ed09f3e99e28f5282f0d86f060c78fd3c1870f0f00e23b60d4c2510a94bb528dec8263cdca1cf38e4e410a31af1c207f682ebe8a635777e379f2971aab71a9a0a09c1636ca3c9595e93a2273893f7154567ef974260b0184a7ceb5c947a8f677487aaddad5fb034fc1d1b12462c907f1ee2399723598186ea688169a6059b0f298b1ee79a1237022e19f315492266828f4ccdabc351133ea89ed23cc627c50bcb294f01e9f81f2c544476f32ca9804811cbe107125b841993d6743a003c218794abd85a1b8da7218303ba62fb3d6e97d89914db6bf05d2e2b3a98c0f19edc09fc0251eb484ac99ce1ed7e140320f586a1dd4e0fb1fe02e66a5866f2e5848da7281e1eb8a7c34c649356240fad58c0d0c882f3ef15bb72123b1deb3b5a0f3bd417ff672b5bfaca0effccfb152bf6ba861ddeb0092a820063ccfcba60a7c874f552e0ac260ecc11aaf2d26159117c19cb02b1de14fe31a67b1eb422cd81b2d4b4e01303196750730d835bac6e2e95188d3bc5e4d07f6e47a08981dc144f4b8101eec8a91892e2bbebcba9b6498d24065e12e4c0556ff3f4102dab231bdc097b2540b626dce1068094568ba1647576584f1528716fab1e73015c5cc289ca09d95ef819bbaae3d6eeec73ca3c0c04f6477e515d7e36dee90c1af688f9d4ed80fbf076e988748f7d257f487991eac5bea6a906a6d556b6aaf8283b8f4f2e72a7994e7cd8f8bfaec649916b34d8e3c04cbaf49d719e85fa294b9350db941e9baff3474a26ae1e33eeb8b6855730a1c8c46d438dc57b618aa9279266488e0f0a7d757c7e5c52e8e1890119825ea33842aaef2ceb78538cc26f918a89243f4aba14974016932c21ab39a73af668d1fc79d31415828550e896ef3b52e69be04ee46ab75fe052509eab769b4bed361c070cba0f6fb0087f3ed74049ab8894dd4aa7f883fc5bdc564d48b8563efc2f460ad112987daf98f4391779f68e85c63e1c63475ed4a9775e9beb6d2b6cc281bad2b13b961ab3fc7f58bc890d0c4cebbc0e44776eaa4f1fc0b64f35a71260ca892bb35430c8c476572913d7261edb64435c599ea3447a7f5b42de6b5070f611562dbb23b1a2ef8dcb75c68f768581dd334251f4b6803dc5fb6c4c28632586d47cf58cdc87377c4488838094085ba2392b74229bc1f25c4e46441eda020b4ba290bbe2320d40a880470f2e0ba29203f0f3bbeec855555e4b990482e03306bf1e213c3d6a9d911313d45f9d44502178f9691e27c081b4ce3841f5f56d6ad01897ec28488baca7b4a320f8aa4402b9d9a2f3dcf65529b39da9893b0a54520258145a0a4f9088b56d722bf057a64bcbda610f0a569b3f6ea2c0bad29372422915fb6b2bd258989f20604a2bc09aa44522a59cca1b05cf517868da2880f723e21c4dd85b73449ccf61200bb8d9bc4b1afc0c233179af5076349183403ac2ca1a8a3b1fe926faa93bca4b67f037eaad9e819e362b5966b7a51a0998a1318962e92b4d5404946460c42cf5c88a05f3898569d14a94a13c9157ded21876ce5a09fc028883c97d8e738a0a5aca51a9b79d7cab978062121667f0d820ebef562e12b4f494224f77b13c3706b69c7fae241c05ba99c473f152bc202b2522998b9ea1b48eafb5e8c57eb4211a428e1f9e3cbc8f0d0cc876ac08016cd2838e3601e4237b43aef964543b9066a103310db7534a4dc0049b644810769be2d9044bc3cc40dd303eaed46626bf786181454a79a81b54e561992fa264626996b1e4b04247219abb540cc76e1ac90810aff4f8b8272ca1b5877c594396855a6f0fdc6366d223f32e73627707c1d8e3e165a26d3d01cf0d084056c7b4ea7eac822e8d47826842463811ebe3ec3dcabff9bc79c5c2ef61e06176d8808843126928af2360dc4a1db153d0c693922ada0bf2cacf225ae12094fa3fb435e5ee6b7a974e08e1b7bae08bd0ecb1ddedbf6483912721285f8df93746affda5eff5cd4f0ef334887350de08158fca1a2ab036cac7212170e8348033086394a25a3db6e7a44f5946d9bf2dee258c5375d742c59ef37904300636580d0599934d9b8c08b81640e4acc12270c4d001f18a70d0901dce1110c9347b3b81099cc742a09a106c859ab47edd281b937bc7b6e84e6b45bca7d511f365149a583c256f12719be12c6c6620ae77977d260081b48e3cdf8cadf1ca58ce88f48468f1256031a806ce003322a77e76d1dcb29c03572c3c71db33bbfbe5fc583f41c64db13e0d6e4d9d54c48a60cf1c36a12dce38c91f8a32701b926c6dfc944ddb262a7a03b7ff4914577dad6cf64b30d9786068ea510a38f66efbf55e11cb3056f41dd7dd8e8f4d96c61332ebce71cef167a6d7d432e53a7c248f89055bbf2a54f1e710d9aaad8a08c941240726ffd365b3d0034e4a09b0b5e52ef4a35c2206e603a0f4ec2c59a0305aaff84a58d89fb485da8d48498ea960cb9e54b6aa35d921ca5c3077babb0e2ec4a3858aaaff76ee08daa408ba4bf3c463cf6650c1078ab7953c83323258bdacd4e14ab408d7dfa9cba997988508748fa544bf55605e89abe672f0138ba29ed8453bec120163df7b80f265d3ebd0a195ca8f0f3dc0095c2d5124a4e68bf53bdd60db04d1938cc0af00b2cb0848f3caaceea3afa94afd591f7f11a3646ac212ab339e0aad29df3809cf53c3623b49114a6f6235120a1bf2bf7f7a0559204df388a2b7c2405389191b0b202f80b48020b3a5360d1dcdaf8023877ac87e87beedf4799ff293d1952196406dfa786980c6d43ef00bcccf0ae85e633db1899f3c1bf136c32f50c7deaec1d2eee1213a28b3d716e893a7d08c20a863a1407b7bb452fcedea64801a513d7637710205f27f267aae27620c0f2a471b1ae2f8c145250c855e829d231a3db441c4212772d8136600b550c200c258882da9ee7f7ed5f1619600a9bd65dd51d042b02e796390e942d3b18dc2851c0468a403deeda06b0f1485b9d55856072ba1aa7ca8f3ef901160a9bb7fa0c8d08c7d6c663625392ddc8b2bf11f4f9e130e70e0e1d51fdb6da9f4d11e761d45f824bb0e807ce6224a1ac3a16a68801dfebae6d1cbf3208d2a83c1f677e4bbae0b719b3f5043ed4bbdb45af86717d5739b018aae00ee631b579cf89ac2143ef64f471b482669036bbd135b5d3a8ad6ec74407dd3656e03f93ea1a7e88f23bc0448150d9040208dd54704a79fd0353fe21090e3699bdac3afcef823b900ae3d9705696c001b31069cd2860057882798259c18d0e7ae54c81f844bc358aaf6d9aae77708659254d1edf3423391413048438e8b2d21a182d2006fc17c6a9a06e8742e9c9b5b5a0f4cd7607d80d9d46edeafd2bbcecf9337510806571ea1600bc0a3b1db2a8a3f1a67688ab9aeb4befde1eae12057992d228051a470e37b42352f35d756108d5d41704daed98a3d98ccb56eb1955153aeb28df462853ce36da7ac2d75c13ad5f67d00b41419d119a079cfd0c9df968cfe15d81a8bcc0912a5e30e97e6ab8bacdfc45375cc547588237885f4c129459db0839b6804e531d1bacb7610879d5dcd96057d857f47972c664fb6de2a0382b47fa93d2c7ed410493c84d1dab25d6bc0aaafb7cb795fd12caf91f828dac68418350091ca7187c7202223ca03ff70a843e77c22932c5299a8d7921f326682c3edfc10b367c5bca4044a282c705bab4598e3917753b2374787125a7c5fd28223ef617ae4435813da50b3db1072b0f937862202b1b7437fc236a766dd14cb503a409bed1b2d489eae1f29949fe784a356af9d4b638f9057a703ffc94bc9a15906a1c08af8a6095459e44623b24509c1d2880ad3eac8f1b5397003adc56d0c41c176f82dd185fb80969e8b977fa686c6331099f043ce9b13ae6a725adedc4a22ee8e67a18514341d9c5619b2c985ba73cbf97bc9059c56024e13e8db0020ac2caa014e152c3bd7aa01146399a4eec568c23807241be6822db2dabe9b90138244cd187223e464277624fe354ee487b8ecb6818a3ed75af04a7b3a970803dbf7903c911324c9e97b394f896e4ff48ee75d48a3c982d485c3b233a431cddd9f1cf8815632c9b6c675d10018a4dc19d5efd805de23408390c20b8948d7986e18f84cb7003eb98920eaecd1e4be6ced486717fb9d6a1465cfc786b985030d5247905449f25c2b1925e1267bf6f75d03b777ae6f5c446d37d12229996a86ecafece98692401d080aa10a2a14b0d150eb8f05e0142612c7ba92fb51487b608ffb9fb34d66340a8f3254e38155a279b7db1903182c8d68fbbb682de90d1a04dca16f10b830800e82d10f7112fd2bd3bd0a182c019b34e1f72fcae4e5703528be92f1275032e4fe56c58ff56cec3fbaf31ae1c707a888482b42b1c9f89f8c8137409c80277e7ba896d76c21991657eca19d4336a2998915a0b8303ae51c5eaa5cf7c3083b80aa70d0663e79765216ae5f985cc56046dfbe3811c7190cb816f7e31460cf249db5536cd946c45980ff045974a71a0967f70f842a3fc76134f86a12107ba3c186d94d808a8261ec434ede20ca560d8811341c148f0dc7cbee6b7a7db7f7c1c341b1ac3829eca9ad4b969414cacc09a822f568bf79e11c6c32a4216a54ae21e0c8c46a78d13747ffc933271a7cc17e5ca963d533a5dd672be8190d2db39ba8465e142c265756bcb52896e53fafee52e12f043ce19b109754e3138808aedac1e3805d491b1c46c025a14f0e7a2cbcdce4824c377d80164b7c69c4c9c820b026e1d1e553f43e9ff134ae114c79a9c3f83e59c037c31724dcc64584326a8469be890253223a9b52e43c6de4780902b8f4bdea829e6605fc460ee7de840e86afe8e38cd3ec6c228b0f55f9e25ab7d5f26bb36e7985986d4e3e16c09a8af955a94971432b41bdbe96c92281e46ad991012aa7d0fa5494b971f0dd1137ae83bec44c39b361290ac54c3d479210cb33a312c54c797ee64daecadc97a9bf6f3c74b09c0366d01a738f4ae8089e7e6338c28a1df3fd5f6e8c13263027ccc6c6a339f110443820560c3463ef1ac3eb80c881a3ae4bbe0c16eba3e57d94dbd2fea6f94f9a09214323103e55d70234825539dbce7d67f4e90a026f982b82ab3f5c72d3e8a62453a5bbebbcce79288cb086b3ceedcf0a0cd412d189ca4dca3c604ff014a6d362dc64637727a6fdf3a4708c8dcb8293b30005e103ce7680625ef1c8a2d19f9318f5294b16e667401735ff3bd21c6df7abc6ec93756bb977ce45453fcb526ea0b5e6bc0c3a8e0c43b5125bf2475caf82a1c4e92b39e5977a74c38c5e1ff37c66219ca9084b2a6ec835150bb0106b8d6e03ae0b840ea9b6c895fab9a31562abff90154f1a46edfb106855950a0f2a5552e3173a3efb5772f95ff13921ddda3b744a4e6746945d399134ff6f8b0fd7bab8f6d32cf8766decd9dd7a6a6d653e08a385a96f089eb200eeacda4c2e28ca76d12438332f406cc93361f9e504c0f8683d67e28c05791322479c8f53b4b19129d6cc3cdcd4968304af4fa0bdb92c8c18d93ab605b43886cc32187383c9404e4bfd8133971ca46d32fc5e251071b6b4e06e4aeb7a650bb2682b2b9384127848ce5911a14cdf71742c3b1c4968e6b31983a85d6fcee7e119ddb894ce673d45781da98db924a09141696222f57859c2f300f7a0f316771cd84a5420c786cadef25ee6df3371b6762d0a99e7cadcdf39eb23e8727928fed976b2294c9f3107e76bdaa785daffc97736d5d6ad83a7104609c4efbde479f2db3e53c238aa42fbfbbef0fde7db904691aea2289989a982c53b6bdd78a15b0159480abb207ce0c70bb55ca428d62321e4b78d5a7b82a85ae6f26c46ba13d160187b09ed8ae0821a6ea29eb021a226b93a0b264f2bcfa9f608fc2827ba0a43993c0bbd754e8f856b85ee14079d151909b2e900b49d25198d2af95bfc825f03ee1052ecdf381d39ea917b8c1d519bc3ba5b3c1fafb7a69d2b5663e2ebf0c4a9497fca7595ae97303ae051987d152c47a98c8975eb1eb7dff4cdce52bba8ec5eb68aecfeb946369df14cc807405d2e369bbc9a872ddb612b5baeefb8e34ce99af87fa51c7d2c33de8d6e9997375e88743c133eded419326237549794b547c4a5a22d776765adc4f49501d06d60a779f6507a3ee358d2650ad3b22b9f08ee149303038c6caa6225c322312c00ced250f8bf908335cb099c3f4d45fbd007807583b3b06352c54d86dd060530841014e572d80bc3f2fac4d867aaf892bb30b460ab0263d85e5663645dd6b1b47dac151cfffc4efbb08fa5150bc1bc80159fd0af9db5b6d98fdafd859c24d9809640d02b393e8e892d2025f2d6418c7d1630bfe3380dab7d016739607882b903c21717bed143f6f9aeb8d55d63c1b8a790dccd83b7e79cca96840facf2ab5655e35387b8a89180d90ed6625bd6bcdea75bdc381d36864dbccf7c62422d76e24a96dd43640597500da9922de3a4699a4ca25c12c0a9b9c4cd0fd47bb582876744dbba633528536ed68affa96d5647f744c5cdc592e893a784e83cded0422c7a3ff4d58473f4dc4a2283129958383a28c8d115d70e99380842f0e3ea1ad8f7688e99e0682349370c5535f030e33a3a43acd8d97419e5480d244c032c450d9d35c5f479e42de53c32802965fb21530a1b1066936a4a8d246cf1e430bc292029e0dce5af22c822c9f4a0241c60760645180eede7c9044010fa50f792466658fab98d4aafcb41a7e640276acd01c01e09962a6d3c61b27edbd84bc1a15e99630e987031a10c29d29da2468f2dd4bf255c088bcd055d31d98248fea314807b5e2629e1b39c874b0b3f3f4e2b69292b3da6fc2d7b090e4ff9fa7170ac6406c57f14e408826a0f0668d0edc87c95d2490833d839d7ab4353c63d13e05d30c4535df5c057d1286be5768eef903a2a77626d5f1d2ec61d5df32393b395aa437c887a7f68baef2289b5ea2165c9fe8aa768a8af2a380c30795613e0d4c7ca6f3cd5c5304405fa9131cf56e7033b5570356f5da0b964d15e5168870ece30c2e744c92227099c99e190ba0953952fd6f133ec00a5732e3ee06f2027699ecdf265594495c0e37b7a49878cd6a137be32e93502c4e36fbde42fc10fd6164464a8ba7317eae118e5c8994ab4c30d350341bc16c67d42ab2a5efc5a00da5a0f2e2f370132289efc906586648a2ec86568423c67f5d3d20ccc5268c8fe2fdef594bb3f283e3586c36e48eb0ca1751eae000d6a9bdad099f0bbae0349c3f0c6e7b6f5374ac2f5857374c0a531c98c85bb6275c4d7830dacaba15027112187624882fea46837f257d93038b266a0f4cb920e9e04aeb43ed65535a994aea39c6ff6b0d632436af283ba21c7b02d1f117ce6ae48d73645a1a19662bd69e57da869a2207c42f14c129bc39427e0e2ac7744d9040fbba4f9194e734d6f1cba556622bfd3c5207ab2dbe11d94cc15ebe8689e8fba9aa5a86a8e113ced3204ede87d737cf3c2b37cff0380db96ebc5b513df158fcabeab3007ccf395e17a21949a10ac1177b3b51c09ee3a926ccf772d385c47556b088b46428e30551199817505bfb31fe1916a672b9bf63dd83c1e67f31a768dc238eeda74d60337a91c6623bd93525d7e3c483f1bca991bde297cf475c22e22c828aa25e70f85027213c9fe112eb71199b8c8d72298caca5308b063e62c62f63b5c09acee5a7a38278566bda35ebf304264eea8c0100841170728aaffbb9898976a1d5b4acbb4c3bd1b752dbf274d7d0f90523fe559f1d10fadfc890794142412631bdd046a52db9eeea3ff624e7c1c0138b81996b2695fb8caacb8f88aba245619060eaea06f6bb65434e765a036ccf046e6c76cf206f135a8a94b1324584b116d04ff049dd68e50a34f80c788b59dc6b1a8da80c3148919c1d04f577a8f9d5a337edd5dc6632bf159e1556c838c1b686ecca0c99a698d5b04077cef7880809e3ba740e5e4e609dbb64211ce70c03a85369d552e3cf4c05d3dac3eb409774188f67457d71d56cbc41a5e8288a5279034d9420a2a63ec6eb9836d979e7cb0f290bae74817a9310cfe5cd62084228b85653678408a697884ca97c713beaa78a96b7a3e7805f4160646fa95e092e048f7a1ad2d6c4f34763cd2cbf647330e3979cfe70498cf5aab2f700d7dfb8267c65cc6f9478a7781a436dfa3bb0602eec3b9a40d520c71354733e0e1092b3b5c74f57db3f2e2fbc73bd9700e08d3b2ce2914435fc3ba50a157eb22b8c2a43a0ddcbe2217265ac4b5889194c51fc4ab6e6b1da5eee1a644e49a85b0e0bb6a805373aedf6e56230e6bc4cf381396a2c01e75462be4c9a4a4a9439d371ad5ff8d151972266b75cef037208bbdad4e74e72455ecf2d1d9d6c150c208ad557a868f99c7c1bf782c6cdefa80190066198bb6c37858338754bad9aa9f589ddf3f640860594e479d9b76d0b3177c22d0405c524c694a397e02dec86b4976d0c0ab00b97513afd251fca8860b8efd3ec504f52308d83e169a413d664ddb4c40bc0ab5b1669ace816dafc64b7211f3ade0404c99ee6c752d688867e4ed31c53078c19174bab653fb84f30edc4668c90f352989cc521ef219636adf6a36b3904284e85589e292ff84764aec745be6f7d8042d011616a76a22bbca3feaa0234b45816e3e25c8bd05c319471d231a84f631639060aea43f63a658eb61271c352f2ac0a869513fc38762c0566c0bd4c3b16aa02063f2e52010968d7628fd320a96747386aaffd35d6ea3ef5971bc2113683d36a3534719585e4c913c2a589fd4491f49f20b8a84f2c63b5d746a733bc0263dc9f7df2387ee4ff680a93952dd0f7b4bb1d414423bba70183982b09d1f9d9a075a56974b7a2234212c17e56455e6f242304b214d8f59d83b35f257d40a7b094a8835f3e0d3ede43aee96b9668db33948f737191740a820ca6aa4a9cb322e1d995e219f257d2197f5e19db4489b3279e360026ca8d8487dbe72021d64d707c05795ebbfd7babd5ebdb29c098a38d28b06555d7bc7383e4334df80c231a6654cfdf97ef573a96fb86a374d4d64504830699b2d1dcd15d97e880c5ab42b65fb397d4603f9bdf365234869f5171da5153b9e57d975989e9cf94c1f2b3cb2b0a75573b43d3787115014c668c8e6e9f931884966108aed3982bcd31944bf398318a8f00609246026d48474ec75ed4d90e7610d5d00ecab8ede9d17bf8415a0bd01a21b2b04c1052edbc7b1a49faed38f8a834070c3b96e87a6c2c801e9fb82d46da2caf01cc173e4f6d87d81eb6f2e4cf5ed9d33a635eb328cce89f2832d1f4d432746c692f852a91727a220701f5c2c53dc29ceecb812ab02abb7362d8f09c30545e0535fa9a8d5cdef9a591b61dd233415d2df3032ff7a13d95d4db030d9633f96ff8e1b3784a86fb554f2033fbc084e3f394a91dc7da6cee70e075195195e60c4058bbe435e48e4ad3eb5dd26a118f4aa82d109554894c5894c9e1f9672cb0f6b1f3b85fbc9776b0510188c86ae33738b523dcac7c1c822acc04bb0b4b184637136596938ef5341c843b3da3ba1984a1524069c0561f08aa1669aea55748a867445a496e7b02759cacafefa62a0aaf58a2e4e721e0ab701ec3eaa9b06d69a0dc30f84c2cf60e203e0c19f82bdd8213eb982ef380f1ce982e7f247b241121638f3fc81d30d3963dd90b6f3b91fd9cda0b1ebbcf76a01fd5189b38a9bb4a380dd935494a2267f8243abcf106076a3c0a1fd348466b9687e2d10a51aebdfbc182be1e441d1ff7072cf8c6524e8d86e3ebae237e70e05d75f7eb1de242c34394d64838ab098f5e4ac630ae54f34a8e1c646a42dc3ddb3b96550eb913c4d2f4e0c8aa558d5813d68b0352635fc3bd75f7aea65a21bacecfb440ffe18f88be2a288d02fe3b4fad230d33f40a56c76bd20a37f796166e9a19989177715ebad5e1122fe66dd6319aec725857b69b6ca946fd8445aecb339a97afa1324b0c19ab8c1608e695892898891d135d49eff013a6093139e3d861d4b39e046650665a6e29b5433905eb1a95966982c88d0e5c17efa0bc61e4b7b24c8ceb27a20bfe93de263bdaca34de80a7dd717eb871d805f7c9eeac1867df96fe17984279df50b5f9fdc50abff5581021ca9234b9f3eaf9f63fd3e092f83bb79a874e2550470df977089a51267ef5a7d7ec0c2f4eba251d26abc03df26e60bc25a968357e672fe5e84c9f56da8a68e4ec9e752a9cc41311142033ec36343ef43573e675f16009a4a1ff695d0a4a68a2a617f81debcd2dda17e49fbd48f98a68a4300339d5401980cc570bd1eb845fb09e1f5ff3cf95a3054ea4c160c5ecbc157e35139713d5aa1d41e225f1acc2d67a81340923e93ab49452316287a19ca510017d9ce02dd9cc8d38d161fd8ef8bc61760a5ad43db2d88ae13ed22ac3726a517972c40031557e93945d75d6ed4bc291ca7c55574ae035f127d305c1067501e8128fdf13bddd96d1883b83504e4030aabfe885d30145651071934dbdc5660d192412e4326ca329d41600842843330a27ee794a04e7f060d00e080c7363e078f574c2e90676b4346bacff3be5ee26e9b50f15ebe4f3fa4318d5ce7e1e6fdb117530192f5faff8bdab8dbde524a29934c01890891087908b6c48196dd94d2bef1dd784f8df28652ea39b8d15e31f82002d310e67e89966166e026387291ab22dbf0be300a45878676c07142e371e9c1a395d91d1b9b01dee9ee9ef5062a93d5d4eaeda0ebbe99c6d1349d9b065e059e2f5ae1a457ea54e5481926fbed40cf92769c287af5c8ae122ce024c735d752c4c576706c78a847e0818de7813b1c00fede6c0edcb15aa175f84f51885a921ab16c20046108a85a57bb1004cdbaefcc537e87b82217e8fedc7b6f53a92381e445725fafaf69dcf3bc9776a1025ca63415f832b535fb2db9429ea29776b9c8223bcb9bfdccee8ce6a97e5d988b2f8eebec77f6bbd0b2ee7361dbd090cefbf39776672e524aa9e5b9eff6feccfab2483e8adcb7b77699647b6f0e9d3fd99f5a520c24fb0a50806571122ce51e177d362fedca6297756356a7da5ae79df1b8389add19bdb31a34eb993b05ea7402c1973bab41f33e29726f2eebce66ac1a7467a3d19dd1f928140a359bd5eac2528215c828052ae2a2c7b58670f0536e59add55acb707cad67fca94c56f3648b96b22577b97fe1fefd1185dc37c4337c504a69e02183572a83b5b62374adee6efb7a5d9dd75471afea2e9754c3df1748909f5402a82be2e2e501ba3bdd4dc77291e404e6fce77b0cef215f254426ee7dd97a6fd7fd00dbba9b6b4eba5cd2e549d7cc13f8092cb374591715dd859a4d939245ffddcca118177b21c1fbcf3c8117e027ff1f7e72176a681d43340d027ac6df398a5ba8cb4d8aa1498d73a0977d340d273fd63352c668315a8c16a3d16292268447e972b9a44bba7ca40bf25d8216fce122872a822b168bdd6a802975483c5535f4f4111380e8b9582cd675a21c980eb6a960341a39761d2cdb4514ac384a80efd02908235a1d727144e3388c69d9abcd933fd7bda82785f50c169ecb8b8d23561ee57835cf95733ca5a308d83f46088f7568f4a4696a9e13da05ab82d6019b2a94d0b246ef7936ac75a157ab61ed46f66a23186c041bd99c9ef15c31af8919c4ac70a253f6894ed5982d6682148cc2b12a9187e8223ac549cfd593dd7379c78946b051cbc3f1945767e3576979fc72cdf19312ed4215a275c8a6b9a3f79a46f4f509f97d758821f2f8e5effb48a49127eab8eb636b320a1a79ca0c9a6bafbdf6babbb9aeebbaeededbdd4efaecbae62637b9c9711ce7b9cc67f643bb59ad569ed7f28425b8bbbb3b08dcfddddd8d236bd96ddbda8eb338aac033e3900898082cdea6add568b4d94c261bab4cac32b1755d419705c4a48913a1274339fee3d3c3936f5f9bf666da962bbcaffb1add1787e7a9695c560596b29b43c9356e71bcd8709589f7d24076efeddbdc8bddcfd3cecefd170b44374c15caa3845c7801c60d57001dc79de4ebec884a506badb56f59080a6e5ca5d66ae1b5028c802363c95c383cf03cafe49556565ebe182e2d2c2b2f5f0c9716161446803bdb2469e4893a6971ed2888b035a9147237f809cecb7f31c64f44e597f0e3b818e1b842fbb2572a954a5ecad312f5a8473d4a690a9d29b7f4122cc9929412fff5bac6650e8d336024975e827f05167d090ce242991b79e14864277bfd3ca4cca590f41e18a469d9eba629fd984ba003699afeb28f1c426099695d5c9147e94229a1dc3829a1a8a8cce4c37678643eaf148f0103965a6badb5d65a6bb5d2bdba6eea917a0427571cff02e7b1ba6875c13c65753c555d2ed7bd2c3210745dd775316a54806f28a4918712f6144bde3b4e24daf16ad72aabec6e8ec3f8fbfe51536745eb9e880d07c2fce05c47872a59c10ecd86035d47c7a6ae56ad556bd55ab556add56a9efc653b3cb207f930ee423810d9fd03be23bbe60b704fc9272eba48d42d2275db74f62167bd0881e5a394c03119a34f6938daf7e84e12928734c38c2b026b140193defb497950b98347cf2ca2f72a09a606942ffd0f94ff517af92a811fca7f220a4a7c7161f94d9a9625876438ca990f94529801510692fc10bdf7ab2e8fbf5a0da1e4d00a0f3989878f22a370f4a210a60612291c85de8bfe734caafc889fc0186a0db5865a43ada1d6504ba6a4744e826e3767cd93dfc82194921b17bd888b44e8c1db23fef21c1761eec271b58c68c9161d1a1ab236e5f562c509766060c89e519063f33cafbdc62824af5b44aa5efd7171865560b05ce9544d51bc92ebed2b278a7d99eb112ef0601d129de292e8d4f560de4a1e2b2cbbccdced3cd168d431912421d45abbf667bf4c6be7206bb5b59816ecdcdcb4623e383b46603dae1c6badb530503b72794adef48cec46ba546bab937068686805b22e6a58d6ddb6ad57a34ffe337ea4693ae798ae395d413fd951b2fb162f0ff2b9c945ea2b68077994326e87d605cfb4b2081da28e4c48c0f3e504f1ea9349588cebfbb8b7b74e97db968b3748d774855a4c176eb084ccc68fbc248a07b074d954bd7714031a55ecb05aade46aaa1acf8c80254552dbdc07b88c127fc0630f098d6d1b22c76f75b9d50e352808813fcbfbe3ea9b17943d2ece2c755e98a2cef0993474af47c50ea5a53c7256e0b16f403821af9dac97742117238ff6a9cde919087c8dc50c2dccbe5e73926ae8ac15c7ee54516be56ee5aea8e32e8d465b828f90501073c6a07c9f1294c08282310d02064104118495374104216f826012044f50ad3a32cc64e8992b49303578dffd0fa747e02478b4b223b0748c318e23704b1e7bf6d3c6130cf002e3483c5060e01195472067230c3c72f98505789c8185f9f5c7194aa09b04991f8120f583cc90ab618c8b1f13f0f849d28b2b079957390040f8f3005835b2b6b18ad42627c0f6666db248bd91ac2336476675649647967374765aaea9923c7ef2efe17c5c40319f99cc459211d8ff063b645d955a8be30a6282b5b62d103bdfce1c649e80fc69c10f023de3417ac69f24e1ab1100f736cb642f353d436309b602b9f7d6dd2291a8bbebbaae13891cc7c075e49503637d9e2742e2b2c3dd7befbd3b17767bae8b6b39fadab6472894f83f84c31ff048472494124e7109598e93ddfb7ec0a2910665b7213d1292d88cb2255d94eb3a3172d4ee73f290649c6f5311e8f99deb9c3d6648694f40e699c7e32939a5a3cc966fe1b5cc2862655ae316c6daa694524a29a594b65c517bbbc8945edb3c51a0ba240b6b9baa2e51fa5ea54d15cbd3b746a48a257cc14ff4e98f8dd2cfa104cf5c7328c19ec77a47a423789583cc01e8190a455e794774349aad1c87f14d56b0a375b5db01b948bb5ad8d15c84c13a9d6ec7f3fa2b7f2b2e91dea3a31557590153feda3e7b17f444df81dc7b7574c18ee2043d9e9360903fda57969214030c2b2101ec171c6822842cc3285e41d93e90ac726d63d743a68fc191d362e4a0c81449a63d8cc2f1caeeebb2accd5a9a67e977344a613d43bfc63b2117693714e462470bcaf4bb9aa73ed3ef803c35da8e46dfe7c663023d70b65f9f926ae883e56f9322f456c36b1b028f1f174d9f529817f92ee08238804084f40ccdc12ac9742753fa4dbfc665a7e322ed763a981178ec6099feb54d558ca75ffea62a4678b5e8137d172eb8162f32a52c178c4cffda7a8652fa75e6a91f7a6d15b1b0c7b0442ab10c5cb2256200946b7dafd483519d24d7ca515a7532a52a99524ae987c1d255bce451c6892b3ed7e7ea6325e44aeba141ae31c0c955acabe6810f7260c51362202832034ef605cfaa892a327d4a9f73e20499fe652121c9d4298f231c34b1824cbf99a87db5562f903092c1441399fee450f2bdf81d9e12e54a05b5914c0c31c308dd8b52dab46535b688ec6d5b93a3add654d52cb4c0b71c4a30cd028f6f032088a0051142d0e2eedc16a56f83b2ac914fcb40e35f1a10f07fc1812643182f0dc7457f224fc0f3d8b21cd6f48c0f9e61e76ab958835a5ed81ec0c8deb2ef1969eda565a7130cb243cf5ce00e215fdbf50b746b97766936b440f4dbb72ddbea5e18c76b05c47eaa5de2668923d33eb56eed48a4090c5c50647ee739a7f7e8af1f437f0de75740e6ca83c54f92e226f8331748f2881a8d5c762358269db36980c711b8028c4346fee3d363d4f82d9b4994285102a684cfce14d5f33cdac8f26d48176a963ffa01cbf7e68be746839ddc59e4fef101cade1c89349ea0ec92e5db8083ab56827678aa6930d8c9b2c8ed4016b55ef0441e9b4996924e9fa41e5e128264335410cf937c9c024c5f78c65596a3e83f50af336453456ddae59962a698a9679657f27cd3cd24e4aa2fc8e4e3829809c854a32c17f0208f265bcb054dc8a3c989497a8bab6b4bce0b4df6b837cfc481a61e1937af37b4de5423b55563d5c755a29d8a7345939a536175a7f650e92c432d2c96272d45443725f247538e0cc7194c642a838b252fb469cb23cc4d509e2d489a467ebda91f68152ea24ff3a51224490891270b80c873e4514ec6cd142b0599389065e885a5039c9165a81aa9384d9a8166046c21e4d5d634fe2d2f6a53037c7f6c71b5bc9a864bcd60a2c5f9a309499e3d392d3979bed0f234bdf2cb120acba3490673c1c444eb983fb620c926277a66fed892823cbd1c834b966fbaa0a9079c11eb99f91e9d2f4119b7a0525049a834845f58c75578a7f4e40a1dd10d1bc12decc2397962569e5f6f63bd1139110981fe32e65b5006175dab37d36341c2f2ea99590a6a4d95e85b7e579b0834c16aa0361d65228f2dae175acfb4b83ab0c5354fd3143331d1aad1cf37c59a46e6695a92a7690a1314793ecbd0548d4293f77d228c396ebee7a39b0c2ea68c1b0d1e3cb6b8627996825c123429e999f9b5a5d61697a76a9edf72c4534dd3414e9edff2f2944b9edf326b0172556dc9c9a296a729c613cb73da9ac8638b2ccf8e45d3412d8f2d3f79b6c0f234c53c359682649e1a4d4a728fa7ea2dcf27f268f291602928cf3729691aff9f3f4316038c1c082168ce39e79c734e1a3ef8fd4022c02a34bec67be2c9e32ac7fff980c709731ae2b13649993070ba4023789c309724780cb283c6cb7f2cf0386192f3c13ffe3fd69c193f561d193f569ea9ba81d61e3a25873cf5f2fef409ccfbd3db54c95a8df6d0d48c4681688d324179ff5e25391c7ce4cbc9eecef2fe5196cb356d5355e3639f8bd3358f782afc70e4b819e188b18cd087680ed221fa8406d11e90caa8cf8cc6002910486b2065426db409bd71911af1933f0e7704e77c3a3c620d70dac0599ba76f0873dcf73d4c84013b9b40cf2fa0e70f943906d8d945661228730d5006046d4601bb499635796b9bb6a989ff61f1bf1a7309e98f8b9e6b181bc53acb15702442e39dc403d527945f6514945946791aa7d84b134b8841ae1ec02b2cc1348491af5785c94c980e3e718fac098cc3452caef0235dad186009fa008f9225c15601ee1c83c1ae10547dba420df03861a695774aebcaca9f8cc01178c0caca977ec2563ec43f775c36346343fec47bda063f40b3277b1732251c2930337d97239eb2dfb943f92579735705660e37cb503ad1818e84601bdc154b572fc4a0767c220e9697c15fcc08b3a46ab52a7f33cbd738a5206699241e520757c932942a34afa8847367eacc159cc063d36c20c12612d9e8028f1336064165fa5e8d0c9ec77a06e646a0c1340e6846980df9ccea8e118a90c749eb9ccca8f42a6f7f934ea4bcd1f06dc66a1c2381eb73f314a33114b0e685d51783c34fd25a3b613b3595164fad694ad8e66c090ee4713299b6d2099bb039832a91197efab1c562b29b904f4f75526715c8e24802773892c0a46c859364600c0a159a46d62cea14e08b1178acb1779bcb6253c57155d6759d4c8f8b5e67abca841330c84481270964978b9e99b71efc52048e3d177df29f7feb19953cce5b9d5cb4aad679abf316ab6517f48cbf0d4062222d6a4398372713c7fbae7b51e7813526aab3e99a2d17672bbbad3d555663b19a006e9e3ac606518dd5d8bd2c36951778b5d64a0309586ca1790d4bb34c6cccfa74eb58231c41ba708356555992bed134f2db02f7c08a8f0631e8191424f846aa706caa6aadd51ad127ff0ed3d0c250c051c21546b06c60a3d9f28235c6e3a2cc2693559bdd81a9006661f5c72abba93b2e6ad127ff0a86adc276e8ac2f18ac569c16da880755dcf17db53e0ef07c19c2c8279245492af2d9f57b8c2828f30465e64a8047549ed4a5b52112ef3ee7a9ae3b5cecd0da19aa8bb4d6703a48a4f2e03e51e0392c10bde760cf5d3df3028cbb7abf96f27c0e5317c366c15de0f9f247cc926768c30358fa0ee71e4503d1244dad6756fdfe33f391f5c4785cd4c9af232e9c96919b22923564071d5cfc796a4ae9575048734070b8610136fcf041830c037091ae34617aed0ec776bf73d639a7955e84d7cfc91b98577f0e65e060f93d7fb22411978ec51319b96f2a4bd21d29df815c755b2c5a288e4643f268546f9ac67e0d82562db23371d5bca9ddd893bb230473f20a47eeca50a008223b105a905aa3d813980b45468495241e148551133ff93b7109bd464f48454846442d190c2db986231b10a2969c3deb975af747faa4d55eae137923124a09a7a8acb0b4b8c4f85e4c303128c898f1604843a6868d1be26906470b2c80334415be5338e9c8b1024a85d38b27f124de10df460aa793d8020b366a7c31e00a281d396670a4e02b3f782d237dd26a2fd789bc1109a584535456585a5c627c2f2698181464cc7830a42153c3c60d31f77b298c7aaa72ccec68c15e4eb4801b70001264e5fb77f0144bcbb7cb778cefeffbe5fb657fc77c97be5fbef0370adffddf34be656a7cbf9479cac6f78d6ff11bfc9ef1fd12c853a7c7f1fd9249d3a8f0dde5142c807bfc58f8151e07585b4ce14fa06c3185ef1aef143020927ad07cfa1b2403785621ec144ee14824872f7f470b3c421b33c09816c3f00610fc6ac4e020822c321f901b20a6c112c406380ab1901a20078e749001e9873bd0087f061882e13824cf78f90f8e483d689e11521921c7b160db8f16712d635809f40103a41303d260a2edc08032bcfcf098c0017cb3d80b5813c38748cf07bee0d2b29a18e0aae58501b8803b585632b480a8951d34b0803854503e5640b145fc29387eb0007e7dc2bf02881f937a50d10614c8f509bf0e107fe903a202d216f1a3907ae05cfac6f5257e12a907cda56f5cca01ce80a41e34d7909278f8885982d862c591ebf8234f9168439edf62fd8ed483cae4ba803c63b8bf61047e3706070f64b11f105c593a157c450adb1dba9d1d8ea2a493524a6bfd72adb5d6da1ed670a444aea44d30c9bd04095b1ec52548d0f21371091241795a8f919fe4720d452359fa1ff84be19024f287e0b0f48fc321d8238d5042981a563ee513b0f229610632b012262025032a610a2e55127d4982a10b850c7069c8a1ee771f137e3507b8c318aee1fc8942919c52e1719eea2641e3d29d5ed31ad2affb39c37136f68027bf651a061946ce2d734f1cc2008f1f65d9cc97379df33755ddd39f2fae7ccc1ba68af4f3854c15ca4f14f049a296a0f488489f48bc9609a9c08f9fe59311519e82f1bff43fb1746f885dfcaec0d741cea2a8fccaff50f9957048929b128e62ee5ee587cacf53f72a617b28c3081ce3291f51e07c8f65e4f3df53a59f3f5ff4d47d1470d4c14be0488447c9a3218c0c9de9c7d099863bc2c04ffd51704a0aa843cd387421481d28254f35e9470e65721cae99a2fb5dd8e3287b88237b28c72038b2676abb3e78f4b024ae101d5cdc00de41880b05e9bd341dc15224027ff0d84142380e245204860960f42e240c16578448d3c4d019c654826f04e22023023c3aea083c7f8632f37d5ac0b38490917ad3040715c31ca413b27dd31078070a35c3f1b2f2fcd8d04e10ec565f36996c36ab65db8fe2fc1135ae7ad457167d8ddbb8593373f04209485b1685230fafccbd046fcd5d1069cb5d28e5cb13fa825cad00cbcff1022cc31462d8822f4dc0341c256c0052c2c8aa83035b2c4762698bf5782f4927936c240523030d2ec3d1179884652a3e78ad802a8498683191c3dae2eb0f4121f183974d0d61e47b1078f0b219225958c05a363c7441d8d41fb2aa814d0d93f8f7f320a5007cba0fbc69cb3403816683dacd0658c0666dda9a6492a322ca846ae37c4beb8e1be17ab23f1312e451e6c4e451b6382846d993fde69c03e130f08861be985b6d4e97ad295bad9a8bf23520550d90277f1a2d7a4543c3020b2b59ab4da0860c086ba494b77ef946344dcb2156be5030c0d342da7ac66b562ea2bc3b2f0523304318fbf343182a67795226b61edce254c977b72989714048d945fe7ae897cbe572c997add46fbf84628a46f54ccba03ec9e74490e58fdd857416165d72c1715f2c265f9d831a5096c0f308996a9f20902ac99a27f95c10d804597e4b160ab2fcf981a6e142723d64c96529e7cbf7987c8ec3f8fb384e4a2992d207a692c562f5cbfefefe1a7aa6dd45254d72371c39fcfd6b14a74b97a7e61152942f5b90a55c21c762afd82bf68abd62af19fa0dcb15503dd3bfb312f35a669c3b79ba3739ccb9cb5fde8fee9254c8d3b43fa511decff7d9aca3c851d0d0b0c00208bebca4641bca294209f3724481e9fbcff74cd472743a9d1e93455eaea7317cc0fef254db4c070214568ba689b98cc9fa98809140843cb3c873499e3e459e13e8fe5e6b6d0eb2fc1d008108d77acccaf92ef3d4cdf3fdd682cc7de69cee24cf6ae96492e74bd88f2b4fa993e74b3945d37473d2f7989c727a73c43d9d241e720ab0f31d2515397cb07ccb1991a7e8e79b5e5ea74321fb90608af20416fdc8e599250a65093c256b4ec99a74720710bdf7354ea5c820111baf49338011a06ffda34062cb3acc5cbfca59aea09c657f397754c0a094caa0db57d068b323d9dd36a2c2873cce232d3f27fbcbd0a6b815218f382a05739f52c8cd426e1ae9cd1dc89474b9e8fd6c427251602ae4cc41f69f4c4c215a255f7df2ff94804262bf89b83b9ba1cc48e16c14beca7c9ff9ebc661d7cdf554facc537307529c3ef89942b8380ee3ef5f14a7ab675cc25096c0a3dffc66bdfa0d28709348323cbacb5d5385e264b31a9aa6bf297043310600d534f4e5fb8ddea8dfe8ec2a719fe4fbf7a967fa6d25245a0135d05122535263f96006373c349e211e19904f3a435905a612881cfb29a534e43c176029291de50d546b80f9b75f50a711cf0349b0b5379db4b968e5d49c23d0e931d48e19aa5fe80e5714c83cc9973f5f2f4fd52c7fe64853963f6912c6d271d293a54bf96d2b75dad840f235c39802d01d9a667ecfc8f72c7f88a7645e8680e7ce14451c6ea0a183c7961d79bd64f93e7cb234c2666c201aa402172fafdc7fe3881aab1711383468ca9fd406796af498ec22b7a746c7e1845a8a3cd227b96b114e1660043158d00d9dc512245493435d841c21998dcc13b1d11104f647f5008bbffac0516698f768d0e96982e8d0549d441a5084f91a5f811ebb3093e9f1f7347d7d1f8a79d1cb214fad56289428fe7f5f100d476ec8535d0cdddbba33c3a30b50ee7f79a198af3b1cfe5e44cd157dc171b18572ca0d47ee0b470e7f4b38bec8128ee28a4a38aeeecdc59ee18b2b7c695d1060f92e409e92306067af3f05bcb0b17d7c4ce0e82e0747dc85f2898bfd366481f832a8ff453c89005f89fdaba1e3788acb4de50c7ea081980fdcf8c10f563c58519aefd573df3220cc0fb195d004209a8da8c4a6c613b189593983ba85d6cce5937bbcb4dcdf3da90512a475904282dce572c7919baed297d64bcb5330362cc08f788aeba15da7eca269beaf58bdccc3a64ae6fbefce54ad421970871b0c237ff5446c14e0c2cbfc8f00fc2a002fc31f2ebccccb84435c7822363db47a9a1f6550eea053ee6f0a0cc57899c8fd7d83032e1c87f1f7fd8bd3a5068b81d3330dc2fc9019c283fcf971c22609f8375ee689d8d400617ed478f1c570888daff1446c740081e5b5798abef8fdd7c9e9fbefd07de2aa14be9f2bc2dd4c950adfcfb5381c57e1f87eee08f79aaa99efe7609c8ef3e8f87e4ec6f938f7c3d1a66a85efe76a1c1357b1f0fd5c13ee36552d7c3f17e42921e79e74aca902c0f777379d1157a5bebfc3e95c53a5fa8ff67d6133e569844f83c6cbdb9573c7062ce8c224a3117ebf0b932649573f08d60ea7a8acb0b4b8c4f85e4ce0871f8623cc83214c0ffae083e118f3f43f942f17403b344ffd34200d00b80a9a33c291938142387efd3160f571b1c16a022b1317fb5fc07a73b1ff03ab106859a035025a57ff0a6855409b02da998bfd18b4401d686dd6898bfd2ad006cd537f0ab44dfcd40f00d0d6e6a97f07687ffcd4df026865f3d4cf026879fcd4bf026861f3d48f02ed113ff5ebe8cfd13f03d6a17972e2a77e15c06a9ba7fe14c00ae4a7fe135867f3d42f82b5c74ffd1f6daa48e1f8bdf7d1326a446598eefa786a74a9e5fed6e9bf3b9e32cd8a54d9ad5d26ae2a82b2c404b4dc7f9bb80ac5ea5e620223b9ffc6ba4a5d6a9e1abfff689e829130a9f32e4cfa5d6c2e4dfa5d6e2e4e3cc5d520f77fb2963c72c7c5fee9b11ae3b5e5fe0b8e9795fb27385e2572ff0d7008fec18a1bd06c784021811036326f031cb2c28dcd900e5c9b24309487941c1b9930090cfdd15d432ef69f7ec044b2ecfe0bab01b64d05a9100d72b17f82413564286732e08b15f8db4358c3648c04fdb28b530b8f52f6f21191711c607828608147eca212e3239ea2410a699a53f6df6148d3c0c060730e320ccf1386e39a05d3f081312e7e1d6444880e269544a2d54b88f831326d645f08e54953633bc9d8955d9e82f9d124a3562f04ad5805f07ff9e025e94cd48ac07195eb7b600e328a2376d443dc0b251891d99070f080bdb8810d69e4bd68eca02c1289bc1f8d462ff246a1e8a2f45049ee4b295f061789208548be0c1fb027459ee807f03a92a910197cbc8cd0201ac07c51cda29f4e7dca905779a443cd1e38ce6491f722703c65912702c714b2c8f346f53930c80ae63ecdd51649e2df81b749425ffee7975ec8dd088157c42c4f51fc5e5244cc6d70481ae47147963b320736ed0b237986fb1e7845e03b018bbe732f394150f6bcb2dd300322c4534b70c880839cd2a6a30878bee86b5c34c3bd65510883334bd08f4c806f1c816f982af940a6aa699c03719005904934c9f72f6986194618797ed74b8870922f08040996493491d25129c0338c30b2cc63db240b73c0935becd47830cb27a51457b8bc1c5f60520c28e42fb07c9973086159259d6ea3f16041674e8ecf48ca230590364cd50e7d030f2071b3e9c0182bb0c9f105f69fb94931a080043cf610a987e9258f0ec8d64933bc1460b67414981964a643d8810f43361dd85f38b1a152d4a48055b0c22a07f3540e90a97dc08305ac6bb0fadcb52dae6706633b89225b813f57196bc387e92cc51d1255001f4d534318186cae5fc355f6ffe1a9175c1c025ec9180d33c847e560f92e65400d40e5945286a30e0481d2a7f427a53fe9c8458f420af1079cb5b6732e7477ef6e5b2bf5e958605b7b62b087460d6a26a5b49df69440f05ac676d75a6b7d3ae75bfcd55a29f6276af704e9b44332fdc9e3fa9c554ce7b8a7949278d0c939adb4abd4d24b3beeda3a7dee8cd4452291e845a14824128563c7514ae9a493930e044a2947a90cb9abc3a44b3c5ba7bbec999e9452aaba1c9d5eaad5fbeac0e6d802d3efa7593e9550e03ce7945765bfc3915e6b030d29f07c1a44cc95629b89b10866d311e730c70f96ffe10eff678c3ad8dc4fdf29a534c70d30a507681b4ae988fda98c1b2c69add888ad2f69d737e45ca422d971e1f851120f393f5029a596d24a29ad96da5a69a993dddfb03bfa33385b67d7fd7daf03ddbb900b4759be75ca04a5744896df9daad54afa94524b47e0b5fdb17e05b9907371ce9ea7fe183ee09ee2f798c37dea7062295078fc2c235f288479d5c9d177772abba02ececfde610e2bf09c7376cb29e79cf296e5fc415669109e488ec3736887a9aa61ce6fcd99d9c4888b9d47d488cad203f3d8b439e54c99defd716f6dbdf37279ca26a532ee08af5f360c976d86c1b9e69aec4f697f0484a0f2e823cf9f6110549e00c8bd426e39471672ff4a079be77ba0db5cb39cdfe09c303eecb3903dcf1f69f7cf0e1b00204c0fca62d54c73937a48162b07a402a8640dd7553de910c9000000004000c314000028100a86432291502820d15461f70114800c788c48825c1dcbb33088719442c8186088210000000000048646e300638405be2940a1ee31b8c1c48c19156a1e356979ddcc63dcc5681fd1b5bc1d52a9c504741784ef63f6a08c32d038896d122c56fce36af4f92b59dab03bba317d89ad11438f0efc9a852b450a72069b6ca0bdcaafbb2bb20195f3eb8844ead74ccc15194641e29c7006dd5fe02a0d3b2366eec1b8a6c4fa173ebd61b3eae360dcfd06b2f0bb731f1a6bbb60eafa641cde77c167aab1eabe420a31e6daaad5dd26de00e593752a9ea91387280cfd2db6c5b452b127a2f0c595b335e8944fcba5acb48beea289e30855577e5a09ca7733f881501b714dce4856d34cd191b2151546000345bb2f6bdf00b98de8d04b60f7d37014cd4d20530a1376d3334a392151f5d592e4c29762a81e208f1c4181253490ca9a91688e2037b2b10306ddb91ceeafe2ef336ffbdb4cb850219cb8fa17c4e025f9d888711b618426ff307b4f7eec8a1cf0b19d9d94bec091fd10892ee41eb641956737652786432fad9044b78a0d0157d1ed5a10844e9fe043dfb47dd2c69315120b6807841d9e00400946115a0a057307443dad9d845921378d4b423a6f677672ab4cd3b827de78b950601a93c818025d01ae4e90ccf83134e89670589cc893aa4696345b47dd954042d38bec3a007cce0e63f3a9c6bc7f12623346863bfb3badd6ff1ebc7d63a9614657f2a7bda0d3e6cef9ff466c0882aba412e3fc0486301191a86620a895f56361efb431c1a6bbe69961bd68d622aeccba7d94d17cf067148d0e39fb1ed1dbe65c3e99d74cf74c18c4e80089b327e530f483883de20da09577b7300c325ad3ff50140bad12fdd09a78ec81538b59b0dfc28a8e97f02c5257e5a4cd09f4a9fdbfeaaf4450efc450004c6220b25f8256797bb8d97690b01eb222d0bcc058ac0f5f5cd564853c85dc8b93dd944153be0cf2fdae68ffcb40f2a4a691229d2df47f3a40cb3adb4dfac0d3b7bcbf45d8a107c0a55a12690767b7caac237f10e5763fdbcfb3e63c29f06413be089221da79d5c411fd19d4450f724834e9e8796d7ddd89f90622db88c6b403732f057e610bf4abf750e7755ae1936bb32b042930f5528e638651bdb3b01af1fc0aae345f53a1d92c69f9f6623e90e041e5c23a0ce1b51580270538f00607a87c3bc304cc7b8499709880260cc1859880c2345949db94ffc9c9a0a404d7a04067485b8dd655c3a941125f30ba8b10a88438d98a5365a55e951fcea30a43b69cb9a618901f3ea0d7301e4db6dd265bdd12d7e96ea92cdc16202cea790190ca73dec0deeb11bda0da6638eba7cfa154fc472cb3910c14f3fad56810e0a8158ceb425d2628c5815030df20fd13a6922526e36559ae14f057f969fda6833ef07e65c63ffeb5913097c47f386b3bf877179a9b4353ba9ee0d5bf5d4537fc1b942ed27cdd788d7062258a87e730d57aae28f78c3e158ac040fe50838914dce2fc0613bd7a80035145d6271adb50132a14a8316cb2501f9bb4830a16b062bcc6b1d983e79b6372c6a4f49cbd3e81c76f424b866c61d3c9a5a3bf11e736866b0eaf1c38f98d3af5c031b9f9a40b98d8d3f54408d924512e2626aaf594c4889e8c98e86988302d202297857feaa145d74621342e1db62d15c910bb21578f341cea6d0ca7338fd7c744d860970ea97a1e21b7bed1974facca1e391d21cba5a7cb25e9a1cbadb6bdff0226111f1f191d463c9cf311b8064788c6da7b93ca10e8dae4137a7b6a04fd2826b530d1de995fef7062b2577f8fc240842cad7b09e7a790c6cc8f38d90b7d64f820dd42e471fb23c3bf73dc923431c266486a2ad6696d445bb2c9860780aa4afb7db8e3543832451de844eea124f3dba2c6e79706bfb2f56a602021e09e43ea24e3a286344651267ad7a53f812133dcd4ec504aff9bbdbbf56a8bcf04c508fa8c45679d3c51c1889c51a7d31f14351fa9ea90da1b7fdde5705dcb904e1318057c9453bbc99a577fd102dcfbd661c726c28cd7d6a5f36288c2d99119d6e5a12e59aa911f8caf1d00a11c58c74d46549c36fc59e01178a9b444bbb9637b687f9d7e36afd4f6ab60331b7286c040069a0d82d6e9db4bc107fe1fb3b4915cafa9b855d3b7642a6031d1756c7199c020072f4f9319a15a8423ea93372d0c9e71b1936c0cd98abf661559067a091453e6e83c1bf8cad8ddfed9abc62518e6c37110a01cd67075de4346e795461b3e1575069f7fe1df9d401f83c9587b6dd4db2352dfa1d3e78ec5e48496e94b2aa66c2d36b88593ad3b5dec01a0b3c16e223cd8bfb7496c4967796ffea5e537fe407427fb378f70894300ef0d6f58ce83562570842a0d02ca3fdfa168eed06ce7e9c3be06ea8aabc0f57dd70f761fa199134210fa95832ab3b2ceeaac1fc88d6d874916ed3b332c34717c9494be84a1061b454bdc74858e5f4f4086b3fb7d35db8dcf80ae4c4e7c236f9a0fa3491a0b060a05bae67d52c764b856a0491714f07c981956f26c4447cdd6c1b00139d927fb77bb774e14dbde5bf0c24a01c020a0aa1158b05c6a71a9e624ea46e213578cd34fa702f31a8b35a8dee879cb17b5acc0e1168f5d237d3ddd3ea59524ee5910628bf8f83cf54674a50663d58f8b3a5785876c61c9a8cbe4fc2a9c24bfb63a626b59644bc134bc851980108d70ae05f23237f70ed3f03a452a77ab00006a86c6f76f1a593471f3a88b981759d9681f127dc92c3b33db01ea0feacc7f9be296fd4a212ae68a9a4f095f90486d1d11949b046ed98069757c7f36bff6ee3ba19fb380d714c05a0b8f3c66482f30b272456c51c39de1286b043d501c16b42283f16a7d29246b093ba98ae78a146ace4bce173a91222a3990245c62cc00ac82185bd979239aa81c0ea36a3b32147ee0deb7aa26b657e7296da922ffc0661c26c1d1f334006e486ca50e9585297fa9937a3e5ba51d11ea29de1d009c0218f2505640d28ef389973b5d22cb47bf3a543ad650c7c0689c9ba79cd52e6b09c8e23300e879ddca61e142dd51bd15aad68199719bf233859fc40391d38de5d41a07ed891009382312633e77c83a2437c6ba1da2ac17871ac1b7215ae6fe85d2f2d18013db928131fc77892f9ba726599859d4c0ce128a93b01cc0f5300d112b122e375d6d9d228dd45988f763e38a4ffb7291e0e85e5700a6351c825253c4da021ebeba5433a0080d21c85b41e4d65d150b2f8a952bdb1959b2818ed9286c8a9743e88eb40250282bc4b49456b8619a570c9826553b78efde736e9fb69418ccce5529df63079f4d1c4c796868545a699150ece5e92da9c4c47f63a3e741828fb0d8edd2748b205b36bf0bb580b370ce93e69499210adabf04b37447490890fc47f0053668536642c80f9b9a07e01488ab9acc8fc6ab817da9acc1beb36bc3a84e31d2fcb48a0dae3956255566ef3e8426fa3002623e1f3f69a1bd34ff4537be2d7aa218a5f71c19c74342ba8eb76aa5525353803c849ffe823df93ae352d8c29c15e687f9e280a47cf442e4de46e0f036f01f3f81d688a67dbcc6777f1cdfdebbe9436504a49c3c500ea01430a10c5e5cbf4568e2d7e27166188477a0f7f334a4356c78d702ef09da87253018fa140b83c6e02110a44569d786a605138019d55db4b315335aab8005fab427b4e27207f8179fd7cfd90db03f1cd6b74fd8369b05b2a48857a3c5ed99011f957a6b9ca35803151587b07ad80c338ab9a496b9a833d022432e3be890bd4e6de2117bf61426d5d8bc2ce97d7e4fbd5945e2318657f7ed3cfcc254348b0d2b75abbb55f5f3f79062b88739b90dbf24eccab32997e8167785f161fe51dc58240b83ddad9880662ff2f4e5e7e4ff8e2c3c23694a42603fa5e0764c3f8ef894abce88eaaeb25c09a606d6191db39fd320f7d3f3a2be3162f83beaf24641144a0a539187d63e727bbdb607fdf2f2485dfb4317d0b6b60bd41a335a9c824c348ad8443865ee2c47b698ae18054d9da01bc5194fa81d5f0d705df2d679a718315078ee7761229c7e48710554b360eaed716e019f1b36035153060dc84957eb775004333f4561fe48d9630c0ba0bd099ea1e7a9ed29fb736bfaff4c76cda00bb5ba5f6ad4e208f35f30212ea5d475937d3bcf9c40cd570331755c05666f28b7cf774115bce403ad6b1c9ee684b5b870ef4755b0d3152253c66942ac711c3b3a1edb1cec4106d0c7ac654b00b8ba4f7450e8ca4c07f027695b82954e9460c1b9d3326c234ba592c2d131347e603fccc3f296e491c570f61c3a757c22ffe8e827ce88c767e4b0ad7a74c0c67bccb64639a70ad241655473bb78dff6180846fd9fd3c7828612591ae18f0ae501a9009ae227ef1b1a07ccc8ae3bc8dd26f5294f6fd8514c8a0e832056ea4a81eb69b0a0ddf30fdc6468139906c5923e3063aa14f42056271d02bc914c22c1871771b409783d89404dab2694b9fa7019b27d30dc7db39d827edc1e04ea5dbef17117b59e857230b2741aceedaf4d6e9aa2743ea71fe066ab07aee5c011388d651126c2627ac3aa38e3755cbf0614e5ce437812684cbf7f95504dc1568fefe15a2fd08628247c26e714ad90c529b4bfe0f12b22ba34e7ceb2eaa0eacceb0a9348e7501c7b2ed10815a5cce2eeee69607f50f048d7a50a470fd5571790d6aadfd30d3132335fac27774b2b0173d39e998f4b67958ab0502f6909032a33e2f9d1a935206d07397427909b21467312cee66cf4d7d9b7b1e5b96b825064b30aa890c6c0cb77dd3f1644b8a9cb6d1630ab6aebf6b06b13cad46d5e80d8ed85f077ba0ee7eb94cfbbc45b9c773ebf7d1f4db1267f37158c8ddb1e5e7c4785d07a180da99af34382288436bd29bdb48ca4caadc7847997cc59a32f11bf5e235102a4c251be5a36b925216e1155378626327255498df14807f7e432772d1d724173c87932c20388d3536e5539bb02f2b06a4aa9ceba754bbc4091e3c5b89791b1fe18611a358b50714a7eec37fe08c9237ac439896429e9af4e97707a594b6170a9527fd3b815b30658a6f05e257ea695739eb36608b140dabe8fb3df203a00d286b2aa174aa24ba22e6d0a071a232d8e9eeb1c461437e649b9b43f7287da09e8d3d9451608e7261614ba0fecf512f08936023af31dd7c5406079a7bf29c60203af6b85201d84723b782e603667571aa67123ce3bec25a8dcc98b99e54b604e9bd959510ac6004ba4b814a32557e19aabee14f9413707093d1498e7c53b64ba9ee306467e4a4c2eac5177b56486a7549fcd2971296aafb5a4ffc5db1fdad010b183dc855403559bd71ffaa205c15a0363bd35966747bef2aad4924425030e9046df31b738f567f1f3038c8ee75828e8c77251f014794e80f2227f9e256261a113ac6e597bab14e72f7ce217e3dd7c423d5efbeb80444e9416a7c0f2a08aae0526324d14cf7c65adc7db0764a1be785c3151f2031626440e8bb1a31b15ee9cca466085a88ae083f0429c291ff4dc46519f84714427c009e76af5fc6bf47005f50efcc2eac0ead918e75931472f4c11d402ae909d54a1ad713be532f8851b340c26976c9be1f60c2d26926cbf22f79eb94594219f44b4bc2fd26308d6608176422c5268185137391e3e0515db9e9227d3517907338abb42ed551480db5d04eb6cae7932e6b5f93a00b07640b9abb421b2d2626cea30549579433816d7814790f40d5a12d9609db1be8224b2073aedcef986c0f5a039dade364e4acb4c08d484484d8eb531bfbfa7f0113d1e7f18fa5a345e6358183c08511a8a1ad6068a8da10759607c75b8617c3a1630c25579292487b19420e92c744c4032f9e8a0e3e0b608a257520ed574a03a7e07b1205c7109186cca8dc3f4b3be8279da30b51308a9a003c976bb916a48b2f8f9e6ea1cdd88504c7418c8f00ff4ad0678acdd15e39a9bc8e5b68fcca2da478771a94d207051d25dcffee2afaa87d958db50c3ba1b645ad64feba53c79f40db6dc6ce42a435ebda309b37aef0878e65bec9130c8a6d63278a3d0105da19400942ee669054c003cb22cb76c1227c252bb89f14552b6caa3087e3ce24ed578b4410abbbeb1f546c7fb711a4484f286f0b5bbb36efec503b85737832fdb6eaffe361f208df26d951c0f706350dd90baf77b426eabdb8f1e9d26580b08548d0e58bf9b106fdaccdbd5aaf6ae6a084bdbd13c35a42cc9d613f867425ff7c99b1aca2183486e4f8d3b362d7cad2811ab0de5f473d48c1b700ed76401ed5e628fcaf7e02147a8a53f5f2b9121e3df84dec01e942ddecaab18cd181f160963e8d593c6b6300e43dd5d188a2779fc18302750594c01b3c26e4beb3da3986a8db35c5cb932726497eb93479c2ae29e41831f888d36e738346ddc548b8e5e6d540aa587b2c2f43fcff2b421924eeeb2766cdbe37117396e528613ba17b7cd5956a7f14195cf0e7634580a7bec350d5ef8967bdb8b8970cc75db6a3844988079a5a8122bb5c6d81b74921f6c67217ab6134c4f4d377da58bda1839c97de3935dc999ef67c28d48fcc9c174eab0a17867ae5ed0e92e618b81007f02a13a2aab76daa48466a216809a6f3b4d3bb2c85ff02107c4b5c102589b2457cc8a9598ad73611d836a64446700e47b8cf9301e710dfb6dfb1c6584dd0b558792f19b002ed8e3680d6bbfa66f863d05deabb98880f28d06d054fb46fff6753abfd2703d2331a9ff39adb2b8523124179515f2120fcf17222d405d62240ccff2310f46c4a68779862e46862cadb069a8c726fe7dd3a27348e281f2975dd4fb721c3b698368512d26a6c1fa7b4d197ed103dbf0332f79c327ac3b808f89350282db7b511d3f46ed5bf4a42cdd1bdbd43dacda11dadc2f6b946fdc4f68fc67f0a22901cba66388707fcc38c4553c4a607e53f610886e9358d69ad1bdba985a39ebc1e37cfa7e8b309d695364be425d4b305637599b705b4039e865eb4b7fe62c4f381b32ff0db64725455ee1bb21231ba060046cf685b4f5ea3b7d10c22d629ab8859d29e293b560aa2144b150d1e484f009a09319fefa53d057f38e3e3ad64441dd4b4d6ec9b3d2d6d298e4f858d88ebb46ca320564b569d0bc1b4c01a553f226611c1335133b84a41adbcae70ebfb237b6b87df042cf1cf0e3d2dd38c345b6f36fb32f383368e855b3addfb6c42ea00a0efb3df0e98b54bfef1ce1cf50c0528552013d31e116fdee9c6016b24e8f9b9cd2474a1839f7556ed911d956e0de183f096d5cc67ffaa3077f34771b179bd6d2efa3687fc7ab42c534f801f5a340985a44b9648c7eff11bf4b154563b7e019c845a9a11cfaf4ecfd7b569b098e4e14c4185b3291a09308a96080cd0d46a2ede6342672f6c45d1127b7390aaa943c681acb84e4e330471c9910cfbbf957dde3ce3694386aebbbe98cab83f7d5cfca5d2c9312ffa31a51cce4173acd6a9e0f09aafd36771bbcbd2d74472d90997bfa47e3da32d7c7cadd26d312fcc724e2c232f37e38a39bdc652cab46f7555765fb508f7e0393d233d51527afcbc0e6fd0da198167c76bdfb82dcb40aede9362f449f06e14337ea9d2ab08ba0f33a37790cff8b9db51b1ffac1b8b2ccf49172b34cd907316ec31fd75681e90d15a6a0120ede08552848a14a0218a42055fa463fdc6bba6f060b04a529c8187c3e02100a6b461f381e2fef625e06a92d40fdda7372fb4bc0b41835afe9942b624ea7a442c5c5659c5355cab5b1a11697b751f8ee5763d374f2b5d0bf57144b08107f5185ba9cbaa0c3ce2d4d7ab52a082bb18017ea25330045270da1f5cdbc11625854e51301fe4a9f455b829a64552888080cf3c975a6ba342168ea8c3c77428c02afd71df9da7eff37484c5f3b8a4a27c8f4ab85a02322c215976dcaf6ba13c92d00ed022d8afc3ae415a23bce67e139c8a1059aff28afa3a03eac90457780434615e0f9571c7ce95d21026d7ea1533a4e427305d293e68c2996b806605e806cc5a6970a6d5e0f34626c1a9dc9b2195f748e659537d50017d122fb200552bbfe8e643a9d7808b094052f022c41d52e4ece91c24a9f8f3ac48966b1404d2fb21c10d4c0cdb530c1a52c08060086caf8341b8ca195b2b45beb03aa927e2337a87559f51955c13b01f9949e90ef3fe781fded90813a34740a2ad7402a4658efc4e6a2230e991a5a7fb8dc892d12b4cf1e211007ddcb1f0b7bec10776b61e864210acdaaa8c513c94c132b3c1e12b3f7a43fc0008eb3435880a714893a47e9003519103e475180b3a48a649f6c14d1b497892a2cf5b29289258f363a41eacb8a109a7bb34885d23ea36c1a5a12a8f71268cfa048bdf44f47ab92a350a0ad2a587f0c570252c29856ccae89d334c2a09ce628ba5ed16093d8e72a07f55a5fe67c04589727b7425910d3a28a4bbdac4442a9a71d4d20f9672308e6bd2c4ab1accf90262a7b35918918611f945285ca590c4bbf9a26a4fcace788a862dc7d90a228c507d3a020611a0415b8a8b087bbbf2c96777819931c77ef093e1e22f75f1a54fa24e5585446a3ce2bc5aab84b2b23422bf07b00d243c90d0185ab899145e421a529dd51c23605e614748b72679886f7fffcc102c4fcda1a38f4cabb52ff34930e247d0969956e0375ce70ac0256c04d7cbe2d5a0720b0e15c1727c0c65fce7b367a48f8e92928fc6b693620fec84f1817cac87f2d1c1a8cb2777bf329e4d16603a965b725c1b0b4b5c40f54a6cc24b3ae7f24db8562aaf407b7aef81268900004ee353194bcac075487d85f0c079ceb607bce9f8cd23ffce599f0493494d1162708f0b40fafc1e5ced662256810530b74d9d14e7f5c5a5a9278e97a016a73db12b038da1a5fb3a1ed497459bd129b6e973e50a74e165b6ee64bda9ad45356f1ea813c63e2a3366613452962efc3c512e8f3715e1db324ccfd425980baac216fdf56642c8b4d27d0d41d680b88eecbba15ce208d418077074e82fc924eb7a18dd8308caaa4800340558c299845836ac0b3670257da1cb96e1c5a2cf16ac3ddafb16280ad0c40a6c9d05ef982b48289f3411209b280fd73c44a0b102265b987bd8a684bdbb4e6a26772106b3be3f644c6890dbadb9c45c85f24842222f451b570fa870a0af92daff7344a7c773af4db4538a03a2dbe26772652a914c4bc1dbbddd63dcd6e921fcb704488665205d4ef9005ff40cfac60026f3407bc672d356502a55d53fabd7036a9a154e69c52d8a5ad4b81d3a07652861db033dedb81cb74a3f675e0ee49f9298a20a55d2d332526d2541c740122b7710e162d490f8561db7a5bb6566990932292b5f6cc08eb92a11992c682aa037aef631d1870a9552c6e4bce9604aa410cbd3df58d5bf65308e5d25bee97737b7eafcae301e3e464bba3461eb0d2646992d96da3af6386186b79f33148274b279cb71b1bc55f207ce0ad1c2e8ff52bedddfcb01591fa414eb94d67b16140708aebe2fbec07195ece25a891d23b32f550901b367cca9a861aa374fc8fb8b80a8fa1a30dc5196e37dfe5e0bf91f39902cbab60db767420cdc040188c30bcd31c20d3e9bb3036a5c05d08a4f11fca3862be708e15113b82d52e6998fbf087196e79c4967ad97be2b8131cce5724559751c29d2ba0d744de3a597e25f7827b5b537135a7830ab4e9b910a848b1d1ff573b5aa371c4206c1e0e47a9784117cd790e0dcfe5c0db803e270bd199af3be5b73c61b7c041376cf4c67377a870231e3e7baeaca9fee0d85424af057623bf0084154c8f22c84d89d7607a1a8acca27b6252c6c5deed8ceedb5d5d4a684da4c24f7af40d772ce9be1472bc12754bc5915a016cf002020769e1d7d09b4e9bf8650b3a3158760e7da4b90d8b0195d8f383d3fbfd1940512e410f212221cb4bd8d024a2010c9405b78b7f73c202c872c8d32c1863614707741d27525d630da503ced56b591a7c6ef2c3a2a5b49a8686fa712df0dbda603f4807c9a997df102638c0d7a5daabbc6e6da0b36ed9d95059994509656b0109b109b3ac2b57c178ce3e2c034203964c697607f110f070b54e4a9a557b192078739a5a25ed7c5aa490c7439590c3242e0de1a24b01c1836c89d3cb2746dfb2020ac7fa5eaed6150efddd537591a01b9b0619c019d4fbabf09e4f4ad376b657052e3e6fd0695176b5c45d0be0ea67ddd01c2b087fe497737852fb88face464bc29a56914b1e7def6b7a828752fba244749cfa2ca4fd65348acef86215491cddca9858b44119e84949652512cb8c6dc4f4dd140378ce25863b83f1c9f3aaca6ac06cd8d1640bacd371416b1bee971d96649c509a4d8a3a999d29c989ba173953fd6f6176567f3137a3610b88b80db0b0d909e3caae405e5613be0d8ea3d3a5cc67aed517a37d8fd70b3a0204223970b1fead6b41830c556e4a9a1166a18eddeade8971beed0a08a9efbc753c323dbc9f596068960670257f95a30413474574a5f702df5683d7ecfa03e88b44395b14ed04488c205b7d7e245b1f38cd5aaae711bce435311ff08750c709e2ab0367846db3e2f0acae30d1720c4a71612146e225ac0de8b26f898c784095387695daa45911041e67f0869259afaf227e98599c382bd45b8018ef97442154d456f8d171f84b132dc99899cb85ba865356a36ba1c7c8f89783494b012c2a77812006c2342dedd1187c92c6d2ad46aca13d5271b234727f82893b6ae30bbcac1e7d95c01f727102f345056c5084cc6a09d1f8b4c492486f32549519c0fd6e1b21432649fe5c2701c6ecf1f72259239087f8d74e18571adc562393ffdd215510ff9564c2bda7cbc41796c5f728de86bed70feb7ac2992142219b72de49dbfa7899c81fb819ba1db30c16a61b897a29578b7cd41d389442c854b99e215cac9b8c812bd605459befd8b9eaf1772def3be835524b7d29a6758cfe0e8b1dabe2d0cb7c6239273df64b2fdc183707f9c154992c6b20c2080d0e7186c868041f71b1aff13a4783ab47db5db976b4bc4c1c4ed9ada04efb300fc9a021f690ea73d59794685b2cf7edeb6cc64745536450a7fc56747c991550a876d7c858bb31318dad14cee42ff9a826ba0ad92d9db4ff3a46c2ce53db00a45c01c93917750e54030d701b7613c5a7802bf993b716360b8447646a805384e05624214e2ea61d452e027d8c47cadb5efcf6b551b29c4e61490f2468e5683b5c3522f4465b2fe6639e7abfb0cc6a6789b9dff84b20d20cef687001d5194da45e5f8607ff9a31b70c17cc9bedfd6e5cbdf9230892cd6adc9d402198c25d503be2fdadcdcaed5c78fd4adce8b794f9004f3cfad7d7179e3f2ee938e831710004671aa62dea5dc8fc5124c18107413d31b5e28befb9388f532d435369cd1b5043465c388c33ead692e205b5c4c523e5c48efb8dfdf510aafeea96049b839a5a291c4301df5efac8895d2ec3a0d452b84f685d5a207d0c78638d2302296b005e64a101cca57db8ea9bfcdc1fcf036a73c5113142c64266e3d1fd760e30d27fccb763c958eb963df5271327327d6abb867a4a372fc5ff272a6c7dd6f20a205a3c5e06a85af30915ac22aad03b6495511a22f3c5286f16d6b79239e2433aa0e3d824b443bcf42f08606d8bf6194005ff1c37cc0275826e8019bc3a752a99cd6931d15c4ac69069a383fc4bcb34a431666092646960d17201432e94e1c9c760b43853fff61d3f86ef6548070bf8f3751b5e6b1d0984b1ae13fcf67708208a0dc8b7e2135dbd0010d4975f9a309307ac901fa475efeeddf4fd381debb656883e97515e21fa395aec1637fb1b4ce20841d6ade28863da672bbd15a5cef9e8b2504a7aae939e96abd80aefea2124dac15c255be7fd430946f7b5c375913f8574b721fb6fe415700e335e1112a74a25a5036882a97cdc12970d359a3436a5bef7f924c32dc50703aaa5d1a5fccebe8d07a0b7b46f09455dea72af2122d692c13faf2062a577bddc72b36d360750f141fc7aa709185142fdf7d06dca39a5497313a758d3aa84f6db12bb83c9c60d6a33a85c80e04dc0804125499db9c1e10e2b9ca46716c2087c2bdfa3a04aa739954480da2095d7b6b8e4dedec9a01ee1aa06a89faff6395cf58675498ba7a0e85c292399da259614090916a6761f04c52f0507a59d605ba54b05cc19bbb42e8ccf9bd0162958d941f10f295a92c3942d9ac989a4577247969736383429a4a0c2ce15993559e4c32fdecca60f96cde775b3a83bf43233e7ebe6d2f896b11426370718815e90b3b4fb386ac2749b70ffe53ddaea4214d34708574efbfd411d3391d8fe64d6c551ae474e9326c6af10fd9138a1cb35193ebc8622570e41990067950f0de4556044ce9b1c4d90ac04cad799d204558654ed1a57d33496ac2d45388f3916cb2bcde8d243c806be53aa5f35fe57f485822256ff122b0912c2c824483884192ccdb9ed9b058788f7dcfd1bdfdf9b63dda3899bcc3f01ee3b25503b2685da0bf7e4dd82eaa93bd5da08a34fe033e4a47a826bb0bb47263a6ffe48c56b21836da64ac9ccbafce7a6b0bbee1ffb1457c7c4d9a66254aa99fd76425c42805c327ce6f76925e8f5d1e7ac41989e9a6a80311b5c9673a65cb10bc6339491939406ed004a49164adb56c0d0533cdcac03b08d849a8be35f178358576c3c8d40bbc412a66b65caee2ef7f9330f135e149f42ff1af50bb89aa38a51040049d02a933932d1d7ee19c821dda844c79ef6a10855144f5193102709f651d8756f9bf21f267d5d730ad73ff3a3cad5be0629e04b815f8baa4b132043c3425cdf7db6aab9df61edabe48f17f53a27b4b5d00b3e5fae524021e63a56531bc4d1384d2840b896688095d517895886a6dc0c4dbc718fe830f31cf8437fe9a63426a9d3c4a081ce3bc63aaf57884f7642135ba40de1c7ead84ae262a5e708f61d826c6b090006853b3c79c48b4f0dcddcb129547dc78430225b1206d2264dcc4d14eef59e927f6ffda3baa912d631720a47001c0f8469f27205e4252c41778e8836b6102352a7e012a19033179be720e88bb6044a0e416e46447e74c31131aa7fe58ca1507b3407723a6a3ee57f349d24c30199c6d11da60b7c34448eee3be9747ac74e53a13ea386e504cf2aa4b32252c6675a4941cf33fb1c6376d50c3943619293eda0d7cce543a6275b3367d6c2c6a42b364e363a121dd998df19e470e02b725fb430b04d8742ed2e6ded5054ebcca810153b08c6f33fda508884e52e98df56c7d63cab0b142a5b527c339cf5d15330d2eb58a5c034440d4d47d9cde98d08fc60cdadf6e40898150a28c90021d0adbe9be394cbcb34234b751a105bb8c766d9de2d7ee8df06c0e455dd1f4b0d5fbe88ff13654b3e235116c730fac7d92bf85f8422198e348230690abb33ffbb7d5d0dfd2371cdf78861767c6bcc9665b59e8861209d96e240a2ac9d0b7e4d2d9cbb7ce9d62c0768889d9ac47cb24fbf4a13d5087d9df2fe1aebf65511eae29c8ca18f050853af294fef6d07042750cf158f73f3885e9397bf3e219abb61376506e733a77378e4de8b531696b941f43317eb362b8d13844c6337eaa31c2bc1a0fa7d998335c919925ac4db95b836a1006bba453422c341c9d4194450d2a593557e0d554ae7258df17bb55b9c762a3136a329cac2244e48e66e47f35636965c2ded5480a0f258ba3bc1bd6c8c260c88b8211b0502fef1cb61d1a04a845141c82e5802d08bc67bd45b5bc2795fc63b764bb8731ba0a018eff70eb5ef20118264afd64728f611df8517dfe322738e820fb655eccbea4c470a839922cf2913783e8eb2d0c1709b2e40d79191272f7fef02e2f113ac0f888bf03f8e30355d6894aca48cba0ff4972e105a2f5dd8e19fc025c54d2f97d3656a0c5086fed3ab8cc8e1af0e669fecd1afa00124745fc931259f22bedeabf5602848fe7142c7f064813d162f3e1f02c7c992939d4f6ee0f30a593b554b765d58bf8919594b0e4dd82579db2624a0e2e6b8012d414a1052a0bab973fb8b84911fc6d13a9a6db871fd9c7f3622ab811d343bb0628205191e013b3a40915b0c7e871a720cf53a6082d93e4d8810ec5bd8b115ba4e245038f6204f1455a2dba173809a5a34f5bd2db5a0f451d309211588409c45fee7426dedaa2b98c8994bf984a8f663973b40b5d94feb4007670b21c5a40932349b300df0db3c697c31774119ea29d665ec31157d6a5847f1618e4171014046efc2ec5caaa2a4004bcfebe73d7428d2d888691e244de2df5aed7a5da82cd10019494b53ee8ab7d48a60b83a8661208afc80960e2d6e9141d6a9b4fac460fbb5cc182ad40f2e90bdae1ebbb05df3c15412ef56730c401ee38b5a6c8b6ae6aafcd7f9aef4f3e370e6d5f0d294a4810608f5c41e3c557fb735ad7a532e588c4ff04f24648850d9eb14da2aa2f836faa0bd5c479cb45a1f9e22d66a8231fd8e216f4a83a039eb0195d26b90bc338335d9ec56719066770bbc5f9526f33f5ac6029a1491ff1c64cf18a8a2609e881780562ff622f2b7e89665a9e4aa2f087ada6f59fe7aa0bc3b731cb2f6c386b352c94621b9781c016826941d0b0931d4a3b70f5be96e68675aee91d32c6b9aaec5dafd2386f4f8ebf3fca456401cbc13c4e3bc840b750483fbd6065a512fa20f1b09fba8724c3b7b012d41bd43c5ba54e1235842b0d76ca822ab66f8d3edc99507809b2e8ceb465e179ca46104fde0e28453765864202b735166cde58255905c03ba5687388818cf76c5c8fbf31251cd2e67242967d9b35e20f09289c0550f956d4e21f523fc2e109623c71e6fb0318290e100bf0316e348e0bdf4149bd8a277a661122cac82706df9b1a7cd8e9d84efd54d6e4654c1f8636a67d6b178e0c06642a8a511db4d460ec89b76de62a5b914d75f85c7819ac1a466be6a480516bd844400147b701f93ea17bb627a2e3f42a7bfe7ea76c7f5463691d901003ec0f4a5f82774aa2e0b87e23531d5defd209a0fc155816dcd41995d0cf0d982719b1088898c42c52a2a9c118994790583290595a01c40cbb4f5c0d9437dbf22f7a61d8a22306a8a072950d3feb3128792811dc31e27d8854e7dd54d02479553163b6fea32851a9626626b55aaed8abd4e5f084269a8d228384099dc038d8cb024f890a4c54416512591f9c6808c682258def30352f7a889365c8d161269d3f9791df0494c345eff4d0d3abc20d1ef52476afc682f5839cf6386a81daaf6471d57a79525dc08b03fe82c7e3e7f957687f0bb2c5becf185b6e50d0b9dcbbca303124111e590779d43c8cb046211fcadf5f7957e2bb6ac8444c61da195d8aaa37bf916fb5f02a4b1ac0556f0af078104830e66de1d06c5e08c792c60eae2ae88c9901d8ed170fe3778a038ce11f68d11a9d1428bbed2180aa73a5755444231358055d9cd006572f31cb1d9be329046e38d28b0cfb7390a2c5bebe60fff4ed61e39d584e0ce8cff9dcaf711b67ddd95c0083440ed25da74cbe51a427799c48e90f45924a18e9df4f0bcd0a0d71b789268a84f9e8f5fd0010fe3a68a8ec303c40d7bd5d13906d460de30d49f01a670eba30a5578db870762bf6752febb1625a02f0c15615ad828d33ffc93160658a08a17ebc67fe54318bd48ce5e77d9801b8c525f734d765eee897f08992d8703695d9783e5a24f4bef45fe53a81a9da347fb1dc7093ecea6b4ba01093e228db4d0daf0a851cdf4d92d23f34fddf6bfdd07657910c39fc357d8593c2941542b8ef6b64bf62863b32897c9df26a35cbb51a5e1d517249d683a91c9b60150f2b2890573598669cc614e83b93460c26e3f001598416c56a9baf359758514e52eafab3710b513133872c18f148961179c28155df3bf68140d3d0a501d38df95e93a157353bda84dd9604810d1a09f5a7c5a558a46457a4119785be9f9e180da843747266bf46b199d97bbff93945ea2b3bd6a4465a073a8026b3ddd703ec7de066753e9d22a0889924cf8916c88ebcce12550501ffac0cec2ec8c012dc05546075cba05ef79116023df3185e9e76d15318519461b7a42ac5766c84942f47c9aff97905019a175268273fe80bd64b21417f48b6492f6a0240c8ede951193cedee0e9c21a79ad2f066b25e2317655e812082173bd572010c884a481a7904e11cdd1f6dc821890dd963facacff07efce5ced67dd25a54ab43808d0bcc493ad41d0bd1105dc6d785d4b7b6aa40ca16cd9ac78814a52e711554aa078ec078ccd9980bd84a1c2fe01442dce046412ba020443112bbd67f5ed6063f3935df7c8ea24d746bcee3ed8433893fbc2f6088100725d75f8365aff40cfa8a24e0dafe5ccfb1df186a922fc305c059c34a24739bf00b8000bbd706a8ce9b56a83655a5a50459a872a20b2e9c86bf795f46143a9a5a5cfd40b4c90fc02af7be1c05d1f0e6c714439e3f5a1b193c1b7ae0f065f3155bcf0819170a26e44c37c67d2875f2f9eeccc1ccee68ae468b4ed47743c36580492ec0b0b273fc8e6cc339ec2f2ead20bdcd68aa9d33c44f1beacbe661956546e7ff97690e3824cac5f0d0e61acfc90c3f0f35c3a387bca9558c9e94fb05709a528647790454226a5ec65c7a6c7a50d6bf204af0ce463b5198784a580fffd1f20eed9035008625519198733c2b7709560b1dd967a8211280bde8e83ca7b1a2f298699049485594dda878da701d0787cd788494338d7b040b30a41c6571750ae4b0bae959b011462d54eeec3d70abaa5bb5d69613fdb6451322b84f73e818a14d8e90af632ffc7d55295e594e6daf98592f437e1d981f035dc76593eb904d357582bd585e866127d27c4a1ba82be8fd9603dfb53e40a0e026f2d8b6a44126d972bfc9f823e2ef0285f6390e19e36c60e571d1b5c29f7e154413d31ec7a32f7001c95a5b9e280a78250ed71f6cd0024263029b93e27594fa4f8a6589a9a518a35c13e15f3432d9e2d023b1899f2ebbfc09158613db7bfa4805de1c616c61331fe4088616db28fcfcba31055854d120f2c192290846cf6198826f01577f90c8672fea01d6f43af22b7941d98da7f4dd4816e6156af7b2f171bcebec41b2ca10508dfd8802a48aca93f120722b08c0a5f09a9e75ee2dd9dba2cd0c84251f231661540afa64c0415f6c4a4e1c85ebc7f1a2dab020561cf44250b45416331e07e57860a3023f6943f7087726b6e9bb9c44ef243af021f035fc0851d74f677bea4163167be986372ec7d09d50a94666ee08f8715cb15776abb57ac507b265273ffa858c7bbd467feae79ac6d97640b1235d10dbca9e2145abd2b1564c1da17f382654bda569f731b93b8c4029233e4424a936acc797c39032098a12d0250bdcd2c962358671a0190cad2bb69f564f5d357cbd1864ee3c51e33d4e5d2860e560c3b8a4864b81792c388b2a0808c330c0ac089d0f2f916959b32657d2db17698f01efc57bc0abd69992736a0a9e980557d365a5ebad73b08c3469b0e95708c0f33d6801e5787c6546cdb156bfbe78f0ddc32e10e061480c0241b2ab4c16b74f51b7fd609ed90bd5b80d9e85751036d44545974917f59f604a7ea2c6c39a5058c30a25248cc4c6ca905e2c4b684df4d0564a89e3a37256a73969170875b6fcb1a4f3e53a3dc2550cf1f7529f897d2632e528e10915ef78f5345b4f686881e4ba1c0dee5bb3b87213aaedae21f8a3de02c49a504d1bde2be54c153166c3541a0910ce9ff102e145b2d9e5296640fb3a784798da00070be69a113800f1dc2892964e5df46c7c8e09bdca66074a0bf99b7ceed49c850a81b428254218e79d342bafe40487d21056aa556955a5901329d354b92406d5c0b8469f523ffe66ad0e17a0e2084bbaec6d6a0cedcc963e4d5e66622a74f30a94a15e8e1a0f78b44097deeaebb3faf86e3eca131856029aee07c1b9d31c6c076343c3994766eaa4739ac68bc21f942846315700d1688df26b3c9a68ec301ca001ba56d05f401c9a859fd42aa0da1400cb9056d9d60aee4384dc0fc69e49e9f8d5ce09f57d6a0fa893a7a783059de0cb3ca7294ec6a55cf30ece969ca765435ea6770fa87ded971125abe15234bca4c0389d0824adc8d627b5bf4072b6fb08c1005d4acd946c0f315bb49dbc34922ae1fb741ad024e6a17146a0aeb04a0aa2223072d218081ae14c8b64ac9d8f122c4455475b8bd224c8bfe2d2bfd022436863a01824947fc6f1d8abf1c64e8bef2e469cea7e05012780c9515fb6e4e50c7c7bb7193066741d4cc13414d07a7af4f6e546697129f3fb6b48b34066d1a709ff2d9a6dd66956d38dee648b903bc884f0397141ab41a8d4d10ee02bf4b89bfe263c6115a65fdf19e822ee9ff0f1aacb2bc190330104a425873b7434f4356d372aff070630c3788da38837cc84db9236e54f4f092b3192492678d4e3dbf677ce46bf73777b1dc684dca9dce69a4e91973e483f74750750cfb06dc65e142a6e33ab1636481d87b60f068c0ac5481ab2fde8c051b1e4034e05044813ae9f4ad1b6ef240a80a6dee611462c47c2a0277c8726dc65abbb95c0bd88c43ba10a86605783620b58cb04fc4d1cc740a398415e6de4f4d5b6dd375fdc85d29efe35af4fba5d59e61a8d4176ef3e48aeb71a6de21ed14e9d0bf40d7a519becf145edcee28a3bcb0ffec0dc3607844db7e83d86aa15b8174c4e2aeede525b6864446a1a50f24285c77efd9e6e8b3e5ba78003fe44003caf2b5d1e2222a565884ddb616e2e2df0dc24cf21fcfb2d2152c0c621399d99d1052716beb51617e350c66ccb9ebc864275687028f70847e1f024dbc252555028322e6a077b31d1e5cee5d81e20885249dc00a63d8bc08396c6e1e700a20724a0dc1733250741952617e68ac9722b054c4707639417e19925a1d3935f75c432754e511a2045fb537d23e94d20032945dd25512b7f4005c8f201c79949443a9802e3dc43f04cccebb0747a00a88a6a936c131c98e879b9781b24bc579eb5874b8c2a920258bcd8c367a486f16bacb53f50f9402814afef52ea5b4894b18a220e812cf4226bbc7ef4d5d22261b36c269eef404825fdd20384653d61b98cd5ad9220cd4263c376b50a8f6caf5acf84781e10d5053ab65de06e398692cc836e292b5864e12ad4c6cdb25a6eb9f89e514c13c6039e44b9a5412f5440d5d5e59605fb51fd8745bde512b14b5f465b7e0380cea0eb646a29242d5db4dcdbc8322971bc290fc4c6878f92cc8100d1f4b21c9ab22345b54bed3309f2dc2587b22600b925deb2ea54992dac76cee3640f322959dc3676c6eb907e2770708bfadf86350cf89ecdc56ee2873096c16a5de49fd08c8b9b4ce607220f3eb6f1fad8495da0b2758240baecca8d1690f5013395be115ac4c52461484ccdc0214e970682db908c67f0a54808aa0421dd5b54c70c5e0bcd21f82ead93dbddefc7b5f2a7e3a034ae3cbc21d9aaaae5afba54e2046d7b440e6df7f01fc012a65d2ad5923944203b2f1c686b20680e2a10c32dd504027a11680940383fed4564eea6c8edd988724392828831ff8655ab928c4455f802b7831d2498fc55a04d411c11789271434d8853ada5f662aa0f017e258646a925b98ffc4d304695076f69e8875ff88c735e259d0e92f58d94a7af33326caadf7ead1db90fe10260127e9323785ca3183680c916e28f9ec14d304760f1a6c4eb715e01b8e7867403859e8c6245da1f929bd2370850a08a1e841ba0bb7b762f621826974279d915ff56a064bcc7ae5bd76348383df19b61308a7d0fa3611f3244ffc7fda9920c921ef5fa123798319112ce3b21b269b6aed6bef63d3a6fcc5a34201cfad6673ecac0c0c3272ccc714ecc4d0d1faad58174af1b90722784b6441a14916dac92aaa441639dc1dce4685e22e6a71078510d87037530680f035f8e3323857555690fa68b3106079917771bedfb515725bad50686c142f3233ff19a8c020aada3178cd07e2d824d9897ac743a3de5fc64626e3490d241b50e1651409e6d7a1a1bb9047ad5fe8d7b0dc7831f023cc085af6f07e4007f4dabd97900c3ae328134a527602d3cc7ee3d1c405deec6981ae46511dd73997b1a2e21aab9704166ac684d773475c855404525638fdd7bc9f6ccbc2d01e86012b817abba60f0ee8a607165636903bed7be8fce32b2cea3bf1571305dad3cebf4a3d567b19980f093a6538285d56049a709db1ad3d29184962fe5d045f37e704c8f75a331a544dd9b06aae12093041ea302e2e7b504c8b88d521618881c58c9150c87c2794d7962279223e1c1bc828ca2b0fcbf92a0afa62ed68b98f80c5e9aeb7936aab445b795e1c7cb129d06ac012bc3b475d755bb369149b6cf1f3a59dbc791632cd7ae1478497be3b0482877d11c69ec81bec07a962a4a23d265a55d6bad7e214a7b48375c63210067a6a2c67ccfa2c2fd7712a4070cfc50729a5003e854ce993489c54dd0ba367550555462613a177921417972cd143c016ff5648222df2e18ea4a74612775dd4060dc2d8c36814c262eefe82428c15f2ce33f8d31594d1ab3d64eb55a28c6162994e3418fadeb6548ea77dce1083bb4b5a6146b2b76df3c7e7271515d512311c3626f40abd5736b22f3ede4e91dc19023f3d2b6f649598aeb87c9afd86eb51c084a9e19d4d7a5b80bb7041ccb38b27b4e7db5e65e5a3d2d28c34f03f7edcd97cab503e524888a410d3a4f6e33bb27d9135d0fbe7f28a20b17c03ba55d043c057a4101dd79565d9169487743d9d681870df742d1532a538a5293c98d121842961e286471185ade9268dcdc6ebf7e2013717f37d286f3e02cf1b82c9a3794f932ee93184c80f9e00bc4364b9deed645675f6b903cf2f4211502d0a2f67c915c91dd5beeb41c99694332be3cc49f76c74b477ea3b7e39c899fce682eef99d3668276fee85c113123eb607d4c183876b36c8ab77600c1a858295e5bc44f463ffb354e463473db73626644a20e97ee09dc5a860247ede93034aa1a8a6f13d746840257e80b37128e2b2677267e2dd68f2f3a1c66b6e5bd0d97df2e1db84d2f72cd158dc009466dddbc45291c5117e6a739eb09f348304907fbd0d577663f76b427ab79e23e051bcf42584eb24358a84af78b22098b9ae4f4b0879e82f966b1a4177763ce0c46f919b4b13bf33a5367489ca7fb460a1aefe2e8f68e9cde0628dcd50139f39f914a501c595099aecccc4e967c7217510b6580d50afcfa0ae75f9a211ff7adb861990c9c7f1ad995cc5e3091fa54aa788e32d37132028e4390d2d6ec8548f3b53ed6a239008a94eb0630917a9aa58e2dae3a03ab5d84ef81388a14031fa8493b53a30141f2f0e71f2015bceb846cd2bc235f2939e86edd290676951de775c32390269fb26d2f5de71da70da57ff2a1641fe28d9d2e3fc7d437ad14d6f5a078192aa23a729f23fb3cd560267ef40a0805996414b015efb94784e91879642141c9863762a08243a5e10e3b08a3f9fafb7824049ae0033bf8cf8af7b00e1210288cd902b0134cc1edca2e1d37fb462665bfecb7fb75f7d8f5d232474459e085a4005a28d66b9ff3b9e6885f1c59b18c256d5e5d9bc8b0be60e1226f3823de446f84c9ec43e14272313b8c353b42a0b4c64d4feee1d2e0f4af0e7c1920ecf3cb491c20168b10936758b54e019bd61e8096eb45c928d46984908220d7feb50ec2c92d819dce80e730dda5470c90f1e7fb2f36dc4060dd16978c06da10ac847ca8fc91eb122ba8543638894a88cbcccda5d11b27c6bc45836db7f2e84627e96c577dc1f049e9dbc15324eb03cab178467aabb779ad7180818033a38d9a77f867e4286de0629204af4330dd89841385678002094efb3139d3a3532b401416b210fa46a7925bdfa3b3293f7424f37848922513cd169adc32dd5d6dc9cc386d1d231056812d809f846e30383533ab306f5491d7bee821f8ebd91802724d85f545e024df308e1cca27a2ce7b0d3f93c8d9c2cbfa3ce4b4210f320db58b411c05484f6c481373f46c24740b06b9e132d523f7004af81c89f838619c848d2007bea0d96f03a9d6fc84079356a9b468043315c9bf70f61d59b87ea819d97a537bd9b2d7dfe499cd18ddef0fe299bc55b311691378fdc86b148eeb32cf09c38a45c4df5c8599bc08add4e764ecb36a1c9389fc294747f9f91fd255180b31a38545558221d1653f2453a0af8aa5fc9230a728509a2dda2c783c9dfae5788df5cbb8fe4a8b0611097a60519abf19420006e1e493c81a6be9a5f042eeaeb711a442169135c74cb0c2323252668965925868150ab807ba25d9c612e404ab775f079c913bf99267c764abf0e15893468fc43b48f632a9f54a47674de8936c92a892ac1a985651f7086e63f0a1cbd467cff8b43ac0c1ac5b4b2dd52f87a356e492083409cb94357e8f8de15cd3181377259c0cff976c8302fd879cb81ec98476f0a7b84b791108510ac7ef5c54edf678f3e8062b6d831bed6c3151c5820b0a52ce28854260ada5bb2f07fa1783d1b126f00ed223eb03a72c3edf40fa6f5033da1c13cbdb34002027ca3b8faa4a4ae9611ce958ac0a6e6c0425559c69ff18a448dd5842d764ee856171bd30f2bd7a4ec4a5e9d3b5412fdef2fa6d040f698e71a74dd9aed635034b537300705e0d14cc586d3cf12f4981724c8ec86f69b576a7b1ddd6dbae87c247e32ce880076f55bac86ba66265b4519056538e5e67da507829cb2b0a1301d533ccf8a4c0296489d14d1b5c2d5097f385e282773d124efcd35edd579b5dfc932eda3da8459c226eda59c2d70aed02b2654beb735cb5883b489198d6f94594bc0bf00e823f4407569772327dccabe2a132834995fc86f0980b3eabcd12ba23b55f4b8d66d3313751e15b8be13f56e719bcfa34aac5f1176b11fe92da451cd75c063bad783064db0adc96adee0bd94dfd71039289a540c47baf8f74f3b49d978ededed86dec87f9b396804d626f92596a6ea150d074d8ca566307ad0dc8fd9a18876e09840ac2b7cb29e97fbaa60f1e90a8cf4fa9e58cf57faf863a69da09ade76ce41afa9934e473abc7435eaf4b3b7f89139bf00750f41b716bd5df27a99791479402f6fad4356e60bd0900a65d4a885ac1c188002f1d0f861d5a5646a468d7b626069b551656ee46b134445234d88c63d8be945f851ddf2cca405166d5d40940a098ea900e51283f370afd049fbba90f514a41c74a59969f6349a49136e98f3aa2d0b808d4a09943d2cfc9f8692519b0cae0ea79fa158070863473f691bbdf16d6d52f04af8fbcd9c6cdee746ecfa31246211dba8cf9809602b6f8d2fb91e90051e81b211f0cc2cf923cfc0d33ae0fda2a7db94c2b22955510183515c8a314ad743aad3bdc8e00439db2e521dae3ce9f0f98bc4e5e5b03e84392fa5362ac1bcecf83ac8120942e557a08efe410efa18caab41011917313b90c08690cd24126f66d51d10ac4263ea1cdbc0adc0826b4ffc06510fd7403b54f5127457c9b1b97df0591c6286ef941b346051e7620c9420c2393de07c2c4271dd507b57d395ed3c649c14fc57908aa10a7dd19a810112ee0ba4519dd6d2c97233638699ea02c1dde3847e338e80fb823997420c7341174fae0a4c8cc35288c29739ece8d67c5a2a5088ecd7e358c86823caa7687c744264b942222c820e728743e9a3b78db07cef8dd1a130292403cf1d1e442dba8d332ac1108d545e901e44e245ca10d6277f35062ed5ede106b0d02fce4cbe85ae5e64578f0017ff9a0258d5a9041e798e6ad5afa2ff3f7bd447b2c301af7b407d68f6db885600055a65492cb64f2a71f2f72d8352973a962b30ceb184b75a23d3494e3573885126d68a22fb48edaf628264bb9ac86c580e956397bf22e2425b8b3b4e4b25a3e85520835413fdc7be6e403d405613a9d3a8893648e26fcb794d74a2fe40ce7e1d5b00945a84b8565fda904f681f7fbd19139ee36922df86ae689e5f8d35f8203d5a1687e28056433ccf147fd04d959fd144f704c1dba28d5ba73c1345d5078ab1461a6613f4b6709a6372e697a10a3c75dba924134d73721f3da674b49507b915271dea61683817e4bbfb535f5ec6728309b779024e11aab0aa2750701d133e1e9c9733efc90d69c0ce3f12b20e9f31b78b38fdda51b0a7c48738711cd57adbb182d67460cfc137510deb88e2249d9f04b09e59c2eac8a59f3743a7889df9fd5ff5e9d543e3b5b27c03060e4aa01edddb96dcabf0331f0942931c33a2bd06a8497d2bd28dca5092cfad3ea14bca0c941bb448073e5157a85a9d928ff27cb1c2f422f9450aa56b2ab39ba1a8404c7c05a8c4857f99d12265ebfe660ad727d7ae92943ebb0869004f2c4b52d61bced52119456d7c9aaecea0c37493dcaec3912b860b4f0da8349a08ad1030e651b60dbe3ff94ec0618c1db2b1028f51c735b84161449467f1cc20df208717e8cc26a2d93837da1ebb10e0ed754823300b823589709406c42d2c10b7775f8dee84baba9513dc16955efe5b9ab3ba8751f308bd5e459b0a36923ff0567ea19602e24cf598051002f33a12925e66947129d9fb029a3c1bb27d34495825135cd242448f8869adc5336ba6d2c8c1b26cd485528538345bf731a275d052d475fc3ec50652172f834fdae4a3d17c557c21e8805b89ed370193df438a989b23098d2333ff8a28b4241f56098aa9d3f703d3c429dfd02db18dfea39eaca772c9e404b50cf725c18f9aa5369bb75de6e0c6db431709a2239aee3b99ff4b21ad5bfae72d7b1113728e8cbf7a8ae17c57757dc86a80f840f2011c3c8d91408c350919caf2196459b35c161bca746a65999005310ce1285459753f034c5e6fb75112c48ff811cb0c9759715f6cbfcd876f7bc68fc84a6982a3ae7ff9f55e21a4e55b7b6e4a2b38e5a07112b64cdb18cdb50d2e82ccc06dce9d052b3079fa0a81c5f9efd71ae9fe1b2b19fd949382ae5ac659c31bcced304ddd1e87722668a17a536fd92e820053f6d14639331276ad286b942e72eae5db05a45e1154b5b159f8d75fc50ed4728fecb8764e591de14492b04fef8360c4f4867640990e716949121b08f439dac2f3100b6a026826f458841cc69ecada981d9eeead87e21d7580466b6866cc2b39e9a7f470224ef02436cb6d2dc65cb6e57104840973967540518c3bbe143307c39c6ed25d0e2c4c5e9959d9cd3a35ce216f0b84476b65b98697208d4abcdb2c00a8a34463408105d206c3932e8a81462625944b29e2a83a4eb5033b0163223d8a73753558e966cebb6ee858c40863250e7136b4beaf6809cc500133462ebe3c1b8a7e880b0f0bb7946835d412770dc07779e14789a1239738c56557e6a976a73d7104ac3e394307ef600aac74cc61da1dc62b682caf418da65a11c196201a580324ba21ff91e178759f8628b65ab328a266f2eee67638d0effaf6dba8d919a01071eb6e23ba5f79d50d30a9be18b9e25bade2e11f4a1564f488a92c29fdd6345c2e2913ec4e54b04558244f7c06d4178ce6d0c6af88b3b1dafab9ab5a64498990d58cb51901ab5d738a0764d423963378ae26b6f7ef3d2eccaff234f62f3df0f40b9f056d13d3317f4c991a0700dea40b1e3cec27555b60c04719e0c01e99558001a3bd335981935da61373eb721307e88f924e16b29bf2d823d111f1c49917ded8a426afa85002742ffc73777761d9ebbdb2ae19ff973eb7a3f93bc5c4b239e626d4d8e8625f2d2819fe15ed43397134dffe00fd11020409b8100ec542aafc63422e790f140db218bcf32765e4da4defacb91a39cef728c3236c85c877389697d9b6fc7eb9c54e6c30f1bb143be5dc74ab644a4617d0351b600e94dc7fca3896654ea19d5f740892a2ed8c3aed99cdd069d4c5356412200e48b21b5433e043d4ff60907ee809c73d147c0f46ecf5b076080c137ba8fe3b18da144e26f367a9e757750e462b2848fff4c2f65edc0a7a07e530e0e6088fbfd2139db7e1dd3605b4357691e9a01e1d85f3acee18e5e5f659776acee832970ff9af99ab62cff4c19e616b580481238eb1c3788232acf18ab01a43ec50dcff0e07f2c886025a79a419ba3903a7160cc033db68e1b3fe8165b69977e5950cfb9f3826caef7e8ab810614a11fbba2215f25176a02474d08bd7308eb4d634f3cdba53a25993ddeec35cf484fe5c39b936c16db708cb3a2e3a62b05705223928d9254788ec866b63370a463c0f4e20df69fb4511cd42c9c039a6d8eaca7637d9ec649a4ca6350479a8cf6400d54dfd71fa8dac405fbed05e12828b0af1d921de4b12c8128c36cbd98c158c5ec54975f12b7aa27c21598c00286137110dc526fbf51de19d2c9b370f83f552dff8fc10acedeb81a30d27f3842c33a381a041d2516d500b59dd1f53632ce4f94419523be5a5db0b1b6a21038ad5595d2c03acc8d6b437ade943c4ce92a4822d65cf4099f35e6b00b60ab13318c3501f36c426202b4bb8f8a6e0af03a9f62f4703d123248e5468395b92e9e29e0ac54bc1a6f0a5134db4f92119226bac8f7639e0a3048503efa796d48de53f211416e0afdff9ab57a5a58bd36c44ac07ee17961baae861a089f5f95b75ecc96cecd1216f0fccb8c15353d5bf9ac3d317355bb70b1f9efe497fc9bcbc0a2861b76097110b4f1f1f31fdb5f3f3e74c8217045ffa857606363051a6ee113264e88972f60de97fb2778d86c9c91371a22a221d2c0ac54989035c5dfa3a7465ed15704207668eec2789bb65045649f46bce65764612a6e5a15f770b0d501703d113f5e243cbb68fb632ec968059e1c0b05ac393a4c4c772840c153212af59b7a14f320534edf53f1e8e1c3a4510e5c228ba1bcc9072592a4360d9d442f30bbde1cb21b6c73313ee8837dfb5cc7aa69e21544ce4b9f05b0d50b2fbc42ea5e08aed77ae5b9066e0de150702574e5b8c36b0ad6d864d65e5ba8f8de1e96967541ba76a8ea362477a7a6364535c39974b2470a2a558ac5da20168e4918757a769403e21a3737379d384ea64864cf018533648dda2f4742e0f922a421119c4cc0be1ece7ca845673d81b9d62fc781ed621c695aa4c78091f7a080c79293ea1186857d0bd65bee2c7b929c71f8ee77a3e879f2086e447b2089c6ef9080b61cc4db2ada8b1abb01a11d54c772058f1eee730d3934b32a80f3e2c5e4a5d6bda063020e1f8ed65fb103749fc8ed5fe380f82786e1af1d6996e0703bc093249a9b3e65e37dfa2458b25a9dd5a2ce61afd088209390e82e46628c46bcd400935ff5d7fc9b8bc1ea830389c06ce12a02dd233ffc0427e5910e7b0e4b68ce302a582f735de1ddacfa05742f0a022c3aabe8d1eb17a58df4e15fddffffb6fffe37fe82531cb1415519e24cbdd0f0b61c8cc389d104ac0f56d5c39d10c2939a81a58d60ae981b56ecfd70f7b74febd0a8cfcc02d4c62e510059513796fd423402694b32b6d0a361858ffca24af8c1718a51a6e9aa46d44bfc64966e57ea02a1ba4d408cda90390f1908e028d3ab70240e9e8b511c1cd46d1d8bed14217d23e1b7c564bb50e32a20025640298aae230a40b16a71620fc22abd80812025b8070838df69cebd02af60430163d4a3bbe1e3feb0de92b6602b9d948eee3003329ab154bd56f1a826c267db9eddfed9244538b852547665b21626cbea5955d0b8eeff81da122e42723309cb62b01df4de3def404564d95bb5eecd46428913978cd407b2d173b5dbc940829a187d7888267a85921b66bfec16b36dce04af950b8a3fe1232db04e6e1e823a8b213d27c66bddfe2c2b5896f4bdbdbdcd64f9a6c54ea02b92695178f661d5b9c4a65657a075d9f0268191af4e64871763cf963713f3cdb6fef16a868d43a5a3b129516c4694d9c80840bb11822fa63c10c33b0c6347c3f2654a1390011d65498b5034956bb8d2bfd8b85883a46ba6069d5bda803803a34b3510d7bb50e4ba6aa654f1708e8b96fb06a3f6f1f60a92028e738772939185bf956bb0e6ac2a8c22c987cec42c3bf2c5bc33fb31ce2ab3b40868d89ac91316c8f7fdda9b332ff6c164f9bb59a3de2f442f6494e720d95b730afdb0b3c9c05663d3300e2c9334498e530bebcd315e1f06d6c41c296601fe40da9ed31da3b1c53bdb484f9d34943bc2fd4ea8a4a6997ae0a1b7b922c889363fd90683ab5dba62168842dec7a99464ad7b7e9023cd9b072be9399cbe604e9e5aac635ac6ecad69e1783b9cdc951d9a8a89d4a80a399b451c76b1be34e6973a8a4bdc4c92ba7b1d0070c0783cc76211591a7e21ac9f0edef5b578ae41fbbf963f12955626c8ec13b60bf31c3ca36b16da0607bced89c8c448158b0457f69612a5180de6b83a4a8fdd7aa9da832ad75efda8db7bb0c780af87eba0309ac698010127ae82d02c7b0a123ce8053bc6527130430926e84edf5472958ee475bd3958f6508f54491d4eaf1452ed8c69e34302b255264d08fcff497fd74e8d507d61984b05af278bfbe0ae0512dc9fbe41b5de20910ba38e90160e7aa9ac1a793951b2fa8e03b7933e0e58210f4c02438484f3fa2476fe47044311d2f827ca6b10aa2c5088de1761c4d6cd021349412386a2748c379256c689ccaa69dbd90045036c35d2257468f50ae25137be7d90df1e14e18e3d83e5c6b8fefd990b52bb073cbf93cf726fa33c3c2e88938e02af3c6cedaaf3cd70218e836e7f9d16eb3a14cfd28c2183e85a70bc23d347c1d190cb7cd60bd1e6dc202b590b2f38d061f50cd20696bed47395ae9d0594bec192366cdbce64a2246ea7bd1ccb6702d872c57c2c5a40122ad780d720c67c294ec01b2517f233b8e640abd596df631ade72fa4afb086cebac0eac7fd0fb90453ac80ba68c58731b8b1404a83aa4408f407c825f93382d9cb9362d886373c2e1b2533b1877f58c8301855800f8ff6b13a1a4f25e5cfe75ca3889692f0d508ab56862ef1e2a475ee52b064b0a88818c9add319ec5f9235f42eb25814ce3ca657c8b5e76a291ca9909bacbd1ef4d29b9e4ed8b5b2604946aa9ae1207ad410ba231089be4e4badbe0a798d301596905777f535a86b1afd8e71df4053c211aeead9b40deac70ef801ff8aedf77085a6992809c8e37f02c9b52e389cef3505789b26c22397c24160771f77c9a0cbe17bb99114208e2de5ce4015130711e58090cb27e7eeef1cd70b679ed04aea7867c2020386665fe5f7e6955f8373920926fa1c78945e4996955c42ed19a411e356461a420d1b7892f839cf2bbf3ec55b9e2d661301a74e8af69c24a2a2de7628ef1d357bdba136f58ef13a1ac8770b4edfee5e98ea68cc0bcea8ba9a0191afcd5e1a5e3112f82b8cb1442f46878721a1077fe56704ef118d55d19b11c42b76b18b6578ebe74557be5b7f7ff7626714149ced3052fdb9a4c653326c772fc42880aaf7de0effcd893494bef258abdeb1e1aa729c851d93512e4d44bd6e2b6c00b65c3869f1b1bb68326f858eb62810a3ac0e35d829f1c742652c41450234ad0047a26256f55ce663be21785797a7e4457e52cf434818a3a1c8f79b180dc1e0e8341714a9cea8ea281cae15205ddaf7586470262e3f067050938f99f3effe764c2435778e0b280420b2fc09fb93dc9b958d3f7faf707824ccc79b077171cf54fa8d0e00a0f05a31f4b1028ad3f56b95f233a2f02f162685fbb6d0dc36c235d8085e2223b62514ecff898ed16d07946b44b53c64f25d02c197657a3cbd27eb50bd405f49348593ab7af4e76315f66b0d45a65596da4abc29b509878e0129713d6cb69b64ca60292ddf8975fcf4886e7999162299012a03eb379fb9d0fce0d0c4377994181c0e1e270fb8dcbf0d7a7e053773ecaf3c04321feb2be88c63d852e1f44d597f0a736c68004db8027e37dd6170c030fa57ae139358552b7ca1ecdf37341ad1b39f5602e6c19bcc6a2c8cbbd8e994b39fa5eecd3efb12bd721f1c38101e4ecf7e71870b66495dcd65eb7c01586c2a8820f4582cf9e872ced4849ef15282f244e17b7b533f3bcb39f61d84535f07a78b43e45879c0fee99cd211d980875be35532f67bf419e8bf45c6999349141451e2e6602c3965289920e7e106b20cf0a874790e56a153c057c7d5768cce4f2bd298f2af304d7b100e9365ba77361021459dcd7115c2ecaf643e0379530c3db4a30d9344b28a8cbb9cb1b76bb3c209f5672a0d1a438224f711f277e38824e7ea6ccb6143800ec8cdf590d2252a76657cff040e3a128ecb8de4ce4604159f4551f469444beb3ce127ebb9f3d8b4a65a1a5100e910b1558890bb76c36e48673696b31a6a6f5beab3f4ac032dc8b2ec5de94fe2c3708a97ec3cf2714a46ce13efa6409d9a48c7787c50d93d9be33c066d84fe562d4d624bdf5447a309494d0da2b4f006024f46d3089a74ec72a16d5d2417cb571b8323cb012cea44d708cd52e21f8edaa0387083bc2f1f30869bcd6af4e855e304f978cf91284387bb8099295b7275d8a04ea99ead5a2fdaa7e845e646287af229e1c4081dbc50263643a2c42a5553fe12aefa258332136e177beb281b6ec42a2699c00ee798bdef3c30021bcabac4728a5ef2bba081815c7b4429b2a6976efa4e82198fd7d576d7c7038166f7838010973fbd118bf6ae02004393b21339800d131e81e4acf362abebf8c140d077ccfa7b3b37f8c309b4be40bb0b288b0112dcdea31726954b6215979881d29e821b5a8b565aac629b8ddb48c5b78b96e4fc4fd86928bc41f0648c1562a9e3450a14ba44af748465de74256d1dcd6477e91a474b5eb7f4453902114c548ebab60f665c86029dc76f534c40fcb95c6c45562a88452f174f81d4d81a2fc8027e58a0ff3c04c80b26c30702ee838c9397f987a069e9045c272a201139ece1c19d3d48f0c89702bd66a2396c0ad91ee4dfcc51e1db513ae874ae3f20dc900aafbdd36cfac6075839f9105bbab4ba325f1e1ccfb867bb4209e39aa9355f02309708155ecece0b2106e8e10813dece80dad53add212dee8c90435287e8e9af03130b1bc529a752354c3aee5ad4200d017311f70c53a1dca2fd0088d485423f69616d42ac7b3379f2d751535ce3a2126176f87c05e809af00295a3288910a040a5bdb42621b12c2cd552894d515215e951a7fed2d365b26c543c88db7a266eaeaf07ab8a3da6ba43144b079a4c8217da587fc2029fe9a335c4646ceb7c10e822ec11bfd1ec5dda4407ef897ce5994242af0188ce1374cf6e28acf0fe5c39c4e21509471d086bda4852bd4ad45fcce40702e3a9e9688b78285e997b88427533b3e2bde67966c06dd7eb0cfa3229efa643d0816446c7d061267eb9a0b13fb43238c3771c4cdec9e9de32bcd44a255a5f7680438832fda41d58fd89364f8bad6d7458f8658d63feb692b381fb15e9fa1b9db748dfc376e02dd874cbc84d022f6d0e367c8d8e3421b730586cf71e3f27e27ddd1387c34d000ec6758c396ed33eeacbebac6fdcf3e4b4f899bb1645f2ff7b389274a1b1de3402d6e282d31b91d97ce01163b6c5c1431951c349bb994923f7aaf264e89caa6bb50095f0b67186e30047b22137cbe25d8243e8b0a627d85d90ec6bb5d0763692ccdb7b04439cf714238774ec8b785434a2928797ece3570c43927dec523d38e16e003f792ca3ff583baedf2fddfe55ab51ed2a8cea5b6782ec7e1910cb1c93de0aea7a6fcb2ea9500c63835a3c8426d7e93098b0dbd1f9571c2ac5f820e149051f1b076150f01fd225aac3b6e1d66fb2e55532b40505d5afc6f842a04875d2d0c0cbbfb070b0a9761b1d17de4f20d39a5d1241f18e1bbeb87cf9b6a202f272aa489083a2e7d95a0d9f37ee0c452ac4207bb38984456bdbab6544805ba5465817c028112ca63c0538588532d248b8c566139d0448a1129320ea65163a8d42a8c8a28817dd293ca9f0c22e0f4aa6fe2992c7586de4864f771a3b624b68cddbf24c64fc56e258d4cb8862763e80f751ee3c213c9536ba37f38923ff5a719f22768b94fbc6e9216ac42e70ceafdbab6a8e0e022706c2862eb57aa8e16c8a3c727498fe1eb680c2d9bc7355f0ee193451fc53d98902650993921b190ad131dfa48dae085ff3f15a0a99e7bf402bf1a751095c4ec81c82ec6c0be37cf234211e321c19ab1241c651167b0bebf04eb2a971787cda2bad6ad3e6694aa01aeb392b05c101db3ad30f76cf33d77e120a4f3058299731e0607eb34e35bc953d953ce2f99ffe93b94e67af632a094b0867dc4dc796e901b26870fbc41705f4f11c61f7db023b4e3fa5f322dba5d69a92c05ea7376c52a5e06e30dae80d71b04ac50d9d68f02b159f48a2efebf53bf4628ca25809ef426de28cdf8cb138127fc95f2a445b491c2be193b7e11e4c29b50e1eb0a54a45b598ccf909f21c327bf7847392bdb5604c0b85c2f948a5701cc3b2024d6721d77b9f515c83b493e7bb40613375bb52ab1d9389b03dfbc1ee651a7edc5899ea6d06865ae7ae7c36ed05768e8dc5bb5c37929712b7a106ff32af77b8b6328df7541b45dec767c4e1d243a62941e45c1d6d358736ce22c3018bd1d23ca4cd969a2e8a3ee5ef31d3072a4b1e94bc844aba4298648824f2d897b63f8e124e0c29615a0242bda36860d125c2a0b48ab708bb1fbc2efda455c4a98cd6ec4b5631ef96c4ec681db19666aa5b530e1540de25f0acf64f646ed4309fc81e1169bde24b4ac04eaa5dbbd592050ab4a0712100b881da80d8dec03a8036aeac12382cad968adab1f416538f6dfb42f024692019d28a314fb103d630880505630e44a4f3c3c8c768175eff5b5e42edd2b4231be1ff5d4205159d444ecde9b6cb9a594322519b80b770be90b5f9d6b883d44c49e9855a7d5add723e2549f004e14ba475a4748d6f4d4ab03b1d31081f0178311c740b7aa872c6481e8a37ab651a05b75ab9665b5ea4075c757e7a321a71a95865cb55a2d6b6dc9a95edd48ebb02cd7a35ef1ab97fdaa5b97d5392757037287027d53bd86d189fbd8fdb0d0342eb8ace9ac1464972eac1478892c2f9d97c81284fe2ae2d45abd723ed5a76badb56ad7e8858af3e444d88a57bc62a51baae454266ab060a6d65a2b0c6a840f6db4b8c1871b845022e5a75f17dcd04627e654403eb4e1d1e3870d6b7840a424544612152825e9f2d353f7b66ac540124538a140871e72b802562bcc20d2b1fae0439b1eae37b8b0a244831a4a35dce0a70d49fcf497ee67e610b95c2e1cced8a2a6eb225ee1b6c802011fc6297250f1d3577318f1d3ddf5d35b394b7e7611e3a7924b8a1c39fc74aefb991176861a4e949841125c84910411684871b70064f9ea2832b4bc68022800971870b9e1abdb2530de281283054f6b68a154c61436e3b4b0f95afd22c5d1e4ebbd2d686163044e90800438ac01ced8cf49fd87d61a7cf55989e44088d8531b6d1c518209c6cf39e79cd3032d5670bce0ebd77bc98001add5b9ef76c48215aff8b087bd4f6bfcf415eca7fb8cbd11c613ece7057518a104a5c8491209585c96e6cc94236fa45b7bef6ae539595aa7bae45c7a3bd548a1060b3e7321944fe99a67b9d52bfb2417e9923743f246f670d96d3fd53cad7eb82e9b647cead1d49295868d76774629a594524a29f5ce689671dc09c143784a6f9e3ac718c8743d75b622f6c4a7ce6b8890e4f26ae54e73d8f99b30aa054b95dfa20f1f6e8cb921e5439ea7a47703cac7981b4ec878862d35ca470fb7336aab7c4a298d6cb868ece9e829a594da9a4d79ea94b2105dbd969e5aca807eb9c32e7b57deca799d409fa5441c5abb20d38782b4a8ab5b34bcdfd4b91fae2be250ef3182db460b9e7a09ad13c9f0a1e5664ebd62a1306ab3ea943263cb961a384971a4e404c6157b3c78ce43eee38b9655b32ff5d7fd2244714540e2024d284638090204423253c50940184762bf40028c7106e21a9c4020243a2e10c691c826c0982f0a07e34591a1f442a0a16f9f12e8e59ae1e542f20a72c5220ae8d2e5e9e5d3bfbcb87440f65d04040e86f34ea2cf5330c5d6c86239ebe4c789d8f3827fe12912263d869de0c1f7c79cc753f8d550bc88182aea22a32353d8734af058ffe2759e7f13d1871311873de4173cb309dde217372d40a2006bd673bc01171a3ee3bae50e3f1163a5144879f6b8854ec949adc32cc6c1873ce5999b28f0d39c82ccba13f831f0e342b722d3120c4b1a884dcf5c7ae74837c99cc98840b809fb70e22dad93f29077f07cf3eccc147bbe90b18719083371bf70f5946ad243ddea254dc44d9c8599584b33130c84afb4d0b357e6c25c449ce613e8f7d74d224e13d78d709207ba35c2961b79f06d1d4b6cb10535b84c199ac2258a2630d28041e11445132898c2d0e22455458aa62c5aa77b6e436956665de94beaa34720ad13c2ae87c1126197ebfb63a11322895cdfdf906ef14bb722a431d0ad6842b7f8e572fdfc52a9e8b263294e3e2e922bd8620b651cc9c069064b9070c0d17b087e1cdc2ce2b4f767dfce28d85ace5939ca3498a022d852a9d8c3a91422ceac2188ad18979873ce39e79c73cea239e706654e9db711c1e9bebce07294b8e3c2cf2750788a0c5ce0e1f511e0b87404386e7fe8d68e00c7b491e3ce2c50be3f6457686384e588c8c9f45896263b129cd34a9937f850f55950ecb1225871e9c7cf40441c20c18e18c201f44568e3669d741a4ae79137d3ad9d3c3a7e04382e557d00e4b8d4d33ef673f614681d9edc916ade2694b8dd74b2d1647a1eb9c347c4f8c241132b80459c39e7f4d93a506693beaf5492c83176089a4b52404a293d529652423961a33a2f9d022e8890745f6077c753df01b604220bb1672a808c1880382e7bf6b96ba971e93759a02eb0d01511c01b619a3dd7305d173e1650d040c4d940a88097d2c31c1eaaf9e20ee996f46e89c84d548bac954e1714a7df52707cc0f345161fc6579cf20c8b559ead4001830d18289a87cc04bf1250c5078ce5999b3e0c25bfd500e55af7838a81210315cf675b32fad56902be287aca3f259394975167c7b7d3076d9c1da194c40d647777774ba74fd2ab9015299d56915c5e6679e933b6ec0d4a27c7954c46df617bcc8ba797a0745aa57598062f6d48fb6111473acb85ea6fde6852fa0870682268a91927420baeed7ea68bf0e4d6da4456b3ea5389e905f164985fb4f23d715670514cad13d2296d511486f289aff8a2309f884556c374b337ae4e13998d5158b7a4774adc2b29ec975a67ba944e63ad1383740a9bdd8fb559ad12ccaa8fdca1b0992da3302969172f6d60b232fd3e096b9d70a29c84f0428a1951acac02b8ec208aca63e9a9d3367230839f3ea79d73ce27339cc78439e79c92841cecf094a6b07421c5cf2986a89cd560a3b1b91392bda41f479cd93f917c3b5b0d04d236cfd951fa40a923e07a68848280c56aab9a1462969f41cdacc607b6d802962227969ce0b205d28b47796842061198f23a8830c0eaec3dc4931cb73d7e8c8d51e5f96d7cda4a898dd1e4db8db88f00c7a5fe43bf90a723c28fc08050f8e94616d02d9f0faadcafbe00177c546e399ea72e4150e3f2f8f8fc4079d8c3722f65e79ccb2f8481e7a5f374cb0706577ac853427742a4b352984ed9833e64560aecd469d04b675a42eca87ba05bec42a1a23ccd0baf0aa85b4b2e91a6800b3048293d106fda99a3504282979fea4ed086913818b06f3eebfde4379f1445b9c377b5726f310f3f692c4d36127b33b1e3e37fd052b23c5779d65c569db0593e7d589f3cb333d12df611beb848da17f64b6306481ee96342f3d4274deb01d3344d73556b9ab3a6694f3fa5f44e85214d283389232210cdc53e8e624f7b0f1e363cf54973319086a27d3094368b241406329fb00ffab93694d6509ea7bce661236547dd2a92ecf249ee644b38ac4ff249d6279bf9d45f0c3c8be4cebd5f2d2ad2be4de37cf470a73791e4cd1306328f487ae624220e7bd8ecdddf3ca21f958a40fab03ed9a6d661a59c1fe5ec9aa73565475ce3c89cbdbe1181d426ec834beb30aa09b3d3a655136dca9abcfdacaf56df64b514f117aea4bcf685199467afb04f437d30fbc1da7b79cddbd35aebeeaab9ac527ab7578bea16f6c1cef2cb8ebaf51471b884f6eb160652df883ed8398e6fcbb39390c59dcc9a4b13d286acd396d689c1ba11fb9bb35b5fbdb2a11ef5290623175563b54ab55297ba85c5ab36f02a0e220e5758cd53853de5748b3d4723aab1c1e685b6bbbbe9eccffae8e1dae6192773b3c0e9a7af3ed2ea49666ee93954b65704047e4da7514e09f3d639fbc99764da4e2a589231da52cb129396d661c529ad482cb68a0f6516e6983f6be5ebf5cf3e78b83d9dbac572ca297d04015bbdfc588838d3656cbaf4e267b33aab3277fc7416ea1d8f282a15513494256422a269bec4949efcf02173f999552a5f2e5f13cb9c4b3fa7cfe95c6bde6777276e87d99ba4f308a3219ed0295d497152c14566e624b9238fd86804376e1371470ea171c7ee4aca253965b7ab635f1076f190642747ec62d71dc26908262a3e6c2cb263809412958e865e3ab3a41fe0630cd74d3ce1c9a3239e7d05322929f6b03c6872579ffa1839a577e40a585679d9c4b37cfddd54b0e4675212114b568a9d31ce3eca4b29c94972875db632e521768a1ebfb0b57c1d931d1b923b72886834b4faf849a388c3eed3245c71637207e5ec7d25ee6cce30bd147be49194d237bc7df2881f3d7a843c2ccb119e6e2865962daf29723c3f094b776324a620c104e57a485a9b4a35974493a7490875c7002133047a9610eea7cb0f3ff0f32bc25d62e01f9243a96f2d7d25deb04ba129dbe8490e35c92126a87cd8589a883b6d246f38f644efa327be978cd89bbe70e386d2c848ee7413f186fd8967ef254e588dd0c6b51f958e60e2e5d1a6daacbd77b5726fb53a86fa3a46821637ec981130239236e6588ecb4714c5a2d211439289678f0c6829331296aeed7ea20cccf4298de44ec75a1a65e1d256bef8e8c53c7729440f6b8c3748c2f2d1a3cf1d03a2d32fc6d674b9d42d661d7c543222cbf3770e88de2d7639e75724530aaf18f8bb8fabdb372377e66177bfd059e64d5a9b9b9b066e39a54fec912fb34c8b1127b35e91d47711873df342cb12d22f586fa79c750bc7e5b7ae79e6bd60dbadc78e8b381c7d18083f7513acb5591814838375a904d117a2ac4b940cf68bc818da088d2f996f94528f3146ea557b0a53f3b439ead6b3d0fa0cd132eb2e7762bcbe7095ab2f9bdcf9429b7d75fb7cba453f14b42f84a47b42bf4f53fac588c33a96dd5acb6ef905db5ed8ce705c7f815b87ce9871942f5964c6c145390af531370dfde970a30f915e3bb64a72a30f6120fcd58954cd6a5606fef630e8b36e054ab85673ce39e7b4c08d4a515eef324aef30289d8775f2525aa02df208291157699dece318b79d6d0cb347ce8fde41b9ec844cdfdc6efec21d03505f6823bb158d49c9596f3ee719917254caa7570489eb3319737541eedb6f6ed5a3417c36ea63a19c7e1ceab3321449bdf5ce65ec5fc6b0c826b9c5b5a4e80992149815a6a9a5539e50146ebaac3ebff0fbed6528720e372a45717a4eeb84c829dd524958b75eeb0280c30de5ab6308121fe541b85feb7eba2f461fe1e62d6486df1ce6dc373b21f59353ba25037f683d7ea5284cdf9e3120e5ecb68b70a61471a46b5f114fc268e4c894becce999b4e28533fc9c9f8c451cb912baa17cbda6444a64a310bda494520d3523c739e79c9c119caaf3e83cad83dab49ddb791e601790d090cfeb486a871be648d775a175e2e7bc6ee7e83051060314b7a9563eed8b7391e338cea35724d28eddcd71721cbd229267ebc4c8c35dd5caa675506da757424e8f1f3c9e4fb74c584086a5b5e486abd96c5369d8e4a4def526bbdf267797a022717d23d9a70743fd6ee61d652f431850af0450cf62cf0fa5a8571995d9b831368585805e17482963945f08147b89f4d14330baec5122bd9432c618e5d702768fbb1dadcc6136628c73b6a668fd10e38c31c648468c71c618e78c1e00a4b8d2639c3263032157338c255cb7e67767337731639c33c618e79c734629a78c5372b105895f84bbbc92367ed5ada25f752b469888d345b8cb73ddba11c7265c294131fa5065d33ae18f57c1d8d6e1209efc2a4a0f14c4deb8296de58bf64acdb758c4f3b2392b6395d8f215f823eeac644eeb8240c565e7eee7e85f5e74f8aa73fe88cccc7c91bf30e7bbae63c762e8bbee8bdd7af9c2cfa753b2498ca7fc7acd9d17ba0bf3655ebc1f5dae7f07e3b1e394c77841cf301f013a2ffc715fbcf3c2b8f49dbf78e18fef8e7ca4e6f1dda28fd4bcead60b8df4907e555ee42fc69795c116dd0bc1857943fbf844184e1f9f0883e9aff8eee31361b8e0d97e32f80a37b4effd42e369b0814d43528ae203cb88caeeeed84c50e092524a29257f2dc50986469006757db4b160c992454bf767001a6ec7be63f203ea922aa04840320423c9cccc352461669e41921ce6ae819452e64089945206a16469632ec00ca8e40c7132fec8e186bef32329674806bdae808103868c279aa8f24452969d094658e51943a58a0c162afed2e057f2a35212529492406a32361b1c248b870f6266a31b88628c3256a48fcef62a3187f606b356c610f36b48ba6c170d49220f66392e676dc71d55a384b8b143243f63926d9b51fa074a2efbf4b0bbbbc339a56ce570cf39a5742965c7c8ad1c2abbbbbb011071a2ebd861767faca818489fb187569f6e35f5e49c9339763f6fd2a3ef33a5fc8d4e3aabd4acc344495dd2593b63cb3b9a5df1ce86f21647cef191373fdb8d5c63ec618fcefa28f351c69ec99b0f6d335bbf6f66d587638fe69d0df265adceacd1aaf564fe4207435f4a004f8006c4743be20d453d8de9c04be7491b091da2435fd01bb0adfc744ed6eaa7b1a593e5e4a2dca151c9b5e956fbaaee6c5d0ad502afe7c54942e880170fa6abf17e5680f338de4cdfbc899a41cfec3e7ab8d275a6c7fb8510c665df6c56a76ca6b6ebd3167934d7caf1d5472b412befca9b6d052d189d3dc9cccccc2cd9b3ddf2a85cea4550763f43a86f96e37a87eb1be6a7d661276bedbd52ba7aa8eed4b79494cc65d24b985a6fea8628be786629191348508e52d7e57219a9a92e5dba201d3d31faf8f2849b7c7426ea629e50f18127e609951e8a960c7d949cccd20dc058c242ec3ab2e5cc208105124f5ba2c380d291822d4bf4d091822d4a6cd1d2e4820643ab89094b742296874d521a615889a1903905a14b972eb129d14ba83ca8f4a0021b1e547a7852024f0e150016486401c34a00582081054f4e8f968e9caa020051458c1e3c32195f42145065c402092c92f8cc2f0cdc677eb140e2b38c7d6ed672e6f766d39b2f73b4451ea10ea697d4526a9bec163b4532bd742b452ebdd4f292bbe5cec69c0f2657e30f636c29e32cb75a32454a151b93af973edc259b248dac94d6797d124965249fc8251f4c2e1d6293c2883d51c41d0dc81b695f0cc442c1c5cb2b5e56f1d26f78b8d248de48b7f6ded5cac8470ff7f5d26d122a2661d40b129fbf199372727075b8cac3d8b425b54ef4c1e44e8ef3c1e4661fc658520f37ac2e1e625569f4b2d68f080dd0ca673c080c2d51c8472abf21921aa0359e759237d2571e8d37e3d17c2cea2b0f0281663e16fd582acc388d53a7a1f1d50744f3d9d74c1038f333c426c91be9e4caa588235d5e8938d22318a5f76872c32a61232c5d0a4afe0c40c3e527471dd9a4db3e82b8b4e9a5ebf080a2109971d50734e33a9ceb7e588737e3ade48df4408c2b345e0463f02ac1a51edaa41662d7028d977193a4d0ccea35f34c730de5a8cd37ceb9cebb177f4979eafa857198188ff9c25026a659a6634d2fae22295cbb0ab501a4abea5075754b9620797882c8f78938d2262d8065a521de48c741ba74451c09ebaab4ceb44949493a986a84d8220f6631a330e488109105c825db3014ba7bb55a4191567a3beca8d6eeee9c1c1192dc2340d6e54b47a1cb4b9f2102e94f86583339a1107b6e5efa1006625d2ffd011f903dadb767bbbbd93aa5ce5e11eb3e6de352e7c0962a0c8642a79c5e7e90cdea96524a29a594527a4bc9e486d64a2ba594b6d229b570dbab7b2fcfb4d66ff662ea1816a6a2a20f6bddf279589f96644545513ecc8cb2273c3ccc8e3229d54ae5229441c960daa54b0ea5e04349d4e45b16c978289f2025f560a55d4ef150c25c32c9a6a10fa593dc42e3a17cd2e1e114222af2e1890fa7d17cb26a0f2713e184f9c0e5c3199b56be77783897be3d9c59e61309ba782a64d31ed2261dfb901efda0c45326be3da453e8921a1f52a60f6916f790367d7b48b7d4a11270f9b012e900a042a94945397c585f35ec27e9e4a88a5c925c86a0c47062994db4884aa1302d140c6b029056dd435cf4d6b4ab0bb109072d663e3e090d71c96149eb68ad1bd5dd3a50e997deba398b53f3d312a30fdb15f692976ea33e6202c6878dd44c744bf9dbb05ee2c106cfc3bfee87c7fdcfbdeee7a380bd56270c8087e0a11101f85c60081f12300a0fff1c0619c5072b5ccfe3ebc0e70070ef7e00f0c5d6b99e67e5439bf77e78e23d1bbc578211bce7f9aa042778cf41e87ebcace9dd3fe87e1c04129cf120b807dd0f084064972eff81db743f1f3cf71ea8de039f1eb86d22c67be0970452de038f4d98bc07ceea7e3c582d91e56d7c47f763c3f2c18a67794df7c3dae1abee6747eb6b7c00e26b5c47f75323c61570b5729aee67c5e37564791d3ed3fde8a0a17155f743f3fd4c0f2cf81997e97e66546ee34e04551ed3fda8ae142fe311cbcb384cf72313e3b7fb8969c1680183031818bf26781822351e8687271e86491b0f13c2c3c078aafb81e169f1f87065dc8bc5bd08b8f7c75f8f4bfc3dc0dfeb7749d35f5fed2083bfee3ffc1dc1157f9914f1d75fba9fcbe3fa9477dd4fea45072dfec5b9eee765a7f3adfbe93895f7a1cd7338cf717e63cfe970f45c0e44449dfa9b6bddcfe628949647a1dc763fa80fa0307acdb3ee47e3defab43834f1560c9b0dc980521f22fa396b5ecace5ecfa33d12d45a6badb5d65aed915a2b94ea13712acc0793bbbda6691d7b8f8853399fd8d37d7511925cf9d5394ad461a15bd52b7b75867244ee44b07a8c3a1388ad8c281e893d370ca4ba02df81f6f9b1a67f94899c741113464f1a0a1ff1111212130da575589c64619c74798a85b1dc5edf1b4cf860e3c6c931fa6a85c6d14b4ecafe6eab802557e1838d6b421f691dfe5837e4575dd791248bc3e561098831da27deb44760c7e34df431de8b6f498162dfa9232858a89b95b600a33722a52a73f4943e26b48e65fa4ec1f37832d23ad183e0e03b6af1edb12748fc185611673a4cc4c924bb05b241e08d38ec4b5896c62262bf299d253f8d045be41182f07a761f4e570427374c3dbd4090f2ec2084776836a5b4748bdd87d30d79562fa972c23a417e7b7b3f7baa294c6549393d47f76c562e901f0fe6d4d3502a078497194f436e24b111638f41cf512471e2396b9fa0748cab1863f783edf1c3ed4e48a4a2c457ef1f5d6ef6118a13487996f69e8da7651ef5a6b7d271456a81c126bd394e4a2d9d2c841b4f6ec05e6f3c312369d6287f74ff748ec79374c3a08ffe69b1f293eb135cd594527a4eebb0717d8d225e7a98e34e6a0d9899e79c331a41997e404840398508e640c30b3884d4182aaa5214d51328a9b5d65a93284193b4808618861671ac0145ca0d484460f522c9932447b00e5ce450c31363cc20a6851012708032075ce0c0731051bb4882c34cb2830eb5d65a85b6d88658c88d98c4a5e960e89a4185d448d2c51a9bd21667bc0c12a2061c8c5032c404920b94dc47a52dc0f8eda3d2165a7ce839168b6d4b45fb36e274958f1ee3ba20f22d3333f394733ab78c38f6a79bddb235cbac86ea7e48f0e28696e3ba971eb1e311b65e3c322e7fa18d8ddf1b616018863915cb68dec0f36761f0c151ebc88f4e3775943a4a41699de9fca55edfd15348bd934a92a924286eeae8773e951471da39a6cbfea9a46eb5f74c25c92f95944afaf65452ec891f304161660eeaa4b454850956a9b56c628b21488c21486fdc718cd1e3ff61b6521ae1c58fdd0a0dce0f0809c81e7db2e5f847ce89f5eadc5db5b22cd3e249a63a69b1345f4b3a2c2d916161a24d3bbe48023e469c2cb525cbb200c4ae4cc9686f4c2b2243acbca13e7e8b2be2d0317e768bd278292d1fc49ea6c468c518589ebaf54d2e1d79cab47d28267953bd3b22a80f75e40151c601d6a35ba75e887a05413d7f494ff9a917dea114faa78875b96e7823d4f9b913b2fa220a88d4e953dfa897da12bd9453b72cb5366a446e14cdd8486762ad13a6b47c656a6a9d30c565c6cacc4c0af4ada7b4d894536a0b0ac5a57534af9ec292626a9d8e5e3d95a575a8dc614a35b58ef62da05bd5ab4fcfc7e371fafdf861b79efd76b71b8dc59e17a2b384a89ebafd9077f0d4a9971a8a3835f3504311a716c0c90d67e84cac0027b899d3020871a387286e39690da9d55e5842549fb9fdf085cfbef791d18b8f628a3df3aba396628feaaba75043b18786e64ff0e0554811a7fa0094dceb727d686341b61342f8f8593fc1befd2d7a3dbc1cef47b7329a657106c98a31a46491f6e089557ae4fce0f15940f77d71592778309d3ecb3c3e4fb72a6a0985a55b00208ac3c56ebdce50be79f7cd2cc52eca1bbae4cd68d7f5d5599a765d7d85b67cf53bd43add57bf48574aef54a3bba475a292152b5f3d86d715a3e1c37bf4d5098b1f3ebc4c7cbd445f5db3196a26f6d52b5dfaea758caf583e8c58bec62c5f61f0a10dca93ce835a42619989cd2cc99da55675eeaa5aa777c4667b951f2e399840249589a770411c715c110391a4a000b1010b98891cb029354c1da4b8c205b6d739a001f6087e584204b8554e7a3bb43c110613d81e3bf103b86595ce4cdba1250a2c9ec0f6f8091a4024cd8cc576638f0c45b22e5d9efe7545114bcf9f11194de042c4e3d96b9adc302747ca3047f6aa7372ec16575eafd52d5ed550c19ca39232466f1dd5770c8b074450284992eb3cb475ec067d96629e8028a7abd0a04f8f40821bd6a20da975c23af4d389e48ef41a04d7a20a44ecd17c562022ceac41f0cde41bfb8535053f2b123fe717d615e8f869e48807ba35a7d3e62407d73e74706d181ed8228f4deed895f3e821ab479bbcd011b9f3a3b9a87762f740e276c79fd36071e061f57324e2b8c0c2131bcd214888c203930dd6d0c205b627c20d201430a062086c8f751004d843d440e83539e901d524456106884401474e590680048844c715507e740d293fbc0122c911020543544105d1143c5001b6c76c4c81061450bc200bb0bdf8c49318b859aebd49040c40e90149287220b40444d24a5c9b06850d182a0c162891034492e3a48a1b5430068f8d6b20129a78c90122895d9400e48c784152c749cf004ab8f1c2498f84225e8004b617c12dc5498f6590032423b0bdc8850ee07639e94528a6fc7004b607802ae00653e4644611142700b7184945164f4280483a295d885c678048649640f9c536c026c3494fee40448411d81e6b3902dc549cf480e81c538e7e0091e40831841ca0f4f6805a8b1f40250dca6fb601a44723031f8a9480ed512128c08d86939eb47284182fb03dbe320370b33a76d4b0c40e8ec0f6a2530e2092e6c9c60686170a2f7c48aa428714c076804151953988729898e24617af2ab814f104dae000c68ffd84b502154656d3340f126bb535b359952cd8582b0071194820ca3537c2df028b5d471208a42309dc220febd50c5d0f73be9fdc7075135b687d54a2f2a444a5e9a31295a5c7f9a8442589f7e24e4e5291125398c005566c01db256799735a6b3d487ceb417fe499fee87295a810f12cbf7e9c03fb901e8fbc7cc190530a40245d022c3b28a2c70e40240a40430008f8c20d2140240dc795357c005b890a8884999801f617857b6c51ee00d12fb4f80003a5032161264aa47081d38190d4b8202908503a929a28446c014ea058831e4024035002049a5f1476d9dc80cd0621bdfdacc79f4b2ed8ecfcf17443eda773979fecd4b3817a9d935267a694764b8f338e395177c2782af6a4522f2fb1e7e5a5eb624fd7715cece1b86d8b3ddb8642c51e144ad3628fa6451ccd469ce99a5bcf461ccd33a7b1c7faf41a7b5e5cf3197bb6cea7e69d5723901a7d683efdc5ab11673ae7651167fa8c4066f4e1598f461cad4b6864a23814f7c950b3a1b60d8592416d329ba3b6af08376b3532ff3a2ae5851e789491f931f3aa62fc42233334326564647090f1242a954aa552a9542a97919171253ca08c2391f9542a95caadbce90f8ab8ed7647e54241433a78b9646464645cc806948cdbd03292b1511ccdf10454a954ae8407547d4978a0600ba5a1284b78b8028c62bf178c043d11ba81500aaa70092306da12cce08a1180d1c4151cc0760fa05028948c0c0a15136364fe7419e755dda619c166afbfbcf895e9ba3c8c6f7c278c6f9afce9d6519e7c6bfbe787c2b1284ff3170f42fae99d6b5e10d237aaf32254861acd9bde7911ea1f7841f3eb2f681dedac779a6bde695f11548cb35319ef7ae731def5f829d7c788333b4f75be35f78211d539df64f743dfa76f5ec82750df620f438cfd07827eba66352f829af6c5c3e99a6f539b5f11948756e51a6a7aa18f9c2afb1579f98cf8e0cadd3c7ee7eca8187b849b735b1884f4dc97d5ea9d87f63bdf32ae7ae7fc51ee0009c52e34f0020d37e290638e2ea0ca6d60702803930481d63b4dfb52a81e5a0754b76ebf30fb19dfce99a0a73ee3094579413a2b05fb2887217b9474cdfbf17451ce5d3ed47cdbde4801f585d60895cbce882107551f921803a3ccf95bb4c08d2f294ebc5e4e3cf9ee4016f45de6c712627d3a0364e0cf3e5ae717d31c6cb3ed81b7df0b46e6db906d9d5e847a78e4ed27438d94524af915a93b7c5cb9d2a32dc25f8c3d42fb15619f43a8f542fb36b3cf5f8c3761f615a99f11ecf48bdf135be401c8094e19a4b94fd86a3df2edecf347822f1891ff81ed63c92f85969f0cf25f88dd9aec41cc40b605c49edef1638e5b3de86da80e14045667e7e82702d773b27e916176ce3967c489cc5d3e469cf8bd24076e545202c9a23ec6ae3409b9e449a533cb6fce965372d9e201ae538f1b7d814a66fa02a519cda8675ebd17a8db6e359d3f2ba5b65b967a946b56e90b95d6acbe5033cec69efe5969a53483924aa552a9542ae58483f117bf305dd7f9ed522fdded18e6297366470d19bd7f53940bc99c9979c4507dbaf48cf04be99b6bee5a4e7bd66a2f5fa86953d366d4bcc709f2b5cf461cea0330c1cd9c5f18680d6c20f664edd4230820ac56d1da18ef1cc6a9ff681d56cc27a4c5758b73ea9a07e3d96e715dbda3fceb85f3535f0c3fd367e4a2cef4483dcc3edeb8637978393d240f0672137db4f7003e1e2d003c8996b333dc6619cdb2d61c2e1cb034cd81830e5c14cdd124896af5246fa29d0388212e62f226da3986d001cb91bc597571ca1a060cd8132060489ce0ce8f4ab0a42930294a6053946cb0454e8c8955779006d3a450482e34484d96f1618c71c9e1c388258a6183105098e0e00b2a5b4c31e280e3a5ab56414b94288922040e3118c2054a4741ee504009071dae3851451a5d8057d5faa81487151ff351298ed787d79dd8b68f918a94b495736fcb9d2727078500e4705918226f180c400e316e7ef2ca74525a02a1bbbaaa951070f000c70e53261c422aeda3d21b713cb1b28146c3135bcda20a0d4c80d51b59fe7e547ac30a4d638b1e79482965a444b4c91ce3c39b8f1ede3cbbc6ec8535cff49b91267d137187164947523aa2f2fcc473772bcaa64572478b37d13da49aa3901a51a62e44c8e2b66f2a4406e38e0d0d8d873034adc33b236c78888ea77b001e179ef899d34525adda50af9e2377187cf1e94a2dc99be96174babec3c3b8c4aa5965a925b973c4f5d3c314d312538ebc99f4f3d5ca7d9a520f5c87f496fee275d7b5772ebbce3cf88460fc3acbeaf022d825406888076c0fbb47c0d00a6c0f614a186a817dfb05f5d3b7972b9769ce0f1e176028215689b1b814b3d09a7a6a9d30b594724a6d497159923b2b79333db5b45ad53859f6a34e820f0158488983910b1026e0a7473640d8b37d39d5ad57b7df4fc61a2265dbd05496c3b8d00ebfbe49aff3f65ea68f40832b3fcca1711b5f81c66d3e56751b8fe00a3a9c556d7c85ce6d5e7c06e99daff0e2366ef3cd00fa74caafc739cb53f90e4fc6575e8cd778339ea93ce54232ce39f55e585e04777811acf122487fb4189f71212195908c0b71fee23a3c56a5f158d5af47d0b2aa531e79236fa6cb782cc89be9339e06e4cd7ca9bc2e2647decc155e1ce69b81f317a7de0a9d5faf9e0a29efecc60de16804332e4bfda4b0bc78acea3cf266d66fc81c37c5945aca31416a2926c8768c962d82517e2c1532df7c85eaa8ea5bf5193cd6906d73d60a29f4b33777fd3591661e629d3d661a22947564f9d26e3d96f4cd355f6173fbcdf0a17c062d82d77332cd8b20b526608a4edd33055be4e17147ce0574ab4acf16893b31aa1077621c226badf1cad70e85ea1d191eb2518e0e97c663bc3ba7f1249cc7d07c2c1af3b12891ce69649665b47b98e00e913b5ce7dc57bd1b226f6a189750c8d1e1ce1085fa0c9c773e838cc7387bd9c7ce89701f2b731a9fde39fd88c87c2c15689c08cd379d73299dfb62ea8f188f95655a48f339dcfd70db47a4732094cb7804fb867ecdcbbebb1fd610cee31391ddf4ceb78f08e7349fe6d9b71e7fe631e7b1503e04ec3c16ea1b124199989a396eea29e59472ea32efdef09be566eba06e98add343a8d93af6069ad91754f61bcacb7ce3ae0518ef3e966de13acb7e51ac03a5b817e73c4808bff50744fe21348ef2eaa368328b7a4004bb6f8628d66798de71df0cd239a77122d3693e16ca693c8244a4b35034be828cd37c33442142e3313e830e97f115625ce5aa6f061a8ff108661efd8dc66315892185fecc87a8bc4890f82aa771d6101dce7a400475386b051ad7541ecae326d535198f719fa6f15899ab3c823a3c563633a3fa6650398dcfa862543432333a62dc7e44a4731e3fab433c173c0fc89b5a613c14e44dcde956f5ce894cef3e5646443a2bfb2eb802613d5a5fa74bbfae8f0040c205d58679b2c50bdc680517af8f5d7cb4620ba6e7828454e3d137116257eeb0d87778e47cf31536dff1cdc0f9e6fdcd603f167f608debfa5c71b9e1eab98fa5398b08ff10e95bdcc1712c9a83c5cd7c8273f39c970e0f1929c699888bd888c6371d313972875f21cfcfecd3800bed3e9db57744de4ca71ec3ba353d532182ed1b73d28bf280080ab17c876fcd711ecb376f4710c8f9038240ee030a023967399116bc8825d100721f500b5ec4925ce08e181e10c1fe84542ee32c0ddc9813218bfb93b1b20c88991032fae9213789b333caf7a875a65fa3fbe442f9e9829dd48acbced2be21ac1d9ef97624e24cef9c5c76ea9a5f8981baf6fdd897a6cb3e020deefc18266b088bb5c3bbcb9cd2b8ca85665cc65b72a7c6a7bbdc59f9f48de579e03b3c1baff1a4afbc8e33933b717a58f347624fcd4f5f792c1538f7c08970eec10714853a9074cee94764739b0fa8591ee73bbccd6bbc08aebc086e59045943643b17e3a15c8707e334def5991797f13ab7de0322282474fdc58552de79e66d424249500ee34242d7855e9c73a1940b75beb94fc778412ed063188f91268a937e822f1cca0b721da999e3f2746bce9963658890f4be481f01804415541ba6872d9b53ce3967ad52d618c2c0359d1a70a6d5c728e794934e293929a594524a8fdd629fb151682899ec514e568204154a9458c96928c978d8228f58024f474951e8d66742b79a7326ad40dc1ead16ad1495e5f2f09a4d9ba1cc4ad58d51dbf1f179c5d6b19a7de10a8794d9620d25992b6ef751698d29313b5cd447a53562b0c6123af0f0246f64d6b8a98f4a6b18ade0de8f4a6b10ad31e43c983ef41c6a438c52cee9d37bc9544e29254ffe7cce9f53c678d3f21e73ce39e9ca63cd07200673360a06955aedc2d4d81b125607f8e81e6572b95c74d25aa5741e62a2e7a26672d986b88342eb10b20fdfb575e3820b39ad960f0fcf09ddbb1099fcae6a5e7eb1c7e58d0eb4f2174b498c541499743c5d1d4f97cf68b5513b45a698258d2c50a85ed224da8e636c9184890cbcb0010a2532d040230bf98891e8196dbcd4ec758042082c48d810bbd2f42117a12145c84f648c6d1187154c50bc40075b803dc6193fac3260c30e2fb08113521c15812dc6b75fa66f57b50ef7cda9aa7c6863f3210f34da10809391d31969c40088001ee559f783fad5ea06afbdf62aa1c5c3a65b69308146467d138ffa26fe446a25e5bc78607d1341a358dfc4d8174343e8adff586f1dcfc65b677e1b1b4e2f309c9fdb6f15413e6388cfb0c85cfdb266681100af71c38e31cd96cbe5aae1e57259b9d34ccd640694fff13287069ecf6935d3b3af9ad8e8fb0505cc31cb638613ebc51a45bc44d6f7f4d477d8377858d5743f35e643ee29752b8616264f7dd5fdd0cb2e36a298611d57ec40ca53a7e97ee86a3527124f8c9852c692132de0d4011a37cc39277f3468dcb035c6539fd4675aac430a4a64d1061a390421871b20f556ebd82c51247162a3aaee87ca8430e7013eecf1a38418c62a62b8808a18738c4184d2186e2835c16820c3ca4f47a175a2521946fcf498181414cc0862dcf0436f2ba805366ca14214050e2c5a60c3705aa2d254f743a7f8518502c2d8a295a5862c5494a68065914307b09ffe72698e00b03009a1a407208a4800ce2b66b431e79c5a10376ccd137c094f9d6bed10460d3fa76f4f6354d6414d28c078499146ca065f54f1f3675a32a4dc4a63713c75da1a01d3959f3e271a472fd15ecbdd1e1e418929892d37b43d94bc48a3c7dab06075af1d5a424d58b294356132912a113e48137678a22f10a2552b2218518c68e95e1104334962f2ea9bf8a1bd4a393083890923e8d512fda8a446d287d7b7263c25eb4a6ba5334a2ecc4725299862ce90228d1c48c14555c4dd3e2a4d31a43ae3eaf8a834850f5e4c81848c94fbf251690a16c09ce0d68f4a53bc5a2aa881246fe24f964a55caf8f951290da6b9a48ba78f4a5d34b1f7f8a8d48594d7334bfa7ce92c19da55ebfa537cf1708c4c4dec162687c41dca246fd8ab5898dc8931f6c7b02f067b523906eb1657a00bcc6b80698f30b98372728241200b16195c16f50f51b4bdb97a7173d5a3344bdfe8e0f617daa5e727b963955c579efd8b67179185d9f0e0562ba73f1e8e634d94136a0b3beaa97552cfbe19b10a9c505cb058b10143e71f1d4a36ac755827ec6ee2b975c28ec173d84e3c4b7163e809f5c22ef9d609ed14760acb6485b1304c0b6ff9f121c384963401e3c376f2c587fd0485b2302713361fbba4752c119125b24496c812d9224b648f2c924d6247512f32573644999888f437843e691d7a44918ee8113d62a424a6310a48f9b1e046ec1181cb65c56eb18658d7b625625e7f2175c2407f2e48699db05fff923b36266fd857d757fd62eea6670b93370c037bd54093131e3072a463107bda3b10c3f80e48206cf0f2e5d3edc52cbf630ab996cca16e22f648e7796461ecb64aebc03c3b770c680774400ae12e4fbf0e4819b304a8eaa8546dd575490c19aa190100002000e314003028140c88842291482c1e09631e7e14800b859e44804e18cad328c87118420819420801220000002232b409003ccdafc4219f02b14d1a0f36d4410edf7983068824213ca2d100413cfcef8e31097632895c7edb6e627d04f13b4d637bef62e521e01c821f516394898a94d04c6b3a70eb0aba6be0dda8db612d9837ed1096477bd86303318ac65ad9df4e90a17e7a2c94ace5a2b27c62f6e313aada64ac153b2b841e1af8e3094f2603357c7732a61d33a170efbb473231d73ba5e249e57fd60c5adcc4a4fa9c10d09bb047689d4e203ad5552dd176b9df2f9576274874505b859010ac8477fdd93a73fa18a27a54835a0451b529f3cf5f39cd0857d46462030604ed86b8a9defffef3cd8888bc8fce1e1ff64a7412ed012fc46275d40c5919b7c008983be74db20eb6e51fdd2e9df1f146389babbc61a7cf481e33e0eb8f0da7c1e640322e0e753eaa3ade005d322482aa22d8ee732ca63d9492467763c2f1d69d853a57c448f6a6ef740db619964bc2f5c2d3aa9303e84e4600ef67a1daa4057046beb50d69e3a52998ff989f6049063503a6e5cd1bd08e353edc6bf2972e421ec34c1131b7efdefc879d27f6c1a010c77a80e8f871bf49ce649c64cb0cab3e7752d70b1562ce6f35b7a6ab178c7dbf868c05c2ff6700353ee8436b418fd9818b2e39fda06a5386e70822258142d8548e994f72ab407157a2003aca1c87a50c104585a8e1859782f5fe8a3a332fc98f53dc23335be17cfd984558b74d7e96890e31284df59e5eb9b3bf2e19d2de4bb18fba40417a007532fa30e3b448510c229d21be9a2f31907cfa3075d3b82a580d96c41ab1ac55d7b7d8119265ef12b04bfb55dd1d973f05f9d54d10bff6781ca3e50dadfb7a51737fe762ec75bf57590904db495e676b134e9d2e08b08c6244455342ab4837520ab3d9da77e659fdf68bbb0257cd5d21f8cb954b27f8aea07bc9f2aa6a872fbbf0ba1479f528616b1185ddc52b11bc2c5ff898412427010c4a03bf25c668e443a8fdb9a0699fba06464d92f240fc3ee05b1eb36053080ec1a2170852e6f8da17abee0848295a28ef7d0e25753dbf13e6fdaafd226d78530dd98ae7af0eddfa15d261ceb59f69fdab170201796e88d23e4dac002b50a2743e76d419c96ef98af3801a5aed68b689d7e83d49a307abec4a0c6279902eb20602674874a0eb6bf49b2c9a0a036a2cb848f9db9e901df289a1213ff80de9927d56185f9d391993a1b8247495a6eb77e6e1cc900f6174456f55c377b6a533da5a25d3fbd3dab769a091fecee0402c31a1d2263b520612c699b3289053bd407f616e837b4c83e4b53840208dac4b933be7b5cf6392949e5cee4dfd9e6ccabcc50b9ae2fbaed7d37012e06e7729f2a1b38c1b4e78d993ba898eb84ba649ee37a304025ca555ee759c0caa1b74bc709ce664370e262aed4c4d09d8fb8784644a564a6ca50b2c3a621d5fcb6d6123052b118097cbad255d7097c4ca9948e99d54b7fe8176be4969f9a8307b6117c6167120e9a42435aae279417e34f29dc4c5ceacdd7b59f498ff10293987cd7a70555e1ff19f0a039b5373d9915cbc01cf32e21f99a26947ec0aa5137a02d94885d66f88350e3198434cea9026a7a4c1e6e9be9c77daa15702f40a314d60d9f350946a606587d8c0fe0086cf806961dd6ee73d43f4c4c9fb81aad30e3dd3a19705e874a5286cf5ed40d874eeba21d1d52b2a31d1124aae0bfb6772c5b070fc738d4c417e347d8581e4fa2b8d3550206f0793a8809e6f238fa03849cc0f418aa4e317097da2e47faf279009efa2cb84b3e5d4e87fa31fa09344b57bd1b4935683409ef97453db0151cd208aa68ea075079accf6c68166380aaa9c441b3ce48afd9d38276f3eb18b09a0c15e0652a4bcdc4949772661b8c0190e896b20a1e28f154bb0deb1d8d940504546196762e79d087ff69b4abda56abc084f4ec2bad4968be2ac74195e7bf6c56c43cde4223d51eb1fd3250aa617f42e3c6631602e97390a0c20d6b09972cd5a006911ecb64d7ddb2b012ab0c74a8883ab706b53b15d15a02e6b31678089ea1314162788014a73b0cc40cdb2358594ded3d0f845ee30e4616f382c8f9e6cf14a760e9f889eacceee17a91e6ebfe026c8161d2d929e28db6841bb5cd93ac11c5d9e814a2e434d5674a790b29234f192ccf15765f4fafd0cccab0fb0e6ac29dcff74c1d7c5042f85459306505896e0cec17a8d1b57f72de94ffb065a0696c88040d349fc789f394eb6b6049cce4e2df1e136fccc59c21b505bd053642cc6251aefa9ef8ac97789c685579f3c35c29c3e3483a0f18a049e81860d56bd239fd40303e5d0a1e48e3f0646f18045dc41e403a7c562b9d5cd6eea69852fe079ab042ea9fc8bd69696352c3128ca2612814e1f765a4ca4e69cbee7a13201f87dfc6ca6003a9241cdc97be6466f8d51878cdfef5f7ebc197001cf0efe09ab3d5b84026f1e512eec2987a25ff909f11dd1369a35eba5baaaadc352efabda52a59857a9d460319d0b40fe6f1f2806c50f92cf5384fb8b57587d944d58dda839fdbce65079648bf4bb729dd7a49658cc37065e04fa7a9d25ca0eb0bb5cdbcae2b092fc7c299451d5423c67a56091932abfaa0e7e10d9c002be33fe41b0b210e7d3f9fc91b3497acf841cffbe21216a01673a494def49f6b9e186825658a273d68a3f4d9ca2a5fa621d0454d850a6d134173dcb8d6b564d3bdf2c7450da834503dd868a74230b1b54d8fa0d585fa50a69f1a039905670c3beffc144bfb8bddf0622e7d90ebb9f1596126670a4f72e4d97d5188eae38370fbb98b8255d25e2275c030d32ccac467ee4d2410249d7ae368c17ab1ef5f6f30368ff94b961700d533dca69ffa16499d737d51857bc07dd4941e1752d3640d811e784f6c1ac5d5881a08f7357c16006afc3e75ef084c9c9f67986d469368580ed728b64c20d18f1e215e075fda1cb35bf14d2ea8e5ad896731366cec648c8602b1d259fdbcf396114911d764a86658805ea5d2921f0a95aa826fde659b5b9f6e941bb8b77458bdeaf40e090df24c92762433245f594d6890a4de3a0240dc5b2104288e368de53761e5142c37749062953b51a18a6bb3753b6b377d79384e08b746d54926506cac8bab2fbe4be7255212f7e8483920d43d78aea12c3d6c3fedd1f5102360b7cf5c92db9038ba4a35456f45685520c80d27e45b076352ff706da7ccac9fbd9be1a990b81b9c9e8cce1893eb9ef1ffee2124d5db9626589f03b6483cac1e4d1d229dbc506a88fadb6625f71f6d7f7dabe3a300c50ffeeff936a17d42eadd586ad416369b0585abb16c2f71b8fa3e14ce8a9304faa251aafb257d11f9024814f8d15f384d8d881050eee1eedf57e9ecdb1ed50eab8a27dc088c234bfb06d010d7b2ce488fec22635bd32f40545ff8990097748320946a9d4b7819642c05ee980340d98c421d00a53b0c38b85f3e0fc97cc88c00534b809ddc2cd0e1ac6bce56b2618ae9ccf7869577a8e8eb90ed7f4ede01ff3f28e087a17f5db76a87e6ce05ad0c83fd2fc2f60bfa9afefa7521d8b669cb0c4d0cbe674a8134030cbaaa1761ab3184b6ebe91177d894b3c73ef4f22c548bd6350410c22ab3d5d2bd44834c093b0048ad736342564873ed728230400f6746b6dc77defad3a4ab3f28f6869c80bc830daa763097bb5a68b88e026aff64972aa3fbc590f98d33370e144a161404d7da924f6b19b42f70013ca092896de21e77daa6bffed565bbbfc8cee1254fb967028d4cd2646ccc903ff3846d38bbcb754991b982471f8c432938591808efbfbe885322a8977a5ed448d02c261e02233819dbab7ce020c5002965527007de7b2529925e015be0035e967cfd60657d4dbd8472c5f9343ca63ca0896a67dba36b466d2f65a4607aa62372be24288eb32bc694b50dc4f4ad420c2cff850f27c5c291008ab358e0ae06c9b455ddf4dcd48aa9bdfcdedaea86501cc5f3f209750c37a248a74f5843fa339fb39bf8fd5d337dc0b5619771d04ae157b43b788924d8a4c87346c4377d3dcbb0b59269b1fbb9bfb859651e157e85b29e4bf1dea03ae05fd36f3b5ac7e2b46d9005ed57a3312dfc7069b4cad91cabd814e2afb82bf4eb58ed5e2ac2c25cf10594f7712a908070d207b3a7cd190f58306bc92874c907b7360cd925f8eff95e85cbe661cd436d326597918a9bf1460fee3c0ecef96b798feabcd7e72d41159566d88fc91cd0f49c93f1c33ae15334158a1fd03ef29bd93f90ef84a3d95d9791059ff04e1209ca3489c73f0c1ded7decc98e64dd812c1847294591c50305941382cb91d7f8f34e301e55fbdf4f398b542e4ea0e2e2f7a890abbed703281620d0592dcaca6753e78980ab10f21338c2b7ccffd724da2462c1073fc189f1fae6af7f9fb9f749f60d8f585f7fc5f74e2cc9ce1a724e6199d487143eaac40cd0d2f3cefb9f3748ec7bba6f1dabac3e4faad03f200ae1d9ef924dcef8081cdf8905e30e7f42be24ccd8118a780d731148d41b86f10623a87cf15e4db2a234245b2dd40bd12b4aa6e17a8828bcb923f8d8ae56abd65516e0a4a5e48040e6ee5ca848edfbf20ac0fba732c6a3b4ff0c3cd7faafbb36804a515db48735ddc9af4505e4f3198dd4636a895b050311756792b07f608db6d74fd5bb814e8a49c1a3117d2cbaca052e46f032fe09d2553494c9802b22e110965539a8baff54b93bcc303916808d19abe0424a29273b14148059ce0c7bc837b36e2c080d63c20065e954bff001a62b86c5f661b0fc83dbd00a249216a2e0ecfb9a98a9c6a263677eea7418698360aa974eecb10502ee098b078ed22474d7c3a4009a71d545b03ec4b55538819d126a6508de96e545854fc27e5d4ffaf6668921dab7f9ea603c03b182bd64b8af86f018e0587d4aa1ffa1eebf42e4f5c3c74ab13b4c8ea1b7a82941aebdfdf8d5128934e39993b55fdd6de9abacc9b5e078e74cca9e07efec4091ba332565afb41b7a655c9cbeeb89ba0606d41e00943a7030870c3a2867fc02740d1a875eceb35ee6c870a9fe82958a67c46b9a12d6d476f29bdd37467d65e6de79c49b48c7df41222d79a4d5767c9128935e792f30d2cb7626dc477d9fa25c6aa826d6cec88463891a8dd8e74307b298dd4b863c25741bee433e19de8e6928c6e83ff72b7d230276dd79d74d7e1199b5987170faf4a27d4263e4ebc825b727255365dfe9559d137aa1b6ca75c82482e1ccf67cd45dd8cf4b58fa604bfb2035e609d8237b45013171dc0b3ad620316048016268bf62b49a0b810045a2e820f3196b8f562b107f6b0dff19bca89fd89f965875ff08c6c2f86d806d65b8d51b10168ffaa98cc5f862cb56dde864a36646d805ca11fed931b82497038ee77739e664a433fe8ca791828fc11367ce203c703854596be703152c40135e0f945c14eada1a655749c5f61cdf08f326c50c469be98e4a14cc4669db10babb25e392f9d9adad613ee0efdbaed9da61995c230c0d4b75f8e9edece2aa14b1f23382148939f93a670cdfacf1b4148ff3ff03f565d1a7a38db2a19891c65f5c7286d2daefa78007ee07f2620cc4a679aa3105f77a107bc227c6b987b847b3bb6d8e7f0dea3a099fcec70a00fc65af610f494fe3282886268c7feab5b9411436b193193fc1edc9b0b8feb829fb39872bb4b9f93d0ef45ebb36f76ade22afbc638e28112ca743036393633651957fee9b280a1ce1c60d5a2ea2ee9428c8e042f33916afa497d8fbc5109d9574358c8a73613798009bae3130dafa0a2dc49828a9f870ffdf6815cb34117fd034389c856ffcd69d76f919b0f371b1fb288165cc608a9ccc1232c71668f1a5f43cd8dbf0eac54113226ec28d807592bc54a8fa477d58e6c591fc80cb0b0e1cc560f49aaca8f4f81fb475ba6079666a51bccb87c71681c12333fefbeee1684c38be0412eb4b9870d6c59a3a1f1e963bf461ad024eab876683f6880a8a521ed085e3e842506d11be5850e144497ee5d7e33051381275b2771d360cf6e6f597f1c7269051d1463f28671a333a5418e8822b6da03dce8b932ce09f6b265515952d1a2f424c00bbd3baa9f0cc7f8dbafd67b75cd432d667033569f2c1d24fd26d8537619a5c4a18f0da0b5d91d353e44085cace1367fc78f6d0b85d6a69e61f60293f94775d544e9832b77619f08c104606abd32b26dd3bf1b02adc0cce117b9bff0eb9de1136adfcbf84dcfdedc1f685b63d45f31999e00742d47dd2b30edb312318c11e2ad0b92beb2dfaecf9b0f86a189788fe34b208d874579b6339a800e3ceb3cbd40e509ae5b12701984f9a59a5d64a4d67f14a9f6b640640c8d4a2836d3a0584e5743a07a482d008bdd761ffc3d5817afd8ede6a0c30eab56803a1ea002976253500407204f06b4e70bac88b958cf933ec048f496757b5bf2ac9e78535a01a4b286e977f5fc25811e122cf2dafaf611b3ae56a7eb549e8c05a77bb374a3986c16db412f5939f46857e14927014f620c3be98058b9f0e56bad9a741ccd143f7b344b454f05cc534b4be23a638f33c2404f3b3c0698e72fb193a0787822e69d6cd0b56d8e3780c2e42c9692b311c47d23215d71f95c1913f244ccb11d34ac69d2592eca1481e838bebe9b873ac8c15bef26c9673bfa4319be356334601ad69fa69775dda99330e38f51a0c28e73b60d3635cb32de90578ee0ddb37aec2207367ca6d91138087691a340fc4918325bc28b29635a2c0d5e5388afda4aaee4aaee7da7489aac7d12630dac18dc71ea697b5b443344d6f7aad9c8a039ae244aeb4cdcfa73ff5f133933851c159a19ac76ad4abff8fb7d588688cdc97e6753e18d17451f50c5777c4ae073ba2bf79162c318a83ca99cb1c23be3cfc58a07f47232489c06f0b205d5318572a9e8d68a0d7796abeb5e2b8ae59ad00ea1c4ec030c3ed928a342e1d213b668ca4bab545b5b49e9a893d645d0a289513f2f96189ab528a5afbded4de998dbe53c8f7f853fcecf45e910bd3b6241c37b3c02d05b68a4d7b9afe7aa6ff3edab969baa14085aae8a920d661da6ceefe38ff6d1479bcbbded3bebf5856989caec88b125692d84160028e592d5961e96abdf57ed2d78c5c143aad13a0aa032f2f9cc74accea45690482044a3d0a8ce16e42124ad8eed95285d1b6af460c4c5204b07df1dc79fc9e65ea5e3009f5ebb26019c11d127b380534ab50a035647dc19ca70a70284db077796dee450570977b223b0118c4b962f40c22f819d0b5cbb7434252aa7b28f0d54da5b1182c0d97d6b8e211c1672ab2444420643d19eeef35351f408ff34d5c0f18ba89c01fcf26fb57963c4fa70168d88a687ae41e4db5d4fe548046184cc2a1f1c0814b02032ca5e522e9c0b2b1e339453b6b83578b8dd15434ba6c8823d805641874bbbcf857d922abaf85a6219c4f207218e369a56270cc205974890e435075dbf68ee36c464232b8d2bd07aa23cd9cf3f791bdec75c28fdd3728f17d346dfb478908d67515772c9c885bb51142c9b697164d764a8b31cc2b180623ae3f3a506d1417aa14943750449a8c8bbb13b9af5629e772a0273e4cac737cbf2a5e1458e437a07d1c54920d470d63e00ae6f1d0f1c2d8d4cb18ef12fa0a35c9aa940eb525bbf7281fed42e9137cd43beaaaecda197c88442fabc93e5497d14d5b1590b4370e310e2d666974fb94c684ed07873d572639a48a1968daa6f6ad7824de26e555c2d1bee1d75cf4e9192f5dd6da38397545814081704e8b452d52a57b99b63721f37346144005ef2aebf00a3b347248e02b7c7e5c368afad75826ccadda52054a68e8ae882f94e1330f50b98096342aabc0d276e4a8edf523f7cf269a3226ffbee48ba9491f361c658693c6ca9d3086cd98cea6abe027a011fbd7612d795b28531bf874f74e035c6b71713cec8ebb2dabb3a4fcd3e5abf0fe32c8c7270e7c0c51358f3c1fb8dd377cc2d9e970e9fbfe89361593450089f62e406b116ac88cf815cd1393d8a894f899f9cebcbab3afaf38b9486617ad97ceaa77aad6e8234b6a295b8f67cc0754374b2b1c17f6042c4cfcd27c4e47d70b9625c2713f1f140d673bc93eb878262a148db4510b180fe994cc6ef3702da9403d3afa29eee51e7121d39354f6b95f38e913aa1e7541e0c294781b1d6678ad054b06de4c16a0813b43487b0b3e0360926065c552339ac2436fd834fd90efcf2b3ea30128733c32a03263186b827c0ac155f0b35a107cc5a9457418133f7802b21e51ac376c31a93f9d9f461f629cdfbb1b5409b73567fbd3a3e4908f0aa0811867ee922e813ed5a063b19138a9c8ea203cb229b27295e143925877899e9db99aff7f61eead1f68496b53d51adb29a3109e46d523907faf778ef3891a27a4c7ce7d23f32cdc77b32567591cffd74521b36e926ff2e2d3d802fa5eb9209ed8978f6f8e84c479879217fb14ac95e290e4c405166e9e6ce368ea79e68ca7a68d58565dc06ab8a43905fde3d2610605899481cc1ab662a202148ac591c117d732aac6689f71f36f07a6c6e53fb41797a8788f7fa43c7109a41d96b68deda7f999088eafb0e3f60b25ae6dce8b985a065ca2db71864eca1d95e6bb947771b8bfb9c821fc1cc0358c0d6f078830430747037a0a4b58c5dcf98c5cfcce0b964ec1ecda65b87230f8277e7b4d71d0162e0032602f6da0aff663e5cf72c0e46645099e6741523b0c8fad3e6d9ed04b49d29be092285f5ecc6bc9015b25522c2f942b6254d4d91920daf6631b797993d03095958223f6d95c121fae61bf88f055e6a7408a012efd7c731c0c2831e7ba6ca4e952a7780ddf05234477037e288881f1ea149c9d097d930879b325fcecd97cef85b8521bcf432672906ce58bd41d045a8529d99b8e5b772d5c38664422b706c003b9d8e978ca01aec69a9bfac44967a23cf3c45d9a154fd3980f00e1a6738b1ff4eaa524f300377fc30e73a4d8a3e17bcf6f5453d002e8391d13050b44305d520bcf7cb7a2f1d8f7ef2084104bf02271a3c4f35c8c56197edb85a61d00932595f2f80952242665b5a4b0a674949169a8cfbd553433ec8b84c87774cda7e5ce1a7e73de34cd772841e765680593b3ce25ff44bd96dca9cac29d8f4ad0bd667e4651d0b4da90ad53766d47248a0e2c551187a4e0baf8a5166eb4237ab01f4bd622c6a6710fda808445a1d73ac4e4e87b2a92a7028b82fa6177722f6a4dbf704ffe3b422cb4e90c25eae43060b639b2980b2d0cb0afa84a010cab80da34874089b51d44f84e78f8edfd7107517b80dd41f04dd81287773ed45feb1540d2e57228b640676b74636bad47c3a9ace751bbc437874a258b4368c8750701d987006b71544f38795bb55499458eb33b23597ad8ec114092cb167378288584b4047a833e1df168560e7bd0bced35be5a69fc9ca508edd26efd390993524a6e8013d435b99ed56c3213c2dc217268557b69182aa571df04ea9d1f1cbc6dd446c2f27ecba2e42f601aa2433901f4d18bc140495a2182d6b0f4acda769cf7f02f3c5e3e2b05026701f620eb0b594dfb4f941e68c3039693843b3e25e2536bd012b1bae142bdf9a839f6cd6df0b3ef8e3787d420155557def6889a0842ba3c8de26a205f74f9cfc7cb69f21bf48a00628b55e1dc90ddd597a96168ceaecf170103a0c9ffa8b41d5fe04f34479746f492b2bc59aff50371181d0685858a141d560c7b73561396eef8b5a84d12a1e8c01ed84d81932d5668232f31683db3c9810516650dccb10f61aa8064901cfb4d71e700aa884b28a9c3aa85f4892f5be2c2a6983cf86573f07c4464c798a0fb176c94a72c12a6e3a1da2760d2bef5751dec3ebaf7a187195725bd98137a974865f60bd37dca69a3c415508a4d031492c8af0d38a5dcdea2d53d0adcd4e531e0cc7304a5b817e993f8ed1af22435c7220f407d6c07dffab085dec9fcc1031089a9cecf8b81a633860cc40fb5e57908b7e95fb86518452a6ad52cb1913cd0f0684ef8087dc5b28956e6d8678ec144a7ff27a2f89cbac95456ffc7952cc476311c8a7386215aa9c2a00b8017ab8019686ef1199658c2043a77d9e8306699d808089a7e559fdfd774e69f5252f92a25eeb589eb5a776bcd570a9da75ff6821d3af23e7cd00d11ec1cd2aaadc01533ce90f1b9edfc7a117d3b3de69bad67f897ef629ef4949b5657fabebfaf5960cf8e8911451b2249b7721cd082a62d4ac4052d4314d97579b79f7b7c620ee8e9a761e63037ffed606b7409abb81f5dc54a0bd3d4a7bba9e75f9fa6cfb44e13b29a72c81bbb792fa8cf025f989ea847c94a3486bfd39893d34ad6557a8557db49dbfb52b4ac797dd45d7ff297bdbea8e7f4f128c186cdac8d5f7cc91969e0cf4be6468f76dacd611d6ec69e082c1f363a85556aef586f4272a57642fe6d68287729509e56a20f5e05da529228ca40eb2cd2e3a21e210ff4ae7eea4ebfb84be4e71f2f2d82f2cda8273b0ccff732b57f6ce486c8ada3918c2b2d257f6a2ef306669ee92d8620845dacc5eeb480a97b004cbf687da4744a448b821f102d854b342aeeacad9f53cc7e4b1f849adf2c22f81616e6567097eda3212b95f6f2862002c0695e092d47db57515f1370c63f4e8590e0b4a19f82f23f29379b06dff24c9b06ec524e97d0efd4812c2dc3a92e848e6e60d9f295bfbd73b5206286d937783807d0def12e8cdfccb44a173423f312b29e4aacc08d4ce3191806cf29f44c56b45ec0c9cf4e2ff6f40368db3372586eda91a0f388774892912b68309fd91cb7a71645e236ba01233c4610d8650543e46a0c08a57e07fb4d0271f427f1d3fcdd34f1c35dc71ae60d1f360f0c081c5dc69d8cfa77c92e6c2085b3babbefa4b90d661ee9ce316ad42098fa2595b44093351866ea2b05ee5c43ed5820e2398c7f6f9bdf1b9a33cd9503584362285ec2ef3d629bec2ec68e51a99733b6da759aee50e751725689adea4cf1d0a048df97ae847e7b7a797e03eb5492190ee94aad8d3caf912ff4422e72347f62e8a9e61a8eb50ae3719532b918a0a7f9caca0460e2ca6fa7f54dd36d52ab02d21e5ee081de184080ec4f7c52ff28312e840764bc2cf15806b0632e325266952351590c014d3d942221e23db88d788e75842a279cc794adf39df3b7a202955e799f1614d1b2d8445f57d8de53a108a7679f90d3a32200fd8f0ae0b2def41eccc4ed7a1ac75a96369c9dcfba34ddcc60a46fd3839ce0a95e1c630cadbfd574e4cb47f9384743f86a96fba4a798c34b9c5b669cd56d609218864093ac06e2f2a1400c0c0c32a97947f460882a75e863c6c6c0a22971010ff023b90a655a770f24c9f170952a613a7a5a0a04549543629cc687f8a75467bc80803075b757a76c32bbc454a293a9cd393f2ef394cd0f94203cd639182d0645069b418c9c09373099631d4eb4624885dfc4077d68076078cadd48652b8248a8c27ab2ee90f446f5bd1af6619ea70edf5d0289ab39e29091cf25f48815a8b5dec2e71b2a9c9ee3ef136fbcdcfbdbba602cb8146597ed940a904f5a0d7e485bb374754d7ce8515e949a15304d09d6c3038f35839f6271839985fc462bfe776478307b5525a4c45e4dbf3a049b81380bc14e9c0a019d6ac2c91b33f2f9a5294d35f991da19efd9546fb3aaf5a0c289e4ce711ceb96ebc3d131b4afb9b7a2beb8ec13f1c96845f5730db75f02a0119c222cadb4ea41557e1210d1fd4d16100054717bdecb0f1fda006c6c90a17554633b4316929a20e079d344749b0a26095eeb84a293d3441228436a13ca655b470601065caa44977e926adb270da146bdc92407364de277070b070f5f50f11ca4e12ff479ab540b3650a95579951335b9fb3c409d44eb409a2d6c659a38d1054190c52e0cb10feac4f9fbe41c332844f3d61ec5a662acc051036988763d71b9e223fbc5a1df1319182b8935299cf53e97be074194091331340bd90f7b98a957b213d731d0649a09c11150d882870706bf0c447c167c71360745755990c7339a7960d735d6369a6db0de157df5fe2019bb43e076cc5285caf2b02810408d205692701304674d0eebce835302e0563d5bd5f9978e9e794b585f708aef2bdf8dd2f3f8d911246f4249c9a14b24e0355551a60e04d74c91589dba72de2b4aa9ad0d409411617db9985e1f504569885fd7307948ef7f51fa4689ea3bbfc8d6d7cdf1ce0e08aceb5a28bc69fc5cffb85f8451ebecd379b113ff6866c6ef77c3b97c2bda6652d07904095b0baa1453b7f41383b07b92c22b7ba194d5efcc3097ba47f388cb51c58feff61a485f6a83ca99d3a4d3ed854fb01ec84a5bda15b5bd7059ec0252a8a5c1a4499df7785ab296ea2e2caaa0259055cdbfdffb607e0b9fc9ffe6033cac71410734b659dccb0966536dab7554d131b19a0d4656020266f554c8571d305b5187beac947f16af4cbbb44348b82cf433d4f3b41795e400119ff5172c27fa5e6552e6dd30a54bbeca7c93f47f0527d219896df772f1cc0026e20b5ecff3b2de49700da8a0bb4224ab9058c1585bb219632e55bbe099ac05389c7b855a84203ad7261c60b82aa8169e1d6ac15cb25ab4268e88b96159c9f53afe2f47d56a16dfd4f7b9a1121d8306ad7dcfe25fe0371856dd225c5f8b58bd9d38913b688b79ff3f120401cefe66d21004dfc98fcb197e84e69c2b38a82d6a4ed051b767e701d31ebb7b66ae5cdbf7836b6e7fbd10650a4881776402244a4395693170820e20228d5f9775fdd3873ce7fc002b7ab838c8f3f364b9c2021df114017691fe895bd336d4b7d68b37bb00371224ba206f2d4924b0900a3d65cd049eef9a9dc45aaa938e0f64a102d535f9c6a2c29299e86920d2ee9bb454cf084115589aa20b7e580fb288de84b60550dd3939da5695c9f393ddc54d3667661127a58729adf68381bb8a40c1fae7a194635e9be39c9e9df712d979c9c3d0eafc1c3b525092a16fab21b0eb75928123464e00851476264a89897a029d1077de3f39f6f9d8d1b4aa0246dc598fdd45c861d1ab4dabf22083bf8aab844c0232eb0020a1234160dc6a3ccb285ee8b8435dc8ae9e94176a94119226a111f17e9833d6b3bd697d398431fb0cde5022a2974f17f9b384ae6a5d02e22055c384251d8bb55d61e9e26a8f1aca5e092f0404b3c74a4f40027a48495637c1c113dcd0542c8716e5f8d06b2ef38372b9098aedc55d382f449613be2fabd10ec1c34c44b4050de1c265af460bd7319631dca0c0deeaf0cd8e1ee91deebe88ff09b60a437895d789d580a8399cb4cc8a081e3d836a1bde3735e8f1e417db1160b1dc2571dd264f5fe46e77b913e82a90fb80661364182520bcbff2bbf00f7f38799f6228a48a31f8bc6a431e07f40adc616a0e3d1975bb6deebaa560e5f44d89378588ead1f726bf403812a143d0d9a9ce58a8b22ed1be57cf56fb3fab11db9d16efccc0a6c24c01f57ff7e17bbc4df7d051c1f7567d2d3700f685326feeb3138ada41e3debe58d3344a134e3f5a6953ebb32a87942e33b689aa86cb86ddf1ae784b0c405c621fbe37420eaa8f9acbc7aa032c9c3c43097876a0d5dfb523e2422a25bd2cd5801f7eb192d77f589046605a549849cd8d7f2ca4b3be16860d317ebaed97c54b822d4394c37d6777f42f88bfca93d34a95eea4d06c4c966824d43a2ffc74ff4e4042089991f4744554c026e93237f8e82472b62ae7de720e4ffbf017b7155bfd38b1eef2b3126df50fd0aa30139d24bb66a7f98b9a13dae403b7bf86694c2fd037690cfdb803266fa267c7add2950a9bbef00ba0968c96b1fcad1ad7aad31675725107102740f8c0e4e7c20def20ee14042006cb6ab5e2773d5c0c41585712777a0cf460139b1cb3776fbef0d256f671fb3d9621fcb0bfd952da8c065e78ba8ef02b2effca13bfca47b7d59757eceb0cde99c2fa6b2fe25e0f1dac97788f4935951f24d7bdacb1c35e186517e2eb53636eabe19dd2e0c28ca786f545699e0e1f7bfef01c4d7fda121d2d2b3a2c0ab53ea8379f7d56a110cb3fc7422b38102051083860460acb290868b73ae5dd6d2bb0dd40972405a69d6e12ae914423931523727ed7b80a59d0151a97d6bb8c1606bc996d55f85e3c6d420f32bb5018719f2de79926d5ac1e6e5bad2151065e78b79f019294fb9ffc5d0e0a969ecc359013860b28d7c82d66fdfdbff3c596bad9d7925ae6c134ddc6523226c93a7c7c04c5b532d1453921b83875db21b50e1389529d9017a5677d80afd35474a6fb43e6f8574c96af92a62a02402786348bdc99bee2611bfa72605fd943e9a5d190d9816eba95ae39b3014165b468e24ee2cf7a64f10682093bf80724d7c89e9e032df096b29b314257643bee3fc16b1bdafedc3954b6470b9a9bf78920f086dba2dbb7ba0fdf36def4c6c661010fe69c0d4b3dea39697a2d235adfd7766c98c575cd35e4c3d2835443c568e92e2e41d17a7ba894a3d7cf3fa151dd1bd315e4009573b3dc88df60d12f0dd242bc5f808c0ed98b61ab090ffb4756b1fd5e4050b2a8f4ff544dc58c482630d06d41d37dfca3ea623ae7648fc9bf12b2fc515a7dd6d6c1cdfb89e4dff567d1dde92117310897479b460754c6e10c8d6cb4263ab7525e8d61faf2bbae03f7de742e76ed07e66678c242ada085e8321c6c6e39e6f5be2c37a54f449afd47cfe61ba131061e558c35c1981cd69b602663a835a8a50ab7c96b72e8fcd1888aced770bb9b7806b5e1cb64a4ee778d9f9d406aeb9f857b5b6365637b89c44e4bf51d4621947819f9cc8918f2bb821522b4ee239fd7b6ca76050305b28499415a997ae550e840672a4c423613e21c028606da059c75e23c95ebd8d0bde0926b4ab5e8bed57634401bdc9823f480017a54580876f7fb6ed58dd24dd7ca7494c01a9143de1ad6e644674905869cd7488bc28f7f7025feb2a42c02a331c9bdad0f25c7902a0225324b996c47138f7e2a4bc9ef8785202e6d1d0062afa9384ddda16295baaa6c841dbc18bdac15292c709db5bc566d7edfb671d9af6dcdb6dee1907377e4511c5e20c352a19999d04128065c45c2eaac909afa12a1f67b462e363fe1b804cff5d00076ccc3fffd33fc5f340819176c5759e748c717f64fc52638f10681ec285fed13d0ddb23d9b069814d2ff713660e588e6d5ea8b41014d179e81c1475dca0021482d1fc5afd9befeb007a9604be53b284b7ea157ae9d428be2a78c5d9945a047be4b92b8826ac9807f284684bda545209055cbd1fe0f88545771bbacd69e6bcc4d6b813575b15174bad32d676c80d34713204fe06b0066004a414345f88bb6f1d1dac3d9e7028d060ce7765be9388a966a9a1b996b93bb8081ab629553852e1e14ff611ae4e9b4c25b02e9c6b2ad823160f5f73c257330e7cc10b34ce3dc53c5d355632e55bdd1e81959f726cb225b61736555e059cd6b192164e9403ae277410ee5954b38e825bec398ca2cad6a9553b0237e1f25cc03ff7143db1ac64a320fdc9ab4323cc93a49fd2f79c23fc5b9e1dcf98255fd18728812b8428551baae5b96b322c8555db40361c2187b3354efe35996fb45e29ec91f6522c81b5ca4a637fd717e88aa66325c51fa33fcd7216045a1e3a997233d60e0eeae555642731d95e203fbcd22b7644cc49252b8d4f8a588c13cd8a2d1889196ab96b97eb2c9376540fa936a965d9c591a3d87309e59507fdc0607759a94be9cd2dcea2792fd18f9a73b737e93eeaeda4322277ed8b07253b228b7ec857c93c1d0e8fd002fa51a978a6d80b3fbc44da125322da9cc10af5ac4cf01c00a77d476b94498c163bb4b9c37141bf6f15e4839d106f3fee4c204b234d787d53cb6e264f428a41bd21bf228388e6a9061ca3df217754848e2889cf03c4641af379b0680a312a56f8c1c2cde43cc22f7cd0ca0f1a21b01ca73d525ec6b64d806b919f96d7b9244f89de500532f6f19c2b4268cf06355acd210e516bee43007394f4a7d50b8fda591fa2837086fee765079b4889cc9525847dcf8ae10790e7bf76f2794ea8bd0e7349b47e69afd02a46c940d0e686020480526d828225f17a4ab4810cfb8259978968d0fc77450f025ef864ba8910fcbd6e57010009e384c44e6a008429bfe55808a7eb1d533aba44e829681e97ebb3677ce42d81183b3e1e8f449c2da1a3d63ad74856f42786bf29f3194b92a2ab56f99f830abd788de15927e630821d2bfdf950a219252cffdd4d63878313779fc8c5bda8d36b0257ac7a73ec4d07650163bd61a0fd6b560a2af5f40cf9b831612c348ac23487d03609cd02f068a573910308734a811b8347e899959a81b56b83f9ded77b24a657d526832b252911718fc4dffebbbd7ed9db85a9a6b555220ce35470a3ebf5d34bfa999a3ff7317913e7c5d7734d931b28b358a9eae679bc084f5142426da75cf5c0cbd7f3a46362b8b8ea385cd10b2b915de2f5b382c3849355a17ec4d2b5c5af38565502d8802aa886279915bc19211cc51f3e67140c61ca97bbb27041b49355ca7c0c13d50ef232f666578d1430a467f81d5d33f83c8af845889d9547184e88ec575dd572377052c4b666ceda4ad82f69526b99020bf9c73be24c8fc7f31dbf6c309c3a840367c0336688ec9cd802baa88cf2261c6e373a4657a4160987b31cd22c547a235fab325f6737f24efad1c3b514c61806f30951f3e3fead506908574aaa0da4b3721093a66d87c1e539c52fa4f764c0d7db40d71b77e0b28b6bac488ccd936c82088a83865c71b975bd66d89073243334d1731079fe6c9cc26dac8928dfc5034b8ece5424f39670c0617a253555ea61739ad238e0765ac0f0e8a487f5aea5470afcad91b89f02770b195e4115d86583805ae119fa50326dbbe0c29698d948fef38b648f6016d6180ce22d85234a7f388d7a7291031a3fbf7183801d52fa1867064bd84763344ca01771bc99ec1492f774be359da2a51de95cb2821aeba8edd342c06beca3cf5c00b448f95deea16928ae7fcec9d4c6de7dd6978cb0a96a98e27492160d49c68364ee2a8c23269ac81c835ae7a4b3928ee50131331a501c1e6bd21ec8bf67dc02652909023f4e89a12db466050150b569e7609d20129d7322d8d03f6ab1f5e4e78f1049f20ad9f83d789c2ef75ba4801cdb13811ecf3d7c958da6c43f01b302f2fee4f524341cd48ca4c229f9a64019397fc4b7c96ea636af3385b985aa760444e1f881ef0fd4671edd538c42fa4e598fdd30277943677798c66680664619e8a7409d5f6a9a5ea30b20eee7011437063a3a6873cc22dfa8c5c50c8139f0147d805192cc83ccabd9b11f31fef820c270eeee9d14cbbe35aa9ef55887767f4ab0b7c82e3307917cb594f05149979ed289acedd2b01e9aa10ddb503c1cb5465562418099402763c6de2798c5828a523826b13713e014471ae9245c72fe40deec0a0e9c031afc947363de3d872962423c42a915923539dda4cc99370b1af066752a24379df8444c1eab5842c5687b93ab1bb687f8f31b2b7ad24cf9ae0959915036c8b3a25446c575ba384714c1481cefca4e1f79d1fe274bbeb46bbc904529acc19182de472ae5263d0fa20589ac4777b775a4a8bd37b03b2dfdfbd4cc87eb03921fdcabaf54c53c4a672e6b63cf4d711b1b8173a348b4aa118c53749bfd8a207752bfe353b7c1d59434c39f08ef473235c1d50389965b757237558bc823e910903f758fdbc6fed08d5d3f1788cbd272dbb172715c0a332c0a3b0ff1b6094e1d15462bf0293709f7fdc5cf21e16980027d285e63a1de9842a07c0811d19248920ffc6a66a0675513686526b645972d71235e1611d92355d00911e28450a5e2e519cd7e0960b5236cbc65615b8367409b26565714ab4bcda6c9230e1b7d93f37812d2160f114aea3038fc3bb37a6b2f21d1da5a5ffe11531c414ab1aae17c07f2395f4eaa12726e4d24090b5ea8d5bfc17cd7921d29071f8e86770c13b9d21aac447505c9102a675f9736dc9104268776589919aac2ad2c516ec2bbc029107940eb6cde51c654d0fa21d7d41052c182e966b46cb2fe00988d03e8b0043f2a22d2edd0c894300e8a5638c85dddd90db317187a2c21424f3c796a1d6ae0f6b9ef5576af0ed5663c5f32cb8784403dc5e042948ef870dfe81470b0d9eebd2ba6e1e67f2740fbd7cdff84eda151d09110f1821026b6d09727e8ee9e5e87e645a0dee9f056fd0082372ae83c914f57fef820067935150ec28802f43513880ff008f589d015e0fcad58227996a99347b4aca2cd0b8bba4bf7dcf1a8ec394ae99fbc661477de6c7a9fb133e3d7899454d3d7877a6d6086e6b5b1f3c7ebdc0cefa0b9215dfe1af6056171d1075b04147912c77be618efce9404696fa6181e0181f88582e0e979986591fcde6723e1fbc20d213a18e6286e429213220c1a64fa451f545510b31f5b38c5bc4d4f8bf578b95ef1e7db2e5d8a1aa780a2db988a3550e8dd0fee2b51121469a4006badf8a7ec465bb564e8a05de8d656903dea4d321b61277f288d846af9efcf6c3c76140e4591eefc934aa1b48a89e69e959440eb32e88bedc03293cc92e9dd5f4af491c5367a7bd57c56b26faf81b51a999ecb5d76eda16dbdaf2506101687191af888bd91291052e5aa7dd328c1d41e6a3d463079455a916598e09de0f71b89991de0a80d130e7e034738f9537a49f7c5074cebd5e74237082675cf68a6c5a0876d234dcab4202a8e108b82aa28e3ae14fd0a3f880b9643cbb07ae504da7acc246aa54ea442137b9a8a58107fe688c94f12509816093873d697879abaeadf88803873d044bc7d22f0dca59c460a9e70fda88a9482a1c9dd4a8212e1e2940d19217ee65b5f7f99d71147675eb8e9a12bb14dc4d4483b9c87f3e2f47b4c989e89f00b6688de97911204b05d86cd211f09230ae7f0447ad737381fa44d6bec3329a1f95c9bc713e610456d2a7e9ace49a2a1ea35efc1f7ae1314eb5f12ad1d6449994da29f07bf95d86c907f0a71e0aa0633b2218a856c459c478379aabcf900e257a5e00c26b80a320669223863a7a9882a2cb7f555edcb9436159e905c7b95c3311f695bdec1c14c6571e0619d4ccb2a91a1bb436fcd60369b1a15b5b46f7b18930e67feb904e8d393a6fef2fc555dbcb205ae39c07b655191730e939f9d540cab8e1cee72654c1de093d8c7a3da5e69de6fbd7598d9ae70b8924b5b04fa01f24c0bf2cb70fd35191017d41fe907e5446e817dc6dd91e05bcabb0b4c0197272fa0afb120b5b1e139a456c19615fed77ec91e36a8dccd014f581c2068c18d22705471fffb7859c8d29095afd40895d6c0ea1605532db814533d0a9c4d1cce454babf0fae4152b4d50d1c351673bce95c8bacd9c411d597fc21699f3386ef94e8d949cf2dbb69a54d06ac89859fc2019e59ff264b4e17f6ca410a10345b25f4099261295aa1ea5099aed44a485b9587c20466ea4037f6e56db3e4983320cc6a4ffaf3fc13fdcfcc55712645cef0834c77524d2164287c9916b23f1438ae6a93d11887c0cd64d5a675355693fbaec490b79555f10922c786264b030daaa0ff0a7f6f396f38ab34de6944846016d6f3a4f6d05dc1a216e36a3785e2680d50f50b108765d461004ec113284578c4144904db45e999c9b1a13f3ced2fb4d412fc1284f40d4e7be206a6764c31f94dd106922cfa07a421babe2f514b0a96195b2f0e44b62028ef114498aa74ec08d62bc01ae226e896922c0986e82e04bc821a6a39e7cfdc1c0cafeed2896e0058f7204dd4e9f52daac0b39258c6f284f551d4da346f402f74f2133e79d2f3a0f8131bb9f80825cce900070d73bbf15a9225e1ad8b4ca82480be9b1993100d34ed8576d193be3fcfc520032f3f9005a9cb63a12776970d50ffa48fa8ffbba0e39d3b2e5052875fdcf9c0b33d6664003e12f9a804cd59185057591ee91a428bb74445328569cd73e0ee516092d6a2e6a92d6892b5ef496ac76b2b9aaa66c37bf2b0522bbd6e5fdc7c014d0afc85df48743469e5d38b1583100237f5d2efce1c26b027c68f98a9930b66306f5febb17b7dacc1b254c977df2913aaf54b268d94b6c264aa69b45b5d0cbc9eab0853f7c45115206a317b430fcf52b7f0117b2f82a1fc24d308dd34b3e1602f2f18a81b5d44d87de42570904bb2e53c3db6e925c2872fe3c6f7db0f7146e60085508e7dbb10ed74bfcf340933bfe203513942f8e3f1a0c8db0171c7f5a2e2129e94fb55695457fb1e401f2b45ae25ef267102ffeb868fe81d49c145e6d572f87f1cef894a60fff6a1f10e448c6f3b29eb2a9e93c1acee48ff4c0a334830d038605ea4df5c6208150dd72e9109076d1b4b43060a2f471a361901395b926a1a00c61a18f544a6a621d33fbc1b692b3d2a4185793bc7b2ffea5f4a7423d377662eb7b58633486dcbdc0546c5e1d2f664391de59cb413ffae420069c4fdfa73ad924a6d22a20991532c1bc2c4a33a362422cb0a59cf6ab4e3eb1c575eafe689229c47645697e9a8f1b967fec08baf1b9a676c368b6c2c0f7b21d2e21e33c20b69ae1c52a1226216cec41138c513296a366caea6931278aa4b3fab2812fcafc0185b6cfe0fb7d671ff5eeb182801bc7acca5d0420bb182b0a21670d42d2e4de16cfe94dbc071d67b365d8792c85532becf6c773b72929c5dd6bb9168e6fb91b050d10a7393c8a35dd6bd98fc3793673c3e520ee98e29d038f3537cfe696694cdd1baf92f31ca7fac40aec3ec3cc77860a1e21f603b154c19afec0ccf20c61dc0dadc4ebacf16c2842b08b3806bd2fd409269d0c11f5f6bbf169648a94907f960125ee1038cb308e9561ba9756207e4edca298c0cbf9d5900d1c06c3184c79106c115b0c07411821d82b1fef15532138face1e70461aefbdfa07b448c3f655d48a1c0032399ba6cce3e41413861a96b1d9083a1857d6c973dda4d2e1af9037c0e1c6d70550bc0c030f30caa5f1710101969a26634761ecc59e447129965f92b7351d56a1ca957d43357423500d8a2d1fb4889af8de60fd294368101fae2d073049f141c607180fa13d428a4156a230632025264f800680da6237aa20b2c9c01d4b56ce335cb20a3bc822d3ecd0e8bd1aeb0028ad6defa59d9ade4bb35ecc05c6f790d67b79cd5f19a28ee4a91daeb820bbaa2fdfdf2d1f4c5bfe9da1bfadd864098abf095326d039b0894686c920e8f9043682d3cb2bce4546dbe15d4691686044cf56b597726ff4ecaca4ba6314118c43628646489c28ef835aaf468cff8abd0b901860fe1985f389a00fb497411d222d378c77bbc078b4791aa0713050fab7d3ccd81342ca6a0df247c17d58d23cf28ae8159304eb869371aa050d812e6960736048b83ec07a914d7718cf278b7c068a66d0129e263059f4b328966f539a81dd0135c423ae7cdf0b7ba1d938bf28a30633a19cf9cf78e746a1dd9cef4cb1ca9a1d3727c4c8e10376aadde6b89b1691cfbf08df1836a4bdd3ffdb0f37f9e43e784005261118722c58bc27da509db205871d2ce4ebcfc85b4e65c09b0c0fe85a4b6a44efc042b479b4b35e36154bc2260063b20683bb1650eeddbccc16ad504e60bad5d9c92d4edc8fa0fd8ec97d88872ff05a095c98e3c1b09e56032bc70ce03c9d43d4029721aebb8ebe7e3709a444df7354e245250ac15947eaa6e4a37497a3557c28d17b8055649fa0e697ce362d7c25b77c555823cfd752d1fc7d7e00b28be2b8180db588934981cb2b38a24868c935f20cc9a756c99812741386c128e90838ad450a4fad607fd8f20247f948d27d7a4aeb1a14a846d5a71c58c3de65bb1c742028a11a625266681239435e5343912ab0fb0bc681004a2dff0484b5e12fafab0ba1f232e792d80cf547212ee73a26507baf6428ddaf965739c659509e6bb5033deaa5215619d859abedb1e5f950f3f175f459f89966e4932af25991428984d65a019cd5804e6a5fc78eee5d1b3642c02720f5e63f5cbf862199470cbd337b184745f0952b897ffa2a13aebca9670346101aef83208a9dc78507e1e384f819942c2b52d9b666fffa0cd9fe3932d9321a8212d13a477d15a8f626506f389581dec688f253241152e3419b6addc8b71e740b88d16efd5110d49c6d3e10137fd29ac09fcc9c4bcfaad4d21d3eb79b627783e0974b60c6b596155c23dd0ec5f8f460b3aca2cd0a06f1a55d35c8de17283109662a95519ccaa8025dafe66cc8c7f49b8fee8cecf0b391ef5e3479cd78475864e20b4404a4391fad20964ff3d1ea8d71ba54088eae9a8d8fd039af57b822babb57aada52d1082742cdb4737631bb1ca82a9b94a7a178000fc69993bc5986c261cc8e807e959c0c7eab83aa40fdfc5fbefae0d88e548c3ef2865c85da3c66215fae3505b7d2687d41d8508b6abc427405a6c07048b35b5a9ca84b6b6e517eb69b0771afef1799869228bf5cdb9282baf4a360578696c1ee346fc6c0c2ae81670813370ae7799e8c9d49ace6f16324048c744cba0b7c9c464f41a79c6b4a24b7dcbadd149890faa1c5873a04f41a808835851d4793eebdb063089a6bf4e199f513d09c9df6c0794f6b1b21e766d3901b3d20e479bbad39813560f46d04d02815d4878f223558df0d5424bb24c3d53a0734d9422c43b9b8376d8a034c2d4fadfd40923106d2a5c36830ddd8a0ab1fef23b2395661a819c6e7516de828b65a4fa35c44dc4effdcf2ce1b9a0a51ffc0fba4fac38df64340249f3f1143c043434adda06b2d28b7ab7caae591e1627e15dd1146ad823443e26dba753aca6a337db493a2ed4213897844af4c32fcbe43eecab0e48c30f8112ef40e3fc6ce02f4449b19e00965425901b3f37b7fb86379e5696adebdeb281500cbd6d9a2b829199fd1a122811e2debc7db72e6f1daa78436d3f835b8354abc6b61b071ac8855c931e81e294a776000c92ab2b33d444f2e4eb73cc4b3a66529b6dec7330e3b21595c731effee27f52554321aeb655c425955a73bcc8d8bfa1d07fd4c8fefef4282ddd0a31143fd3e585fc2e236413cd0d1348c1990b3434e7701b5cc7dcab996641a61e7792e1aa06d20d7bbdc80f56c5793375425e3883886a3b03adccc30a9ec3e790d39c51b546852df7e574bf6246743061920a58cdd236cd66e9692c1b6b7a75365f9941a595c82e7caf1723f4230ea5a9fc68fb2a3b363b3959dca91e5b6ccbe7acc5a903d59fa8315992ef1950e8a30eef7e6f2a25c686dc4da30cf594d6e0df93a7268cc6aa0c8b72891def188323ca19c49ccd4127012ec13f4b7609bfd058481f10a80783b8adf0672a70e88270ce4ac15fe4f5ef1ca21c3620a21ac3c6b182094f3a0cf0589cfceea8d7a0bd686efb16d447f02f0b0d03f410d80b8c804223a2ca8898e71be342ecb150bd192f94291cea2094b8ea94aae3d09df2caa02c1bbe0f45133a92f56d0c76fd3e4121f208a96f224d7881c8fb35f3eb1cad5c7d3c7090a36b67bba9e1e45ff6a8a931408ea5c3c3e03bb10e2c7edae30736d32f0e61c9a707c448cb001e3cf7348cf365d75ea11143d7ae2383b743bd5cd928a15eacc77ec1d38865d9911e051361185b29634648ecfddef365a17b887417fb8df6ef08dba119558bacfecdfa187f156a15b305cc0ed57a35f539d2a4217b72caa071a6b7b86b9da44a7cbe63ccc5f1689ac242e808a33647a1b88835c888388a75522b9b19c935285ebcab2fc51662044c7b35bed43232ae81d24c0422c345ed2138d28d13d15526ebdc72d40185d7ffead962123319cd2fb8f8178bf2dbe8f5bd57c538fb75ae559aa1b3c3d00c33cfa2a1380f5aab76aee0d3fd7b22a68ac99c036680f9eeb49f4cc55b73b266f15233f309a4b3da474e8e6f3c2b69953e805efd1c00614d1715a6483e29cdceb49ace23348fd9d069b9eb8624d28fa35ad36ab66acce3485acadbf561d6587957f9ec67351444bbf631855471168d16ac50a7505d418f0bba74257c933a5e2c596705698eb2db321ef0bc72936802a16dbe828651d30887f41fba99081b1e008b9a4d7445762174128f6a249688b3a6a740097eb085101b1ea9201aee4782fb4cf19c0a19442715208508000c57662846233ef21002788e51b32c42c14db9c9ed8fbca513fdb7e0c3db195255f707a3a5712ce280a6cb34847f4c46e6bcae017104e8c9295e7fafe514fec82e0525d71c6fbcb8d4958b050ec1d2703558708a3bc8100cd541678ed5d7b3198bcfd848b084d17f02176771a31586aee3e71e233d64b683d3d309998587bb18b222cd5c2264a5f64b2776fc94e8050c383d67fc88139b71f8411fae930848e4d4c36137f765e184179016801ba4200d025d4c774fbd0f1b471615096158d2489a35f1b3d2088811a2778da9c1d254cbad9ed1aa7df2f2307f093e245995f134c24f87fd77ba7f753951ba29d7ad619e928a9ad88cc735eb45c9eeaf847a154dd20522497e5222cf0a401f1a05ae76bc67ab8c1ce4ae9b8ddbf1d3dac7eb640f614591d651ce3bb728c8950b47b881e724fb7f87366dad872e27621955d54b187132cc126a4b2bd9f3e80240bf2b1ab61dccc6810010c4cf806e454c1c97b49e69148968c39949930f9d7b75b97f082afb5d10ef661b635f86c3a1fa9feac439b43812f587714aa837320eb17368fab1f2bc1279d324e1f1035fa5d27dc7797b70f0ca99af853b92507d2eec74a93755b4260bd54057e56eeae2dd85777058010c8b5551828dbd4d91da9809f7a27a7a913097dc0799e7f21d07c440cb5e8f9bae2a3bf69faae66d33a91a33e976bf09d99a5f49cced374fc23d66177f8f2d2fbfa26f6e6cd5d72a2cd236f7d1f8220779635e0c5e430cc3f3d23b9bb6598045c1b80b4906780ea9ea0f7e5c02f76757972b01bf50cbc12b1e3eafd7abc0c8290e6b7a40a495ecb49f84a1357f6f5890ca3fc076f4d78eea8b9c72c4dff2ca3ad1940efac082d66d896bfd83697d000f9fc3112aa2cfc1f7bb4ba1526a52e9a494cd1f3bb39f6a93110a802a939f35249ebef67ad0bdeadb065b5f78d7b7f2572b01d7a96a2d456360f629d07978cef05e927e415319287aa278f584afb4f4a93df21b4289e6ae63ee87c364fdbd16cc7f15d0ccb90260f1177825b18046c6f6e07c0023751022879e58eac4d63937c0f61729c69163d46817907b1389dc50adef6a116b0780780b0b2c4ae304f41b5d57b44aa2cbbeae29ee4203369bae657ce5b309d097fd99ea707ae1244463684050d1da578a9f18a0f81260c738026b97de8040d229466e74b6d36d0ec350b3bcd9356406433f775e6179301e0c09bb375dcfd0c82114186c2a25b8006428421240295b829c403804db08a39b63e07e075d160405310e2e334a7ff40ca532a0f9402842c8478ee891b6e00355c962662bb74e610514f49bf1a81922a008552603d56fdbb84113c4cdfd5af59e4d3b1f03043146e352ebe9a337b8d220d54244cef0fe16e0c44041547547c45068055906b0e7f93704b0d11aeeb34e841fb9a2be65f14ca7a041c9e5646cacea8f57b646b3284f9dd7decef05f13ca3165d581105cf00aae58388b47c385845aab128decb90920ba0a9261cf36f843a14a188954e9badab7a44482c8fcef2253cb0ebb12b5fb27ddc8ab4f67510096a2ff9f9e39a3daa92f10b82f125e7b4260aafc7e9b70c253d8e7f9c913fa3773dda362f358bdb27efb6646be3b98ce499e868f77e37a8e3d73c92828743ae50ed8418e603777f29581e54e1d94d77fea7166891d15962ac6541f2edc022e065bc48912ffe0af884bdd58a8a728edf9320cacae852f3e09813d17eab3ddcbe52074132497c7b53e97f7bedcd6be29553a1e0e100a2ea20526fbd73c77885889415b1ed87b600705a732785d32716a37475a0f44dbebc62a2ccccadf55045aad6ce89d4c39f1a82549dcf18ef3d4d746bfaf4df5e68a5d1ba191e295efadbeed2bf3ae40c1b95d52d75f1c020db847dd5913b72c27a39cf9a5270158b9e8505d827d89eadb697c0a6c30655b7ccf717affc7f28771a33fee3bdd7827f810cd33defcfd34398d8f1b617074c5fe28653541d23c71351a0c34957fd2db64b7c4f61a7d849e729d8ba1af9e3a4bab4832b066c2709bea0dfd787aa9c8ca31ccf4b12b2f12d037575cf998805428f7e4f87d76355c0ebf0dab85ae103f6bb3cb72e5542a94ddc308d8be13abaf0a94d999d231de1f7ebf9e0c00c82e252223456b1efb7df10e5790c0a55f6eacb9ae74b6c75c82ba0b11f4064a2d846ff9c12447908a27ca2151693568b85a160b95a1f18806a2456bc2cc5c8956059f1cb099618c2a993c8c03299ca5ff9ccb223661356a1322608c1328db7d163fd18160fb0da171220072513e70587d4fc43b4fc010700e0568bec4802ae885c483dbe50ec7d765139d8c708e91d97455ddc59025baa5c5d100ed5bba9ea645208b8296633b2e9ffa0630a5bdf4203ee17d4a8ec2fde6aa2446514a91842e164fd64ade9cab81e122b6714d100619039cb0869fef9eef8b40b2425d22272bd67d1b66e8f2aee30a7520c24c31a42a6a1eb0628c3b9f8ca67819251e336f75dbddfa9cc763c5d21ed8f2c46cd57955a432aa15b3003c246b8d18d54592c91f107b936970542183e8b5150df7c7dedee1a92185d09f9c943cd34698b5f5a1a3c8be6e3d5df58eca1184cf96922403aca94f6a279d5a6e433a494747e5ef35ea2e9d84b5c4ead8441f99f2313ce13c360c78896965f9b211c8e118f3dd88c8ac69b3281724564537aac335cfe516c1ce68d4de2a00bc63de83cf81e563e90f5a3ef4e386efbbb55c5330559ff90f137350557d2a9559ef1649dc247088e897080041df087c74ac3a07c3a21fd55bd338f32512a15fe8f4fe5a38cf2287a1556212ad86968e99b7ca86b245f5a3bb24a8159ba024df9e0b761947039cfe7e272fad25a8b70875287a5c82e9271bd6b65dd79281f8699bb3ef07a6073a727f79293bbd9b8486b44242fd79ffe8d6d27fc556dfc2e7f6dfb5b70d8296be2418f999a92f1f16420dcffe1d71dbb5f80296de96073e12744471c300c6b418e0eb550f775d9360d45f7866521e9d1527df0d32a483cadd490bf3a290055ae7c62acb1dc441a4df8a9d1059d53dad215e109053747702b67fb7a22570f05c8f09c35983dea1387b8b413b900e49799102497ad7a377399132060bb59b5e05630b55def81638e6fa7054e0e5c371d4142cd884ec3c0d47016de0d9454ec53b20029c49f1b594cd75a52c5b7d57ddf6c0321fc27872847e66409d39c541fc012e0dfbc4c1374d2b2fe3a9b577d9010e65523ffc17cf654f34b6d5272e2e4637bf89742f97d6bd4901e5058ae91bc44359a3d4e01f8bc22257d6968bc346fdaf590dbaf4747983cc191219bed9ffc8e155184c66fa14753c8328c2f40f575767289067a1837a144adac2a6d3049759a5dbe11f94a809d7479a40d69884a327d999c17756326e7f60d2914f1206835fe5aa21f71d5e3915f77a4e741e1e43e7e6b9d9cfc6eb5293909040d73ad9fe8f6ffb3b883b64342a2f393d9c7f0bf01bfb5a50c1708dab6a1738ac6d3867a1b8289621b18a4e9a550ea4c03e0c570a4c74d2bdca5391dac4e8422535ba777682999572216867496f0a188f393ad408988c854d371f491e6820e9cff1e885ea9662954cd695c740572d4d27447072ef6e6d5774b58c6224f8ece8870c6d8bc54438889812ae4bd07af3202f748238c80df6829f6da7fa0ed746a142db0580a5222335da9dbc567bd705f59f312fc29bfc9e89e0c926e07bf2bf6f18867c5b7ae73a6b4d4d16b88ace5594f6ae32339c392d975a43f9c57e09159a8ef08e7ba00d47f9d043f658dce271603e623906679f907cc490dc7e9fcc017aa6114d7efbb4f38f6c567c416b0eb16be01db0b81fd7d2a40a972680a6fc1a887db77a42ccb9bc36302818d1f46bb69e4842aed1f5f00cfb37716ddbf1fb4508f155fe4c7e962af1bbff4eb5cc9746eebfc8fc2e27dce54490438ea53df536a5451f0864f4d1682e13d8c27c77a240f2746613de3ac297cb7639b5e980c534e24a8de52ca60d4a2a02465c74b5a88bc4b23d2ace3be8b2db87d40101bedba9c21d8a49f35d47d226c6a0bbdecf3f18ca6ab50160344fa06dd3002b3e8bb7929e83da8a8e3f0160dcdd77cd3213d657c9ca42d894521c9685f773f36d1f1b1ddf6cd5bf8b1d13260667c6cba237c6c1f1ba274c3f497fedd849c0997bd78c170348783a3e8eaa6184881143a6132ac3ef75480c15ecd31b828e901de2d9e8f4503ad0cd22d5f9198f523eee7c8fc112c7b7b64dbfcf9429148ecb0d0bf7a7c8081397eb15a5c47e17d1133ccd0d8c24fe636d461d46b9017912153e5c13787dc310d797ee55867da71ec970729ef73149bc496d317179c18c781ef35a47190b8833246e4ce30005846ba21e6f9629a772f42d4a6b8ee7806d314b247a8b2bc38e411d6a5ced764c3343b289eb51fd98f2314ce6b5991029b2045e024f2ca5bff24cbc1a59189277ec0c3f1e0e06325cc84b652ff206876fd73744e2267410c3ca2e2cb747d6a0806faf3fc5268a884c018471e64fee3d9945f1fd0026b922c88bf7574a20f60457e07027feb2947f7216d8b2652bf537be5a628f76b3ba723c2d563008c8db713488415eec5c0c968425aa6c31a8496aa4f2b5eb721f9f3e7e3d9c736bc8dfe7cf1bdd102ee62ecdd105f2000f273d8071e2d02fc00a3c01ecacdddaa833dc16e206e806c7435a03ab28df29163421bb1226e645ded38f1b973335b39672b1d02c4943963daaaf24a5a168d0504c96b75140ce9d5dbfea21b6c55b16ec9172afa9f228ac253e81f7f32cfdc7997598d00a30e0d804a46b343b83a9b78693f69695b47a54524656d0e0482dbad0f75abf237de11be9349ee626a92b324426de5eeed69c205b0764aa0961efc5b2169d351069c252f0e9b390b1334f9fd74bc64556a6ae4090826ba4d13d4ce7120495cfb2506d5a2ab74161eccc97aba68520268c955713440271342a098cf2b720b850aad483eb5610e3962bb35d8b04b55cc5152d2e9fbc346645fdcc9da8a03f439ba4b6adf3ddf41af6483ed363f0b31e7ce0503d80e53cd1514811aab81b19ed2c576082745a884784581747a71ac04bb7b54f11c5b5f6c67623a41f4bbc1517fb255fc6651c961685cfbecdc38c7317cc286701db71ce3acd28c3e63c359623a841973f94d7e7d85888d0ab69f09a1c344760199ccf82fef3e97d1d50f74240767696c0e883d31b634de78a9758c682e37cf1102573fa32a47797e1a8759745f6e168805b44da0e57e0f305cad801e450ea5ea484a5b87ce0021f2e67c8e4a387b0b19a6ad605a7704faf6dfd12d23ab071c4c6359860d3a34d5301dc0ed9bdb22ce71595874f74414f46d502da49797bdfdb731da300a37836515651b90a54607d032929f7cc4294284131eab49a7db55df83a4ca71734e23b1985e9ee85629ed602776aaa6a116586ca7ec90693a1cb4de4dc2b96115dc3117ab73cdfb04ff6ea45109edc5d4f3f861c54b09a79bc0fc1ec0de28559c2b7912628fadd493846eed593a412d30ed074b3c11646cb0f5045eb5b038cc96120cd8deaa33771c732b87fa7856e875abff240f3dbee77ecd24356cccc75c79c265441ad8e4017d90469e1730c8b880c2becf77db454220d6c8440966a1c57a0f9c4260153050a2eb43695cc5be92aa210996f3c020e31d17d258b1bd742cd08671962a0de48d0d0445c83882c725ad8bd56b37c45887b9805c25cf4aac93e44cefcf3c60d21c2cb9c935267250be9103ded41f9cc38845639f87583a06d2462fe603a9c2c81f835f1bfeb130456e8c72e87ce4eb943a464c5ecb5f9dc58655c574e65cc64f7284c7503e3c1871e65c48e5f2259543f42da9ec60562b7d1ddc3d573fb607d5309cb8eeeecbaeb6f26b3fd2737082d57567875027e6d16c7704a58704126ae7f20f4ae28e3637f05a5d1fa0032dccee27a1d920bbba3f31fe21783511455cd49b44281272bbe30d30e72b3dfd5f1a7756c6c0fc0315fad96d48353330e0e92abc77d26b16e6d255f51b566927720686b205fed2c80637c4a5178003d2f8e5dc53ec938d052bedb09dd60d7b051aa0d6810c884921c45788d94cc5bad9f4b6d8a45fe09678053903e267db3b0a73d90bc4514875a9469d621d14488db6448b3af704221198d8103984d855882129d5e02508697c97aae477055ab6796e7dfaaab4b05857dae58c4af80020e47a7b95df6ee7e2635cb764dd90ab1d6610e789db977ecf81bfec1cfcef0d7ca8da6c246634b30702e036b3961bed41bce25026a18e98c00b0e83925c02111d32cd5d2cebcb9f5daa58266ff651382e1f469046e99f4b8a2549820cf51917bdc09dbafa4a6640f6b220eff10ca240a25f0813721b5518a14800b5786bd33cf8e994b9284497453a90df4b4612bd455309418f7035cb3702b0e7d4b06c8a35bdbf8881c609c05da32dc7706a2286d76a64b26a514267beb8ce5b0daea7763b0f28711c8e103b7417148c711212f9d5ed4206898b3da36bfc985ea20c6b223eaca4c221a44aadf5e5bd0cc4de21faad94b26e953db8327276211d4cf806879c1dec0a6bc23e9ce05011c632acdc40276e3abb92b4bc8d86a37933f40889310cc528e87a599c69af31ceb54550c6ec8da65353b77738f570d49ced7a18d1eb61013e5fcb890cc3d8df2d8c02742985075635e30181c571570c4f076e571bc56a3adc95f894b03ae2a6007f31320d712fad3be69de100a5bdb9268143877b0865429f1325b95c94ab15b0b36db40afd0f67d1d977d0f4bd886b9d063dc1b1c984898e4a5f7d5d4428326aa143c63189d93ad1c1ebaf99fb4ee754ee8ca6bc61012f9d6a35f6070d471a027e2b2368648d60120d8fe09fd0413ca168b8574159c777b1ae16c3b4e98c04c390198d5f19a4a0393cefa6a3916d1000a4cd821ee9166b7909f26e285bc5fe1c031e1b919c3f9cfa39d2fa3b8b53565cd3a331b04417c6920c0ab123c90752b5686215e0208a3f6b8127882c612c05e7fe74582ceeb150f6072559a34e8a5fd0b5b118ce7893fd5a2c4ac4826f61e71c763f44720e6829cdbea97190b1b934a24497d7eb37cff6338c2c8db35b1f65a720674f0fdd738c1ed2468d32640530f4dae13a64fc6291f03da9f9033c64a462371f005dd75ac5f5e1123f2f6b009ae00fc33279b0557d8412ff90888e9fc50b09847d600dd2a91b0322792716d7977945857886ca1f76e284b6e78418f1d5ee02b465dfa6d7b7ebd8bd75e430fb56cb81bb9872f3147ce8c25dbbfc0582cc5d75b07f87f92dc6b70695dce6528a03ade3c25958ccd60aecb6b312675bcbe340bf7e109e9ca41e91c00437117c76b92d3b90f9af24b63d66e79e44d0ce2bd4157ff62248c4c26cf84260583e9d355dfcfbe5289244fc64355fce3132ea38e46ad382edfd7a0f13a2e11dda736dca61443f4096bd0ed6990783b782524798da98be52fd6f38a1bdc1c3b0c50d0068ec68dc572478715eefc8a5f802b99401b74d95d74ae19d785ce6daa39559e091568abf41a194c19de035c7335561facc1d0a40938aa84efee824371a5485409da1a9fc9e4be995335ef3fba52ba4cafd1faa505714f5a5c88eae509392ad04b039fdc4d3ec932cdbdc578dfad831b2083b69c595cd26b7aeb12c02a1c660b8605865b99728e6a858e8d5a4ecfdcdbce122862fdd7c411ddcd79ee9dc889ebd3f428bb0a29928e7040aeaae8650ed8f5b6003d6f797eef2167d42e10809184ccf0994a5b2e5886867655e72114f779262cd753c04b482a03b7a76f06c93fa78f3b0472287283efc453b8bf637a142b31c64b292868ffa4b7eca40101b41a7dd6a4bdbf520e8652180634b5c0ed22c0b826ba3311407f6b39332c8210893eddf6b199b8e6c90c3f0e8db2c164a7dca81eaa6c74891a0cfee165e4ead22b3fa89684572af4c739fcc16dff4d18b73ae507dad2429e12d5434e9cabd7f81dd2d48772a2cec0d420eb9cf198105e64d6cc90f42dd581d162273048d2a3ab2ceb8fd937dae37d2cb8c0adefb4780639eaff76a6b85904b09f9a311aaf257e63ef08585ef820ef7c7fe8acab272efd5f4b64a192b5d393054a351f3b0eefb894924a7523f9aa633c959a1f6a7f0d6abc9d487f9b8bdd3886f3377e418c6af6bb1a7de009bf77ea14d280a4c390e01a1d58ec1b9dfc049befe0a1c257af52a7a05cac6bb782323800be0e5cd670d13a07b2fb7c601451ded018072f85c6fc75f77270fd6c6819430025c3e2fa696b072b410027f090823821feb4f168e2e85976851e7e1ffb6aab156b429eefd8c788f8f078fd517084c694cb93b8ec72678ea147f979deada2866d5e7c0c53477733e710c9deed068f8a60c658dc5873bd7d24cd70b8f601920c423dba0193d2e5ed8c69919ed9fef399e41716744cb157d940d07186da87bfc05fa8a2f1c02d5af3d68cd1161ef65449eecf3e38666d03eab9112664e897e64b966e053acac8c1336667d02c0e0e66abb7b9ad09ea7468e0031365cf5cd220d254ce3bdac45317adad99ada7482ae0eaaefc633fbe608e61ae6dfe6d6b57ec645eeed44bb8d9c4a73938fbf480fcdd73ec56ba0776463367ae5bf32542ad8d8da148954b6953715c60ae8662624ff1f9bfcb8c40015c7b12bacbac6044220c61b9edcd249e542612acbc41d72e5a0fcbc67ae797eac80a5d73efed6f9174b87728fc8af01d079068aea4d4708ed2d63bdf95bc198c50e15815da3d533ba9dea1e0c9e2b6e0946a77184fb43cc67627891d70da3d95dc1073022c78ca3622b7245b1974be3218bfd76483e08bd70760f09d6886bbb28aee0e7a82e94ee4755e08f6d19d905d787dc1dc23e28e1ea7bd40b1a02c70669c004018242fa206b19dd7d8a628920fd71b047ad8f3e3150a72c6bd1a6699615219170618e473d4613a32234df9e41338d619eb02b43543ae739f39a7365b8c302b7ce1a98473ee76eeb839f65cce15549c35e587497650555a0a028c80fd24585bd66a7c12de4f6171d872163dc1a5af64e1acf354ce4a0edbf7e94ce5b4a0e931e3917d0d537a2ed90c4b5adab896b566dab288e45c2bd910455d3e5658feb23724e4e0c0985e1235bcc8674ec8a753d2ab969d0991e0b8a60013b78a2715f14dca92626588510167eb71db6f4715ccf534207a6b4cd59b217787c5c4e3f67a884c171529fb37b14f698af9213453b2d5de507e818ecbc9abbfda3fbcfad0d089373e3f063686826dbb40b4c9b7669105ae5f7d63838904f5b8cad77f9733fd19fad268529e3776b65cd9a153ffff6e82e07fa87a2535630a7a4b905ddc521ed9b87b3186b972e4c2447921c6b44be0ac05eae854a82a1bb2e24f68f8ed01a93583dfb6edf6e4f31baf4f46ff8795cfc1e97cdd7cf52397225b31076bac2285eb9cce4664775e8feb1162493b5a463172fd40471e41d39756e2df836ea7130d47f0f1823e8092febcd82371247efc4dee8bb0c20021bd1b92fa4bc25d11fa55d0602d140e3b7975a74840e833780ce11cf40e735bc22b3e3ecf2342027b880911c5a80fc301ef16d965be0a15534baaf34a2076b7640ff45bb3f0d7d8851e0942cfb5dece82e080ac2ab0df7cdda8b727ed7de85d82ff87075efcee7aafc22c3ce185ed17ad8ece773953d9957851ac63ffc212e2ae42eb5a40cd760e27a9b82b7488094b0a641bdfa6bc5b0b2607c9eb3e9c46e2c10bb08e71a4487d45286654a0dbe90bdbf169f3a101da6bb097b047788d7ef41f8419db77351eb6bdf1343bf095bc260ff4d31d0e882fa87a62e3b74f1f73813d9087a72521949d64ba379f80d75b39413684d2b3306ad5c13649254228be124f16e92baa98d30730d57c70716caca574b9db532a8a596f77ce0f2c22bbbe8535b1e291754dc385025f1a7bc2abc791b120ae1ce438db5fdc3189e9fc0e754f9bd3dcdf45fad56cfe233e6008ef9433c8fb924c4623e04b3392005c62a120840204453809f7850b46f83144710c75417d18b742c54cea80c60d8323dfd16ce1905da0154842bb34270d9ced711a5a8e8b86bc2ad648aef6c8b264bd790f42a40927fa4ff45ce7f16e1f290f82aa5113ca9c0ed354db5c85ab5d13905806ee455208ab4bbe57b6398804428d3f30615ded9f6580c71066df57ca8d581cce0849e3d067816322c0be4ca44e5ca5ff1094bb4eb08468738cda036a68b49127380528c229347ae23620b0623ebf4b638919550fa5a4a42774840656841ef47ec3c3b5d3e193e70cb35e6fa2445409baff1fba4154df9363959870429054f612668da4bf67f5f0308aebb39fc3ea3aa6a4e1325a4f7ca6218039bb269fe15c9a133cfd12165db35ee5132ea9f72427cf136a3b55288d44a8371b56efbb01c782b528ae755f324ad713dca7913b81b12e74dd8f6f11dc35e66c22f71e802f422d7cc81dc64d1dbf957c10797e8e38f587d22e92ef848c87083db281518b529061232e97c14cb3bfd584eff7d9182176d9462358c53d0a3c0bce4f91b77caedb5aa098f784a2c145074a18386d36bd288b2fd44d3cd35807addaf8524f3b3a3a2d655b125a2b9fae01711d4ff099251b176a19b45133ce1be9a7cc7e4a591d05569dd04c14b7df13d96163f85f041b94f6f4acecfb95dabd657cd99eb318d540c3aca809ad3e4c2847506d82eb5611d7b0dcee0da3f2270e4203fc7637b8a4a9f125ea4f7354e417845287b63f7bc1e63bc2f133ab28d1e4a54a35bcc1eba9ea867db79b396404d80fb249bcb8619a4bd46b3b7530dd03b5c1e24487f7a58314fbf5a5035276b113b7053be4a12b0aa7f0e1f78417ca63e48e4e68d874c5b0c9bbbb6979358dff7a834adebb2f346e22b43388af6073427baef804c27131fc9f5de5c27a1775c57054537ac30964d177c0902502707f3c7870c47a42078f5ffeaade4773103665aad344f13dcf5d4a9ba053ad1dc4612c83a8b03d056b1942ef192a0dbf318cfad31b41213d9a2dc889f0a64148574457c1378e26d74076b0f73f12b533cf55c4a3e711bd6a8cc6530d14a3f5b95a9f79c221d605452e7449a830260e9542f2a0828607524442f272bb51c239e8f08acb4469ba5af6a49f413517f39e14f869376f2ddd786d57f71fc4a787fce6b943708e3756e7a5e45a1005aa5220b8051744480d564f92ed62c5fb440f205ad875084e18dbe34166cdce5ebb66d04504d87833fe01cd7884b88ad52393c65ad1fab6fe11b9ad2066ab828a805117f4445358b4cfaa8f5b38ab6c67afff9d06d8fb79e28901effbf01cc54856d4b4e23b50eec0f74a9e14ecea2a7ae8b335aee1558de4f54c183a959441b79a5c8900d5ef00998700dac6b8b8a6d941219fd4d1feef0fe8148d5ea523bdcd175d1bcefd8097356f07684cda649ce99939f44baf50ffccd42a7afbd22f68ff932aa45af25701c561ee3e4752dcfc95512568a70642fbd44e047ae43b0a01c6c4c16bf31d702da45ffa324ac215add645fb3dd81cb3026d9584026f0774482a16cad2a1abf7c02c572a0865b6e3c60df5b8e25f063103446847cb37a0fd60550b83ebe0fc8cf34b2c759d6dc8f6186c8b710b0b542f6a8ea5b5dbcec6cbb3514571e3c36e79eaf5b4461b11ceea90ae94c3f2e94e6aefb5a47ebb1f596b5fbdb52f938579898cbec3d3f7308226374d6b1be22d7ebfa4d8107652cbb02e8eed9151f6891cf409fcc2d18f87994edb3fc423bc6a30bd3b5ee37f552f76b189ce944c6dcda316e46e945fea0c965d4b9d2281cf4b2fb68b16111c34ae919b711d62d103b00ba7961019095e88655df625458f2eb46a7cc727235173fb1b2995702c93389109cd45b50ff994051993eda5c48d6c0130c34475e4c3a15443aca8f8526877e9ae2f3ad5764a18e248ba57754c305d99f8b7be4dd11fbdeac99c01233fc72699f81765e1c57f1a28fc0eb220104d008b30d9cc917f05da7ac3fe4c28be73e117809524d26f54bb361969fb93258860d56501da2fff10c15a156a3dc4559865b1b6ec79a25c263e714fb5b764e136b8eb45b70bb2a16a2f2051dde59fe6fad6d12977548cae4ce191635f8e9e4a408cd51f9102e348094418d44f0304356529f66c4366d19189aba9f9df064ca865919d51709f7f055d27443a1e87f86ecfef4c4ce5462117a7315f1c9ac2656bc7e6f62db696b4b8f134184e75bef330b0811b202b937308794d1540757b954ecf6184ce8ad55205cfbabd13d86cee2d035de872f0428274be937233f01addd713293dfbbc10c87194f97269cc04f73051c926a51adbf8b0fb761b611041638898b6399a6a6ea4d335acdc1cc39b88510a91ad63bf71c77f11e18bf1a12389a56a83e84c341b354102ced767d6fbbeaffa96a947ca946872de0fdcbb889dc36f2ea8ee6a2376d8374d919786be7c95a851e09c5e807eaad5d3b4f169e4bd8822c3b7a53e811730f3b65bb89b03248d8dbad99bba1834bca10a113ffab4ceba25e8c5ca3a84cf3777245f4d4e728cfca0a11707ace9dc5b928459b6dd3eec1102550889a75072eb8e1a8aaf09dcf369d71a5468112a2577e80674421cd5f7656d37e0d176e7747e63c130707021581ae2d570edc81a800fd9a5b1784af2056005b10789f23a7ed39bfb160d19db235a15f2cbf688a852877474819b6fbe40db33ef381018ab92d724a0214e192accc0d1e0a13ecf9ec29b57a25aa485c7ceb4a121a2c04d7070c6955319810857050c35041b4231f7c94f386e6c172f0f9f78b0b9aa540fdaaf6a11f10106df41e880eeb13f7c860c7ca66871ec5edcbcb23542704fa30dba45d3012b2d27455f35ac953f0bd3885c5b86bc579ae6571aa745943ac60720c803be3bf61bca3d311c0d0a62c468858379b322987611e036085616731b23835c4f502a4d0071c75bd22f3a311e23148b88ab4d1da6ea346468decbde5de3b2e0a3e0a6d0a193b75d236d6855d06dc264f959ea39f24193b36d2ac33b7d09a2c29c3e27304090666ee2bc23c1586c58454882ea144abd08aca337bc2f9226acc07f74f1c3f18f165121d8638fec31806d22adb314d0b75280be34159d84f50c2944808d3a12c27b4e674ac85d4b1532852c73d46b7ed077e16babc86a30c3229673c688d49a4ef63c7693a522232764e5a196fb24b28c43e1582da7350c721987de6ec324230e6041eb6c50865163178f1b874684d76ec5b8a7e96544e898cd92991f118a10ef66d13c721797b168e43b670cc44212d4304b34b170c94063b2782d96bce441f4e66b24cfc1d60dffd14820dc8be7d935976db71e812166561181115a22cec272e70f616684d2602a12c2c4628bb17302ccbb1cd2e676ba5a0adaec0b2d82067e2489d640c7b86e15016f622d40bc55fd41b634308ec801a5de0294a89430ae92c9570220c9466faa0aca2d9c31591524a21596a405ac931c85351e8841f60b001fdfb2b8e45804c9ab499a134f234cb78d4b135b2c689c43e0b9570c74219fad62a6f90e413d4b7f612902a548461628905cb29e70ea5997f96b921313e988a3f7c80008fb20a0f5ad302652d818744d9a179be055a638308cfef68658974c3924cbb07c815e467a63b9367c837203ea4955681b2e625ad6df39c73ce12f04c1020c9f372ce399b66f921048629e54d5561a3de112868c1375207984f336cbcf00c89635edec89b14708008a1363207413c3c3c36b824d20d47f29c128b6c3ae698916326db228f37f254dde429e7a4333c2544509425d20d577a92b2666a2c4499fef6bd7706f905679d581a414fa6f39c3d4af12ca1b6c860185034f033cf58602b478b1ea580dddcd093133cce293a409225468f6aadd92e7b0a1c7eba514ab406a585d68c73cb92b28ca82d3e7a85039e61e63eb802cdf632cf73507e5c40e2a05f25c15c17059e3d436d9b4513a66d5e504a216a490a3cce284a17ab7d1bce20bf0835208f720c0d1382f5ab1771bd84603dcc8bb86698eaa12cea03aa0ee338850489319524d3a792a48052419f17630a28d3a77a523ea9d4115ae3a1504a3c5e0e7355e8c2cb61bed55016651951e03125949dc8d525a447bc902209f252e28851346807e61f0a0b4c3884451e305f893babc3fc0b77bebf1c86098c5f3fd231058443d4920f1ee78852caf429205a1365a9697e26a5999fd827b1bd8fd991287ecd62b55a73bedcc3c8208fe8910eef614c9b885a0a51584294d20f1e69910c45236a295394120a0bada9b4467639b3452136d719654e2e8880e79b42a12cda222dd2019e3f1501cf7f3ef8b6f88080c5b1c80b6e90a7787282a90d2c30fd8462333879e64b0b943c7b664f46ea39ca12a9a7084a964821d8926951dbcc2c94865e9c4b9445cf92021fd128f7e57d05bc9c9e76104aa96d2650d32ccd1fca5af1d0a229f412d2ae41a9c394e9375a44b1b49145740456327d1055a2596a5394d2114d4297645a44a7138a524229a194e692c4418f820e789c4b2922153de104f668d648810c407d25d8697f146269bddc0f64f0439e6f79c4fd40063ee4215f1137e70ea5a1435a42787e8852cb0558ae30730e102153c7c2cd55e3baf6b53500452f2da6ad20eb860cac0b849e1c0e688e7274f6bbb79a2c915e3fb9863ea7e2ec3971816f5a850aa4c50bccd933f1dc2109e9035932a5b6b6165087870cb387d20bd09af9a31f90cc6f5894907fb091451698b89427b501033c522525bad461cfb8ed33a5b4d4365aea2705a4034c3fa67ef2e444b0277650ea90f9a2c9949d138960cfb06727a2ddb43dcbb2ec994ed34cdc3498b833342ac18e61c7c41ab213695d4ba2ac632de947d3f0ec0cf1d06efad2ced0885d03a6ac04138904691a14ac78c233ca2593f8337f9a669c3f9938d4e76ec2699a394f7801d6ba63a79b0ed6edf20991511e67900d2fb0ec228a2d8cf0b88864077576867860f7aebd88cb137786fad84fe17412ce2ee421ca7346692399d434ad855666871a48c0ab2807912101afa2ac84480d98924b3bf6158f9c3f4981a98829b9a44b13793025172682530be5699a55cff4a13d9435690f769a643f459a65130bfda147e8924895286b9cf98ad488b26814ea6406214a1ea9162414a8ed5203d11a394b5094c5c92bcfd9482813900651569239d2209ca9fc4895a810157200939456e4525c84addf0a02833cf8a713c91288a2802cc8cf4f71045998a012935883d4c23544c106c4203f652e92e58be4221964e1c6404550bbccd9a90862cf36cb7a0257a8d9deae96666de540029e3932eee6c405c658495041a676868aa32caa4f6c1b0e86925b966072c541cbbc9dfb3bd84662217a6991bb832ead3eb208a1353b788019d7aadb3980da186248dbecc8252684805c35745ff4200b97f79d02bc3c71e8b2a03d60479fb6214dc3da318455d831a46d743871cc382df3529e213a3b86481c8d323dfb28b5cc3c431aa629d4c26f6608acc29c2c9ca77e66e6b7ccf4871c94d56f221c218fb3270579631ac206ead01771cdd9f3a0ce9c2e7ab0878b8ab8699aca16745a61df4ecea07ebf650b75bf3322281711e10038bb427fac28c1d8b76a5a6a2024856049eecb51468e734beed79b26fd69892508adf921bb8544ae5e08c5c511b94ea19d69640748488e5c3b350cc00443585ca9d7900021d8e00b176e9b1d19176071edd42075f0852bf51a6411ac70e137cdfd54280335401f8740a8017ab2907da2c04d0b5ad35322d960945ba96da8ab5f7da4a53fb7509b1787644317b97d50d44846aa6ca629d46037d643ee6b937267810201f2fcb54d4ae885b94fa13e89e3274ac971dcbbcbe14e5dd789d3288aad72a810e4813a9681d6c8f0e347f7f146eb348d9aa6efe307129e67000c3f8cda2688e689e39771d80a02cf9f792488eb79d43dc141ba3f203096516111579d464266b6455cb2b36fa9a3331625286338140641b9e250198080555492ba74a17e6f2a4485ada6e9158f0a2d8beafb30fca8b5ed29b5a736ccb2ecac08b2504f5f4f4570be8ae08e16fa7d1dcafa028ff288fe7414c69553130e1e6079d311adc94c55729bace4eeb648cc3e42eaa8ef29b9a7c8bdd93bb336c504180047a697238d4cbbe3e63754a65aa6b4ad139cd64e1f5739fb88ca59f6296d50cf2e51210ea5a4f1c210d3c21916309b76cd143b42e240620acc144f72bfbb1bf3410049f0b31324a68d668fb0f758688f4062d2d04f9a2b8ef649a6760a6aa7644a6758a7461c51a04dce8cdb8d213095925b1c6dfcab80152b56945fbfc7469e52c7066696f43ab9442ec063e6a36982c046b466bc4b72bfef1228f812212f91d431e525d232a9e3122171f4fb9ab4b94588e32a8f97283712d97d422471f4690978fef4cf77c46493333786c033cb0a8bd460a03ea492ece60acbbcd22667702c6185d6d09baed09a9a6e39e52c3aea166d15caa279de04c40f2e40e02d4b249e2f6cc0a62c9178ba40e2517a8127a925b46d54de194a6b95f6d45ab1ed9d7dad95d7562bc46c4a35d4aac353569d4dbb67779dff2b10db56e9a4947653ca9293d2b1d23925a5f607d3de9add5a250e0cb39f2c2c93f576ed6aabcdb06b6b865d16587bad9269ebb4d9e7b4199dac5befebad99ed1a463c04c1091eb39c437691efebbd3c34c9f7bbf7b22eebde7befa512803caa7e56906b15b922fd2421d7e31da6c8f5f6d30205c6d1945cefbdb70b2ba77befbd36721de2071d57329c7da08f93117c4dc4f83cf10363b4a2b2c38bea93a2480aa30ffb68b1030c279c385104837dbaf8514d21f47283085ca02cc1d041879416550e487a504f04b9e490a407fcc42895031226f0682084ca418a0ea7221e9cc3153c74357bf2e3653914e107ee09a3930878d89828ea506062828753a235f1b3e9c0c4275ba2c8549461af9911124a2393e4182dd1bea31d6e1151b6031d2c143d58117c72a00021e9b0841c729c50eac921c804394c242191200539e440b1741a488c9694aebdd65ed5119cca72ca94571038a607940e78234219815d9268c2141b2c3105c9862957b64ce14110a62ce11b8ee0b33ec332ca128a166891edad51462921f9b0039210187f061dfeb0ea5bca128a2125b27d5dca473c3c3c392041c98153ca120aa256cab80a8691e514a429aa2df097e514a41f20815db29c82d404972ff029cb2954764001d6b29c4205084b45285bfb3b23a1804116141070361404952621a0b2440ea8d840b5032a55523d54b09091e5941b28b5b8116013c36e137890a06077fe708a02abb0e3aa40593d77e01431753c16a47c912d75ac72bf0be50e60b66e194fd5324915c25c267c59c9f428354ed3748bc592390e950971fa14babc0b531ecc5f3ebb17a44be6b353a95eae3a8cea45467c818f3978bd49396c62d89d6210e6b9b307f3e057639fdca74a6dc3ca581809cb4bf08a3a300ce87748b0fcfc95f357e6c8ee74f0afee3145372dd64f17d36eb96f1b86d93b83b4a1f63c657e7a17822ec8fc74f08a3d64c4d3bd77077b78efee89c7e953285d1b18c2784ca8fa17a2be0ab1cb61fc95baeaa610d4818179f9364d1beaf8af97cb5f300ef357ea2fd55f2e5ddff5b0bc843b9aa6af0a7d8440f0910baa5161111e1e1e1e170e254ed3b4b7e429515687022c2fdf5da1b202ec170a4a7a41843445094997a650e1c14634650aa23661d7049a4c2a60d2e6e547ca924f673161d79348474ae4be8785d6c0e88e850264aafd8658786a9bee26d387bd62ec975a7b85743e3dfec1454c745545e36c132282cf4d22fa9348eac8b813f1be7d9c493d4e4891dbfbf61e9ed8038b6fa112d14d2772b4591e51d1044a965430b192475c832ca96032828c8fb095e652de514abce3d096131c83e9f6da8be46cdebe578c61be50356b1aef024e6b33945a064e111fb970154ad35f2591158147a965b6966021b9f4c4950a77e6910d920882ab067965896ba889263ec247f8dead507f863294d53add09a76d5e43b5a7f74c94fd806b8306be9c8931d467daa738b6506186698de9591682a6a74cefe13d251249dd3b2a854d229e73d458f8b2e95b168e1f7ead6076efd2854330134117b6e3f7e0ee8944bc73c7221122b86bc07d26620f6be0cb267104923551860f0b55e816284dfffb9aab59729fe18ed6cc842ac63eb4bc85248efe8c124a9be98229f429f42994420a3972dcf83e146a7a8147199c15c968aa8928a80c45f1c97c078f9939a350496b468c9b12933aa4a43219656559cafc9346fce4fe7604d66a9839f0f9c10957f79ddee1c85583dc2284ab1387b0b77c30f7d375723dcdba6f93032f1782f7bef37eface26bdf7e789df3e54d6345feeeef2d4f15883721fe3a28632b432ed23ce461598680b652d515673a7709b6db3fd1916a4a43b0ea544452db660807786768676bcc3f8b6f16c620d2f479deb56c2e96084cd85b491b25f8ff492d4c199c42a82dc36e4ceda8bec43594336968055a2500b354dea188b3a4dc34364888727ee0cf1f0688e128904c1f7c41f4dd3ef3eca232c4e2bd4007def039e289b8613274fd374dd7b10a9e19d58039119e47d8b09f2f1c4211b4a983f1f206a1aeee32492ae367a39026bb8ab304f607a4db412b6952c4e43e0af7b7c7aa339067aed55d3b4f19da5aab3d05eca91e58d9ee931e3b211579eca23754825c9f5a9a544ada5df4add4b6dc511779276abfe5471c338259af4d043ef70e4e5f12ba5c6d24b156826a9b308e5da537f2a92d6d244b466a4442dd4577a90c76642446b324e7bd631403b8fa492b56ff4f51bad956abfa1467d5eb4461363b8c7de35e4ec63cd3108a159ac3108a1e3cc4469c318462134c770df008d3ecb9901a35b288b1efbcc1bc54e296bcb0de511bd92c4d1a759a84df5a1748ab4e920548922a1a4d04a396e7c1f4a6909657513095156772ee8d39f4ea453244dff48e2e86e2c94d549fd6da9975a89b2fa9d0ef0487d727f4915e716ca6a2dddaf56a23c99ba64f4459c8876d86082a534dad157de5bbbbbbbbbbbbbda2872bfbb9b01567bcb561018ccba31acb1eebe5dbbda1d3f29d8e48c7cb98277d0d0060db0ec3a2878945054a035f2d5ee68010b7978504a29edfa197a23d34f15aa00a0c0d9c528bde2ddb6bcbbfbe7fec0657983e70d5b4ecb09aedf61efbd14716a380307b89e012aeca02c6b3371c4d6da7fa10af0787bfb1b3d4315d0b7ffed67b26ced574960d629ab2b02cf7f5207b8c2ccf7e00c33d7cc562d7e36da01d96e7a8fd4b9eda673a9a4a4de44109b79db502e48978b4d01fbe47aec93f40348db98ae7d944b3ca74ddbd13653e868c7d11571ac7873a33a0e6de1c7ed5c902ed55347a56e0a51db86faf6d4460388893aa914ca45569ad7d01469a04108367257bf9ed536af9ebbfa46eec2c20a481cf537b4c044a17e86412e40253580157f7420eb00ce566c01cb34d00ed449a16e3bd05e661ea914eab203ada8b3f5b8c141410bc60010a9e3878becf271957fb84c1b5a9808a03db9bab03d751edb53dbadc8c37494b8436fea74b463ef27b9ddf41628cbc49dc2d9134ea0a6a9dfbc1af443674792d18fa55d159b9c193b28d7f8cc695cce84d3b5d10871d19ba6b67a709fd21f0a34438be63462ab1c8d901a596e26bc61baea52d5420bcaaa4a33a991386924b4a6daf8cc4c4aa206a8a7515f236cc500f7cc6d883b433c689c86b8d397353ef39d3e8d197187bec6e93470542fdf258d012ff3d324fce964cceaf80d39e19c7853786dc665783c278863268a230ec5f1bb387e8638b26488632bc710c79b8cd336540b4ac3d332e248b1e47a501cb31871a45946fc8923dd92eb61c4f17b11c77ee57a18a2aade451c5947723d4a1c5bf5f3c4f10677224fe5aa98f42cd27beb3c42d2d43ba12445ae753a9138eaa5b77646e07b6742e9a22fec6f1bea84a4a997a24a4ca4451247cdab2160da437928abd6a0d3939339afc94cead8f23c2acf303ff188c05ae72b26721ed19c392a6d2f06797ede239af7984c4f893c2f63d0f23874b64744b9dae5f49f60d90de1d123caf31e11ad9151e022206c72663be100cf274860d9e57450f0c862d514ea93cddea4806521d4460c52a12fdb56daa235a38dfe8dce91a50efaa6379cc093b228cb0615bc357763082c0fd00dd1d714706e4e4fe0d98dd319814f1802a63f80c4d1363aa6c8277da3c5626959b018786b6e760c40853b540539f8f0b8627c9b5c8c706712a1c9143cae9d1a86b2c7f8890a6661bbbd2d92e951ccb1d5319817ef230265c0a925cbc86ca742740936a247d8e9156c0e51449928055bfdc87159fd8bcc397f3689b9b0bf857409b4dae93de83d91c876fa93484f847b5fb60d68ef326402a77bef817211679cee89a04589a05d5ab22711d4660e8f433d20dbe6b7e174da61c6c729647d2c0519d23628998f536812f1cc9e9f18323e4e19d4ea6d339d14b1ce0a62b199204d836d930bc321d0057cef3dba9fee12ce382ad46607ba80efdd47f7d3c34c9cc18a3a261b592c0511322468c98ecf0f50128b81dac6070602f26165069338e341964ce28c19bfec641cfc2bc663fe10b4ffa56b46e87dc67b9c7e9148f7d36788b8d53633ced1cf108974a77dd004f2d86e0f6adfe8e989601134893c3ad08a34ccd08099ed5f90aeee34e498aed31dd444b08777fc1ea76f27effdbe868b280b0761202c2384798cf0e5abef5ee8f253887a8685a08e8b0b4a46285d3142e992f1f2e97e7fbd605e2f7fa98e0a37942a9c3d13e80ba7900b8c54285d41fee2be1da5836f677018241ce29d564dc07289ebc314c1a8aa88a20c3e46d2f77b702791087dbf139b9b54044db37b931c37a3dd3e08ade16eacd42223a02021a2ed73c16eda661e5929ba29d244d67f3a0e4d61f658a1ac7a4d86e3388e8371d54153cb84d2e58ac902cfa96113c3660fadf1f97102e50851122122253427b466bc2162a24490a59ba59b204b9a0f6ac27ab49bb49b6ebaa90673187fdb80e2908d11417b22f4dcc5211edce98974ef7327826b671f561f57b967e502276d83b96054dbbf3085ba77ec6e4ffdf542cd9fa04914cea2a6a9ef3c14970aa50ba32825363933e2184b70bf851dddad42db608b401197fda461491da73c579fcfe60d703dfde8256523a52765408ca677946715b397d5e95e2ba5fdfe0c4b07861b4979ce4a958c92b4981e963c2f8db2393750073bc8e39e061670ae6f9dee38b4bbf0a10c94354ffbb55dfbcbf40c02d2658f4369949075b3c3c77cd3cc23cfc833cade52a55a157a812cc4407ea02069d2698352ad7104ee72ea7ccff99e99a335737e9b019a2f2054c9459572bf302cd334d3c6715d773a79dec5185b5c9b4e2cb9d3b8934da60c732a4a9bbe6df70c33f000aae0b1955918a350a95476eebb1940d2aacb0995f09c73ce39e79c73ce39e79c73ce100878004903c8620059b8a2d8e4cc5893329d5c03691bed2888d4c2d3b2c52160fa4c14b0eb027569df6c6b0a1e67bbecc106d43c8b68a8ac9c89fd339465199e3fd92cf29908984a4c14e8e948a96bcb38203ff43d403f414239a89164a03e42593f4a4c04749780095ae94989e9b2eb315d2671a7069776023ccf812f0bf9727f06f945ee3bbb9c9a74ab64350a151f8c7ac2449426584481018ee283094c677d8655c5a8512c88528226b0962552141c8ac0599648513ee08204df2c91a8d8f27ba92042bef7de7bfc41c1aacf06569a26c5041c53052be12e8a5411453f60274f309625121544ccb4302bbabbfb24a594b259df97659dad95ce70dd2dbbbb9aa4b46196dacd302cbbb55a294db27677d76e8e76ac96386bad73ce6f62ef86b2bbbb7b95c5a35e7bf5346d2775cc30f3e48aa8ed6abb7531796ddf1475b57da379269b0164d9926fdbd576356d19aeb65f6d6f1cc675f7e479adeacdd0d9759da4b66b77db15a328ca4bb15a28d6aad5c2975adf6861ab696816b62a76b5509b8b87bd9384f9805c593bc7b6ddfd7d184b29254c3757391b49b0d786163a250ecce1506f284bdee75ae029a7ada3b5d6aabaaf0b96b2dd19ad197bced91565ad6926cbbecfb31c67e5ef002123d775d65a6b378fab310353c55c8d1968594d612e9cb5d65a183064791b9ed0022c431966b2bcca5a185c8d19e4d15a6badb5d65a6badb596d64862662f4ad44842cec4c4a081ad590c4fdda9a6d59c73ce3967b5b79b0adc53e0be569c3d754a81db0877304e4d5c70a88446a5f09592355db806b4916d2fba452971ccd96da4db48779224c22cdd4ecc264d6c4da249b5496240a5adaf6c71c406d6cf26a50e7b9005e9832c4e89a3bef3cc23b8b5d2ce569c4636ac5ddbe46677db1f8aae9de2942c9b71d2b4327d9c9625176ef23bc1e8c0b2dfdfb44d77eef2386de35d5e0b77286b488b06786add0a9db11614784e9f157a3ab7387fa0e0f9d3edc40904349db8d02e93d87a02cf8deb5e3a18a793a702f2c30563d4d134125f7643a1527895511acbd2a0407699465713331113ab8bca033abdc07030df06838f6fc555a9b38a01c25152d3b6f87af0712ed1982cd3ac8420273799189c944cd2502ae4a492291065891fa0403132a9638616248e2752869c31e37dd36ab5b089c9c31366b819a9a3e78db9a36db69b6c686bda50a0a69108984b1305aa63b491a7112acb24a9348da61112782a914cf03cbd0dab2c4d044c29240af45a64fa2b990a234c2331065c7d31b42886090f528ad4154da89ca85614bdc008aa0bdc5922512194046fc9b244a202c9eb6bcd82e811112724ee89124e4f38e14101948a0287cf6ece8944050e33354f737a737227cf9bc9de8d1cd94bc109aff32e87c8deab8714c516d9331aca9e679444f68cbcc89e1447640f490a25646f0a25b2370513b2f75397e33d9fde7539a7aeeb3a2894724703257207c510b228d727b60dca4fdea050c9dbd1e701909ee8a199cc132c32f67bafb497e34cb637b2cd630e7b00ab007b59e58a4580cd22b7bc6af2387da0b0493491ad7d8693e041b6ff9064fb2b8145b6672d0144b66f2d3145b6bf59628b6cbf838924d95e87091e647b1f3f4a902d52135664fb20f64378e029b2a20705d922393185d648a4273d0455c18194c40b1f656f7a5bcc8af1c7b2d29af327ab1ffe9090c82200150441ca0e47d0a1892dae6a85161c9ceebdf716a08a29aa201d41050917e47ae9e48b5c4f802ea77ead5a0700040eba10c1132c28d2c1553322e48a4509b9d65a6bad03e872aae9ce5c9142be509844c94113219a10198183113f988113272670c245be6f753937092a8081429763391d32908111b95e005d4ef581008a38428a0c7af23d0e298ae8e1847c7fc30a114c04a0cbb959104d412222887c0f802ee762acc3aac75dfca0d65a2b4d9753ab0a853ccac44049be3158220a22dfb3ba9c7b0512f2fdbd8d2ee71a0108971a5d8ee56090c41012723d8d2ea7e2eb92efbdf7d6cc54551d5a22d7cb255fe4fa13ba9cfaa58084a22d8284b061881d5cd7ee90ef6d5d9125592d21e4fa2aee208a17282160090bf27d780437449600bc00887c7f6fcba87281109e0cb124dfcfe872ae0e5c2044bebf8c2ee7c65851854d140948018a8c22ab080154430826941c514285100ff9be75232425dfe3ecd089d1e55c2b39660756646e2411ca5746be484968d00215b4c028dfcb7439d7638295148230ca970547588025df8337075b9c085d71040727515cd91082104583795521b3821d6a561045beff824841be8fa9b5d6245831d55a6b5dc9c000e5fb8f8b201bf2fdd7e5dc252d304034c9f52f5d4e8da18206f1f0f0f0f0726566f2bd916f8e146e952bf962d141965be66bfa9cc008f9bede674052f23dfe807c90efef830cf23d4b07a41f809025120a9ec8f7382ac8f73b74906091ef7dd01a89940223f2fd8f1df23d1024152cc917098810cc9e9f24d02075efbd5755eb0998e8c108b9c644e96105a9272650cad75b629ae00453f23dee726e9623bafa3caa7424e1c812b9fef4717144aa83e245aee7928065c346252022d79bb01458b0c0f7de7b65b6b6872b46b57d3d0a95a25d25fe6c269e364e7baf6d532ed305a5927feb0607e76d83f3bf08a7a84816a95029d449ca191e6cd9c46aadb5564beb6d5c6dadb5565b6bad95752d65d548c2ce9b1a4948b9699a1559cd6a56b16b9bd40cbb364956b38a5ddba466d8b549406024c3b29b558c6b865d9b24ab59ad5992ac6615bbb649cdb06b935c8b65f55aac89bdd762496a766d92ac66b549939a25c96a56b16b9bd40cbb36490f3f584629edaa050101013596654744b367f250a2a9a424e716f40839261cf0ced1907b2f965d4d0b92350d3ba6596c5af98ac874ce9a94659966aa5cd63180e6fb8d6b9d1cd4661e1d8d01b8d1a2b92732da421d6a8fa21c1dcd4b6464c52a8e734b3632d1a28ee3ba9cec5c8e92af69e895b26f5c37b16cfbad8e1193b74b746699419028d0df2dd34aa65b481c944849931247bfe92a04154607c30367adb5d66abfafd60af3711c0cedd32e853ab9a85258adb5d6aeb5d2e66aadb59abefa4e21ccbbf0e338ee30dc878116e6dbea0d169ddbeb5bdb8ddcc5b54b4785286dad6ddb4dada803f3f108e1b6d03e7d9f8a39190c8cecbe10260cb17cc3cb6f90efbcbb84a7a342eda9109fce6d9be9f5321d3564f60009350dcf4f1051103b532d45c5a0f310d335ed58bba789e0ecd9aeeeee3689206632756b5a6babeed349a34d3b88f7eeeeeec60dd35ff7edeeee1fdd4d6df76dccc36ec8a4dd0b4da793e9a76b27d38dd156d3a4dda03426f13594fd6504bb4c6fd38d6097f6ce3aae6e6a2dedeeeeeeee6edadddddddd1747e2e8a9996e6790384c7dad3beba6f4989873358d5efb55dadddddddddddd4597a27c70ea8a17d915503613645d98b04841deacd0e00a29523e71850d04b0a9b42601d93e7b72e5064b0003e8e028b70d8da01818612af02c42cac1d192d1927c7f6f7a22073b80f95e1bca8103a57c9390c30a1702c00110f91efbbee6e1e1e101aa8debbffad72b9e34e10a0c7f35c615786cb57eb4ac8c52c9fe7639764a93ec6e4c2379cc916f9550983ab92546cee33c726792c9642a4185c9932a47dc8006f9524142159e7cabe4906f1523c8f7064de47b8327414248556cc95722596145be5656f07d2bc01f567df58911727d7f587a3afc61d5476b20451133146459d6a22d4e0a121db4a44dca103ed90a3f0099a4184102ac6627006a1a29058a11acd08394a3134801420e4ea464e1e1c8480956f0e1c80713ac00a4e508063aac700223cca4455b18553191a2c413aa1f609ce51429354082942b4220054baa0a295a9c404d391ac1eb6805c7e28b1b2a5a3333151e54262aed16c2b6daeaaa75a956d36af57df5ae13c2e180887a400cd436d3495110060ac24e9877f08bf92ae65cb85abd7bcc2a833978d014b3fa74489b979196ab7b0f76719fa292d71c3b4b9afa56df087675c7b28ecb06536859966119a6cd98d5b1cf307968904d904ab0b7624e1613b302bf556e86180bb1508785c81507912b069238ea05d004b631ffee637599db90e6d9e9c4f82a2624b27acc6d0d22f3c3879521ed31e176b999e5ef30e7f11de6bb1579c01c1477e8618e43bf83dd57a126e60ccbb0cb9d70aaf3509b2a05f28888cce99b9e488cb78d21f3930f7c14ea2791083eea1d3e8a3b3ac222a8cd2ce312f02177a083b9f8918e8fb6416901fa49924a7249cbe441819e96b69940415a308f0e7683e960a6cbe0de7147851dc6f81ceeae0a890cf190f9e94462dcbb4b4844e627d3410d87ca9814ffbc8e80b4e01f7c0423c9f5544bdbdcdc68b9d1727363758e348eeb4c22a89dbe752168df59f1d61e75192fe2dab6a34d8707e60383d4f2da4cb6af802ee073e781cf893b43a6cb10776288def14d220fd43b714746043511b4228fbe07f2a03f9dfeb479a1eaa797c78430609e618c5ac1fc85ff82f197cb513e800ce9c1aa9714d639795b747c00e178b450567da5411955015d05d42b4369aef802c1a4bf3d2d57bd11ecea5fef357c1969b94eefc1ae97270ed1809d0621591682737d873dd8f53a893d77d51bb9ab6966391cdc7c3fbd387111849c4a79f726ec060a522911ec64971d87d2199a4a51fac944c134c651347e61fcc2fc161d2d612a1e2a2d549d46e87299ef338f910abfa3c299546ae6a97fa999c708a76b9bb2034d220ef541e8072182a0266a42c2f4f416316926131309fa827481e22be634ae9d5e763498aef352dd45073c3dfdbe99cb0edc449d54ea0b4d31e20bc66988add7ccbf59e33468d8d0a1f942a2239c38f111117dd8957c8fafdce9badfbdbf918c78c2c799742d3cf97e0211614433c7a1da0c16521ecabad7b86f93a3b1f11a31a18d7f618deffb6ee3ab71302472facc65c29913f14e833bd885d265c3077e5e3e1b5126da24263b51c7468d6b349c44481cf7369c454c14ee64a20411d176ecdb313107e3b09087c4714fc308481cf735ec3169ee5bf220dfcb69805162916f9fbcf7a8f7fa5e9f88f77aaa3389e6119d1374a1d67e4a24e2bd3d3123e26a5d81ebfbd6868d1aef4efb76c2c57322b8bd473d8df7e8cf3c6b1b1ad6b16f5907baa052a95c5c8e851a973acd79a44e23ee0ca5ceb130ecf5347e0ac14d74a19e463d0d90477fe6fd996f36c298d708bfd308653e13823f219471318cf187ab87e18c9b64fe8a713026e6b3114a578d50ba6884d235134ad709a17489e1ea33fe7ac9f84be6317fc5f80bfcf710fbc01833626464ac5633680201a95c52a8ebbc6e38cccb61c80e877ea1c47850d6bd7709a7118c705a8109a7922a944df312624c74a94c918cc61862cbf0860aa56babd8f3b048843bbdc7759e37c34b5dc67be0cfc09781539e086e3264789e0c4fba304e81312a182f3231c0548c8631d6d96f93f3665c4638e3a950c6679703eae0bf205d58c6f16734c185c59d222e19f742e98281a5de033fa54a9d66b022a8dd471fdf07bd77da64c8a0e105e97ac5b8cc410dbfd536dc4fa857b1cfbd07fde93911d44417f03deff41e4a04adc803bf8f6784aacb0853c7e1cbbd10c66384df654218305c7d7ff90be63054aad48c50ba64c4c0aecb577fbdbe544c0a06cc4af5f2b9bcf0bda750c7a12714f63aee5404a348465d78b01af6fbdea494d8eb0b610cf517c3c6bb60ba9434d5625a835594ae4a69e85749e00f1771dac5a5f8b23bf71eddb97bdd390cded17581e6bd9d0862e071e826734cd7ad26d00cbaf77e9c49aa7b552e40da665e252077f64c1ea07b6d035f589d0b633ebbd9e9bc6ee877bf819e337d87398881a1746d5401dd5d9a7b56dbb488d3ad6b230b8c2375683aa60d12c7fdbd6bdacc0f4c9afb9b4a4d1e937582c70964354cd459695207f6fb2c6da60f92e6fe62e20492382e1026e69854a0f73250d6fd15370134812d0dd5ae56b6d33e1f3f50d08257407fb3fb19247564190402ef910c199036d3064973efbdbd17401398bab03a781f3197b9ec74624089114e2019d0081715e50beaac565d4cb80a890c61b2c3441e74fb42ef30e1e92f21ea30427c9770bb2a4cdda47d3064568552861934899aa6681e4da12d9c404d738300a1ac234b5d70d8e44ca62da874cc18aa8a542a4423000000006315002020100a874462a1582c0ba551691f14800f6fa04282509aca63599003310a2165083184004080001181991ada0601fd82634adc2824281203f473de42304f27d11339012eacda6bda324f075527db854560cd2a2dafd6ace87109a59fbc5d4644c52b48f0b9ebce3b084059fe1288dfda16b8b1e8d8267c4a0dc133135ff4abb014e23c32e9d45ff23fa2640984400bee8a8d9128a6cc1d82caf4ce3417545e2218fc336ae83a81188c6d94ce4e0dc1c279edcbb3e92c5618edaac168af66c644283548aa99c838aabed494cebc5f5540782f2707f1928eb3fc54744fb0b57c4e7faca1d6b72c86e4656c892019a8a38cf884415589ec814a06cf0a0d382bcc013752ff51c64ebe01cab45c3f8292154fe191ab0b94605c72adc1f08139bbc166d961e2bd6a5b4aa47a774f94f0dd4408b6bd595a29b68a30ca9eb51e8739667f81f2bf67f5617633dae1f3afaf4b871efddf351b20b578cad1eda0b933965a915d86c620d87d3c27c63aa7f06a1c0939d70428a358bb251832fe6acf840d47d56933019c0a21177a1cde1568ead435a2de0ebf8f6811afa61b28ade96fd4256e2ced26918f4bcb4b7a150c85a4f96815dcaff0a1626f4c547226131ebf6493554e9cf09ae507ac54c3bf53a7e33f54740d4b42d030cca261919468fc7b658fdde789209e8b2bd1913774feeadcc9fb51ed6362b9a3320d33b91ecd29603688994b3be4648c728f8007b948be7a66a06b3bf34f8b1a5ad5be075e86e670abf38fce4ebf8e681f2b891b8777069ad4471224663aa0bf1df263e4a9187c5df5b964a49bd43a4888ee7a70cc2c1d9132decc2e4ef97f04dac47eefa582452f575f1581b64bf048ea766e04d246340b32ec81c91b32d20478f1c5681951dd320d3d51f1cf3e416951453c6c40eee16220b49b3d9177fd292b94772f6504cf4d7c01e33b557229dec5ad4b517b7af946d2ee0e0681d91c799a91b815495597dfed5dfc1cd568f925ccaf0a33acbba12d1fda020d555248c090214c050229ef137189822c6cf2c8c9c612388a477c1df0e3450254694e8fc1dee0df771945c397bd7bc9a4590334f266ef45070a2bc4799c7484d8750cc7f82158ee9727b2a768f83ea2f45114bc82f422722bfff6bb3673477ddbd5d704edcd1cd42624b40e697c1b4b3a3adabe3ceb89e16358d8536204716e62daf3c2110f53b3fee5b33a46c91212813f96f75b6c05c7bde29068e3a5f28ee9ca4215d2dfa1a96f900a8d5b15a6a1ee834632ccfc2c9116764d70988ebf49c65d271af59b2d5971e75ae9cb796fd240061cb7c1fb21f09ed595ecef54dca0d29441732b7b64c1cd989bc1b1c148da8d9102cf6c9dd1835f4eed130cd573d56f2e4c34bd2ccecd37b8f32b0fc497d23ed200c30373b98e6a4d1f7b4fea77c28354d19bd8722af74e8df654173f7583a8ad011dd873e763d9a6bbe35696f50d9eab7e3e13c8bec6ecf4cac3bbac32ad96c6e54a4f542f5669b8927574e28cef24eb4b3cb1d5ca907ddee314a6a10b45ff1e7de2ef0b408dce0ec1d37d14b37c9f8ba36d2d62d70a8f62a420ad12e2aa3f7e0f50167001cbc536b9f7da5a06b093f89196b40085d5a432fb7fbc0e0852a259b20322ecad870095da9602f54d0475aea449442b8a7540f59803ee68880aeecf026927ae861628775f2342f2bfa6449d8d3bbef9f33d7a881d411445524397914a95c0f24d60be552265ef2b76b9b8d085d2421a0c78673d92414a0f75fc8128b7a5a0459de5ee38cfff42d7264a34dc1e260691c4fd3349a98043b62cac9c6e88d928c7680102105371e92165c0cb21caa201c46e90eab6a2194e2076e3f76e92d9cb33b99893586dbdaa7916f7dd167ac45b1a7fab370e1ad2c5f528331f1c775162084cc0a87ddfec7cd982215a6b26804365b067a9ea57466b988c358052357d1659cdbfde2410d36105643e6abba3d094abcae8421181bf92831028c50cca4b334667b08055af137f48c1c037078caa1f78e1bce7f1887fe64ac1dace0efc6d52f8130145cae140394648c1f659828ca9b02320fe6caa1a912391b635605c45225400b42a5ef72e279ed93284e0db4188d035dc1dbd0ccd000a07a5975d7f6dfffdf6453e0990693ee1ec91e4fbc59047c2d8a8af5a373dfe0058131d222c690ab0868d4a6f7495e4a133382d8f3e3014d24229cb45fb84238a2df2c811e6b7622ff615acf0f8c63297ec3b738104831462d497d3cbc0465f8fbcdd88827df6e25bc6393ca6b8df87c223e8c8a763b4fe49996367d495ba31868652b2ffb9f691b7afdbc5a8bb935f99bb8eb3bbd336b84a05fa8ba1446e98bd75ed0095abcd90fd95c75e906e9c6aed8e6e1080f5c2c603ea2b31abb49872f57dfb4dec30e48b9197833a6940135a2661d209d0e0e24f69457b507200bb06c4dc1be04a53f52e34cdd42267931647bbca22a40dc227ed7e42a98aea07fc213f64481df03a848d6065041ff2956feeda2d4be27f25ad5231f0622633e48a8ce7cd2ad4e20aec657e75091febef9f4594a52bd76b462b73e9c27e6e6c1f372fa5e6ac7605c00610bf4b607f55c8c8e59d97cdbbf73310ae897d89bff334fb63c55142b6728a7b8ed09c4c5f490f93f786e922df93b93b9a453eacc0b76c1323a3660d91b1fa10f096325cec6bc0c273361820517a0404803d7a98cfcb0e9dbc2d46961263c99e7e8ef37a6f23a71a3cc7ff3d4317e0802ef43bc7e2dcf7775c8154c180c795330f2f601a6e8bf9f430c61e389d36db628b829a860aa457f0b0e7193cdf5c44acba9b1ed4089557fc8b70c18bec098192252b2ced2f195904b14a121f4447a83c98614dd2202e2d2c4e58a10ce06b0f181dc80842ced673ea4878bc4b793fb6f953750bdda4591c1fc4e479cdfa33f94b4ecb622e3a24941dcc5931b64025f2e9523d926bd2146ba40213f98e0888e0e94998ad6360b7d4127d878d1cd01872fa448dd9466a303ac71472ae170b2d1c28e12fb07deb46c2cf46d2da822d616ab8dcb253efd62f2e97d88ccb95d13c1593b7b0bd1b2d37bfe88860f4d0fa92a762c57faf4ea3a17de0edfd73e799d0cd26d0f4fa6eb15d12a0f392689bac1b8f9338e6be46e9b02acd6d48424298736fe255daad5d2dfde3765c30d1a09ffbdde2a466fd2a05d83b3d5ce97afc3577354dd31714d8cb2b5a9bb98bee65d69ac1a9d13fd240935e42d48a57aa8a214bd2984e6d7440046627aced83d0601767d799b7476824118b9be3454a3f863db8fa252ab87859fca89a23018ad2b8ffb18dbd68d6aee574bd721f129b31feecf810ebe9d453ca54fe14c69dd4452c11481cb0626da7ea018035585fd51eb11391ffe679154925fcdbf639f9cf153cb6a6f3aae714fdb67d622544fe27fde8bac0a88c1bc305e400b8326f23af6159862470647e7ff0e755acbf2211929b7be8326a1d0cf5d01337e93d22aef00bd63da977b7a905650e43f815066f411088f7234d5e0cdb397f99778a5147fd405787bf8a3787dc5f71328dcc29aa1d84c3c27fccc110992201078264ac00e650feaabb52cf6ca22045425ee4a6cb284d0c422ab4ebe3873e535f290c94aa2d61d25b8637b5828a7f4479bfe07139c391196815632baa2a5c99f097160ec07e3b97dc312adc4bf9d2a619340baa0f1b9958bf20ed2ca8ea63135e3077ea171f681fa1c80fb2d7ad9f706948b22caf19281b7b999d3e5b29e17e608c6819d876885b00859fd81ee885b2d172f277edded648e22361a77411c7a211f1e660cd5ac40c2a7cc84f97e0c13da92b88b1ab8cadbca65c5e4bc478790c416a0159c15ee86a62747f4126f632db1a2f71f42dfed8429f02e9581e20dba38280a9a5d402cbd139b63aa5b124abdbfb041614ed6ad9bd5fe3fea28bc8337e7b7fa60b864f9c49e567a53359b172e8ef409b4ed0787891602818c81405bde68fbbb0a0c95bb5ac22efbf14d897891fe472ecc5098cb83c796d4f02a3d95b2a63ba2ba035a807f963418e1ca3e3a117707926fb579ee71ad942415e6d24570730c3e058f60879d99a47d0d6d4f6b04f9421282bf1e41cb6953d0a838370ba42e14559f32ddaf30a828db94bd9956b160e98af56aebdb02d5f6c84180a31a524a6d31dfe83bedada17bd0590e70df31f97bf572ba17e0fc650971ce86bdee28de19181ef3a3ea8ec288f74e10c2048cf18214e18d30c81829e0d243e8b9532ff38b3c5f5d2e35e82fd4bb22e9c5f234c53ded9014a1b09b75549b9fdb1a86c21bf5d1830a7c5dc851337def32153af699b38d48024e912c969782f530bd3296538f4ddec4bd0703573aa0f2671c365de12f97b4cec4a4b29793c7be99038df64cd6370d045efed2807c7a075747a84adbbf7b233683dfa900959805a1da6fe1fa4396cb864fc4e37fa8f62da5c8a0e33188e1f6093bdeb83c223d1debe91bd6b10f2aaf4c08af09647638b130b719340595e06c3541f6ad1f172931855b2a7cd84b0304c93effe1d9e014063c00c809089f31a90d7961ce422191a534e0d371e264f5e4b6bd295503ce5fbf6025357898efff21ca739dae1aef29e0b1c732b4946e94d4c57db1d0024e0815094e67f7732460d4a5ece0674960f2b3e529d50370a0c10f808100ff10823b06c1c8d2085f745ebdec7cb4c2858d02107c7425c28dff607d8d1965460b54b8d6f0472522c854153ae784451c67eccec0aefef0d6f81b52408d63202ff81261a8eb420ffe8c365f6573155669faee76b964065edb1570300a203ca1fc541a81c18c9ae97f8c190e0683611cedf632fc69c3b545b0320b82a1087140715c8415d0d4803115ee823ac205ccae1985e557031c087c4e183d08ce819072e16da8d33189c6b25842d68691061193c2f5c8e8bf5cb40383cbec5f441237815711771f34b07f590512dba74c310eec8687c79f6ce99c3b43a3278e6168c0eb69274ef8539f290a2ebb9faa2c5c0d5bd07ad9862edfd1042e844e84bafb8c3bece908592d4089aa365381c8825d176c2088da7e88d6bfee49ae73c0ae63daf2bb6b341230f64aea787c2c0fc01e97eef6b7090e0312ab59b4ef7618720dc5266dad0b7fb709fa20efaba616006080635f732bedd7ed0351abe15c19515b012b6c7f4e3fc4bed3872a17fe5874cadba4807004c577cb6374cc683a0404f2ab08d14cf24522b18e48cadf09766b1a7d69c120c37da772812f78dd57b45fb2beb470bbd2012d2e23031d4e81a2c8fa278381ed54a062f2aabea86ca3672632323e81cf6ec0e08ad0db115293dee081c0508f784f4ae2f4e7763b27c53312d03f8412eb4ffb6c9c1ef441d9e8d0aca2a4d022daf369c3a53e045a27fd1eb33557dce3874b917ae16a6559f63bdf2ea186c1a91de975aadcbb1251f1f4ab0b732a23e679375dd8f398c286fae1cdf61a02d7de1a00da5c940f35244710ca70c86582a8ed9f37f1d756784c8188477f20005363e10080a0c7590bb91aa3db5856b1b3ca33efacb48e9739c66506dcbf315f3c9877aa6f33e94dab09ce20e55b40e899fcf41c795236c3f0c86c7fa4c439d525aef85cb1489a2fad2faa5ef4741ccf5eb61ae43b3e470ef9258c613511e2dc41aa4bb85a027addf106a13a8241abfb0490e81a44199aa9352568017dca71df1986512be286b46e32b7cc0ce601dbcf3106a625f4a1df467fdbd52cbbb71b5d47d859ee403739938f5da80f42d45842d382352d85f2529860aeab45aa5235cf7eea910e586458e645cf97f834af6fd021883d62bec600f72ed6ad7c3909cc9f2cb2d016e256c8bba6cad365e8ba5a926b3690a61bfcb2005eac33a61fcdd0a47d69834870620db92f8528247ef39b0368ddbac509ac725ed1bdb5f801e73855b4342b27fbad3971851312176815481ae499e348a88690b595156f31e8ffea4c8539b404de85028aa814963985072034627698fa3b87b1975bc893eba49440e52208069bf613450dbbc610f253fdd1a63839c506d3f49aff0d66f1a7375c8531616e0954050ccdeb98e092c2d916a1b44dab82f62dc205717c288583ab40efc71959b9d095c0a93e85a2cd1821645245c60d93a3e1d0ab4e2416daa904a49c1fbf983eaea916eb3d3075e2a3efbe9571efcc6da265759a8b866a76ccd1da370f72c78e0536b0de3dd7b9122f477c6382b90ec37ea4740c5161cff3ec99f6facf5b2196959b67c7deabc4946bef553a317c92ed24b0bb76f146da23ac83fde5ab4ba35d73785b55100b39aa2b4b8d92229da926c02dc45c9caa2ee98eb911655a1b6d98e6cd4e483b60030795ff48664e9222440637ae06c55c14ba6f3426434ee276761945a77254af51badc37a19fb9b1ac0442dc756be51b354dd6e52613e646ac75ef7cac456a51182e9cf8ec55c1493bf3f4ec52792040dc246af54b8524f5c24a4b6c40a9f15e268672e9b6531ef726495b355ba35104fe804a6447d2f55ce89c45c24a9e851cca5ee93454b3434584dad10376c682e19a87a499483022dd8fede9330b1c17d04e228be075d6d69f80e1b0f04104a34e8e8958e1b88df0580df1af1b0dfbf5748681e6ce861940e63ca063cec7a4fd1133e2e9e841a6245e0681e466102785fbc372894bb03b51ddcacbfdb971ae806574bf7855f64b46a4515227f72a18b615d86f03d77c782872700f667071bfb221c52ef0fd04e032355ecde2a30222716272bdd0f63cbbc126fd0a2283ac8e5063df6a18eb198ab71f31430438b760c12400a36957fd6410eb2464607ce6079a5a6e68ab53085059a86078d81d0c02124f05948449ae5e081964675f16996e49d832ca4c4ab8ba984d36215cd4979e4789a7f35fcb3e8496158eb615564a5d7fc25466063b6cd232c88d3c502515479c9877bf1bbac02afb351e86c4a58a283e0926866249755a6fb43168dba1ac8f48b3e07c5d562c3e466bfdeb3d8a73668639d1315d99a8d4bbb50cbf802a5b929745144249ee0ca8f40ed469db8021f05c8528634f20aef442f812b196c811a117a7959a88ec8d698a787308e8abacdf81a5491fc439854cd54ff2452e576cc3166baab65887e3cd8fc9b231299f088daf2994b1529a502bb85370641d3f3967784a7e70506c2227c7159aa06aec3e8eb33d0f2db9f985f409e2578e0e1d3d7b11eaee9529e8e5d1d03d61eb9a3e8098484d543b554895046aff22c2cf177153006a98c8dd86becedc904c90e3c4435f7c40209a55eb0263c6db41b59619da67e5ca0e7bc030681cdd11b51d14b9b004cef65bbc7c43ff01ac6f5e6cb58e7094ad3dd750f9412285e593e8c997642dd4664403605ea60053d1597546c63922ae4130f738f31f5c500f583d26a86d6e28c9199b0b443e384b16c2e5b6c170a12a8f369a12e8a2e773c45b14a752d74e35613496b1b92425c396854d79878b249b63b9e067eab1b0da87be0849852ab9c405f92baced4bb97c00bb3807a509251bead6467afe018bc65d7beff41904c7b1ec98141742655a289044d2a551152a9c305dd9e44207a1eee534a02a7017565ca15ca69029abe066ce045c7db8861c16da434a088453414263631e58e527f07a94e22bd19124868f7ab6969b075389d7ededd54226765e739cece81379412d342673b105a8fbd2f9316d55de431197094d589ab908cc11114d06369689c1aac718a280508fd007f2de5a4247b998598f894567c3fbcda8751b97dc1945a7dfa29ab0d217efb55e4bbe581c3d7ba8ace3c11cb7fcdfee29deb705795c7cc23c78f44446488cd94ad923cbda241ab23409ae6559e7abb98d828a037f55d6f024e5e33237b1fd62918dfaf461c98fde26467be9d1f659d2f3fb5117ca971ec4d479a2d554cc8676a826481433c2e7e72cf8506374daf91e28e953fad9e16181f1d040042597220a07d41d241b2f56d920953f76397a7d1833b760acc1a442d6d0ae88c25958e083d86c737416b7465035b232546ae119689550fceb0902fb9bdb3d3919eebfaf3c5159fb30f37ff8f4a8a9125e04d6cbb05f7b3786f778afed1daefd9ba8ae175df0790ce911210ce283679c3c1c08a3594705544724161e23c00508ee21c8e0c9c2e8a3600ecd0458ba5c47eacc35416209b0fd5ac607f71a486fdd13a0a3beef801a6a458e7398a77355c33df48df0a037a541b69f0327a938300c93566ee88643d6e3acd018aa1fc117cbf3a5eb5e43d9dfaf4fe5b5348d8bcedd7720613380c8a58b14360e00d4790ae316f7e59ae45893a1140c8f45f9ad6f75890a9a9503795ae77c51674ffdcf823506b2c48cd4a2141a6b300a569b1ea9a47f563b52676d1c176e840dfc9a9d9219c10b952e8ac00e49a54905645f0128266c5489fc447686f2a3a0205c2246168fe7dd232eb971cd9b7677f554de4995c17647e649e3e6f6b44522366498fe32bf251a038db83255ccc61ab9e0004db90a6b78b7ecdaa317e1200b24200c1ad23cce28b0064685d5d3390e8d927d9a7041bb98f587a58b506c35440d77c613135321986fceb92b566d806e3bd79bdfd5de1c26945d97c39f0458d1cb1b6090977f36f632cbc5a187a5ca396cd6fe1681f8ab22e6d4094fa68dbea28d7ce3d21578ca0ae4bdd359167d2d7b4f43af95bfbf6b1f87815b8086dc606ba158f47463a9fa6935fa324a9bfdbcd961c122d744d0350f9ba824eb72675484de81c01ee84f2561759690fe5fe7d61954f219dbc2832ad1fdab300773d27ff6d4fa6a0b018bd0f9c3cbde52a0273316c4eeafa4f1f033d2c9a6767d8e60588004563bd8be14d4e0ee9edf5ebed5d67288258d6a4484b174af1dd2c311ced6dabf900e58abc0cd9337d994385f9ec1ba98b488194a534aec155993d5287c61d639235271dd50e8465f394eab49a7aeee0a3751d2861f0199e659d519e2e06e30b683185f221a4cc6a4ac6482270d584c5a0fb1f95d60a04f56e4537c1dfdaf04587893dd7307dfa4db2e8a7088c18c362539cd18e79782663dd849a9b40809f0f6ee6a8acfb03f8f77bc1916c5a0e01c6f0386f65e9675f35db96afdedd554d05e4474e5435572c62e823afc894d5850c894f352780fa75f161eeba5a12a2e6abad56a4e3bda768573a2e44e4d4bc6d584db1ca4407acac2a3c54d3c95ae8e05159f32e862bdd11ffd6d1e46004ae9e67c419466186dcb431d4093b31fcf677de1151fdb4d0f397a20dd7182b39f4b7a47c6ea0f3580fad3d67f0e7b38d93d285dc9d4b856ca4739e196d844298f3310e594479a95fb401d3a38fe61bf9dd206a303cf6af7a8c0e55fbd302ff1125b41be4bf50789a500698e23fb0afcf898d5d4066a0c786cc301f82586d1a73bc09bd089ab3dde2fc2f7fd24c2c97b5a707eef90042417c3fabbcad05ec42f9143e1c5225172b775aa2a84def827f0cd8fe3702ebf9b35278065837caea1ede4c46fdffb9808c8cfa9fd3489d6c8ad91061182baff03787a116f40ee87ece79d8721c25040a905a3d9b4e70fbaee914df7276c8be7c1c3167629a909436bd717cb9bfce11382e1512e8eda9ac53ecb3700bf3082cb736405c1074350c795ebd2979dc92371015e402df2656bbb003458162db40ee4533f3dd93a65777ae6c50edae9456b51c0d6842ed11ab6f05c12c43900e83ba959f2ddc97f1a352c26ea38ec9769f2f1373a4abd44a2c948f5c941d6b9d4a2c0ca9d59667291b8c18fd3918111ac6a92d54ea4eda7cffa0b4e6bc13adb04da33407f53281b443e0b6f048a0068ce669641a01f0bc31368fe864e985d76a124b032286293b21622722850c7ca49149989d0f0dd748c1a86bd3188d123be5cf781a3a567a822c7d7ea9895149fae27f846a862bac9a7221ea970476e69c5e2d0ab2aedd07079dda75c968d535db44e614adecbbc617ae6411c52c660a21a81861d44b5b982a7ac9d961496004a743b7db26a051b541f3b11a510af184240020bff7495adcfa239df36b28da5805510e20f5977fe0fecf1e19c5e8c3ed1368d4057171eb85318dbd1e2662538ba24845d889b65ba132fff166c95caac0a580102a7ccc05d99f87680ea8334352055741022d6ca21de5c7bbefb7694b5f39cd07714b8e079728be1dadd50ec56e33abeb94c0f3c0c7227583011ca09ad9995be8a86893e84fbfc2d0bbdf73f3a447a06762ce91ced3fbe3552a53f4a89a0655e018fd06315632d64540e9e8d99b8c02fd28ab51bc4833f19a3c7e36e5171e798d80d0aef70a84bdc4bd6fee6ee5dabe572e5def264d78f86f55d5681bd183705aae738554efd5a6eac73b82e95f8860dfa579babd7e5921f0e6258362edcbb263e1f67ea370e2db296958d1a9fb77c665a28c2e100be7e71aca40b3424cfb54d6ab00ddcc7acfb0f12d68050cc596a915ab0563b13d76105f55b3261c3058729b0871ef4a3a65636549918fb8631f13de9fe78a00e9c50c01c1c6097af56b6193c1bdcf62361746c9914475d92221f13ea0e74c4398042740470cb9cd67f5b56315bb0794059858233af823bf6222e1de3ef77187fb00410055c4f18cc719250381885bfa95184e2a6cf7c529452ead01bb8892fe85e9c32193a11a30ea2685b3add7c6b605432fdf222d25f00ff05fab676b2bd837f1bd47705045020277ccbd4e877f0ac62b96d8839bb49dc697aef148de76fa21109175b6d6904a5c04778cfbaee74f8651c10d4c62b3f612a67eb27e6c75e822d7a87d6aca06d3ab54b8c22e1864722e12630fb5f00c7cc2cbe53e88f66c83bf6a0b23bd9ddce15d46995e51476d2df587ecfb72aa571c949a153874848502a3b79e047deb52b113fa3d38b0c603dde66c33870a3930bd5a4ff1dcbbfa6242dfee2bda3f286623216cd866295939ee2cf26a76650742e8ae3b435644a9b90fb6f461f903ce4a5294111fcb3baef7d86423964e53ecd8b6e7688ac390176ee73111895e16c1535595e70667c7686ae80e76ab31fdb1dc5cd01f2d3059a731e4b33a9902a1fa32a646cd1d9f2e2fe00f2dba588c7c32b3400509a80ca5388fc308feb1e5ccefccbb513e093e7198fe02c6cd468797caed74cf9c3a78ebab3fe8896d40e0dafc818ea703dc31f5206e4cda8bfe46ff3ffbd79c813cf8dd67c6e1dfc5fcbd83bc586448d8bd1c20a3639e3b23de64034cbdb83909d1ff513c762c81427b34ffbb88c5bb855698f4027710f170702c33b5242ccedffa3d7f5dad702d8b7df84d57dcfa72f36035389769515bbb0b5add5e1733b3a549b0d0458a85cd880883990398676b557ad190c40c5d09b553b3b165a9635867ff309b9a582083e6ae013bf14f029add51926f9f5d512c448f82ce207789a3c5bb12d666218224ae9eebcb4ca2af59d5cd15a673634f2726c20a8d663a9ae08a8b6cea5be96c1e3c8f04805313b2e0eb4b54fb86b9578ff8a3148652952924082fed2f172da1d848ce500914c6aa768d86403c1d48b48a7948215db87ea3c2da7463a69268a6cff945a5850570530aefc0ed6131f96302f94d03e9b4255f73e1e95bc1f0905a7218c0315242394e6a2347b8c7f636cb7813efbb3fdad4adb0be902a09ee618f7d524843dd6e98efd92f0e6df4f0c1dd9affa89915ba2028abc9e9fa3f1f58737846b11b6c55582b35d63cfb689f20135a20cc887ded373fb187d502a2aed8c71dc4f7ecdb026a591ac1a87a1093838c9951682419425d2221373a162c60f07d20e6b78a5fd1f669025f217272fd1042f0045f9684d8ac15088908fd890db5ea96fb00c4368949385f1392acc18dac4beb83e32e06ab8dd455838ededa0313a6013ebb9ded9cdc32891a8bbc4707c1c3b380e6f235c79cd1d2dc3f09401720420bbe84694c13290db4341de54c68dad05814b7ba96f11e6585aeae59bb7a949009295631efe3e2d162016435e3e32c04c1ad1dad1b8e8eabcec260a7d0865fbab7c2fcd28b173e6d83e80b92f8db7de02448634bed61b34e07574649e8c6cc81cb6bca4b42e07c62d17fe568cc742ae2cb85d013839857346c46686da435a8fccb5afa63f1313fa25326a3b2a39635f1a410294797f45306058964654d0801a79eedc64c1cabadb54cc844c8b4af50cfec5922c3bbb7fc0e91adaf042d167d6f021b7a1e8335a1e9763f814217cc42933460d178526de09b6f459c13cb1105626b20f7d5a81596d51b1a703a31df3616acc9b51e48779d1fceb29cae003e0626d8533cbcfb56024bcdcce656774212b0e968328c88125dd0593b0d9aa1ebc76606fa78ccb8569bfd43d1e7e31e82bb3f755ac8e4dd27c4db0d2b45607881feebddbc6b87b160c057371a34763190acbd42e68ccd374d1e16e06f472bf940b990147a2a973139a9329d19f19ff7ed094727b3b49e5a656204dd2d2cd7088f97835d47853569161ae0246fdb592eeaae22af2d035d5e884f515f560afb860c6da17d1702be3704895394b6515a4ece2eee862d76bd38484c96f218b89ac907292e342737c7a5d79ae5d0709549286b277d13a1d12b9c39f6f04178ed488a25a674fb0b12921d4a46266152bc53cfbf59508d713b59d0711752f64ed0a28919a4e24840d90eb65d2bbad32dcfc0b475fd2236bcc24253856798b5caa257c13c1bf14fa0f83bfaa28ecaaece37ae341568493786dd4eb8d037db3dedb02df86c66e6775d301f5ed061f482eccae39aea2ddcaea380c58a5685e6fe0ae2f042103ffbc0b470b54e2ae441c4c0806d55be5346f48a438706960103c128f4c42c43c008ad115fc29bf050b494806d815e7f60a3851940d953787fe9fe214d4ad3605d52b3455288eeaf0c408d85ff93582692bbe87f7d8e2d5a633598d4fbb4107f6aa415bb1d1486ce011d88d6c647b092f7c31d21717f40941e7c2e43bb70d2040d734925f00bb05f929141e9dc53c2936af79d51d0aca82cb7ff097d953dd412ae5fd60c32030ce18a16e7110b3906bd883e159b9d5679897a26040714036ac2e3b746e931283df9171b4f66f3e52c9f4093a14fd0b3efd1baf9372b704292c64d18a0875dea3e3537c0dcd83accc22cc6db7f0d04890ecf6673010174655ae219a7867aa1d48032fbc67d4700b4ad77c2fe40be8f5821303a145e61f6598c2b181ed74bc8905e6e3d8540993c640c123fbbd273d8f81949b787f1988b3cf996ef6288786f86f60c0a888fa14413718e2dd9a760fe0668c668ae337c08c1e20019da1148a801fa36d324f8689f38926132cd2a1f08ea0fc224b3381ff02b25229725d41676f29d28e77d290f8d590d9ac8e64207c2339a8a6cccd497e25d7f67b64e0da69d61a737654099e09673d6c7f45e330dc063686a9b24cf44ef3ced2bb82f001263a0044d4643e82b3daf6ac02e7a3c88712bc3b7c3928cfbfea74ff2891a3a04c62c67c8d944bbedb5081b01c04f2010a9b6b5e74c6af0a108c7731e5405de87f76868d587f09ad179414bca77044921a03a283ce2cf412ee7d228c812fad7248990cbcff59ce1dbfa3b8cd2415a8ba35b1270603d9fff2c6801ad9ad98842b3871357168b16c264069a9fc912d3dfc11e1bfcde4bea5de357f551ce7329004bb9c1613e9a10659765cb40661ac7a5bc618d02935c675572c3b5ce39f9caf87f9d600d5dc950c799226f5c24a059c0740bd6daf395b07e30e51b4160e284382704871468ded74ac00a94cc5ff5bf456693da604cf2eab0ebd59cb3df1ac204be84d0f07375609d4a1f96c39b4a49098bc384a3358a2d9845ac7ba029dcd362c7d62fd5743c7dfbbba7b0ae6e2d258ca0ac1016ebf3b8c0a9f75dffb06d3709f64fe1da3f2f497224c4676e84b45c7db5fd8bdc41f0892e73ae790cc99c7d38b2fd27d6f04a8463fd8b4e739ca0c5a969a541aa2205f4341452e325b27f3a869e419c7c7749b33820366d2fee339e1d84bf47472802eedbb605867a9aa17af7881c6259d7b9bffb0f5f4f419b59c2bfe16829c1ad92274b5e1d4afc7896f5bca176a2caa4ddbfa42db5f1c548d0d45fe57d1b35790ccc66a19f18ab08853bf6faf81136914141278231660d2db216dd0338f18e0eb0c9774677c8bfddb34e68cdb0605ae604afdff8469d1e41370f066d2d151cff29c4e11a10f9c947222185c17aab7ea432c2b2e2f428c5913fc4b20523f81ff8812a7cc8ed6538700a439679f5978170b96c1fbf9f8a277bb76f77201a9d39a99cc3d3cf49e9a3d69333bda6ba51aa6c2c44440069994cbdb03efbeb28ef74132b6768741282c167f46ac2bbbf7db823f6659a08890ee87130e2d8290eabc84a8b46eda49523c5583d9c41bb088b7378192db1ea7b32cf10badd52dfe2ad5805459a3d73d6d945560f7050cc7e01e2f73e143ff45e898e0999073845d2a731edfdf90d88378e7e8b21ae672f904631b6dd3cfd9a225917a5cb035a8ce8560cbbc717e38988a1168ecbedbef7a11992d51ff32c12eb0cc6ad583d5ce1bec47e3bc8f78ee9022b1d75bcd228847e81b91d5924b870705f38a1f309ea4ee477f567ab13e3a2a9fffe619fc005dfc424b08e3edb0da028bd2c4c030104ad4f80b2a229bb460c82da2c0d39591357efc1e58891217d33dbcd8936be2b74e3da283091e2c691bc64224578a125a96b2c43a368425125aa011f8fbd89a08642b5a079b1cb1f59c2284011ebc497208bda0512c83867ce3e9f2d4e6085398d164d4aa01fbd8ec4427adc54ea153758effb7a1b462ca4e7805e5232b601b90be90671f874218d78b33adbcbab5680d9757756392035a0f62c2d790ce996a8af3df91c4e0cf94ccc2dac38418307610bf7d9eeb98afd7320168b90218dce9dea615469c2b06d70879745967cf3c904799b991f7f8fdede8b9ae250ac2c9188740ab73174c3386c352688ad96550b1280020d8f7395dacfb98e84b3ee343cfdf81bd840aa96034157b264cb5b6135d347ae036dee257e9147f608b8fcb041599387dae52eabe2836d91c2f2137c45005925d0e64431957932fca927cf5e9d8c25f12384ef5f2c320176bbe0fa5a8da580b48f265ad9062a3cef66e606dc9b85c9be367506d4214ec231669e0c56689f6f300245fc86b7ac3723e111328d6188252a33e21fd7e00506a42a8654752125b445fe0185b6575bb9a5cd0e336429a8e68f284f06291bda852425ed9d53a7ab7d4123d6f9211afea2709d67b94cc93de84273b090901ea7007693425f1ce634e25039e46632241366cd9468502136f307d337749972423f801bda7a5b44a1895d10b361eb162e83b0958bacacb22894412dc3d2fa60d93a37307f27d8a0f823eacf7ab7ef8009b98fb2f9d90a71e81354bba058a32e844187bd4fd3e40c40942d8eeef3ea2e79a86174cc339f9cb84473229d20b32cbecf6389c349a1e60e20da9c52de1d58ca2b9895c6009d470a4013464a460a36de00321c28276f4d69445688c3c6ad1de9906735c52b33c54646acbbb604413d13d30b7b55a299ce9913de96be23d96437cdf6658aaff78968a41c3be0ad196924b4dfb4e44b5591a58467bd1c6ac4d89c288b7ab0b37e7610db464a26c154ed6cfad8bfc691eb6501355f3400b61ce9774540414be58a90787fc2a5aa9a746f684967c18bffd9d6a9ea3ca4f68c23bda359af240e4650e8c1a13bd2198ea162dc1d747e6e3329b9a9a59186763ad99e32c4f1e83bb53d0f7f73067f839c3e48ad66b27950669698dd01090a26fd2dc2ce416224dc7349b0f71ca05778cb44d02b44c310f9985d3a03361abf03e485505455940fa9455e1b47bea217cf62654a127ab9e29febc14696b805aeab0af58aa29bb1de05b144ea96108763e712c5ab1607c7a7803393bbae1517dd67ab2039b0b4e34777c4ee8d58b6e96c52f60892f477c21e287c3a899524c7aa5b78ffe0bd892f364d285c869928f82ec28718cbf264f46ec0b8c962800bb976f864921b3e1c0692e2bb938a7057df4964290ae1198572ba9d43b782c88b83d6e3fa1fa79fcf2a06752ba524bbe5577e2324efb70a26175710506e9c72c4cde18626a1b1fbe33ea809a28a30e79baf17001b3cb93b9ac63db0d0b1f8fa6acc18f8acd66b2b4218738fed7e9eafa43c52a5c299f15a8ebc61882e9397966f12beb1f17aff21eb23e75683cdf204a648e780ef276cc2614e38e7913452b944109844fdd6187e83421fe3d8f8427356efafd8f02a0f82f6839b6903ea8a6342c6a27a3f49596d3547e4fc3e67b015f9acbff5404fa6a504cb45a702210bd0114b436d4fe89af3204750547d2949c1f1e9d9cdb321bdd0f6b1d4df37c3842855380d638a1b0aebcf405e8274bb40c9b01c0f09d6b4ae1857d69757523c0b8e10b1dfa1ac978c35c7e3558183a034b7bd2c48a142300fabd5a84ff222e97866e38f49619bfa4a48d64fa232d5bb8c12f1ff73a3b0eca4f9e39484f396c764b9e0a84b411f7d9314fd2a013b22dad01f09cd0a1382524fa2fe4c0023e341b31668c4cb842a0a8794e2308b1aa3aa3f48cbafa08b21f989f515da64751222e38940464188f7784fae83938d9b639045b17de563119cb65a568e22ac4d30246b3340b4dab44c0957bab5b0e485b073f9a77224881cb6df160adaf6c0310fc64373778858a891e82d0350ec7333a0433286a1522c6a770942fa7678f308c04cbd2dd6c6fed921d674a92bec4d00f111ba6995cebd0b33fb90443c35018c756d7c9adf5d7ee169382547be84f211c4c991f02072859fefaa0d5dab88c2ef0e395ccbaafcec794f658191e0b741565f62414891baac1694fdfe88c3aa4295fe94f98e39fce973c12c79e3437e4399d155494c91e3552db8423aad2bd809e20da4b592e996eb6e5fc93418e4306aa9ca9f7af26e05a8033a8db440c8cb1d45a262e4384e8a4e19d26c47daf376a949f02f154e6895d0a5e54e633253f0a72a0892ebb4d6b112641a85a8133e50e54618381f059cb697c4449a486527bcab39c91b92f9d0fd41d14b2b506dc80acc6eed00741459a96f39fc3e1530642e22e741180449998824092f150efc06a117ec3fa48ee68e9a6e8a04da730622acd3e10ae83dfbacdbcf2ed82298e2420f789a173bb3e864362ce29e68a8ebe682ac1819e24fabb4442b09570b9bd4e622330e4b1d120b8494d4f679af1e9274e508d24cc832ba451f5d46bca5260b2b0425e77381c8e2eda117eb43dc925b8e45a973280d79bf03e64395e960ec013a312ab059686141c0856e8cfa6503c036a7845fa68a2b5c67dc480a72183ca97434dbf908694ab84bf8353691aab9ac1589746afdce1c972de03b13c1052fb1baf35e3fd8c0cc137a4c0c21f77f92ac60c93c1ba60cc334b70b32a4cce9f07c255ffb2531c318d69fd722ede9a262aad8307166a4285e97b3d4d0fc347f3d080f09ebdaf7f234d3772c2da8f526b58bae240bd4f55e140e1fc6b73305bc7c0e57336b784da66ced8361814d768085d2668012bc886029980ac2f3989d7b63add697c8ca255e41dc3f3bb5090cbc4fb85239560d19ef01c8b81d1f8bd5c6791c8e0a21512c5ae2d19c768a1d7a3124016fd62e6d27f7f0ed5c3388ef5e41e0e1c4a82860f09f7dc1cf17b83298a09aa6dd1b329a0aedc8a6def5edadb808ce8ed27c0b4f0d84994408daa4b37d00d01ec531fc404a9cc44f674b49bcbc02361e53a1af0eb805efa02c006991515eecb0c2f9f9420a41f5740856670396e3f1db539cfa6d56f9ffe6dcdd588d7f6f724a2b706def2813a154c00880bab704e8e09dae68e7a08c8f5408f4b70e541748805e9aa83f277a050057fff12964fdf9a2da62f0e179b062469ce179caf0945bb101f92b5d175851d7a505fb9d34e3f91f709b51788dd9c426bcc58f3562a20bfa67783a87ff6fdaa4b19d1408f7860e6598fbe6dcb7e05dfa10c4fa74393926ac0ecbd0b16bcf40ae5358203eeb5870581bc9e37a051d8423d95459488966a9f27c09005ea1788608e0b5e854e004fd96a418b84f9961c0860e01c00339527614d099f493112211fdc421135d7780f6fb10cc3e5b3b309d52b4df365adbae018e011f0ab714313d4e52bc19f913977e3192da8ddf058722ec8e4b86f71221ec0920d38eec4318d918182184cce9366b2bfc287e5fb26e75a55541c4e1e05d178545d7a6ad67af288dcae4a621df1861b9e56c18948dfe7531ac117c7d29064385f2151cba9a3f90f5ff66dc4a4e64a8d877c124adca04f73f56dbed33b610a122a379cd07d856a955c1d3e0aa6faa913662a0ce22446eb5801283fc6dd74c18381a217c8799556a7b2ecee63ee4e37b9a10e0625c34b855879c5c8e24f07854b41878ca8c50ddec38361880d908d3cac62e2aa7e131c8612746f12e92d7eefcc2d3edd172eec9d35b7f411ae5e2a7765d3ac4f0b44e16570d4a916b9d66459b09a89b7aaa2027b95f6e1a11b3d7bd7d4e38c708f87d476dfdb934a8f8f5a99c224b238ced17cdd95ff50d21f9800e832a1f76d9f9fff7ecec940cbaec2494a33c32ea52fed172974158019f433776f97775bedbddf70c37c8d8cb6c8fa741aa0c13a52a2a946587435e4e6b3596f326f86d58e5cba94c3d66328c07fd0064506148dbfe8f62086cd9391c08240742c7116b65188884624fd806d648af82a4d180c3f545e53cb2300bd529a2dc0e78a5f5692c65607e2fbe5332146430c65a42079eb862726af92c85c16a59e61d4ba86a7c7d1791708c0b1b9d5f76a63d1893f16afdd959e6c4ba70307d078dcdecdc6746cf298e452c49a16c1beecce7879a5568256c2f86caa900c7d549ee98ad3d55a79891881915dc49d86760799b680a057082189d2583e9f5766cbb4fa7f85490d6e4d3609448617e6e2be3a9a7fae74b24f913f94835bb368b231fe39caf22d0c51527c123f78df37a96e2e1bf363221778bce916a3115cb94eb81b8003ff23efe347c1820b417ec494a16ca327d52a7ad2415bb650e7e11312cb3f0a273a78a5ccc978a7d7a0030a7d29eb2efd932bdeaa56c22312da8b23357be23ddd3251ab8098cc1f3829bbc15f71f81b03167e3585d274f4b870295e72b3a67befa8471c06d259636057a7fd21fd26b413f4bd076ffe3c0dffef1deb5555fe0b6b9dc01866e66b2ac4a119a02684474bbc65ccb1ddc5b82bba30dc8be9b854a7ac793d5e0e3651962f44da81ddae1bb4573a49cc0385aa56753ad61ba5a61eea9f2d2bd620273be68da02c6b8a8ce027ae4cd245e3df7bc84f7d4c1dad1112992fda2107ea68c81d4ae1a581852b22b8ca08dc6623c605f23032a4842af7c4996450ab2d698dda2d317461dcdc95c902758b63bf8bbadd2f7e9741b91f90a3ac6762eb3fffbcf59d1e3bccb528cd4477d6dd8c7222fb98f0b3ee2475271a13152b52e30c9c1a1438b464c5455ea1df8797575dc081c55b9653c244ce77d485af19d4ea5e341c75488bed5dae052bb19ee49be0d82ee6a74fb2d3211577eed127150a4c2257687a2de6347da9301919249232a6a0d0a0d20e5e73f08873faddd807c100ad45bcaa5016215f98fdc2137f54755282792de45b37f77d421a138c17a74600638539772fcbda017499673a06af6ccc0462978639c4d08d59f2654af1b7721887dd4b50dcc9384d3bc738b34b0ef4eaffa38cfed10c5fe8a647068c59fc3fcf65afa076fcf66f7640e7490b59375823e9aa45a041f6dc1ea1407eec8afb6fdc5428916720f360d7cfab1707260db006c0b9edda7a89c1dc02e0a26fc771b041e8308f61ec27e28272cc9de72303837d0e0655830b637f45df93dea25d2145961fd8012482d32d4e27d45ed5cdc9750c7f0f35705c63e4c5405910c3d1ac07e408c9e688024252226bd3e69debb05aed6ebcf681853835c859b745880c04cb6ec3adb29e7b1c93aa11904e88023c46992816cf9b73e8e4a97e72aff45573adab0ca90a937e7b67766890facd520b50d5c17a5b7ad4b61a8c5ea7d88ffe3294f10f24355c9ca73b2cc0c8e2e31b03e1480739b7d9f38750cd263a94415fa78d07539faf862522bb2b9c6ba8386b3b16e8fe1dcbdf578f6c15d116c39b18703650a3ef3194617dbc08205178e3927431b9b075deb907ae8b6dee5dd60f7cf3f1daaed72246f69c002eb6674b585394c8443a5ea25074d8b45dcf9b31ff6264ffeb8b017b58973e50ca93c8e2d32e0a405a03734d439e3fdc6b6eec8a30427fe13b8bc8a9bebb220af97a21ec8a3a3200419058a759596424ec473b1056f275339a837342f6754e2673f316f3ecfe74bcbccfb2dc2378e4bd98dae033683419ee53163d1c4913aaeccd2619f39966725c249bf3c3c771b1f4deaf6c31a2abe31feb203c2791731f6e2622cda8efa573ade394ab147e207b3666d123de74267d0227dd6f247886c6592d640432d683d7d30435ef8690ae7cce5431659a50d3e223de61f67a7d584cf41600f75aac1e1e23a81342ed061a6e4b25e260d9dae38693ac72694424c2a9a57b6b39516de0fbe06179562cbd362e78c3a39dea3856b58c728f05a3b2f6b8f4969810375210783e3da1598d7281156f7dd8c889bfe974bf0f6fa6a63ac84d69f8605dc6659ce0ace41a056c91f192c984b845719442534d1dc9401e1df10a3924c62affd09d890865ba48f28cb1b305e7c62a2c35e74dfe2d51b38d9a0e61e5fc795351749c1471dc853d744d766e01217e40ccb6e558f005a357507d0e2adc37aa4efc1c19bca040711e1b61c93a56563cb6e38967a23460b7044e2f20fe48a8fc23c2595ed1cdc59a3531a0349114c81501e1988b9c62aae564f8282bca07c45d9f2cbcea0a6f6cd36c6b13742d44f0254bba8e48f6338629b1adb87a1b6a54b7da1516850d183086ef24854d6cb337742eb1304cd20a0333eb02649c85de28acd904a65754214095721e19e4dd222a865ad62aba48aa31e62067e051fb8f4bb42b320b1725a94a699e08295574d11972324779605a1d03d98258a959dd32f318eed3b74e92a709539f1af4088052b80e922c021f09fb32bc42cc8e3747e2e21b1ac2977101f80e5219cf5ffff17a1bcbd38e41a8076924654dad44acb3d91122cb39ef28dd4407b2befe4332341be4354473cac902b3d69416fa3f53c34b12016690a91df54329d9a873129a691a275933f1afc1f3fe7a3ff9f4278c18c81ba6cd690b7b55cea40f3837ae03ef936d2b5792b1d47e52507deb780b5a0d303f009f05e3e8565b4234875c014fe4032d78d7cb0205e18b55645367ccf0d9a999aca766f8d768714ea1b36f936ba4adb54dd3256d0dff2ef0561c549180c63cf0d506b13b9d89f33a9cf042c04b5a184c9268b99583f785448a7d8fc9a490a3dc38147bc16d8d18797f1e40b48a41904b75d5dfc233e068c8d389aef42a6cf5f311d3829a2e4154f58f611dbd7faee6920be7efcb54063c33f7ecff15c259eb9c0db3d23b069a5292d1f60655c482a8d199354c81a18e94a9f2e5ff723caabbb120f249c36336f3fa0a5787c1c0b170867283ca6892228306f6ac7af173eb32a7dfceafdb252114f809ceb8bdfea492803d7fe62a5a8cc3d7c1069fb971ab4142d5bad0af3f94f96238e26d158400afb4caaa1fffd338cb3a0f4864e92b9851827fa4a6ad0b49776dece3049f6623ed37ced2b5d90d62e49f5ba2bcd744e3ccfd0088df3edeaf557433dbd0d799163b4381324cf34c6bdc46cfbcc75c369780848f0d1d03a1459c15e45b41a38fd3eca3afeed13416f45bf839ed0ce6600a21e858ac9d1c4b841229645bf860525a24fa6fb1b1c99e7e64c3b8012a1ce68f2e380c866886b857a2fc7720c61d81ea7cc09d198c047a2d0e51e0aa1ef83a4215194e6d05c83201807a9351629903f4998c15a29d1c9d454fb75342db3ebdd62a6a8e0ca73858672ec1f2829a088247cfa4be11b203be54ed61ee0c551098897ea54e19bdc19976972f96279eb9abb58d524572d6e836a04c721765b6c7c1b0e54dbc4d5658889bdf645db76ddf36a0940a09b4478bb0bb9bb70704cc392f5415291feffbe9ec86f8740c57eaf7f8b24fc3a673d83611aab129df57120aa5a67f250ff0571da7f1e1a90009ebd758b768a7c682c0c9d36d50573b2333f84a30a2079c052598584c07238f0322dc3134b5f36bd4fa141b48114fc7edaa4e267055070829c25ee87b735a16766ee66fe522750dae0eee91a08174b427a769350b9b59d2ae57928180698e03fd9b2ae96692e90490832f12a1ad27707c071d4531a02b824224fdb1306468c4a94103d33d23ea3d0d2ec5c6c07a7cd508159cab06978d386605b9fb63d7a28e32553bdf891a17f841a880d1bc850f5250622edc525d50bb101cf4221e8dae4578e1227fc6be09ed166c806e4b5f53b57f478841f175606d4c3f70c4ec67871c06dea0cd40c81e1908a90fdbcae19222d026d0461c80ac9215767b4943d7503d188554f1f8c3af6186b100d6e11cf868589e45a2e52513f730df59c9afd154dfcce1d4a72144a2915a853664b75e210eeedea77931144dc7254dd1619585f8f76de7b8e805021426f7162646b38fb86fa1ff45504827c978eefcd4c068f9d38c92722030674d0ca17f6e53ef90def4a5a82848d1200d5d8c42e5389cab9830dcb3936cf564add54ce47bc0373f863e1dc85b556a998733aa1aeb8d0ab92e8d8cea406dbb505128752b9dc5a4062baf0fe787d24f2797f0224f052b1041ce4caf44cd446cecd9c68e6111b43afd081c6f5fb71d34e8d36f2a8f91dfd127c9df514d681ed7a9e6989110f115d471d05019e27919b7e172f2e803abef38c1acd73c406331d602e1231146cb723eef322ce89171997bb3428ba7a37ae72e8807d9782647487a7ceb8f4edfcba4156e584b9ee40c04e23fee8190f6389d4233d3ad120a736ec3b53d508225911f3cd431f0699af00a79c3bf55119e4fa139f02133e9a8e0640b4258724f96bc8d23308a3d0626677d13b33da258906dc65c4192de0c13f3f48d0408eddbae4816b1dd60281f04f3d983706d6dc89475a145ed791db41bfb5e538778209e3e0716e97984dd0d551c6c19a5dbf4f861c75ca32335efc508f52e659e2ac6bc9fed345c493d3537e090b866fe1d2cbceb7e581c5fe478843ca19e45e8c1f359b6aadd715a8f5eae2e03a844229b107ce1a3ff621c82568e857c215e24c1685faa4cc8c4d1381b6846cbdbfe325c69f118d0377be13a5caffdeeb53ffc5ce2a5bded5c511e49f1b9b4566971ee4383642f844433ea0df00f53425cce70eef3a0fa61720ced3180d0fa41332d7032266e8e14dc403e007a394495c376b303cf7d7d8696d210db2d1f1cf8841ea15d4fb9941bd26d48a0b5904825a1f2fe37fbf4e21dfb053c3e0b21faf4af31a4e5072851405c50f7bd2f063867dc4eb92c79c87c1dfc30080ee2b2e92ba4ee1ada072e3dfb24875b4b983881ff0309db854cdd804529cc7836ab5f9ce39696c9bfcf714d5554d54b406500a997a55f1af7944e12061f2df401bd39d9e6d53c178e2a3f075214d2b98d695052e2142583ac62dca944ea0a74a08d701e374aaa7d8e48420b17e44c6221aafe60085cd4c17cb32e65099cda205ac53c110f847dcd2f327d0b158322cc5af8e0961149c397b9e73b4d1709cafd4401895a4f3779094d3ec6df84cff5042313ad759dccfee5ba6520aac6eacdc3fa2b9880d937e9e98b68cdfd9ec685dfe0d6277b3ba0a5d5e8d2b6122113bef2af0d92f18ab3d0fe882f1c015cd456ceaaadb459b0c16667f240afc16eaebdd8afdcfa72ef35e9f28bb0960143c86d81c320f54d0981f9013898d3ef85c3216cdd1ec5dcd89d611557f3ef0189c2a8adecc1ba492067161bf3abf9a64dd77a2ce468a6fedbf56fba4be4f746fda7efb8a96eb731e9d8be65415cf353936a3d7e244bb4ce7e0086989596447b54c879afadc88c5118ce3ad20c6334f58a184f620e9ede9d8b734f4902ca01454b36fc79df9092da18087fc0c1f40ec469d2e5bd31e4c360ae26aa2ee2bda9311a067dac299efe3777e93ec1ba1c8c45c5acdf1ae36b3b0cb98808f39548c7ab52ff2cdd65702bdb0f8c4529869ed642940ad0912c984065e1d0d69d7430f3a6683a7f78b057e01967ee0486d348945140d81f4b77c61c2c393d4a202b89603aaef32e08321c1cc0d6d75d8a17cc738eb80cce382278a169374f0e716337df38c1f69dbda2530e75a520a3ec60826d40364fc8c84ffeb7858a18874dd0bb6978681fbd14ec98ce247d1eeb536a75c09cc2d800b0761b6dbb5b4db6b973d6bbcfaa06fbb6b2e03c51f2a54e134665fc3e8c1c6a539476313487a96060184b428cdbb6f5b8bd83fc34043b799ad1a0d1dc9baf175b30768254b52a6b461fe5f640b685eae121f887c114a13e6afd78b7de3ce9436df70f654756b4fa32d50835e0e425de3ca18cb88144aaff308e09f57ab076880783baef055adb8c1a167782e28532735df38c92de259a6f7ae863926a56904e7ff6df2544ddaf1a853a338a8f41dc8ae500cbdb541ed950f2aaf672931b50531a8639672782832ab52aff6e1b892e1a44f8f653a301562f32cbc7ce65c6eacc9fb14e9e35f811e9f06d91eb0900cfe849579773c6e880adc5d91f963c06e0b2f2e8adb3558a6b289f235b1135bcb69cf3461d5f4cd0d7a58a39efd0b318427200e231da14acea0126b48390928292566c40432de6437aac3985cabcc9b4c0100bfca7dc4e560bb981cefc9a329a2c9e8af143ac58e7da723b1ae1671f6243f159c9e51dae8d3c4b9931b7426143e30e60385951bba573f15b0bf593050c671d206a10871d8272d38eba0cb1e3c40c10a1b54b060010d491555208a6003fa60a9ce587cf233da463709693fb96d5a5b5560e208acc966173d45c445c801f1860152bfe12d5cc44143d7d63ce5e79f0020031502dbccf473c2d62101780e974359c0d560b0d52640b1fbf86c5b6f608d274ce2ac97eeb456aeac23282f82e1358760d7750ee6f62c15db16ecaa0e5f52db3fd0248e2a7add542099c6c340f928903fd673bcdfdd07a567df9063446ea4b28f909dd6206d13927e515ef03a11f01d69f5c7c90b76f327f0b2879130723fb01523ca15b2265286cac7792d74d5ca0901a2f09ed1838d5ef732a3af6f4c2f3b7406c581121ebc5c50deb39eed19978bafc066418dca855e879d720c567c9eb7aa3aac26037a72e9b6a36cff13465891f7d9af8281fd9c45bd080bcdaa92e68a5e1db84063c36d1d503992a280e95d5795a07c9dd84401f19310362d0368a809629cf10864b887296813501a17f895cae235cc091e936e8fd594e4499ee3422323fd00fc9ef6e49d687d614efd909485c0a07c98c87128c463113a7fc8a06170482bbe25f27ab2429718f16e05d6729371f1626c8c9d34dca3ef73d889d574a19cddb6eaf7b9cbb2883d89194011149bd54ec65556c56f5bc6d2fa9c36372e6e68b352a822e150baf497d3273d6921ccc7af6b2080e4ca106ca839d3e2c76d05705880ff920f08830b435fa36613d4649a7721f57a0239dff5b35df9f5973f84ada3d8a3758729a44486acdc27f55d53e76384946439bd7574731e0894ea0391b0a6fb430bc130cb594662ca831be459e4c41ab8d1e2a32640fa881c4070e4a9fed4b6b105ff6942d4219e9555d164f660572f611dae10668b53fed97536b319d67566e43f81d73fd1751d0a4479ead63fbf489772eae8017e4ab365744efd038ffb0571a11063d6bf85a41ba11401ea3da21baa2254fce4a075c13ecf45295b1e1e1853c2ad0ca90c57495fed144a2b28413acb91a0774be6764410ad17efd01fdad4ddd98850271110390046a5f77556f6a64afd4ee6df1577ae51e1623345dba957ecb5ba63454a07fb6f38069def8e89fa9c478830fd08547fd544058c9142820524004660f04b2ee646e3a2aa8adfed8fd0f19587b80b8ccca8383dffe072935d3874e7e639d12fc594075bfcdd87898025646a9b9cb5b8481bffc08d894593b02b79639c0a8924ff7c744e4b872c6bc289cb18c9b3116aa6e9bad9dffa65f255aeedde90afc226e0a64ffb261cc8135308b60969373877aa9c9481f4384967c275beea2fa9ea786b23cd72386384aae326ab1ac11450c0b3460d7d0c85148abdc591b32ee84fba78486c57abda9e000e283047562b9adc50245ccecf8cf5191cbe97931011bcd025430f49d7fbfd6204fa42660d0d2d1adee701e81ff5a6ef46b7cc1e96fae9309bb0c10fe6e4bf206e3944fd85c90a71529d43cb6acea5643815c2bfa8347d6148e435570bfc83d754b5fcd32cbdbf33a2a0ad434fde62b09b71716d1d1564f6f6038bfde758d9344a5a01f149d605d2659f8ca37c7dfd036166fd344fbd460064caf33cb69947e7dfb7611b1b199b3217ffd1322cad73a7e0550e44934f2dc890826afd6611f8ada97158e06a7e66cc79bba8cfbc7cee65164d6d3fdf7824690b401ef5ed8a1fc865e002851820dab950e85bf26955ae671673af4c8d2942721503d566988fe8ee229b5377df4e463485b5a0220ccfc5d6186ec4c5ec778eb9d1b0ad8d0b89d5a84ab4e70f270b67b792cf0b0d4f5afc2e474fe283bfc8f84b1bc3a2daefec77b7a063dbc10bdf1949b09d5fe21156cc8a5c69b10b217437a86d31320ead7c4278559cf615f74d8e7cb71474f1aff696587b7407dd1af2ab0b8b263a7c694fae8e6fa6e3606c036abd621b2c90f6bf69c1582753c845055c171b8749bde56f6515219d76d17d73dd6a31a7354dd106056aae65dec90fb944500b58c8d115bb48052f3ef9f59a2304a3dcbd30f3ad41917ebac44d4555261ecb9bf2e6c0eda6bf2b390b120a8bb54568c2aedab1357b8af14ed37f1ef723ce331aea43a62f4907ccbb372751bd92854c4197129b381e7d9005e6eed85f343450fbd38ac01f30bf7e1b2f6672ea97be9311a7ca2481393a64a945096df928e8f899fffdf36de8990433145f3ac3bb5b909b7ae4e432fc02966cc2b0186d37d407d8b37cd6c838853fce51e12805df55766d81716e60ee8f679baab061b43961a40390805e67cd68e8f7078f90ef4712f5f1aec0249151fad89933676a729d5d710981dc0998fbeaf07e2d21b4b84800d484239af10965ee79d9557714b11585fb528caee4e4d21446a986a67dad8df725445e155ef8a652c71e5ae6f857b6692777ca44678b007bfdebcab8d264365b41174bd4b0071016e02d7e44d88abda4d741724bc30beaad26f470ae48684268e02abcb5efe428400080e160445dda883fdca5291d4d94bb8cb9c13f112dba99bc4c694612e09f81268fbb9e6afb18ddd16dd767f72a9a4b7feb8b9449254aad4c1aa8468b42f793e702433b86b1aeaaa7e2db647a69e45fa0dfb81882f6cd1887d53642e70686190b10581b55353f165b8c25a4545467f3cb64e0966ba292b14c4c19fe18e31a240441eeada3da227c30a7220bec0241f8b707d390c3990e95be3c58ecb05261db3f5ea3e5d30d408be36a8d964afd62b9d94eabd74e04eba1bf231ac1ddcbe6c36972c113962b537542e4b51628d5f8623750df22fc8b57348e741757011b3f27bada84c1bd1117be6ead55e9d210d8a0d3d40cd80304e434670710476e8cfe5db12212508d58821ea4335ad38f71e2e391c8b9374615c64c3820fa8fa33b0a8e0c84a68acfb842cb892f7f284b8081f023d93cff7111f5c5fc507543475739317a91e01434b590383179a1b951148ad004797dc4972f39e679498f626317a4853d0342d9c72faa2f54e6f88e2e399398bf026838835cf3db49118a2f4ca133fdf8a4e58d8ad49ad651b416239869f23ca326ac4c30617bd16177fc7f8ba816124247592074ac095b36fe252770f2145e3238d4288e4388b2e1ab3540788f27716881e4e80f1e97c659d1ba4a988cab6ba30fde028510b60a81899757c07bf70c467b02c22af1e271b6e36d2fe83447290ba36c0859e2fb846d8a828e5ece9209bd3f0d39e36c1bb153345999b3750d539a3c833978c90573b0f103daab471dcae8c5ee659a9b637a12813530fd091851f2b39e4bac7b361d93c9ec857266c2e3cdcd2602040f06193b6b56a4f06e955c1790fd44cacade5af946f561ca0fc81df40764b47348ad14f005ac53f48c64b1c10cf68fc2bc421290ca5c0ca51a55dfc69a59f5a16a2e745f6c85ab9a48f84ca2d096dfdb862f69cdc7e6c6f29b8f5d2451d889bcd846bb98113bc6486fcb7b396c1d347b0865dc397948b4ed16b8aef9bde2260bfa3d0190a32d4cfe998054da9f717e85712861c851d41b0916b5acd9891e411e2d2233ba22a8f13b20c41a188515dd5d7b4b27de1369569628268e363f139035ab2f78f777556aa89d30876810521769b76a4cd4e943a1877ea84da434f23b45b73033080614cd4a17a07a539a8b5c100649c70c260ac86351b2db24d900baceaf75ceae506a8a0baae081f865690098e82bf9b6a6947aa8bb0432616c9eea6ca0fbb4c4ace0e29f8a32d311914949dbf4ad844b68c662eba41514f0ac6763491b3423f099552e037c143ba25117434057d00e686f588f58bdb1f0cfc9d59a19270dc9783827842744aaf4400a8eb8a9a275642f281400ecb2a060efb11a57694b870b73090e254af73e627db5064e6cdaac8cf9198e3ba962aef89dc30268a5d4198343c84df57bfdd006310e8c642ee39ced6a370ed5b45165c4ace0b01c26e7fb106d282f3729075a101074d7aa014b625027db1765a1cd7716d1097a88c320f5c4940b787a7263048ef443c43d422ead99ab4178a85159e628da6e80459dd5d6550cb0a6a55752fab18146444be08e2d11a8061936477bee1b5432bc84ab18eabd1e78d8240818255e4e6f7eb10589644e25a8eb10182108eb6af16db440e72f1b1e34c6d74290baa557798c1b92dfaf8580f12d915248f0b8cc589e31804d1403858b9108bec9cd0c50f1407580b12f91ba10d9edf495f586443148765a940cbf3ff0d81f6f54e53292dc740023d40fbd779a145179db6f754ce9ee6246f9d292b97eca8c19a71426565522afbc709452af98bc57fc2e0b930079d1c0096781f0919265bf8b1f7f76ad957d3abc6a2564f43a74cf7d4801f4b8848458ab659451116d3a3a08c1e0371d25b8bf45a42182e7703a580df91075d30764d9839225e871d0fa3c5811f68af9710f4b0acedc9d75cb1da7660d3e2ef14ac9ab246bbe8f0c5391e7a16630938c82898caab2c235ecfde3b86e8df047456f2b5db3832e473ec37da351c69c987c19d1b8810bdd037d5980dd705327c44d4c3505fa261fe60aba8e03c1b8b2e9d5cf13dd8a77d1b273d1c58d09f0d991666ca8fbfc09efac037bb72805f1d8b90582e4ae54d146a26c826f7b1d97d96740fc0b28db8fc96317262b570fbf8ef0a49cc7e98b45be9aa0505391da73d3ef609659d351a9ef7575d13bab893051988accb4d7abee8d864576c576c3cc1b7397979464b07f6d18098cb22e387777c762521cb1d1328ab53810bc5998727ca9a65aa1aad7e94e5624b885032e6846d9786f4720ee5e1ab444d70c230f0091b55661b1d83224b2604acb712134e4222e599fe487381ed2041959c929b5a10d606b8339c3a145d2c336908eb16ac01c2cf5407ad0412e0c529d3e7ef3b40eaf031a4389fb33de4dfe0b75dc61bc3de89916c9b5bac6b3b2a14c4cabd243b428cfaecc2b389f9b210647e75ac84f86cd88c975a7b503cced62df6a0965f0334d6b0766f7d0996d46e1d6513783f33135d4ce423b7a59909bc3b885e0cc3ddde731418220856401ec7afadf2790295f97fd85a43be3861800397c5bab0876efd42eaffb30db404b90547b7a6376a1d095fa8f8a4be6aa960e22d1de7432634e4e897c9e0431981099df2cd24b427a590727e4169a0555a4b359edf549eb29550a4ec9c59476e5e5a065a5efdc8482aa5a9a0300255581ebcc48e01ef515675b8a7cedf446cb11cdb6eaf5d02e4d27df6c5be27c63a74d6069d7d9fee467f7571e62e93ccc6757d2c4c5f67716b309072868a7a458a323330ecf55d2802aed31b31ed47bb01b06738543d4ae927917747896f01f12a68de6ad72e2fb983a2651d433e8e62c93c8a88cda8ddc12f2f0934909a2656bde57e833a758a09eeb82454b2fb2b8008d3c79c06e63ba624ac4fab06e96ad46c27a61e84f92b5db0ae83ef0056b4a7b69e89a87f8e4b627e082a92aa7d471996b0385e2a54b0a9591ed4066b90459d0bd112baa07aca0f31ad0c4373f742a1c2dbe8053792a45b7a5a696631255efb25ce9c1e258d3c62af0a87d7167ab040294466618668236de1161f3c142799fcbde1f4c3908fecd21c3877d4741aea3dcaa83bf1cb1320935d48b427f932662219b6432d3992d903aba33241344cae7ccadf375e34ca9325e95f610ea17a4d29e46712dcd469f1f32cc4dd105652c0ecfc7b11177550a90c6c8518d486ec341b5e84936d24e0e4a7fb7e5b2e79dc91a4ec2d339393811647140c8e2e457461c298b99e170ad2a48e6a01a583c6ba80eb1d316a4037b026e598d5e50312d9432fd2e0389176ad399900009eb01246773a0049aa13d08e77f51263f313ef00b63079f4e998a8f31f66e4cc00aaca00934f15d578b72984254b707d48e5529671f6448cbd78f99a04edfef914ce0e3973907190a98d1d2cf74b6651f8a2142880e1ad13d0f7977fd465aeb26c8002756905a1fc9a52856a8d3e93c604f019f563c5d9ca1d8a7271a6e234783309cb2f5387a3e7a906b115f994b8a720a681a589354b969dcf5addec70e22740070a53c9844b64e31043babf7b58492744b37b7a084238e7819d081188aa4d95d52d4d04d47ed5fd81fef0502c0492d1514a2fe1e23a6557ca44e4a505cde5fe64b32d5072178fe8d8c36674becf90039215f958d1979477876322cae8281f777a9ce0c24494f0a9cd9210feed6bceb245688323fc43cb8e17084425ba470ed86d654d9beebc490ab5d59855dc1c7466175b27c1763bb49a0e9e3f8132c86f6ac58e214da2581185bcb0527e2ea592fecba56373023e585c33518e138d2e00e4bf2337562988e5a0d4c43187de7b06a0b55c1fc45c4f3828670317f8502070aec499a73e8b3569796470402c57e1eac8d5678b119c42aee3023add69a511d4f1c2296b44c70da027bfe1f11bf48efec3be3a9f454bde1aa9db52de4c608c2775b1f549f237a51aefaeeadff7cd6ac2e43360a7f500788310980bcf04974063a122cdb09d70fef772e57328b882cb71573a0223a6cd0d64e652d97465dbe1a185fbf42cc7d5a9391727c6a9f330e019d9563a83ded0f67b989e95b2d269734c8abebf383d7e4cee44a6c23bf234854afec3c6993595e0c7eda62eda537428426a165a3adeb378770d139c7abb50d8dcaecbb346f22e48113c35900d49396d0f467dda8beed6b1fc0b71a1442aab111e8b051e4dcc27d0340e0337636266da05ae6269dda97acad6162cce6b691b8c15f27657341c828a4df49aac0c5dbd170601b6987cb424496d329c64771938f309f2230291bb19f618684ba9d36e382f67458c7c1c9ad3542be1c0af5327688ebef0be37de469d454cb043c977e48be1ea78094e6f044de3aebbe21cae43e764c6daec42c3146adc8f0171e823c99366abe14b4ab1c543025856b483e43a230ba3e59ab7a74c9d651a8a916c72e6cc1d5289759bd618275acba0e815ff8ebe375a53abe69da34820de21270a2f5309f45e118c17abd33c33c49250834a1e432d62e54b4f8d366167ff8790eec86dfd25df9b8045c09ae429290cef010e31ecaa25f18a690328a7b0888a339d3e24086440e7595b93f11e8a2cc98cdf039c76c4dc9018e9199c87eccf94b5f60dda4fdd6136d8796adf8cff26e5300da93fb6ed3445165bd7c42a422718020a2fe782c08c496e86ade4596ed74e271ec946bea80a379f82de8b606a27722b7af3366ee5276d5336d2e6d8dcea639142547676cb4c74b1d41c96b6b7904fd876845203109c5899c26555a8909d29d7cebe32b0f49db60d98813803776e89944e5545fe6baf425544a66d50a24c8152c8e6641c5f04cc6313f75439e8faca1f321ebc8d1ba4992829a104e95327e47d168d501a21cdb7987862f49c84f4660b4d30de36cb75623b7a3b9fcbe61ca403ec1681f1fcb52ff527cd5c93fdc3ea328a35b170ff09690b9aeffe49a29c5ce11edbd287890f0c274dcf3efd4c38798ecb8efb1731a07a4e3224fdd597108eec59ecb2f71e6a97326ef52687d89f63f38a421db1dd0f4a3f8b52cf27759c78e56ffabb5343034e4d25617960bbaf93359f64d84947ff9511ee8e7fcc51b97d9a02289becf7c51adaf0ceade5ebfd211527da8f989ab683d24f4fe0850a0a653185260840a85e1f696ade945023657653524a196b0db8e696f77a2859ecc45d7aaf7bb41eca10ea61813da8b70447492c26f0456636572d96e9e5e993e3c87c1ddb92dc540695940800eec9ad87da402681b31fa99c5529f52268a48f7d6148d20f5316b7438bba6add866e05573c74eae24692d267a79b007f0e892352fca82b1dd214c4add11504686ba1da2b41f958a616ced43dcff65e8bcada86eb74fd3b06019f443c30ad27b67507fe059db8b9caae52840927580585d0292efd8d1b504ba7a41ed6dbb2dc2d2f818b519f6996657af0c73c2419fa3d84eded75cc918923405921dadcad4a6bccbc607cc73d2f899dadafc10a6e9266b911c093b9ae02aa0a7101283486990690c94e68f5e86d7bcca63fb4c2326fccb860bdc62c38ab561c0340d2b2ae1fc9d955cfb06086b0c7a3fc611635c8d4d8b17a1c4a105f2fd604ccd9558d7149eccea3387ce917c4cc2e3eea0653723e128875929b284975cfc78fff1936eb3009a570aeed03c337b36c6995abb7e5cbd5070f7cf065b2095afe4bbf0a8723d257b751c768add73826fab2c1ce559fc0138a4969db6cd6ffc26072094260cbdf5d00a65afda5fb4d8c664080bbfee92e5eb744ae802c8cfeaf9a839b20de91e13021b9e4f4d74e830d9c2be8c36968478356973930de5c10e7a662d1349d1b40b34812049d06ddb49ba1a685707f0221007a1a3b72d350ccebe1c9217e36bae0949042f9c77040a7efbb2592056aa9a0ef1ebd9dbbcb5bd69319f42be06956a18d430f945a0d3fc25aa06bfbf48843a6a19776f22fd7812843ab41b00f8410ffa50b35b97a4e16e60dfd813ad9557f0159fc945fc2c86466351486b7b77db724b29659232510b2c0bec0a9df7f9fd4d078e873c625885e8094658fdcd79c0a0e74b8c31ee3e1eef7319cb677552560b006578282eb583972484b0a7e39433c040e30c200c5d7c46106754a177348a5294ce28018f9831bad89e0e33cc6032a352f8004611343c6320cdf0595a220616a95611c4d015b76a6c59b9248726482c61a2082d5870822240e1f3537388e1a764bae249d7279c6c500413e88a45195c68197c690d2f5912e0a21f3e1e9682f0d0e4b0a8f7c564e4c0872132aa10c111193df8e1898ca5253e4b440822480f4d646001fa2943c9921f2382f80054860a8096caa08192203f04fd04d9a18c278408f253c69425417c6a5edc596dadb60b2daebcc400bff8bc2c31226f81821eea511194747d77e1010fb5a8b1c50c30d3164a84b1c5133c6c81c5cd220da0283a1d52604e47eba2b9872fa542505742a5f421fe2ad495aeca5bfa7fdc5797026f3a4a81513afe4c164cc1251cc310f934484a5ba9ccad3939c7dd1f0698469348772298904a5ca6232dca97394459cb43570ce5a73a6eabf146be2b95b7cc2186f287b46edeadbc3f227581270c80a611dd0ba4b8d44e7e7e67933e0e9b9f7d7c3e055b1ece1d9a29f096b4ee477599fdf9d267be05b5fc5af078c3d2f20750ade33e7c42d327347542d39796ded794e60968fa15d4d5daa7bf7913a4924a5a3f219bb6b45221c49c5a2df7af353a0f1dcd131c5f468f32bafb8cbe030e419a3669696dbc1ea760f9b1c79bb4bdb1e3e2666b541569e97181e3cbce658c51f544cba91af2503e5e3245f82dfc25dc00cfdf9e9e5207b3521a220531b702bc690af0befaad4c212328ecbaf8d55a6bad9e87b5ce3969cb43242708e1edc92b4f10c2f4e357df652710e1f7d0dfcb93c769fe6f7debf5d28f385e027b78b62edeba704a42c1d2142234e998547ba2f74daa31d19daa82467ec8b7cf43935c1bd391cefa43c6998d50bfb9bf26d7bdcd32caeda50a3d596f51e0ed69ccb106fedefbc76e37eae3b71d913a888d207a90a269166223881ea0d0f487c4aeceec89e98b446f6fc12d8dc00f242af2b20bf5a94fb2832413f13e9591c409f31ec9f7497674a605e71b8959b264c1c2f95ff9c120818c4cbdc5b4cd7b88155dcca1288ffb0b76e0e680ee62f776a8efc023545f0f39b027a637eae1143cbfebf29646fac68a7288fedeff6e076e0ba4391c7417d357fe95ff815d3632750d31cde53d44f7c4f446f5d685dbfb52e61ff0cc9205c875adbf5312f098402529c17db23da051747d100c0144266303d9754b9680eec9d2956a9c76f1b37feff6123ce2faeeefa9d194661ff0c6baeb6e8dea970cad22561226d8c9b3e20ee9fa9b6a09021f5d5f7a939e1a8945db0ee85b6b0fa76badd9c8a6b7d45baea146dbbc65d03d359a3e96351f714dfde3477f8b6a6d552277628d77a08b35744269617e73c7dd86973cb0f2c4a7e30cf57e89119424c61863e440cf6f5931621653b4738190578c9fb7d64a9a1838e0a8bcd1425f1e1061c9b4d6da786bc2a0c1a551b4640be80a1fe8961593262b50f4316b90cbeacbeee557266a95127d6c5f48fca489cf7ec2e45721e4d72862bd42cb8fb1f240cbd9519f5f02e88ee785942e2790771d77f87b640a0b30004d054b304e1d5a91d278922f6778c24eb8656cb1b40406238c8146136a383181113bd60b1d10218504155c27213a850eaad45a6bad37b5a12150984209255e40831e04618929042a6e70a7a022074aae9309ba32f48e48610ce95845cfd8b4a3d395296118419584a029091f5031440c72d8991f4bd2440e317842c887a53296eccc2f329ff494307cd09c8e53c2a07147f8e847972ad07c7fe40e2aa20f99ef0ff1664679d4ca914f2f9f681469c2e6cba7f9ffad16c6ad16c646e0ed4b1469ceeed723daa64af7e74380efdc9fd95ead53da5eadf36a73d6cdb781eadd29e95816f8fe48153c14c41df3e7df1fa20f2f8978337f7ab578038477e70dcd599c537edeb14987f395a28f31e2cc8bb025a9828c3bbc880e4694d197d1cfd0330b3d77bc46ef00714a7411b736b7d5daeab2036ed171ca107620021cac5c80b18e5386f0823586904437e7acbace5ce5ae10dddd1d67fad74c7dce293deac0dddddd6194d2c8e9e812f2691bb4e7db0285f449abdd2e87ea5a38aed2d9792e9b57acc840658557acd181638b1489373c70465ca545ecddb6b854a488fd547a6edfb2420aeb75ab60262b3a3e115b296e15a1da978ad425ba34976ef4e172a38fd41301b8b0380375d4227e90388bb4c8b55fc4e92c527f698916f92257b26e30e79c94524aeb56e7cc31aba3661ed94d474d4cc7ec612b0458fe7515e85f70631d5dcf9772477c39bb395b975a4a2d05adb594ceb89368ffac545ae9bdf7a2bc70c7c055ea5dcd790005c6489174af56abe36ca5df8652e3769cd35ba78be234e7c9ceb9ef3e4cadb5956ea8ce7ba9e171dc4bee7297bb4f1ed2fac34ffd4a6594de1030fde1e7ab62cab4c8188d21ea86a63786281492a757abc5cdd4b748a79a50470c27e79f665a5998a2bc581698be4b15e6c77fa56af7f1fbf834443a3d611b2eb044a19e6e4f4314e5b6a71c0a95e482f2895a6f87d3f73cefe3b1efa1cceea748fab72c9de4952727ea14bf3a5d52a39494560c330975f243f9b12faef8865084be62e5175ba8b0b1bef06182c18c8273bef4a4f3af0ff49cdf7ad2d38b223d997c8490839eef31ce70fda2255b588825df1182544804e165214a4121a24302d1b7640404aac95bda810838a00ecbcf92163ff466719ab0b8d405113fd6305aba52c48f233475e1c3103f8c00d4c5114224e1a88b27421475d144103fb038756105d08f24c436b7d5da5a8508a8545102a32a5a80c9a84206483460a2abf3e512354a21d10bcd7d273d6f21d4f8bf2e765e3f1ed577b12ca820c19511147d61c586465ca2efdf1df4ddd6d03c88a978458c2236d12242c45f4420c4cd0abfbf1db94c6a4b34a3882ac4bd31e9bb55a1a2efdda6d0f76344c0f7abd2bdb8a3d3a7dcec0e0cccc36424310ba11d9b5df363be164dd8fd243b1be8220283c4357ffb985c8990c81e7080050d235ad4b40385fb243bddf4bc4041183b4840c0f9b003a5e541308300c50e14aec7113bbba59f566f90d1924adfdf8a5ce631e1b2d4f6fb5b4f7b1bda9af47d19a0f9e32e153563e7c5b2c05fdca11fcf10d7eaed6fb9ee78c2a1be7f7f2e90be41fade9abebf35cd59ecfede9ef475d2f7b7a38d0671b6fd7def6feaef6f51a28f7a37257d59bf5f716b62cbdbd00b2b6f95aef58908bb7f2b92bebf3725f4bdab5a9bb0ab05be3514c459acd6a20fe99277cb0638062cd07722c58080e7cf84ddfb3224b833ef8b7dfb8de86380fd6d68c2ee4f24a48ef3008005af7ecfda6465979c5134df5816786f433074bfa9cba37e841ae5a02bf4f07ed441ba6c64ba5a80e5d35c0bef8d13f847ea661ad4e98e7a7f7fc3742cd2970511c67dd3efeb782c0878cb3623a131810f9078a2ed6cf94ed87df9f5084f35da4edcd16166d9f7874469f9cd6c37fad157c9c3fbad2b31371ae202da40ef7360c8bc7fbdd93267d461f53d257deb7d35bdad4c0ebbbf75d42a0ebb1fa30a558bb8e3fe20dedcdf386e9cc055ff8d1358ea6dc8c35b9526ec7eab85f13de2e1cd1ef0f03e073cec82f08e4efadedfdbd08f50e31c431d9bb36d28c2eefffd991ce0aae4b2386529077dbfbf5f99a40afeb756f1f0be0adc55a9854f7fb9c6c6c07b2229cd1b276a1bd2f727d29421e9fbcebdf4bf990618249a26e9d9a72fa5a455e6df387137d473206a1e406ad06af99c8ea3c0faf648b54e3a87e5d9648d271e9891055f31f47efc4408ab202eb43042524b08b0bde287d50fa200230946605d815d66e05285052d2740c14a072e481083205acee0a2872fb8a005a1ebb06ace9f33ce393f77f73a77477177d3f1c6dd5677a713c884c5b7f129a5b5d64a2975e9ee94babbbb7b4b4b2a0b41fac4b8ce1695c6b5c5a6e8ac484a76fbb6eb5daee350a84a27aae33aef7adfa68144a400f0f828936ac1b1032c3d3a6b4a5577f56dfdd613f875ddf28e4fb6b62c147e39d4cb54b7a926b5bfcd6ddbecf6133412e7a493c6a9e39108065978614807ce0062c0438f33a2b1d554fc19c543772b1e3a5df170f3593489d29844f3b274c4de4a13535caa1273556a62aa4ab54aae4a72ce7927510bb730ab65873cacdd5801deb6668528562cdaa9118d0e78ffd7b87f8eff0ab445279e7352774fa292a9114cb182f442d3d35b624a85df7e47f3baf1b4a2bea35dd7a1baa72890ab1cc7715f6374f73699e8c4d414638cf708a51733452b4a5568a55fb95a6b76252373dee8e97ad6976f79f97d75cb04a24118a686fefc9fef565c86baf1f3fdc965367ebe637119cdcf9f3497bd7e5eed2b6e536dad78b8e4e17c10ac55c0cae4e1fc076b93874e60b501d627b0be404b3b72f991e6321ad747c66bc5b07ed754250fe7a5d13c83629e404a6f27100bb4817e5569ebd0db9584f47c0cee542bd5a4e6e19cb3daff21298abaac5b02775df7f8035b4be06fc6da72ffd66a150fe78355e9fe5c2517e832f130c8c3c9ba3e3ae03d81805c56997893599bf78c5aebad55894241434257af2ae9495f6ec8d515d48b93b69dcb9922e0f89b4a461f37da7a4a55b6a58d292ef5c8c1f33c506a1a24b4aabc3069fb97e60c9cfae8a10958f0ae3f3b47db971b04fccda8f1d76b79fb174711f0be61eafbdddbaf45441f46367df3be7fc3ac40d1c7cdb4cb3622cd99ed5e490909e9e8c8c8a8a8a8cbf6898776d71f6defa7e47fd14315287f932e84eade997cc672293e63b992fcdb99781369455ed9124bfd613949f1d90b90ac22a95821a56cc9848c229fc8265b32914a5a7e472bf1ca8e58a490cf5e8826ccc7672f4833f86c0e1571d9f7b14aa412adc079b13c1a84b70b69c9229a33f77198fc4c041269fb2ca439934c0e93ff79df1b79631b48da0ad1d49f57fd71d59f99fc6385962f937725a2a2e5b7f2ae45317957a739933e3ee50bcb89c572d2f65f80e66c8617a2695779b7ae60f92d40da5e55de3845843467353e87e47b2f48dd0b927cd40b92b6bb85875e9c6c4817a5afa565e512638cf3e557be5a61bd7a59bd8346e2cbc78fe7e575dc63cdf7cec19e8fd1fb086e4ffb4fd0c8f4283d1e36c0f5a9fd6ae4b228e548be4ba9cd995fa14f2e63bd7c8a65f5d2e5e5cbcb96c7aad4cbbe2f921eb771f133c2d3fd0fa9e37c019dc99540a7027a15d097406f92afbaf2e4a16371987c2f3b928c10b04f34bc4af150522c95567d3cfc01ab1220216d03f51c29d3270f6596ffa581f422af181406974a94524a69a4946ac74344b7429548fa9cfe6a4d4929ad2c8cad92cb24923755ea56dcc97a5315d86b92a26da0da9bb010b8736f8a37ee4dde54e526821e18ad6ab845c7241f18ac9cc02a1d937c6a493e403133ac98463a9c40534ac7d022054068e11b6b4839e2c56a5d81e4b0a8e4c2154b363e9eba0617402e29462e4839a2c16b9461c48c144aaf1f2264a278721d11d4aac269660645c420c924e1a8069345532b094d2578d1c1514c0884602139c11001c42a8a171410e182458d154588961d00ada238024751e4926529081515472d53b294c12435c51106faac50e94084a703a7d4089674597c49d45055d43c289870540c75341f045d298250b4328cd88e8cb89f262eecc04908c8852940518470c18aa5288a70018ba72d9270e1282829873556e0c214b5a4102491840b3af831a18a0a001db338430b37c2d1733e1197d5a74fa34c6657907bb3cb7ed4db27917d3c947af7228141169e4aaf385bad1c32c09df476f880eb539eaff1704e1d9186391e4e3d3fa62755a393da484ed8d4b186ae7377f769757818abf1b04ef759dda5f4a2fb74f74d5503ff6dab74c64963a5b3d2b9d969b76d6edb0d8d38fcee4f279d17b5dd2df5f146be51758f8ac7b80455c26e72b32528a1448c314e3c77a0010f3cf0c0030f5262dcbcd6fc56ebcbf1d09fce1fff807897673ed27462d5f4440b16586a6005bbe838058bd0128c62411130c042829714e0948e53b0f8f0d4323a02171e12aa784868f251828434f49635a1212de5132d65142d6f94d568fb95f65801094e8e30064b4f7184202a03234d8d9890e1117eacbdd3b2304509b6583212c2c10f3bd60c6da1e0c10b6dadb5d6c264f19daaeb6f53584f4c949eac683ac50838d0f4edc743554ae0786adce089076dbfb6a6176664d1f9b0b6660068e040db3482d0360d2fb44583a6ad0dc7002245715c01820b9117f772dbb69d7106d0d3151d94105d09e28726239c145d6981104857961022c80a7eae5821441027419e0fad6d5a6bb3e8410f5cca11400e8b41418ea8bd8ca0074c2e69f4000cdcc22c988e592861c16f3473d2f7890d91cb681497d5affabe4dd19c591b237d3f35038cb2c921c2eedff83b7cd8e1c3109650de0e1ff0d4d3c68855c6ad09c345e055cd65332b1edebfbfa2cdd9ece8dea37cb4a2f120c27b6645dfdfb3231b9acbb6b4f171d97deee3f17e66c567477336bb17e32304c49d166e36d4fa6a70eaa5b9d990bef357204b73dc77e075013d87719c8d0fd186167de0e8fb363fd187ebfb364473f6f2f76d8ce68cf5d70605fada18a1efdb08a1af4d91be3647fa7eaa09ecf2ace76c68f1e67e2c0878957180825f320e2fcf7ab97a9727b2caaece25bb6626f2925d5d0e3dbcb0fbacec9a9901dbbbd8c761f5db33c0babcfdb8a3e3df1a78469bb3dabdf89bd17426ec864360f9de751f8f0b01dbbb3c91ed5d32f744ec0f69c947ace674fce23c61179a8e7a4b1a6dc2ee77333d72c02eb40eeff001db1d3ee0aa2313de49f4fc12e03b36347025f303bcfa02a3541ff7137471d9c6c627dedcaf19a3fbd4179a51034ba37e7e305019186954a6613ef7f1f37290d58f2b6a8922c27d1f0f978780324c30868bca33a0eecac7c3558f63a069be8b03473cbcdb0648dffb289487c4fd48451f41598d7a897a14ea6d7c6c7e3cbccf651b9a877745d3f7b70d4ddf5fd15ce67f7fe6c4b94b0ab018c5eb321a7a585a7eaee10a233d5f06467a661ae60eccc842efd84f24f0befef286f9918a6e61d3f363fc8c98a08fcc97fa1352f5fce8d3f3a4e8624de451c3fef3754881b9df576ad7d3a7f6ef3e07769a02f339aea545460604c330cba378e3e12b8376a28f1bb08e54a1bef7f30b107dd8772a9d9775b81c3d44fdec5eb9a16891f85469913993ad960c9ad346d3044df9436a8a4259a2cb835416785f3d93c54573e193a00e29f0d4b1f3cdb31fcff7d9edbd6e5c272d18258d33d582bfe7218682519bb4b5da3a346773a80ee1218b6df06cd57ce38da439a2886a382aa1a0fd65883ea4f62fe2b2d777067ed5b72f73548a4aa911e0fd9a798a724bed2432148941e9459b53b8d03447733ce43efe046313c63dd7e990026fbf43bb75d4f3a226220a9a7b6722fa58fab65fe13cd0dccf1c0157fbdce732efb977292eeb9ebbcfa190305724c7e0a173f4b9a8b9f95ce6a292e6b62e06048c031422f35d1e07fa2d8fc37c9797cf753e36b74184711f5f7a51bce13e16048c0394d4e3d0f2f471706979fa445a9e66d7f648a010593deb3311979fd9b56527feac8c6495f184713fa1105941f17e95a1703f3f21b28987dc7bbf8155cf4f880cfa0da4baa3de067e5aa808bca352c8b52ced1627cdb52c698efb8f20f5c06e7b14783ddc507fef67041c83cbea73a8f7746c01e38e8b174d18f72d46ded469f94ded457316c384717faf126eb5fec370c7f40f8a73230fb947652ff290e3b88f355ca6c185668675bf5c84d98f37fa4041dbf762575b6ba3edaba20401ab8a54441eaa88b4fdd6cc893e6238df7ce38da5d9756776ddecc47fb6b0e9fb564554377c61a05ad275b2d5fabf51a7cb3965b531462a91d23ba78c74733be7a4f5ce39e79c736e2be0c4429d3a29a5f3e59cf34529a59346d50ba8a44e38a448aadd620c299596da1ac3c999b158744a29fd226fa9ad2c0f5b0070c2941a7564dc7bf1666fa45c8d28d4c7433f4a524073e6a3832a4b962c597666b811e432541397792d460ff6f2625410ba4abef40549e1fd5d2e213838f9c8d4373eea1b7fe35d46db17a484eff1f6e3e991b7119c2f618946a3699cec6a025d4ba08b6907f82acdd98b16809c8fb7e5fb1eef8ff3b2df2fda87f37d3da80d74fc707a805f0fd0861e3d7a7c0a3d5240e175bc07e88ff37d287c0af986a951c84e6610a279667b1cdb9f90f79b90776893774c15845f35fdff9f23478e1c05e058e078b81f5c0b5c0e1700ee72ad56ab058220f85d4d4d4d0d0c0c0c4c4c4c4c0c013ef63b1e007ff33afe84bf1f00fa39f75bb8ffe33ecf7d16ee17e0be096ff3dfa7f0381f0df03dbe84bff128bcbfea530f7b1c3ff3f9773c55a19f127259ced0931491cb5478faa9282ef3f1f42d007e0e98f316f8ce7e3c167821890540499403de210f29ad25d6b2a305002d35971b171d2e27b8e4dc045c2297013786fb803bbbf7c357af56ac8b93c25542e1e5c64b092f2f2dedfae8aeab5daed7ebf57a1a84b7d77917adfb0ae47d2f05f2c6b848de2d174dfe04f24dde57025947de3802f9849a0c790681fc80bcb103720c79b71a901990f72f2013c93b54404e0002728ecb88e6876413f2be30e48d35cddb1c20ef96a6f94fe60d90f7d532df23effb42de58cb3c8e90bc5b5ae653b0f12ee47db58d2f21ef1b246fac6dfc0d2079b7b48d470187df4256e5ddfa915379639e0c7bb190774b8705c81beb3000795f1d827509e709f3f8a877be93de0e28f5f7d5ef60a89f733e32358fbac38347adb356cf75c9e6a9eae9094fff1e5d2b5c967afa17c9653d9efef5b93f2ec379fa570990cb6e3cfdcbc465293cfd1be4321c4fff367119ece9dfda1572998ea77f875c66c2d3bf4f5c8682fe485fcf2247bab4be0ae00ce075587e1f20f6307f97f24af8d4c7d3e3539f119ed4ff90ba84b751998002ece3c7037b1defe2c793028ebf616fdcb881e3c65bd048c4f1f1e3c1f13adee16cb5d66dc6ced499b7a091994596a987f927107d1da04bf1b0fe09a033795899d0f7271ed6c7017a9187f571401ff2b01279583f85fa30d09970230feb9b00fa9187f55100dd0a0febdb805ec5c3fa2ad0973cac5f02e84df57bd4ff3e9e99ec421e56ba5d83db91c21a1be08e5264c01d9968c02d7da4ce0537d5fbbaeecb65ddd5bdd7e5b6cc400280db856a769402823b326de9238572805b32f9744a29214de713ad3f4a5c969f724f7f329941b31b00b8a5c6f1b92a79c9364b5724bc42954af4cc072a585f676e604fd5db1bd9740d2c3d93778fee61e9155ec7ad00de3f1e01acf09df40600462d8015e693ddc3d22be449445fd0f7b98d93dab62d561a6ceec1d157e808cb3570d20e489aeeb8b4c38f137a572b0698ba6bb9f12d1f8f8d6ff98cf0d0fc0fa96f7c47a30feed322368939ffc4fbbe1fdeff77e0ff0c2875e7f29944e0f6379e6369fb736812e100cf279368db483d8ea4ed7b933be9c09bdc8a3b79485d2d574dd3d606ee1774cddba8e28aa2fda2e921976d7f3a7299ec845c56447f3241add0b44be2a189bea163d20e58747e0ec7db9f020616172f69072bdafe4d4a42c99eb4298acb62520a8434fdaecaeeac686ac5fef6d6cefcccdf7818b8e3131a86fa0c609fe65f42af9acba2cb665c4302e871631369b47d899fb64a55dbb74fa30f1c6ff376d53c740d1161fbdb557309b9864e00a387337be65d340fe90decb98fc7e620ae9fef94604f618fc247d8a7007b1cd8f780bd0bf631b07fbdf7fd30e161df75294f7e2d84cfbd05bb1970533df319dc52e71b703cc73d8723db23aef3ebcc0c46dd81fb866902b87be2133a3fec3beffb113e178246b86c64d3f6dd8a87ddc3aae8ee33d85d1fbfb14e05e0774ba73efaccb5efec376e69150abf7352f81dc3f91dbec62e1c835f5869c7ef56ec37d62d3c7e5f4dfbe8b357eddefcc600a84b2e0b9fb6f4ca84d5534bb3b0667df4aef6d15f8aba87799751f72ae47dbbee03e032d2f45d4bdde7e47dbb9fe58dbb97b996ba7f751fd3bdabfb1e797ff7387987dda790774c778f42de39fa45eb9e47de9789ee3e9637ee7ec78ba6e9bf6a733683c33a16ba0740dead2bbafb9bbc71f73af2bedd9f90f7edde84ee6179b74230d6e6ac6616754fbbc79177abfb99bc71f7b9ab69da759dabe612720d751b973f7e5c4e3529320348d45cee894742524d8a8aa0adcd3c8c70f7dee48af9785cf537d52b2d3f185c415cdb776db3eb47c665a6d33c9cc02c3d3f9e7804de2e9aa65fc2effbf45d3dec1055c8f1f45d3497cd3c7d974ff451c2d377fdb8ac84cf33709bb55dd4f1cce5fc16dc9eceb99bc901de70750933dfa93e9e99ef66408be3e367710089257cf8099125cc94d0e369eef3c70f866d86ccbdcb3ed5325b3c9940c778f6f680f7d5d4266f24ba86562c4fd769abb5d6ed6ef7627d37d048bc52b76babadb55efbdbdd365b374b8d669187f43f34f0be5452340f993ca4b529572a35bafbbd005d4344d77c2de0f84bc5433ab3715ed0f6bb984e89fb1d4bf0378323effcdb77ad1d4b304ae7cf600f4e0f8eb60f8247a6a6f9ef5e06dc52cbbc7f41eedbf8a86f483df3d1fcf4e40703cd5b9b8f4c6de3e9773597d13cfdefe3b961e3e3c76383e66f989a06064df3b3c8653439864bc565f4633e23525b8de30dfd967843dfe608dcbdcc77b4538a69d5a5392bb2b4346566f4dd2e25067ca94bdb0a6d2bc5326d3e7b93a2e9a7c0bd3169fadfaba6e97bf53bbf9dcae3c0a8955ce6aaf1afe24d4cd88aa6572a952d0af882090f59ba36d5ed6e979b3c28310248d437f7389290c98392206f9a33ba43a92fb96c63ea7c68ce3eda14a2377b583c74e2775d726d1fb91c7a48439c0d8cad60738acc59a74584d1ef4287a1e97745d094764af186b25097b62144e8d7a54ee985df9dd296817848e391109b5f4645fc0259d82b9337cc4b31d22e2eafe32e2e2f2e2e79d3df60c08f79ba7d4c0e7ef9eda36c75528529a51466f5aa17954ab57a1d5fbd8eb35230fff2147cf98e7afae5592c168bf530a09129bffa947a9ad2a7f629a55230a536407f7afa66def8ab51e6c753899ed4e8908756c8439b6bcde8c9c3bf4dafd737e91998bf4d6ecd65a9b77f85eed07d72eddf28452eb37f8d5cd67afb578acb3a9a26d7effa548b244c858179aa585c16937608d2f62dcd652edabe0db2b5a41d5aa0eddb21fbc4673ef74a0fdabe8de2331e40d6c76531e94748dbb73f9609cc0b959f01e4f4e457bf36cdd955b2d7ca0ab44a3cb4ff02a640156831687f3cb47682b605da261ed694ae92b62ff93add260f3f346ed4712c1edab761c296b66f93b67ee536b98c7b1e4618e65f6a2e7b79fb55c865306fbf0ecdd98db75f89e6ecdf862ed8e5a3cf9ef5f6c861f495345d5259a7771fd2db9fa81b398cfe0dd08b1c461f86b610f3929dc846df7ec503b715d2f6695ee0f621176803d52ee0ae45dafe0cb8eb935da5d0e0f27303efcb802c0fedc7d06ce3cbc76f7b91ef9b7ca7da05ac616afa14e5f23477543ae902ee9eee45bee4e861f73a4ebbee5db6eeb9dfbae77af0d1a97f79241128d127e888c51652bad845988769bd914dc37cccc7cf00ad5c6f981a264a86790a6e30f48321f52fdf6d9e7f2d709f8aa23e69d5db772c93367d56eff214dcb617704bfdf23030ef9f11a96f887917d0c8a65df60d53bbe42d754ca6af3361c0dde357f4cb7fab3722b36897a7affa17306a16b86f989abeea572ed9c8a65bfe86a95bbe3eb9ccfb161c752605efa7c016f670d2ed4fd908bcfdc9eefba4ed5b2c9c8ccf20a1be13d474827a1e4d60a9fdab06a24c07030588617d21ded42f800162589f0598be3f06eaad403586f9f56bfd5a2b06e24d05e236d0b53de8b22f5df6e30e2ad6bf1b788dea56ffbef4c1b08edd0446444d44434254bfbd7f2d7423c0db6bf265766def2f85a83ea949153ddf41dac4438a33654283785091d28904401e2e81fdbbf77c83d4dbfef49464fdeab713d64abecb5c433741c9013ddd3b1afc08d5f3e74f8984124d754dd288686d4a97c52413306169aa895ce6123293c4b07a56468203116856ecb090ac9884c082c90e9296fcf831e0e56364124d64f5503c6fe90f78f91821defb7befd23e0c08c55f0506a13f1ca4c1be7c18bcf79f5f10fbf283c17e313060f530445e1eb002810f9125ca0eccdf20f5263d8fd5ca301f034249c16428deafc078278c6a4f0c4c3f050e017528106542833cbcba9bf6270d720229908774cbb2099401de5e7322fa74c7265de432fff94d13ac0979489fc8557621cf34365b7d5c812ee4301a242c89a6dbc002d7e40c4d15d885bce621dd01d6e11e40e2b29d030303bcbd76a4d2a1195c0664888714889e3151d0e964f513ecc991c181bc3024c77334ebe9a424883745c41df42f4899c41b4a832605aa0e4ae9ee534e8f3f4123128c33d6c7b0b2d722acf35aadb94c88be0f39124e12124c103161a429752a9a3a91a65fed247219bd46d0b269ce281387d1a74039b2e0491fdc024bfaa00fb0ff964d15290710467afb2e7a1cc7d1ee5bdfe9ce2913ee7950c15d0462326bb5408fe2b0ed25e85d91342d7158bfa2abd56af5d45b21faa8addb42a124b554825b87f6bca72b441ffead998f1f6a1cb439c3912913876dbf01fd50200f73c0db7b149f4d276f0798b7f13bc83c4dccffef30d37af07770fd4b47531074d5073fff0dd075ff1fc713f9c7f1f7716424506e2672e36b32121c7fe391e4ff4fb2f3025d08003f839f5d445e8fe35f8fe3694057b501da789a2762e369be3e0dcdd76424aefa4976681e89ab661b88d4bccd2720eef8e009db3ebf4d4682c4e66d724d4eb263e393ec7cdcd970e4b893330826d979b156df490f0493ec3c1877e6648147ac9e7902800a6cf3f6deb6d160816b4c6890f72ddf5d4f829d6e8150e8779cd742e9ef07ebef5370f512ccfaaec02355f7642ddfe5250caa9741542f3fea1c5793c9fdd5b3587f3f1e56fd249a82fb8b6829f57d59fd7dc928c81daba7e0c6d1abd7992de0d6a15d5c72cbebcc6e7a4e35d65a6b964e404c521f8f534f8ea61f432969dcb6ff74fc15a28fda02728f41e99402bdf6f17cdb7be0eed131699320ca84021d81b75a63a2c0f4b7dfa1136d3a93eebbc9270f85bcf34d81e60cdf16fe9675a2707b0a3467f762dc6afd53201de97c0aaa340df2507737c03ba6b7ed3e6dce3cfae00c2803b6c01830468d54fe51ba2845434f9a44263e1e460f60e9712ac244440948fc18c389384e2174b1a6db3746a37ce9b663dbce966313f6a2632c9c301c5076a8bd490cb4ff6b8657b47fcc65333a0594540226ed4b4078d2114ce81dab54992bd2574e389d72a0958252cf120869fad2c97d4a7376d110e028ed6694840efc2de724ff9763099682ca01f4ca1b88069252c1f40465b8598d0f97cfd13d389a86faa99fbf71f44cfd487dfdd4d7d7e9024b9d62216ffadb7cfab564666e747c97ea2718a9c3a24bf6e24da4f916b81d54d2fe2e70fbd27630b43fcdd178083804d0f531b80cc3d8e84883e3bf3d1df3eec9d1f187443006257024e2a1a7dc68de60bdfccba773ef5502263a0641c7242384b4fc2e15583e114d43fcd76f1d3af5a9c7a9a76091974e3d98772bf59f37be915f3b95777cee57af33c1a85f608f0e8d5afdb4016e3fe6bfde0648f311747d17bf20f35f397aa84a8136c0ed552a30be0cf87a1a50e6fdfbe1f2f15ba0d433a98fff02772ab7e0f2f165523f03c609b63e82331fe3ca5b878e9fa23f2a8df9969fe04ea263bc9f01edcbe898ed5cec96de6e6494df7b1770f7e0b40c8901ab5235aa4fa5521f53aad44f559ec161fef7e349e5225bb0bec701e278aae367303fc67a061003608f870e468ecb455ed749cd6f35cf82ebd48033e800292273034ed7cce0b019ee7006b0c8cc65482bdafd67ce66d37c252524a3b86414978ce292119347a7159672726aef30424b394880637386739081770c6b8fd86533bf447a8a9f834a8cdf05c92831c618638c31c618639431c628a32f75f3bb09c220df2524fac7dceae40783ccd83df9059199e5617c1d49a4f480114a74fc17930e808e4941444eaecbb6fbd4f34bc70f86f9546fd3695e8142d26d3b7a9790aa3f1ecf528aacb1641115078a3950cc81620e1473a09803c51c28168b12c3ee40d40039900e070ab71d74a476fcb1072cdfbb7109a9498e25610e2de27fdda33ef5405c76b78de36aadb5561b36ed5ffdeb7d47dddd7dcff6a9afdf970ab271ef8146b6efc09efbdcd76ffbbaa968e07e7e37f30a5d4755debb4f9f3e7dfaf4e9f3f7abfbd9bd2a4533108ac1aff324f53b3d8a2bcbb66cd8a6ce5455b0e77eeab78f479532e2017d3b2051a3f21117728100a13b79f778e10d1a60ff5a6081a3a1c7dabfa09dd11debe52002ef88447756c84104bc57d02e21f5e9cf9ff8143cd2f1adb4bcb764161c266796bc4f2f04b8d3e12e9359c743b95483f4da09bb8b922ed66c4a73929a03f7adfc1ae8d360dfbf6e94dedfe8b66ddbd39b74022a5a46c7a42027ed1f9d37e7c9e4b0e9a24f35cd73690b4ae3b4d981680f52e614f270cee9445b94668d40b76ddb362ae596f7945b8b556bc68f6328167eec5deae3a46b5a42b1340a45350a0586152f84aaf7d489f71da5d1317ae8a8bc7b74783250fc8e46dc8ffba8bce76f514f1d34cc9796becf8e46da3f9efab3cb350ef3f7f0298bded128d6b2484c6972026514da14a27dadb416ed34c7418e32f0964ef1a594f82f928e3892b89c3bd0374a0a6a12694e6282249f357ef0850e5828dd27359ae0501f4fcdd2d25b471146c819410c48e013a483a6cfb5eac75a2b11b050aa447b40c320c204449891c4848a228688a2e96f313180c0b2375c004dd15488214d85e081a65f5b39b1488589213879328430c228821a6714d1423f9e8a02928c0e1c4d5758020446d21235806041121046086288a420928072d0f4670844a378898048d3a7224843d324254b68fafe35acb5b29a88f2040c4ad08225493bf5a6a16b55e95a6bad49482d6d1d3f4f7ad0f4b110344df2c129e907a4181ea29394527ab1175991fa9c53ceec60f09082abbb688f3e8b4a4b3189872b4556b4fbc73d7db457badd0a378a4be8ed357fffb1699b2618c743f9b14573998f3c9ab3f9e432fa4a4a48484747464645458ef4f30a7d979dfe6ad935ae2b21399150ae551ce6cfa4dd8af62b61c512d36e7d72642e54ddc9d375d26a37299549939a37f9927b9746650a194393e64f579c3c97a1ea85213a2cfce0fd3a2cbc30e4a2a1e5cbe050f830ddebd9ade8c0c2524a1672c0fb75d75344862adddddda3c7211e4a201e4a777777f7173c942cd0e480a50e79456a3967f5e96e4347287628434fd99aa913743403465ec68fc752b0e60c4c97f186fece408c7adbe466b7ed7e17c10c1871c2e53b612e3360c48b37d7437c5be46f4fffda6da3d606f947a6e691d9c8a669903f330df4af9e92be05f396b38baef3284c448fe2611c7299fc1865f6210fe3cb9f9791a63828d1f64addde82d56bade9fa75bbdb864423ede9eed7a11e569dd49ba6003e7efcf8548987d482483ca06b8fae18d0d5cea06bb6bfbfe7e366e9fa5b0d2c4d43cd13689e70058d3eb6576d89c75fa5ed327afdb86cbb969a5e3e5789d015fdd23149484927b1a4635212483a051d939268a26f159ff940b9a1e457e956b94b9587272d4c1fd9c71950f81f52cb5ec72fd2b5e21e85f41a35c154ba9a053ece07c4f07132e0e3249200261ed20752e221ada67e3ca4291f0f698af6aa39017efdbc6869bc682edb79fa291f971d99a91f97f178faa9a73da804cdb5a5494b95a21900000000a314002030140c07c422716030a087d2247b14000d83964e72521bccc32c49610c1963080104000000010100191a9a46010751aa955ee08f71cf558d18a41b68d123bdc808553a6fc108e42d4cb2b90c891b926198631668706e09ee5c5863284f281c125c424b93f0d34371a06652a2c485e8965b3cb9190656972485a2cc75fa4b560358a533eaa22307b33f8e3b9ef301668acb60516c68765c3ef9657d4f12071e556a48136ea20299b5feaf9cfe6ebf4442ba0a19d4589dab8462a5f6e68c2e7312e638d756e13a0b0cc789a93b53b85a80f1293bc67ee9046e66b11dd1e4cd695518fe4334250998f0b7be080a8f0cc8e894b0e88db2eb2bd660ddc1b6a05ae393a513e708f60edd58c10d68da577ace99950c36c015f83da600a92b505361930d4107197f84bb6674acde6b720ec51dd79b792cd73922364016acb793efa4e8510f1fe90a4a4f54c248e3ccccadf82fdf0b8bdcde5cd05e7a06b01bcfe51ee4711d588d6517e52ed1844a98dd377a27a05a9500439b3000dbdee3fccff79fae360f5e7921b0c2adcea17cfe34d00827e77ec938cc446f2c3a9cec29a72588e8f0e12bf84323f03bd65584fbf70f10f74543653e39b41c9338a816151c54e44b64f95e54aab92d24ac57842a3654f65e29386b5f26a9a046f7f5d6efb254e18438da757d96a05e6b52b7dd2e438bf6768f3700bdad054f3920a82d111a20cd7f4344e33ad0720d85b707a14d4c351e942e4a6fd4c64a062f8a847370332eed8e7d2d6fa6216b28e62eafb6001c1279f02f23a1558c232711b98e2ee2a509c3612a4297f8f4f0bcb644b99e5f82ddd7d23577cfd6e86254b13b7ec04ef95304adbb86022114e22767bab5aa548366fc384c5c5f51bce7931a81967eea48f35c9d89e8342f07a02971b68376d7db110151d27a5f6e9747973527952bd37e1517d73778f22e23d0ca5194c0ca51eb2d5db22fdbb81cb18639a09fd9426b9aa3a21bc2e0a8bd985dbb26e7a24c1e0954371f1801359b375f879d730d85fae09870a6420fc8876162b4052515f57367caf0c562c7c5c6f5608cb43fe7cbd02d5e9ccaf3a55da9cb973932e024661eef8fb1c66b623412a06ed753a4d33ab194e7312064035199f5e49f7a18f71291fb006013f3a78c3615a6952c391bfeb23308b4fdd9d74c23ba26b2a9d8c7c4734e4e895eed6809d15164954ed6b7535d3f3b2c8d81032bfd2edfe05345b6691b2a8a2dd7e05d274bf1b3b01d8a2c1893f2dcf5f2608847e6e73a0390da3a29061c929e65701b9bcb95fdc00baa78c28d9c67b489593024924adcd2c9a0870cc046811ec8de7419d5f6bff35712e244c881d925c01860ea731d4ca09ddc1cf4007f689f690c4dfe5dc416bf397ea5580f361845ff2eb52e4558582eab255a980cd9b3bc75308f13b8fa08a99ae975b6988ee6a1ebb7a988c7c6cf248b39a8ac6bab54bd779546141d4c4786d0091f8b81d86a7b2aaaf5413d0c1ea6859e3bf177c9182858e012c504e8c0dc48d6abf6b5de15c7a3ee04e2788542a754235fefa3cef293e21c8995cd6e82b436daf7f9ecaee43a54f403f49b548566d34c5044c711183fad28f549692e5e1a8dabdfc8adccba7b88eaf1b0dedc084ac620eddbbb3e152a61b68573874f8ca9413d9ad3743720c94634d8ae57ab3438a4e87198542d723a09d71f5452370527ab76c9d4fcb23c5fe4ef87002ae00b6eb814dcdaa4dfccd83772238a9669a8f4b83882a7fc8dc6b702dab0bebc03fc388bea7842cf5c28c1cfc9dbff7e19e89a3fb33afdee323815535224e46feada84fc2db8cfffd4a70a7de0132bd45825a08e6c3fd2ab4603b263b098d854283f8916e62b95c7d50653fc36cef17091665695b662a1719e0765583b82ee2b51c5ecf1ea1cbd6896c75e32c0d04d72da33afbfeca8cb8190e6647f2a470661c210e6f2b90cc64c46aad6cad4d21638312554bb3595c44397d849b8fae80710a06e675972d82b5647f061c7256a9c20ab8e4251908718a0f26bfaa6a588084b83ca63ed5ef7d7792e502df66283f426588984fb0a5b1e750facf4b79852d5dcc58c5d159971fe15d9af76fef1df51303006c2f78e1a4d8a1b9b6d186e2b2ad070ba06a66b21e792221dc8a147ba0c05c0860570a5290ce05d51bfaf55814185999e92e3a5abf2d236dda4c66ee41c88b35083880cd6f151284371fa306db4e43cca99d286c7d05ea55a49e156b09140ca37b597758f5db9a6f953a5230ce1d776fde04968711bea54018fd564ca7e64b99761ccc7b7a9d447c5d01d965b38ac76b1e9c8e936e3b6e363d0b25e29562a15ad6a535ebdda34113afcf4d71604949a25048c15f5011e7a86c3c968a3663d1d9a882134c721c310dc764cc155cde785712ca09c3c87b7b92737a59e0d9efbe25f06c2b822968430f88b5a91cf2cc3c7beea3a828760b1439e877c6793cf46116f105677bdd22572fc17204a2ad0bfa5c5f6848541698d5870d4cc164ebfc0826f5e585e55c93d2ff14d87edc0d8c2f524714c9a3161bdec850325c9195896b33f7a961da65c005e1a711d42d75f0b8c442cdbbbee41b849dc12e4cb13479b59518c41fb899ecdf76262670fadb2daba0eeaa6987b92d2c53a548e4042a58ae70b963798ce0f056f66f194997379e1eab7622b6319069ee6aa01846a95579e71d56752feaccee50f18d9c4c7149898a768b9ee755ba86c439a336a176599b501bc0488731f12457fd7a93168854544f4936ecc37c4bb24434f30467f7a2024c97634920dff8d1a76a445440a199778ec71f7e7cc34ca9479641de293df917b3245b5b85d88cc3176ebce4ac1c75d67527c482ca5e9b5cee6f4cf5b7dc6235e497c7c34f4df4c2fb8bd4747ec7b2502e9176316ff2c3ed324d1dc7872d719947ce8ce9fcafa7381a569cfac98174b17e2d25dd5c15e98c13f998f4ec3e4aa5bb3606a6e722c9ccc0288b8833e3a5152176519253c5a27acf670ab160fc0e117b0df0ddf81e02ec1a6c8d529fcf65f0d8106d0a497dcaa729a74681b130a35dc73379f6d2e8a73d415462ce400f130d5a1c07212156d942bfd1fa1050b0086bbe994f4b85b0c01796aa11d1453d64c312fcdde9ebfbdca615eabb3ba2aad3bad9b3a864243550771d66d879c5ba359eb72a123e7c50f97e1d0d0f9fd52e6a84d525b2f92f311b213e75bba699a9b6afc985cf867c30b6a77b40b16fab0e09f6e68ef098cd1f2215e839454d80be7b1a11140fdc7d27ac475981da850bcc3f435781ac321fe2dab2d729fe53c740e6c765c9acccb81e3c0288aebe197b7967f9117478285e2b387c3daae361d1692b92be3ea42dd3ad235c485fe307f5191f437eca6e82d83d9160f7cd63707c3778f7c6ae581d6b04044e3310a1fb923ad2bc24e6374867ab2ba73e1a0e31ae45e2df8b162a69840cbaeaecea4bd4ca6b8e5b86312e1095c64bac9d01842691e51b085b218d09a996c170f40b35c70c87c3aea29e731b6a97c719175e4b7d6078a935478cb170dd7bd007db40cde7a429d58419c65056b22bedfeeb058e7aebf179908ad5fdb94855c61cca4ea2416f5db87609c022378296fcc335a1b10fca502c1f1b3fb3385ea8f99a6af73277cbd1250fb1d1b3e529e02a4c504717ddc0aafd6ca0a51b9cd1feb9cc9660bf5da407a5b4b8bbbe4952f840f610aed0a3f4ceb024b32f4d39bc7145d12687d3f54d9fd35987ce6695361b229f77540453d42d1210998740be6d62c5f2c9ee15ddbe261ba377ce807eeccec33d11c1e9cb3f799185f872569eb7532d3e366d551a7c6b51c324010d367cc8879d0e8356239be8540bbe8c2d27b6d3939088cbc502d15f5097bf8fa91ee091b6e44728ffc6a9797f2f1b58daa01cfbaddbc1c7286831654bb5e760c2eae4b537c32f8979f7c59ad5470187184fed792a0484ff5828e0288f9965ad1047025257731c190035f89ac0e8dbeb9372d4bf89a11cd8f47634e41b2795ae2fabb4a1e129921852353e39a4e3a58e5d63bf2b6d6ce222c692e1e1c9dc413d3af7cf44806baee5e6ebed812504e0091cc550ee8289046240f613fccf94ebccf38b074d8cc6b9b75ecac2b201fc404d64501f4ed61d76e49fe543b8c7e82d9bd6a32d850f8981fed6e271453367624ea51a52b0f6731ca8a4a3996f17022bb16afd0b0b81da412a6709f0d1031cc4e200eb403e77647b6fead27aab84206a0f6730140468efcff2ca4ff39627e58d102ef1f7749ed41f857fbb712e3b57fe623dd10d7945aa36d98039cfefb3a60325cf4d1256953a88b0eee05843e10d69acbe43ca0bd066b6fb524a2b9c6346b164a9472fb6dc27e490e68c1d70b50d31321811b1217e175e5519c8dd852f2a265eebcfcd59f939241851ad7cf162e4841012f6d43f11531b571650e4283bf9e5add7b25f7dea27d41dfec74261b4ad961e8aec822a5411f16eee561fd9e3ad10023e7eb8949cc1686ac6b36cbbbe6998b06d91d0480f00e533b0e84c4b85e98c0982cfb18ca77e511aeff3d8d52ba1b192e841c57d0c7826d9e5636dc83b36d734d28a6b595c8aa4161ccb21571eb9d31cda2e83fc29fff6e1b768787c62c717b2b9453cc9c5d64b5106e72219f30df4e3d58704b5cbde1a9143535a6e2670506c732f302f34c7b811f60af3bfcdc413d190edb1af73b23671ef082d7474f2d5aa567915566a723c769b080042b0b8972a9a3291a744dc959a32ca7f61e325dfb581fb9189acb3ae981619b5d8b9e55b3c9522bf6e27c28978948814096f117a9015a990442903fbf0d3adb9a5eae01d598a58da0d4dc2ebf8894933d870edccea7e63f3f6d16bae229f592fc78741c6b57372652f35b5ce20041d16c077788edad2978f99bbaec1a3ec73672b5cc0e8bc1c1c06b819c834183cea2c7b13b9ad7d5109fda1d42e2baf397dcc427b711fba1ac6fda7043f69e6bd55d925303657bcf249674f636ea554a0a49463e7fbc5925004f7db0763e79a6483269193969795e66591ac1f2fe9a655423cb10d0fc6f52febda579fbd8972f624b374ef284392620f011adc19b78e21a4e1b6952d38b58c6cea6b5bf942b6086171bb889cda4ac78de03e09b2ad58e987c768ec71526eeea3b9077001fbd29e6da913025d84dffdc2b5a355bccce52c11ffa5e83d67d0a8100a703a3878c497b8b83a05ca17f73ca68d878694bd0f05434684d5044b64bda90d84b47ea37539b8cf20f12e90c031fcb2a3095abda3152abf69a594207d8e4a5c7397822154a3fb5cb22a70f32dff97c3e56e9aac233354103759ae0b3a9ae6d3d16a96343defff56e2100eb15b01422aaebad77d96aab923d1217c6d61dcfce8f6ed1ac8f7d62d91746f6f444fbb68f234ce27969d27dfb8991f6228d467c1f1b2b25836b26b7262d265114cdb803e243a078420ba798c511da6745dfbdb87299503bf6e0752cc1eab6322685e9b82a13b43e700c66d3c97bcbffda844e69fc2f9c4750b95259fd33420bc32e4c1b8573c75997ddd03f502dcc741d2ad22e511d89daa59cfa14b2eb2837d4aae537016081dd076193a8874f8951c764961e6e6437b24bf60581d12e6c8dc71564bf67566904d291c10576d24e9e5baf84e7e597003a18171463320c10f9909705ca5da6a6ddd5bbe5e1c0293d9430434258f3a6be563c7735b7aeb0568a80618ce35b43a8f8be104375b1c3b4c3cebcd06c72601032bc3f4710bd523cc6de830b475da15fbd5203f4b3c4787d5d14d91745e40c9c1e55e217cf5058ee037ebadd1dc84c3014f1b962bd3f3843d0b7a45cee88e667415139503652b0d7dc540236a598d667e360b659dcbbc6f8721a7bc711a5dbab08a8b2647a19df039ea8761ee3465483feb39228ad113ac5dbba415803fdc1440d13511fcceb12efcec516f4fef6974062c9f067835a1718c1ff19996c74126dce9d630a821e9ac3d51ed7759f2da643197e8ef1bb197265409be51b931857de13c7af1b4e82237061fe0685239ed9a0fe3d9d1fd5207bf9e336d56fde9890913fe9666d148cfc1c6c63459a30f111ad4cb9529555b2255eb84f7dbad42b0cd242e7377e5a0bee37a20053568d361cca640af5d038e4e931bbb3ad1acd0cbb4368e621fa4217cd0dd179a9e8e781d836fbb91d489266654b9b7cf236d9c85920f05733443e27b4e1b0710b534f05a3abab89458633148388b3cb1586a483999078f60d69b0dbe23e1e9b961a9e54752a9db5f9716d5cda804361467ca9b50d1b989f6ad110254c318a2d36fbbdba287078edce923f4b866692ffb53bc12bd7a0c3b1ac54f00b50d5b28d5475f4e9357a48f165900b302a0de88afecd1b6e203c69c660b9ea40d5458102a0ca2019fbf5516f1d266ef4b9445934c131e336a4108f8f4b16307594077848e678dfaa95617ac4ca0a56c169620e1e321fea9888b3cc555623a785c277346d1eb51d37ad247c97f59bf20d4aaa8a564cf4842904337871598eebf55a3415c6dc683e805e66c0badeb3b85bc9b0a26fd65ebd6aaa3e7df7ba7e72eb8e87d8785dc83370d5b9caddfcd47f53b801f70c7c90302ad984fabce8daf3ac8da70d7bebc61e32a0756319809445bfd477cbaaadb23790f3cae31bf0b695657531f2b1042b771b8fa079ef625c7b0c114a5c02a4f0785f55593ea2579b0dd25b78985290aab18386dfe0190972aebd8f9ecee957079bf1b92b2341073e1179e3224ca5a5369aff130755314c66722ad068b0410f3bc977941f9f33acbb5decfd2a6ce9d3777cb68a5864bb8d61765057ad2be2eda03dd2532c58b946eaed3d5a325efcd83bda36f895121c68048d4ce91c8dc161ad83d638d41ee02737003a05a01bcbc037003c21e774b80604776f2864c57b8b5b60aa39eb10c760648937ee508d34956bd82f6c9d49764a93b240633a5a3a6251038035e23ad29d33ffc2675fc08dca6ccc96db5794ef8eccc68ac5d882c94334d8703a2c77c32f8cc7191c4880e0a5edad5885dc88450ae84cfbfe04353282ba13b1187a6aa12997fdd1b96f3c1cab2939723e751e6e2b8a28f118b372251649daddd2f6a38cff038845d26277e7dddb3a1c46e4d22069983fce80df93eb0f3a05fed1d6a3ea37a113596c4af33a1f93ffce0cbc41aea8ca15c76ea62790fdc9e3711da43325a69c83df2df4d783ed22152c404eb5afcf51e5d96a9eb5e9527de995b03ada53720f1f5e5cd4a8189d2bfaf088d1389c9540141356a9e2afd552b7297f5c4ec67df2a69fb44001488745532cc7d35816dd6d36e0e81de8e16dd25deb72e0f688b651a2963d6e90b0be88192fb8e4adf5ab2a6e87a6264fd2d6b9a365731a86b29223f77a7bb98cbd2222d34c4538b5f5da7c9a40b68a50701f02b4cd36938737f45a3b936f7c88568570fb1aeaeba538b4fed368871c586b436cd782a82f2578efd1e0aabfe1ba50702cbec0cdeb671d18dd12ff24675acff41d4b757af1264febced42e877d2cc80ef3011bd7c3f5af7b3ae7e4fa310a41cd9af55cff97a36460fa7e1cd6a954a614e71ae603e5ea0a5103c3d5c663ea2d39f7c9b416789e141d8520b4cfd0a749d4185221ca5b8c020ff8948d87c1dbf08c25cc81d3cb7bb625f9f01944917905ac147a479141e02319a893c46d7082bd6bfd7d6ed6984520817e5df7216b4d4cee42d6948607cf62b6ce0ee430e06db679c1081a1922e807b7c03c05355a16ad59196570b0dce89d5374055954240127ca94685da9a4af6ba17237326282c1c0b1652dfa1122c101a5dc1bc5f495f8585b2d15e81b96db144310f45098cbeab25d046550873455852a219375b31466c95320c64b3253bbc87e540aec5b03c64bd224cc3cd50f37bde14b506dfffd4d707c93256c563a08c224093a3c8477d2119f78ce82ec466805bb3e601162a392776ba544ce3c004b34d10ae4a899220fdd8a797c5c55b6773a788d22f61914a9a30ebf28759cb565beebf995f16becca94d408d3730608526a5700a188231db51d30fbbfa35bcf521801c3b067e3b13e9694d68baf75315f2a37afe951b838cd34e96392df041e5173bb2c489bed745a576597e00edd72899f67d34138cbc28a2f87d480fabf017890ad9cde9521f4bb971954e1fe684b1361706a1a3482230c2a3e9b67e05464911534c915a2145393e98a7886e4c02e3e4914ac92c05bc5934af383543e4dfc15530a7b95a7e303bdf17917a1d198dc1553f6ec29a01fd1acfd06d8a4ceb650927ffb335ab5c64cb0423a7dddf6d26aff4b18e59637a2f5fd1c57ddce1f641aa0af290c190fd4a7d94873be9926b6de8675c0e26204a17400a3c88caab926d1d754db0eab1f6d6728d36ee36f5ff34fe9ccceb45a1c331d98f0dd95bf98a40e185818392b3f832b7aab63b624d106a122ae6982339d56544601faf0c593ebd28f1d002197c613f9e5850372a454bad8dd6bfac4ba3cb378852451775e384e372920278f004de0639e4b4c49380c8a6d73088ffaee25dab598e59d0a9fc953bde2b08d30852c834d5f6211b9ce11a8cfe2c91fa48d22376d8fac9b3f40328cc6b09364e8f0be24f3d83ac37e51456bec8d8c9aee06f1afd0b14eafe457b9512c8317524028cef918086be53a641b227acde538952727bad3f456ee16798d867e9618be36fa095381d390989621e871acbb7c3fc9f69ae6b4fdd462b1ca658187afa2e312a9611819bbd74f51eab2c50d50791c140a4e4a4a7cc148c44fb359693b244aa1ad442a7b2d2a72553a8b76a5f3b46e851af20dbdfd29dcfc91a5a14bd912f5091f06331f767844bb740d32281732540c4b6d033767c5d25b5597e2b2c6bc08dc8f464e4affbeac3d5bf7146d92b99bbda21ae8b72799e0ab2be635b8d9c998ddb95d69cc4d10b9b68c39a3a809d8f6e89a126a687b73d9a6b89645d23fc230485055ed3dd794e855c508d2c7d468ec2cf81dd87f1bcf49b26ec64cb05609d7db4bf4d1316468f069cedf74a68a28b40c1c44cfdd0daf3ac2873e707267a958fcd27bff5a19d07bd65293e43f31005d920fb4612a1fac6e6c9011d390f049ebdeebc79f1c4015650974e7954da66fdd03df0777b2c6f8189a8622be6f2051220c745d27abc32da5b12aeb619fb7279d54f04ca7f86216b582511afcb10c15a808b55f031cb20c130ac208321a6b9b24de2aa79b7becec07378ca44a68fbeba8596111d53452116847a7ec434b12441dd90bf89cc710c2067451dfee042afb8f3684241f8d31c380004d04fa40d988681f817906c0f0af28b0c35dc145e6f22b10dc2d1806b93fe991e87190b0269ee6d45432848729e37b2d19d7bf62285e3b2701e8f5d4d5a37f9b4aed4325590b3832a590cf1a62004e380da0c8aea95c7c98983371bb7e03c20dd89252cc216edf43aa8dfe46baad3ca6825885d189b3ac502c6e9cec45dd5c40cb48c1fdd14da411f26ed9735646e6b241dbd50c1250dc1f934019029e8c65aeafbcdbd6f819996ddfedcd5a2ad70d5d8ac614539bcb724944c497a35d96b6f088794fd308d4695d2ad0ec50cea8e639fd8cbd8fd98246e1fa2ba8ffa83615d6dfb77939044d6cbd8bf7bc36516936dc8e1dfc40385ac8e40e597849e4e88a8cbf8c5c6f8e0c0eee40bc8186281dcd26cee3a1793f00671a04df50442e2a84469ab1491ba7d2d62d8df14fcbb8690075c9b5fa70fef4ce5fede7c578c83f3390d58bc87a1fd95a4b56dbc9a64aca9ec81c4b8aaf7949807084db3d44934de8afc48730949435f47114d4512cd89c9a2b2d7c756acb1e40d9d0f26fed3d4f6249f3f3d869bcee987d8ddf636df16660ed293fdb6df287b5472bafc9a1bfcd235e9f548d54d2971715f5f3a832a99dbb748886104193cf8505a631482fd74144c87c1719fedb02aac8bbd93020cfe8d0ee9c7bbb970014d78d972e5b4fddd83b9a8cca4beae91eecdd741a80711d85d357d63318eb9487c58e61d9318e87e5ef569624744dd78f0277cd5ca7cddb95729dd9b230b7ebfbf382f3dd58a2b9c9ab19ed8df5b82002aba907ddc97ba3a7136094750e91ab787e764164e445ff13b0b74a7e883e9eb7d1430c123602a5f3ee40bf4abe8abe4bb4d8558cc5bb3195e1766fed21501f5dddf3493e9ac81ef91e9de5d7b5ca0ba3177fc31346685e6a1eb64407838819c38dc6f7ac69cdb5534c78de2ee646503e0c0c24f3b0f054dc04b24d9b3e6e02634a6c01841b141237ef79d1b71d357aa9d982a57e0bb9284f47c23748e4a8ee0587484d4aabb56d29e9833db0a964c832e8de3dad4638c04582e832866035e79dfde84565d8be58a68dec80d30f87a1ad8ac3d3081f6dd52dd35247e3a8489843a4506a1bb3d99858f845abec920cc31360c537ab5174204dcce06324f8419c187313403e734e83aed819a293a837525f7b4a8e06bf0543a9e6924ec3147b131294130b777e3abb3c819eaa665401f0855c97446726dd41f0d4f544a83b6f9b6d210f8db7d961e608e7cdfe2449c5459527ba876e76e72d5f2bdc558f3f3fab7e1beb9a4bcb9ba612791b403c8be7122a07757c630bb163175862d458a7fe868be1d0f434881d6c5339c7fccb90623584025b8c623fdc8b729e7972297353e8b32995c4699ccda9721295e1608041b723231a985ffff4bfccda49cec39b62f544737100e78033128e6108ff78fd259d331e8a3f49356cbe6503ba061e475acb9dad3c300701c9acfdf03b7350e68ab465467ff7d1d9c04071fd8143ba112aee2f0ffae9cefd77ebedce20b7d3f83b9afeb4fb771c17f31918fdf0ccabdb0abf42a4c5bef158b4c1e77a746a121facf288fe53de432de99bcb7e1636e2be25efa187e6d024b95eeeb76a1822c87c8b029fea4382b53e5e7e4bdce04032419dd782fa0a1278edd46698522228a0c75d8cb9a54ee2915df7e05d030f5ffa2025e7277e659cb880a2c7ee1d9dac38b6969f4726619bf8727512f33842c55c9ab4725fc188fc788f71f0a8302e346b29c20e6703d5ccfd473cacf737885ac5dd304301223183e9fd1704fce2b991075ee32d80563652971bee02b82d59ddc875aa20e39ae19194fe933ea316478c5fd976db9b2eb8a811df787d024ed42bb9e6c7f148a27ab64c1b5ec1d707b6e7f05c3527f945dbf3a17e39d0f5eca4846fe9c1d7726c233fb940ecd0060cbc87e149f3b9f6b105ff5ca06ac981b8084a1a13f1e986ca2754ef096dfacfac80abf67ca848e2951b7d6f77b7a56266b63880378991eb39b4731fdc7b13110d7450b3d8ac9479692e7e52887db42d2adb765b7d0edb1264c39bb3c10561b3199528617d8e81c422c07b5d7df3a423bb1c85e6e07a24d6fb84f8b606d5f1c5ef69ee437a52469fe74aa4dc2005b14ef4647a962f0709bbe6cfc41f3469cd0e1fe7131678beb530d79966e004d4164ee4005c57d5573414824ed8567e89cb241af5f50fec4f755e02d35878226aa0c17e6a6eeaa3e9fdd24e7b53b3714b979cc91662489977fa28110bd5a81a40ba0f91b0e71620697fdf9b81906fa1f6e9ae7eff2c8b70cd92b0147bf7a3d8ae61c3111640208fba9af54fe3e0c45fbf7386730f4eb4e69550a407ad1247fb6dcd6454fb7fc2a819cbad59917a9c9935f35ddc20eaf78ba8584b22a95d88ada05b04788b7e393662d4396c7ba12d41c187850357f51db61cd636621ad0d4835889b1562754623c2fc4b669b4138db649094d54e29779aac498508fadc4d86146f50b68ba42f3aac424eb89a1c1a77885c3e24d630b2c26d7c0fdad782a95ce571507b3246e912772758a97899c55013fc54b859033a17353b954abb053bcad2c5155dd53bc49581c5d4ef1b606e0a778c061531174a7587dd06aaac996f1b2f5fa45798a4503d229feccdce47e3cc59e255500488f4f00f030a30af3142fd192332e8aebc012188423bb816531c9a0ae42f86125452b5444c18bf32d417f852a68f0a1c7bd69c97cf79758a4643b4e152e9f9ce45a11caaaf69af203107f87609a23a77cfa8a14dfbda08651ad244021a7c1c0f6ba95a64504616343dbf207fe4e3660a84dc2454d15d10fd6ec1ceb6c0587c71dbb960193ec93a6da664b1f2cd9cf70f86366046c1af40462cc0640193cb7e89422368705fd45d22f48cd2235e0b998e4b8df2909cfb481f3219f9e377323c930bc300a68e8abc46b511dcad4bccadafb20fbed0e371117c1fff1e675c0848256e70b6ae129af4e3957505edebdb0604c750c262e23f9f3bb5f2b0dd6b2b2253d99b38406814497d3ff54ce736e1b3a67e57466a4eaf24b619e49b5a6eea1a1af0dabe3f7d6833c61d35716e7094805d471441d964134da542f3d3047a9468211d834b5dd806aac98f86e2dde4970edde4e2a8907b85e9bfe77ccf0ed555b95e28ea21a0d359bf6b6f8b47a9dc7075f050de596f4acb537e50bccbb7376bb2bf18a75d05d3bb29bb0f21404f5e469da2684de10149f3646c80be0ca6989859e9e915d3d602be796fe6060f52cd29ae2e7b57b314fb54eea06b38045d3ee3fdad38bd8e140fe47604ccae6d4b604f25d25ad96229429059813a42cc46df39807231c98f3ceb9e722526c6933d7c503011d42717de83b12077d6f49de501cb23f93c37c1da8ad9ebab4534933ef33bf44bca3c02b73883e7970d87819c86f9b5582771a3bd75bf65e2f57c3fb45c8f13eff293367e3c051fa23e1212b320b8dde051bcd40d428ef4c0e2085e74571c395ad1d785258891c28146170c9b80ebec734029b63e8dc98f936e7264a23ee961e859d44b449a26e567b617e0ee55dba02cecab913957ec2a4c6ca73c421230680739a13d1f32107816a8ec7b1de8ff473c808b6d9140787d8cc4765b626c4fce7662bb3a8054cd7eefb40da1efbedca7ecc1d16b45603e7e20c55b04bb06cfa503d4dc0559eae40a0dbc568909c6016aa9d5f9a489e59d9eca498b8777a12945e52a3ec244d4d95c2aa0bfbe7486069bebda379449700753812bb157902be93fcbe55d417fec8b3911155d72d532c1ad382027c8e7c0b666421e0594177773fcbef964c1c59bd76712f4e845c91dbf57269c14b8e78dd89cb5a70d65be3432beb79ba3c0fd9a18d503d3c6fa461369b2eb63dc6be7427fa4217d95a398167bf913031a2910b6436831c64c5388c36037e1af905bbfc80e9ec1d26a763703a5d227b7782bfc300b61d8cf04765f2ed1ca55f4f546513004794e17f68207244b291cd0dc98137a074fd0eb7b50577415b9e15c2d8c7d1b34a38f2f36d51e2f1d322e50cb79a986f0817c3bdf475448c799f401416907980bac66e514c45485499992f2a1ca4cd6f6aa1e38a878a0aba8d29c52d8794bede7547c04f8addf1fc55c49a664e9e1006ba22fb6fbe7c13b0006f46891b96932d7a3f5b1911940e3cd05e34adf4aa3b2d51fdc89378d665515078ab8e931d9c1e9b8cddd8abe4c5d438031f300a47c54b2c1d4583f7caf874ee9e61a22e98132162f2a4a9c4b95301e631ef05db441274365937fa38f1a41467e9ec1fe8ca19265979d440bb5d9e27eb1f4b676c84d157e185ce9e8c4f39cb46efe85b3e0329f3d3825ea0dbb4139f0ce0f13c3dbd77e9e7e0e9ad47fd37a466014dffbcdcd300ae5f45db2b13c3cc8944cac9ef6a3456cc7a054a053240f92715d66e0b5cfdc5557cc436195b4ffdca14e2ef83d38c02c620a97d241442e30c8b35e70963fc4e93af4dd6b231ece5a6cb40f0cdc97ca9b58bc7cc66a89401106505a9570eaf869eba125892cb472f781970c9df024497a69cb5db162eb9e6c5faf16b31c06a3711a05660ac4f98fa87de1dc1ecddc3caf7f414b891061e0c003be97f7e4bc976f7453a526e3e5dcffcd5c0e79ba17a97e7df5a93afeceab22d29461fc06a78a015649da9451507f1c6d35de89e7250d492af745fc0d4a79b30533de177d7db12f0dc7292170b98956dedee1244acc614ce0ac75bc9269433787f442122e402d800e2d2a0a4663f36fa17ea1df47d041f4dcff25c57bed031b73c11b013695bd28ced4943a66b5b27776c2dfbcf1d1d2a5ceb28a5dd52bafcc07f06468bb1faf31113983fc72ed061478c03c433982ec9734eff022155d643604a1c4f1c988e147493f9967582eb0727f4c33d2a85eade01bd28f04401dcc4518c0c57d8c45f7b6baade5aa9cecb276e46d399b5a2b34e2c17d001e28a7636d9c960a4cfa64274d93c0d52aa1f8f6bb7b799183e485413792ca982ba05fd969ad88e6a27ec13efc6c65147c888cb47414d98308593d0175e6604ea8c668c833515b503dbe15738c7e00ba9c02ffa3da11c4f35905723629eb2317c2fbbcc5299467c9fd68991be1b90612dc98e896a9552f1cfbbf8c7cb47a411df5b84ac2f77148433df6c314151e8d4e0a226150001047fa3c9f6c22cd49ad8dd28fc9ac59c9becd2c611c28da19d732f57fafc2bcecc1c2226134c33b8bf78a3f7767223e5615564b6dfd86988eaf541b1e9a7c10a163827988cd741d8321947c140013fc3e5ba017db6137a44daf4d9eb98c3121d39597a17e74327b6edd3c90923bdca3c657d12f0411f7bffde108c9a3735e4e58bf71e7ba82d4f5eb27d04427e5cc8eee4c0e4ea9d3986a299622fea24fd75cf7eca500be99b1574fc5b488e4ed897c6b80f33cd3956d7225947ad4397d7df06ae685ba690a15424f1cb8d0cd3a7b3674de6e40aec8895dcf388c6d006eb14b407369c321cc450700e0f254cb9946c058e6b4463d980bd957a4525f45c151c57131544752d8533874bc41c790591792cc7370f5c2e86ef0cb7d2cd5e0f1b174320e427cfc542983bec7c12f1a6796dd03a32680a596d41adf7654a98e5ca53c17463f741d6b06365acef864186a2b319eee88681568fd00776ef0dcefb5d1015c1f785c999109ab9c6172e3e210cb05436b7dd9f41db9a52651c5bee5f360d61557e0619230c0732d6d4f95880309c1ac5255ba0c01496af72dc022e3e1dd4d1c21607de91215b5253bde7909a575ef09028f59afacf29d509130a8d3d076264323a74cf585a2c69cdf1afb37696d2f955641a330ea3185621c45abc6c32d775d2c5460a813a8e0e63418a7a2c40528d6c27f082096c05a76141eee6d3a101603cf1a581bc9229acf619a85ada69434ee2927e416189b58647e2f2d0b7a56a9c6e7bd71ca30c5207fbf03e289f87aee2fe6f6d2c7626457fcac2a2c1d62715e0ba1609c0ca05abf4ce1bd39e0e0a40757141900a6c5bb4bcc3c3e3549c870f9e1549fb00006168032b36b9e4b7162a99bed16613e980c6ace5f700cacad0978091b3efe209ddea2a6f20b66bbdc6bdf9a827d236df9479a1a4708ae7d442345ff0d9743730f0f2ac2e75b3412074785767ccf331dede0d4c7cecfec249b6995fa12f407c03d3c48e3b7e64a788fe653fa9640af0bb632c750092beb4317297ee0496e069a4477c441d4cbc7410715ba02a5f3342cc0e6531d0cfa2f68362f7dc696b2740e57268031e8d87edcb9daff067ea25f1fc20f7c04720644f482698a994702a70f933711d7399b1350c48ec4f18ff89388acd0f9b828fe0e479fbcdcff23fdb650d3ff2122e34781217041bb4a2d5674416c2244dc0ae5beb10c072e23ffb194cdda2c045dd65392698a0ce958a568d9344239e901b5e2e8569d178a8a7bd631ea6db40ab43dc204798b45d34076fca301633905ffd408b24d3a304cea3aea7bbb3af0fa615e32bbc596dcd125d08b3ea20f7150ea552f1a6b5041aa22bfe9c23601e4a91beead48dc22fb9976da8c9582e214269ced43f4b95cf85e0327abe67d8e98db4889a50b23c7cd63c4f3bae7d684b9865e24b00522acf372dcc0f4ab388862b96dc1ad66387a6100c0949e8f788cc6b233ead658fc60c18f5f5d9800f243e777c89987e428d39173ef24327ad007e721704b4be26ceec77da4eebb17743aedbfd0e959f143f851c0972294771439b1a9d3aa543421208cb73d0737082a54a9255406c4e243ca75f095c5e8b839ae2ac2ec086030b4d84b352c4bf221c20f6e0885be3932605398a07d1854c3bbb704c9426ffbae0e9f68c7f61c68e4b8d9a6b63c2e79d23c82e1572318ec1fafe4c7ade93e1b7e90a03048f09dc5311c863381fca03d6dcf2e612dcc4205d97218c3f8244e7e6fe37ca8afa63b72697694cd29b9e085858ccc59dcc246e89667c29bfced600ba734732e0616bd9e09ab12c8eb6481330af2ee0e3460b15ac97cfcc990db329ee609ae784090905dfd1bb13b742742547f52fc18d916666f4217daacb1214b71801a01c683cd1d7e2c249b1244374d45520c0a503d8f06a569e246b24f3d90f47f008594a68811611cafbd33482eb2ff3d1abbef66d98070e87e78abc86bf53bb416b94654cf74bf0273d778b6792da2ba20680c43426b16d4857c0e69296248a4584b5f2b8a350f7dc087e283cc111418a5ed2d91c76dd72cf0d4ca4218ca9dbffa8c85767a2148ee095564e8d0d55fc3c676dc479345f7b165b3f57d704ca6e4ab95d7dc3d606ef0c2b97bfa4c2bc236128c1ac2a1ea991c28d3307931b0ae1858c46108c9546895d4db3bd53cff5e2992e3cfcae078a0960e2ca6467172cc577a95909e9aac96d2811d6a2951d7a047d4fcd2779673c664b726e591e4eb239187cb77b2acde664d53c17488f81698e35e2ed705d01b492d62643771c9947f76fbbab78559be55c05c5ca314a1ea7c629f9c67bb6966daf764c13ceba15143ad7977ad17da6bb936f550d7e91d237725765f2a129b8ad6bff4d3d0c9bb3768dbe06aa9a6f93be7f859ae129882f343fd0c36cd713ea3a1d786e2415ec6cb3066e96d76986361d1ab6198eb3bba1dd66e015b6ba82e01d5ad87cbe619273da145447904843fdb92383d5a4e034a999ba256f11ea2211198c8329fd545c69939a196544fa09678c327229a6d358982a426aece22f377e9232d8735d428ab5fd4a1f5fed88820ef968a5ba66a0bce61a5015d5950bc0adc2a93410572473df28ceaf3874dc4ad1089c17aea169bd22ab02f86badfe287766bd9c041491d1efba90c1cafccda7968d98ec7575f30486ab03e125131b6b164591ca2fc3d0977b8906e7911aa3e5b6a15cba92da255044344472019003d58ce9c760866936629d307a3350c05fc8f88ff04d70e54fd6f08e76a073a8949c569863bb14b73538d41761ada121bc7b8f458b6c3b13185ed31b81a8b919087ce0e9aece8aa0e5c82cea89853ef705aa6140a51eebdc15ed281d444da84f945b3b4c6ad6898313f9c9c05104480f9570f911cc79e5fbae4cd5e3caafbeea706970414fc2b73238e013a55591e29197112d96ca82cdd5d71e57ee766b2bf70ae60f01971827b1a30f6832e6aa2ca551944e0f0da9d43dc1b9e2c6e0c3181380c1e7059577aa1663929a846b1e1e18ed127e18e809932a6f6931db5d37d1fc0850636b9d0ef0ef90c9bc9a83190c378b567dc32ebf019a384e1f8cc322037d0c4fe0675631eab1bb2fdbb8d24843e5ce486d898a85e285a79fa2ad13d02e638c4c9db106006fe48752aa49e1763328c94709353ea5b64a16882f790c3280009a4b107ae9ba8fa652f382386e4b34c7e6df77f55dc35c4b221ab48cae8bf70fe6649374d956187ae5c29834222807600eed5cc0fae86cce3b51dcd2c6b9dd6df00d06cff4f4e3c57ee1263b1d0354afb32724948fc2f82a61b1a638fec4980b70a9b6d90f178ac47b251c3955752c13df5ab2a610cd435468a8802d43b4cb31e31f40cfaf57706d9f1d8437c0011ace60430c49580db38f11a901a13dad5e7398d8b1dc68cd80b48760e3377c5d08ec24574cbb7372c20056961db5cf4f7bccb124d464ad76e033b72b8f9b6d5ca69aa1f17a4605edf7378c5e2a1093fdd85221629c5f3ce95addd159c3629e0b9f629aa8415a116ff3c649d0aaaca45c91e162abe25b70802fc36f0e6401ef769881d1052a583da84468796069f169f934f04d830aa2cd51af608664eb3511f166bf6957c61f2fa8d9403eeb48c465c2adf9f79c8d44167f94e35301e4438447c8b5f0d6e65015423fe16852f7082b5231962ac093d3cdb1d735a9e47b1443017dbc9a16cabd1e06814680c9247efc8f899e9dea248f95c9aabd78d23227c02438b0b7b43ae56254b360731d10fe6a11364b193695dad8219aae904ba813f638639f4007373cdac4167ef60ed76705bf62716ee83a3044e153547de134db670b8a3a1fd15dc5dbe00ab28a5728d58286bb6df63bd73165bef87f20b1ec53474998cc8b5e70225f22a61cdafbf59205a925b0b21c6fdcafc6403ba38bafe0d3a474b32453bac1722b297803cff6dfc66510f5110236307901fa63d910d25deb3cc9b6542b070c975fb232cd568adb0ba0b2ad8d617343aab703750195ce0a29c1785e1772317deb1954442afa5aa5b36c48700ad5561d60f66ed817bd2dc5a6ba0e74f99bd6a78d37f1d29e2fa6adda6e9dc990c3ddcf5154d615b82fc57e41cea237af93a2d6cf5dedea54d43ae230ed7554f4de0b5817457f416d51abafe3a26ee4be7a3b13bda61cae6ee7a2b6ae807d49f40b721595ee7456b4eb1750b62cf8e5728b66424f6b0607a1dccc48dcc810cbab3fd1dbd5a9e8ef735f7d9d8b58458e57afd3a2bf53c0eab2a8afe82b7ad5db51f4d68d0074d2a057ada005bf4474e6a79468392b89c9bc6a252d794b93337f52ad757969cccc55546afd29bd20c542e752a9486192cb8c4ee34187bf645369982712b3d416c169b0b5edbfac27c8506854ccbb2b78175288f397a704d01ce38ec7e0eb8b58c134cb123502a41c596823123aa668c185f48c1592b382966483e158f32f7894971da91e607807c845f5347a1c0cc5a261622595c6c6f631e359914ab9a8c642ef44b0a14a21bea75964e841a063887447291af279c5b98493e32acdf398fe5dabfb07355e1a2f2db587093f9607f68242e0d9af308a1fd507215194a1ca63acba83a5aefb7d654e6994fea561c5c6b36b61c7a724729cf928e5dcb364590945ab491c61e8328ed5ab2c6c85b3418e3eaf3eb9d689c4b282dab93f5a117a8b7ec1451370f9af1dcf5506676adfdcff3099d200fd1c67d32c3e2c85547580d956b9195d1f09ef7ef078a3fc9ff807ee18de89cb422f28734df487e0c7cf2dc6c49dd00c1783147c2d2a58c8ee824d0c28ef5b9f84accc3bdd0d84a39f2b68d8c06452e39e24d47f034c460c98dfc51dcdd1f34b36a01bbe6f0877270abb4bf463bba8bd920d4ae2cf3460c2e9198dd0ad795f83b7962192ed93447664810cabfe24ce7708d237c50af49ce68746231ffe6f79d82be0388a6f801a2addc3005d87468b3cfc90b0cea943b46a764a197c13dbb6b432d3df112fa788f5525fe2a3f9f6363cff56c026e1c5d4ccfec5d5a3102fea0fe2034d21e86cf0adf289e17f9b3fabdbe2ca533786c6e4609bf404d2a2f108586af9ae7d949ab60e4e3992045ae41d067fc7f39e9a904f2c74df3fe5f093fd3aa8a4f071c09d456037eb3d62cba79d28f0baaa7c7860648d9aa93f908c540c81729722f542d38dbc15101a22c59b586bbf62620b6fb60eaaeddd95831d9654a6979e30c01f0f8c9d32c56df7918ddbb1231b02735cf4de5fa4b68d16a1964cb53399088e41cd548da08731eabbf3d8e6e9fd478ee9faff472b46333489e7621212689fcf7a7d1fbd0866bf1080f3709246e59c97859fcc2530a8de500e11b8497c15149ab1a95235c7ad38aa8a5510085b6b1d5f3f1a81ee07b798bb4eba9aa071029fcf383ce01f98faa9cbad00725eea4b8d5385c6aae9a30fe33249f3b0cf919d3474e855811b988197784e69c9adad9496fdbcc96eee9c6ed20482907b6dc4c463449ded32fb2f9e541c75b09b89e064585f93ee0b74c5e70bfebe53c4590d2f8113a8542eb3d3a3f7fe9df36bebf8f3afdd2d8ac775a2a6c46d38885988a23988958892f195bfc80a14d37566d2407e8e8cfecf25e1582d2e7c5545796d5b00ccacae90e96f92c9ffd935b5970b2a357eb6436cfedabd3fd3b4ee32510a99178b75f9558721e14ab6f0b317392ab68dedf123d673ba64697c77b73d2ccfdc312da0ed11a0a5f3819ce557c41c81d649b601ecb04a4bd7622028aa10fe8a0dfd283f79599fc892cd722ab3e5eb1815771f5acc83f6415bb679a45fe63538de12ae5a846c8bdb65fe661909fa1a318bcbab25d80c9fed6b17d8a0d13092821a54b987680c2f55833b96b4ec3b4b1b940b57357fbb841de46c2ddb33f784d51b2ca5b5140ba083e31c349d4955812686b86512e02f8a406cc1cd57b67ab3ccaea410d3ca4b3003594ba1393a3276b0b7ff52c5df6c6cd44da1a1ff26dea3e7969a797779418dad10bee01a267ef4e5a026484692282d2884ec022f43dc4c2d9498e60544a265b832d81fa283918d2d008908424082e3a700a091e6dca1361040270781105847c02d8236031640a85ac8c41243f3e9bdc0b3531f0617401ef6dfe0b52c736da281e28459ed06a5c86dc1177f282c171a6238d16a9764fa229aa4e2f5db1df2eea474d0926cdbcc7d7f1748da8e98c4bbeaa06658ebbc3c790a692e2cf32ad911a3474ce539c915b32e588674efc44a21fd450ff6147b827891474bb911612c9639b8320e7dcdb0ffc716a5d22be269439fbfc1f1609d6be069ba4bea6ba2f24d47aa3e7bcabfe57a8d51e2b737497e5c55d92908bc848fa3c43f737f31fa91e040ffbf81ff543e78e8e88c276e8dda32b4426981f0c390655928499502d4f777bc1e058083fbbdb9caf1169c4d29f8ceb33a4e24dbc052c69bbb922b1597abe87f88556d7480796f986087b9f634405dc2e713ecd21fb2940b1da90562928ba81bd93dca411fc1c7b0cf220a0c015e656aa1a8a050c7e5575b7d5851d5456f0c8051d52ba8319bed957a8574ac38ddeeb1172ecd44f87020d535ad4ac7075089bda86b9d5193c5cc4ee8fc9b5a327c3bca5f88c6fd96e6890ee9703a4dbcde9dec02014a8c6a041af4f803ae321bf6a420ff4b1c6242d44e75fdd3b0b64e30b3d73004d4b925442dee2c12103f2a72366ce22a98ca9ceee9421135080c0b9774793e2514874b2237d5771c7c4ed8d8ae41b9f3fcb5da972bc817f1f45c2b90f25c5640fb4f4195807ab63620315be4602ad9ddf410be8d8d8e00b75c9d6d0d47da5498568ca1a2f6c814b7a15777264a3d168e71d60e7009bd54910adef79c367a7eb39a67fc1beb990755ad804c67fc7d79e88e544f3228f1e4a6223f425fe87a8e5108b9b5ef81c762259ac3f1bf592f5e6d44f10f3dcd8298f6a5390fe1d7c1893e3f75445044966611169365c878c7fe6be8a24e111ddb4abbc8168837944a7f17a23d6c420b6c74b83b810308d7599bd53a98406a0449eb875f4f2263f60e4323b8ae1b795fc761a53f3c7c717eda41ba8e9d8d58c17b66b9acded34371e7775d353cdaf5975cdbc31b151a87565daf264cc8943a8577554c24792304d9ea42587131eb139fe371c3b602ce4ad3e38edfeaaecb7b248285ffc5ac5ef3c240c77ebca9a00fcc676e3b900425eff372f734e61ede7c11bba2b8173a24cb50c6ff95ff9576f9f8d98c75f40032777333e70876afb5c590ffa471d7e32f795615005e8e2bec1b1b46977062c0597fdaf1bfeeaa2b46022929a3224cbe1634ff27ab4fd1c6e21e152271f5355fa2d950b7c72c4cc08fbc46f1b7e1e73d828845bb934d57a13ce68cc22053f2cd14710fb3f729835b25774bcfe162e291ba2b8e1b4caab7137676e2fb60921d9ece6561e530a8d2c238611cc3369ba8602a9796b565f35e33ce65e9762fd402f008389cf284030b5f000ebb867e497f8fdcb9a4877ec9070b02405692ebea015a97c517b91df09fe6e8531dff0b3f0e0e4192f3f4f4cce00880c457e9e39e5406c8f4a529aac4145c77f72e4344f6e8de7b12cf5a23a029fb98509cc4839eeb31d7edfeac891e8b8eef1f327d4d0b921ebe28623cfe838f4c3494535158a524fda55db99f88ea31a51f7eece94713f844854e981a9160dc3a8bfe804fc0c8dacf2a385eec727be38fdc8f8b2c93edd4d1efc45c15e8dfc5765e23e723e098100c2ac6f377af122cb658af4538a0769b91925bc0350c042e02a0951d3707d40bcc794ba7b42c02b2e3ab8df04d2b8e405c9d3689e12cdb2a4b65228d25214e298f1bc3c7ed41970b661de2929f9e9d6e71b1e0b82b10857c891ffb64e268a699caf039a7215774a47b829c44575a923856040e4a3128dd15ba38d5566497040607a97e44044f01618e8c5cfa1bee7818f1daa9d29acc7f8ad405acb2cbe735ffbdfc385111db1df2df56e0cb097b99ac7782cca64908bc8a6db6da56f338e244e93c80a81561b46870f9951312b6ed74763bd01fda9123b09c10c11f67ac4f13e0dd13b6321693e584f1f19e547a81fb74b39c38bc32a036bdcc11676d39492593edd45934a715de5b1090ace223c64b4d3dfb7f4569e2112b57e347d08f3c04a90cfe27efb80c3b18b7b27cad068c0307133a11bd00fbd886747188edd43d246e10b0ae019bd27d70818aee4f29f7838d5c734a671191ee7ae0ba1af29f12a174c5ceda33b7dcb3dfb636d224a808f67fee633331d7f589e261df889599be33618ca106ccf964bc0ba2b7909df7098f86a04699166ff2dbc95b9576ded9e44ce6a0d9f4a6ac75a73f4b271f06af6fc946d8d3e08b588c79192466770645a40c553ecbcbdd02a423aadd343a328be142c24b990e239496bd2ba4fa79a4def82b01062901761c035d1f0fa0fbce725e6fd4017f4eb7bebf6409e0c47ee7203243460722419e0d3b32395d98958684118c072db6608fad188b41b17b3b17c7ae3239d2e11a36db716e15bbc1973397edd7459ebf0751a109a838909295f911e41891db3142e72d946f664b4cddb61eff0da7136e001ee102f135fa7850edf417ca95a0b4dcea6905c1fc38d0b6480f42391861701803b1c9453eb0cd63321b4317c6efbb812a7b2ddff72d97d084dfefa52ae015a7413ee8f0e07b96748ba4259edffceb1b4e232a6e801cf71843c640df22d57dd60b54fb3b4727575d73d86c650e57bef796b1698f858aa1863cf7566d998d1a6930127fea9a2216741482ecbed6ec3bd7b55772166ddeb54dfa04267b0ee965d9a55160798ba7d0b6d140e74c76bb8914a9d39499780a5f76ed1e161cc33f05b45ebb8c6749a46f0db15a53a1cea9c25149d5cf0092c1e359a5c7d3fe1fefb9c8b13b37804628939b29b95de6a608c62181d3f7f78979c7be722abb65618e9e17790dcacdce999499204b26350535fe7ab340f62c09d00815a99fa4df36d31a2c1b2bec5db47726a3329094cd2be9c3b24d4a35251d5471454479cc44f273173f4a0875cc4827513c61f355cd11f4260c215461e10fe203b3bef2cc98a2e853e1d6b12bb6be39f782f7d30e8a37fa128d72b3e881fce14d7dedf51b11092f4c5c74b0e60a6aef23afbd64851a2d86ed25be6cb1eb2fee5f955d3fafcd4d518816d94daebbb681f4ca85b19957a86af0920c5bf16acf938bf8ba4a0abb111abb1fec47dc56276e7142742c7d831e7b16b9d4c78e18081bcf2ffacbf1cea66b0101b39c7cab16c7e40eb38217222d9536e606eb2e4dc5c03f0eabfad0c320831bfbd60701ed1e37af4b310556142bf6f6ca0937092ab2f4c88e0f21ffdbf2d5498cd553af48fa4aea17beef970c2ddf650ad25f28296a19f8395d248c63c862dcec5b8eb1a1fb8d2fd3d1ded2266da4aac881b9ac507de45e859d41dfd97449050abaec2718da52f5637f8ca6e15891eca1ca83370f59017b67578cccd60267d9fa4fe360edf7bd46afb172bc985266d04d7985b337de1d4129a549edeeb2360eab0e375d3ff9952ad3e6e3c4a6b88eb45658ab4e826642b594262f792e074ca72cb6dc5f667c70a30088ec551e56acbd71b37f0315e25b55156a9bce1abd8c3956b35ab677aaa505486b7704683f6b7db8ca66e72ef44aa11ddafe2c93fa615a6851c7701b4db301635d571c5b4d8d24456ddff792e9aa5b05cde2b51d639935837105d97e01b3780e2c71240c63886010237015bedc39e77bb4fcfe4f3c703dd65f2fcdca1393e35ca2a224e8cdaa3d97b112591dba9775089f875fa832fe92f1c0eddddf9c7dd0818b2a33c0e01c5731a5bdaf6ba3574302faa18b6c5b7820f5c117403b875e9247efe2e224ecbebe35ffafe5ddaa7349c70a695f9a52b68641ef8dfbef0b73e9f27fc9373262b5aa85c3c4dc12b9a3bdd4f20d68ca35db04c70ce84aaef55dfb289ca5bf0873768adf8cd9b557c270f5a362b6e2f6177e3f90dc8dd105baeae4f5d5b1e4713e599ec9c90360324a0c3a5fb90352163dc7d4b584d0bc323d89e2308712995ba35ac7a0cdfc9a3baa7c5e7d691434665f4a98d3ac493b23ad67a92d28618b6605910c30a68785ae184baa750153741f2d40d989af951c089a9820a0ee82405c777bee186b7ba7d62de4f46be94651cdad3c7c548dc122a6c99fe59fcd48a3bda7634b7dc37b282954996fbf29795915463ad9183c056e3c752f0b5edb91f5175fb82bf0b243f41fbce38b05c95ff17979da0446d5875cf7a573ca620d408f7d7a1afc5668a0640effd0ddc43ba4edaa502a751eb2e853e5be53ccebdd0bed293c27de6c5ae765b8eb4fba22b1866c7fd6c3636dd42b7979bec475beaac10ae2b1afb2ad570eadfe98b5a774882fd72d700f7fdc0e29d3c9ddb7da568b30acfc853edecad5c0baaa0f5e75143d20107368b5032492fa26602eb29dab260aac6bb30e87fcf93e3e77793d56e9100e598be80ec21586c463c8b490ae9b5790c1fdc349c642f7dff4411f318f75833934a31f9cf996e4c222630b3b799ec02cc1d286574c60ed211c3c94a5e906fe3bc37c5b74c1b619df5b2792b865192e8ea20530d5e868f7b247b67ed374eafb8fe62837141e728ae8641628369abb31e80e69b32526d6c70da4fb5d85b4a9b90f0dfb3b75c0d7182083dd5fcba8de558eeb65d4e82528056fcbe44706746b8315997c544cb1d89ef5c40e7144c47c0ed531a9dc50a5a0c55ee942ea45610df27e24a05956ace6f43464710c7e528a9172adb1cc890f082917185735ccfcab754278a525d22c2c97c2a28469cefc923a65246af70bfa0e4a1fd5f8ed2efb32d1953f14ffd6a5c9c8d76f350076d16826b7822ac2606cc3bcc3e134b16552a1b9f220a9cb63d7cf16e651d334ff4fcbb66b219ec20a01ba1ff1f70efbc2ee6661ed8ebec2cfea809b74fb6b45edd28724c0f916f25a013410c45fc4b87e794acb0324d89fafe6060fed547f0e75e0c7c49510c1ea6ad71323558a17f9e4d1c110ef2bb3155c54065729a1e7720f45381a95a774855ec9d530d86d242fb5ed182bf32a25db9519b24a5ffd8ac3bf9770a75788a462117761092b55c99db18608ccaeb56a859850250afaa0e2f5d24b99e59b6a23bc588548964f65bcf8bc03965620000e23e814b83859e40c1eaa47481775921419c6bd7d073203d1b31c575a3bea34843a42700e965a58dd39485c72fc8285b2cda97bf588e9b3febf75cbb090ae35f9240fd7306d1e2d0a3d4b9335c1781533cd39da2eab044f8333792d1a502a86bde45558ed9f3bdecf497bae78036c33db2aed59ffdfb17468e6f86516ee9f7e15a708397effb4a67c1156a70ff6c008a1ad13fe0e5cdb8866a43762055f98bce3f7f6c5d667ecfbe3cab342fe91e5de91fce5f3e6e24787142e42e6260353301c7baa1e62dca52db8239e30e0f0481477104102536448639c901ed550e44732bbd3dc6912dd4f9a8b2df93557d805b1c461ad21297f5ccdb8bd950b887d444c0bb8ef57c72cde09a7a12ee646f93c3df32f50275249cf00bc068614c1c477e10c0ef2126404cb4100502764c0ee764158984f1650f1374a71220e180ae65d45c2b58dec38b804320d449e12e9bb38f7c7524ffcf4c1613d465263ccf95d4e23978ba1e1497df3c5ebfafb88353fb568bbbaf23fa6bfa344f2f8ca9e8fd23d92486c9b17d278e9fc71460a9bbee350a87f6317941b04158aa46ad646f31b5e64cbca1fb5926a5825ec44bbafb0d75fd761a807cdc37574b7efdd692020c9cd69fb9c3a8d38cf9a76990a22a0dc9b6c224c77475daf95150a9b1ebcd745bebea5c55d44f8527a5b240d4c38cd0a545359266742db3496ef48e0bc71e5bec406ef689c1502125bc3923de7d0e55924b7f059753e10299e95f6f9c3cb4122c3b8faf141b520b10199196dde4df32dfd9c760375b3ca939481102c228bd39368b9af0e379d952438ac8e20e74cc1c090b459d641e88cf5e4fd37fb37afa893a297415b913256fd661f3c9d44c3f908d1be4317777acd6b79a504de73d421a1ed10094bb6e9e56810af962ee8c60991fb7c275b2cf8b758c15202e13a31714d7a348651f228266be537bbb09eeef1a0d3e58c77d2cb67b9c44d7a32676cec063e0f3860e95d8c5eb90490171c1fb6698b2816aaafed10083a20520f4d3f74517700b3c31d592c109dba97c99e50e4bda35b31e4e63781f28950570162a184380c280c0c9810bf7c387bcdc8ca40aea0856cdff91e02c8ed8b4638d88baa9429575762d8c1357ddddad98d7bb7f7302c289e4642c387310e98be81b067fd088d9f992f8f14ca6b788979cf3062e5b9819a9d0edd21387306beca24e9dfabf58861e2ffe5b1432716f32e0f698961a543dc89e2f85a058408b88037ca42790d81aeb338e9be331c2017c935af22bb4d75522120f07c1672450da5d97f68065c1e9e7d28e64d46a3fc8262d8e413e3d01d54c8c04e879e5b2d399ab002229d48cd8acb03a1d6162bac2e5ee1bacac8cd484421ee1a9093986f9bd6821c8c426d82e8bc76b66df2d22e96200db2b9754be9d4e5485a67659ac3f40e88e491b89d6d5160a5712f6df0c691a28a0a004de098ae74961926d1a90e8fec64f685de380205621dfa2da5e9602a2251fa17fd968ade41685a40e16705a0e3df42f38daac2685be940909147399fd7b051327be64124daca7f9aae3000387cfebf3106f3688da870db57d561ae01c8ff8ef49f4141378b18072b9ee991c7a594854e4280a0c11e48302bb5edcf4538c9e3ff66be0afd85449c8259d048a90d52f24d905498c5b11e507bbc598a26901d13fd33670a71355e9067b9d08cbd64f7c70e1af666f7d17fbf22e9cd5fdd4bb1d977c95054be7a73c1532b9b53bdbd98eb16b9472153bf367b0a0c44fa57eb1b3f60a8c5c8a8d623680affc71e79b289310ba392d32712d731e89d3c6ef0e1b5ff9446e4ccaefb15b43baa1815f43582b199c59bf3f9a36d64bf453e04ce8ba83ab0c97d44948108f51da882381e85abde7ff26ab0bc7d0d3de7ca0a86d411b04f0ad27338e854ddd7c405e86fa8793e81e89c74cf61b2a3c0944cc9ba26832323b35a33f765316308670b70c236ef764f4962f93c74fd7f501293bed0041efc870a4d225c426d10f571fdfebf26c7f61fa57b5073006446669eca4cb39a61ab92c767c8069909def8390397e4f1910a06eb4b34ccc31bfa2e39db04ca859c59446f9db7b3e58973e86e795ff92537b0199c170a81b326f0bd88c8ac4b213f8cf1046fe6d0785b38e043f4048621b0d6b849461a83c51c98cae0d5a1ae855015d44f57d58712a61e071e46e2ea10c4a914e7950314b3309939aa2c7417b3d9ee523cb3c8720959d1f1fb9030b6c9e2be5d522e3d42522ee964689a4ce6ef97681e8ad7e714f534770a3f28afd002d5123358b0e0b9230276b3c9e74f71e2fa2403953eaed16fbe8c82e8e3721df479af948cccd4ff2a2a817faacf2d876a2d493181c286d963915d08d6e7dfd4e6a3b2b54806c7ca6299df9e28c2e01bfa745fec0d20787bcdcf2dfa2dfb9693256d96d178ed1a6d02431673eb5948cb7c34bbc1bdd0064670317f35d179538cfc3828462cf08b6cde7ab477e1f582df88221db1626ab1774ddc560725f867fbea1b0f3e6dd1e1e81a357be9276885a604bd43eb486fbfdf60099aaf10d050b2fbba2cb175570f22ce4c8228b685889adbd5148daafefcf996d7215ef10f7f11289c08de8b0d6b123d1b0b69f3d4b81cd3a971a5e43b9b1173d1709e35a1c04dae47d6578f5c15a91adaec852848990e47f3ef8dbf9819104a60ee44bfb4e14697154718c6d125fca2891ff110f1e2621c002208bc0b14f9ba985f54f3e5dde41df5453b4c3a06fe979fab73e4c26f98fc4ac4cd8d8c788cf2e57b0da45ffadd1045826d114f5fa4722d079e86277ca348f65ab1e74cf4b597819d78fccc3362d4a83f421df6338537ee96e919dbc61bb3fe0bda48c75a182fb48d2d666d66ba47ebae232cf461083d3ed2e74f996b77ccdebf7dd1320cff0312eda9edc5b238af3edfe7fc603afe2bcc6ae64d2327eea87187b5cab36f00f25c7e02392796cba22eb99b25bb2dd846a04c18a5454dbf08b9f6abfeb0835dc33257b980d9c38c06dd71ea416b19d5021e07b688a1c1623a005056f9936f925025b4b98a3f1c7333806cf6d744f78663bcd734a96cbc0a924d0328334ee35a5d0bf787f0e29819cad42414f8883ee0f747951055bd59a8eb06269b0c65b359f6cb8b740725d69ad8dbf3b24590adf21b7867101e97ca787f5e99ddcda2cbe926c39e956b05a08e6c8db0c79cef15d9e3e5d9f9eb18fa005c3b6b47eda20fba9ce46cca837ccdf54a5315ec18aae654d26a8d84c4688ec226115ed6e53bde8a1160e3711d5fb488f2022d55c0a4b45e19d43fe55391b9f57addfe69e2d6829a9c0612c89a6cb2e7e535171a2daf7c9c698234a264c4d8f0207bede3c0ba0d27027427a93fb69a320590a3eba0d34cfb177196e6863285f83c4b0f13d1b4cf297c4611ee60daaa013340d2f822b9db44990d1d1b94855add207204931c37827b3cd96d68cc14ddeaa25d6b6230b039e660be623559d657de1223b0a387b80ba082e6ac703927e74f419ea94778f7592ee3f4367a51d34c16105ac01e826ac4cf6dacf1086eacfb72399a92c33e2bc1ac421b5a742808c993995432725c8ef3c7315f8357389979182b46fd4b4b2b9bd872efdb5802164c2b97a8b77959b1823ebd4c155508ee5a136d287355cf45655f4a880669c6ae98a52c080de924f59969f1104e7cd99e010755034761e6396231625f54c790c9bd01bf1737e363ccd765098a94fc77e97130f4ce9b1c2e86be87173feffef41d919c7f6e2f0ab4b82b39b7e4c4be376917ed5448c79cf5ed7bd58166ebae189cdb2b255cf0f900a44d2a3180150e0d73158b77d8d96a3fa64bcbc346b78bcbd7cc0b3e7c4454d6366905182fde27a523f2da85e5e5489ed66405085087bca4cda09df6a8a53171fd3f5b0132057986642ecc5c0aeeae51e16c265cd24e3bd81e55dcfc81aeaf3e03fd41664be8705be655f1ceac66e867200a861d949cdce43c8d469bf39467539fddb794310f6b16a7c723e70cd198072fffa33d34c8eaf9b32ab87953e80cbff2f3d6a13c220146ea0c1404a97c0d441231d1a79396c76c672b2caca3153e2a4e1a4c1288122eb548dd876771077c787ffd26b7d9ac1097420cd1c7a30c70cd80e37e076e4d6f97d799e738c68aeafb3ec38fc12e40fb3400447217f12de765ea9a0e8c6f83d4e45960b505f0a6b1c368ba0a69f37276d05c822bf6c4dee66422ab272407abe8b3ae4e57f33c42503df376964410e93eac374ab3fe3bd92c5c8da23a4f79c2f5e70dc7d66147fe1b60976fde84b885f5d8aaab166aa42dfec536621f9cee76c60b0eaf70629393c77dc52c7ae480cd82f4538c22b242f99b04d15694fc2e33ef3bb3c15a5c5cd20d1175c71cd2c474b4252b5b0db9e5a29d5a23031f95c4407f7b5f84e2efeced098daa348048ba3301496481e1eb0bf32e9c2ba1ff35e333f2a455072c439a6a01fc250c3b8196c700cdc89647a35dbd6d01af0b452039ee502946e369cf16853d3b24d01d20ada0f65b21a805432b0320ed0e0a60e9d6f4de54b1591bfb0f7b3e10b6046b145e28160246f27c2fcaed7002b39750abebf6abf2878b804f1d411cf73c329fe5c3edf53ce70bbbcdddaad56f67ef03c29962e5030a0e6579ad56e397e57e5c528d258097b4a99630e770493f80d8856b9b007823967ca2d3068db3d4f4253cbfc93fdd5c7bd42e9510e396b2fd60a5a4eba6be1959618eea012e696a775054ecb948cb31066dedcbb210b153f1ebdba4a70abb795a842b324137918e5ef411d41200d7f1fd58bcf1d4ed796d3d0003b9245117c80cb1af84f83ce15477dba983f38ecc976f7d7a095ce9bfd279559ae57a4b3a5b498384de22c6b7057b0970249c479a5c003052fd40ed4778f6f0881b987e0a5c3486b0212039df8f522a8dc5228687978108241d6cdbbe83ae84a825bfed95234a84c30b076448e0e11224f2accf1f49a4992b15271698cd237566fbaf2061fadd729bd5e3d86af352a3b73d58dccd8171296c78cb26916f5ea232da438cccf7672a4a920bf179f45b3a934313446119ef286b8eb4873932d545ab97453292fc1f72cf9f360dbd3838c056a096aaaa75a16505f52a23127de6fb3134554785ce0b23f186c9b50920037dae68d9e816a01e117b7f950b45c2e1730c1272ab32df13c532fc4e91b1bba28e28f85dd026ab52b4447e0fd20010531c490f2568837e0ca164f90488369d06d0058f8842c57ba3e763d06fd14205f98e9e12ff209ee0aae9ae60c6ea43fc2b6c3e3472c927e8796620825a3551b1cc07ad8f96e25c2e70ca806c6ee7e6894b741fb5380a492809dbbac36d4614f4679e023c5918f393787d2d6f75ec5537f4473b57f878da2b2e9a0981f91d7e8c7b57ad6197df2986441256946e0b454ce59dea22cd8fb9700ef03b697dd17a8b6e042164434e4b4624d35da96e2938c0b84507e40490f30a93361533a3b9808c4ff618ed38baecd00ada596e77d1ad215d72745c2ba30179502e8818a66f2923ce3bf51bf6f5c4cd771c714d08c440949129b18d7bd830b2282d45e5074c9aaa36c8e92493408fcfebc5c689d80b37c41e55915f6616ad996c061b08f7c83b5fc70986bf1887cb47626acbde7418bc03e74f6502c162158b85f960998c273219516aa5794f21c91c35b1e01f6bcfffedb96bcb4cae01e880a77fd47ff0c890d8b0ede60c6dff49acdfb7108ef64aa0173e77bcaea6acd8bef3fe6931e1275efe3494706bf8b390e023d7f6870c7748599c20806440d89f1b1e5611d36aa5d1a2564d19238a0e455e4c9da70ea7f324a8d24cd11e8e92c3b160a942239a1f050ae833acd9ffae9b29c0d636d7e7d0904149f8e8e95d94f1ed621ad7a70a605adb45ab363a273113197884da6e928f43aae85654d255a402e36334d7861d64e9386845773fb338d03ce02fc3ab0ccae66c7bb85ab1036f66ce9c29510610bf47728cd9be306152abea4fe173ac9630888d57d4fc77267ecfe42857b6e66a8373f177212d2b1ad87ad825b1a2681c1a2e9a2134a3f260365f50d784f1d9f3afdf9c39d363731ea0b06a49afaddcbc8819875f6b832afb34270c8b7eed73fd2cd38ea9c962e2d7b66d51cdc6f0bd70a424866e191751245c380eb2cdd88ff89a4fd0bd8f0cb7cf6e73565335e22fbfbfd780d19edcf432de058fa152b6668b09338eb8cd9864cf855b0c9f0232b492fad8184fd83244424ff33d489ff371cb6683e9803814e60062e2dd9fd10a14331b2bf209d2c3ec5a93496f60c151a4c0fd8767be078a2418be9b35c5f49156e40e5db8b70118950425f270a1dac3aee53193968d06cfb11378552840f30849ec2359680891d575e7a9a8e213a69f8599e7ae950d6c5b1d660eb519144988f6ad56da47861c2526526dad7491f8303624d44318c7d1b0a742fe57fbc1906d741958d64c24a578464de6c845bb5c9bf8155dd34e2fa362011ba3d3dba6234368d97635c005361824cee0721990c0ff17a426839e25fb6c650622f548103d8ced1ae7d00000afffc3966c9501ea8d1958c1064f6b99f191dfa1b81b7488d52ceaafb41dd2e056b87c4634fe3b097c591db45fc500a567f0644e3b4b4fb5d66eaad2075d0d640118eb8a449973183b50a046a3dca9ecbdea895091970de2f6d89c05442ce345046078ff48480dfc2ad05e072603623be9b77d198f628ab1bde2fa66250ad1f85ee781210c4bdc48aa2027e014b457ebbf5c0a008865b50148c781d75f3cfe38070a06a1478938c7fe381ea71737cb57e593b11370787a87ab50e53db40989c4c43bd327116323c7724577ffb42ceb337d32d37c37d70def2e60b551bee367334c0bb72e8fe58da5b212d9b23e0cfdd2610fff07c27f9e20b931d7bdde2c8b5baf8b9981018e39d3f4580fee817cac3f58dd6a4fa8a53440a7bbe963ced6a073ff2bb613b6363dfd8add7063e2c94576a09faa9ba98931a7e7399a0d8be33f392b76d816d6a2b2f1eb4a95d8573fffd6c2dedf2f5bd3bfc3e3a9d3d060b7f61010b456bb697113cfc729dd125c47b793ae9495342eaa7a9deaefd7876abd2c8ea389fa9d8ea1cc16cd6ddfa6eb8ed5f9c8487df8f94180f537d6170ef297981e7dc92d3552001949191ecb0fde20afb00d860dfc14bc61e6bafc4bd0e94dd95a9865cbd5732230b1abb8d2dda71efa977a4eb872fb7872450f2d177945dd5dce45b14641f445915fe62f5acdedf4a240fe1cc744c31be8397b84c3517045616c2215bfdf8966974ad9f3d9cff2ce7e62116323052d6d4cbf50b73443fa2562f54e4e208c8db09f39c97b48612168e916344ce399bbd8c0e2e6b68b349dc1c10239323381b4217ff4ac45f9c08fc6fcb3a3bbbefbb2523919207e3166f56aef1bfc93f97da7e4d67824c5c150fd57ec6a25f25c6ba711be130588b8af6c9c77ee6ac5ae34945119b37bc698b2bf568a5d87bd98a6f955d46620a71e1a7d545518cf633413104acaed84dc5b3ab54e044e561ccc5da1bbcee71b2172763eb70f7a482facd2c0177181077b1186aea412642c292264b36a497ad9683c86ae6cb127dbc8c587400d6efa3d76dd237a285f1429b4b7dda9e88682908f911ed9e4bb8052711a146c5d7a0efacbebddf540f3b0fdf876dfa3cb78411f1421b6cdb03c928712d41ebd1b651f9b83794130862c1e0a0ea7dd2a3bdcd62be6135a79109c284c2d76419a2c35ca2338f267b62f42644a0d9676481e46f349f899e6805df7d95af1c21493fb5c9e511583747e123151d54179e77e8dea31576372c679abc89b400848b05ba86afb06b3cc3d022ebfe486a6cfc209e93454f7a8cb9236aabcb4ef7818624638d12b207d9aa4267645b382584a22bbfd9df157cd89fc6349608f3a4d874f6ccc32c0370e311bd9afd17b003db0fd70f923c9f4f559e056f6e24f8ff3d3c12970b949a4dbc9244f83c27c1f5f31c35f63ede2fd4f8bd32401a40101093fc3c3823fb4ff2fde7657aa563f1e07d309167612b0a09c1ce4742b3a433f7744e5e3c7a85e1166b8de10a77201122e1313359bf01764f5fcdb02113aac27f0c86498d7f2d267f350b69309bf9a0b46f2ba90e1f0878dc06b7f830cf12198660d699a2920d66f852d04815574aa46e126a0dd0a7cc14c95f4b631406faf01891ea2c907ff999fd6e10c3301e62ac08bba8cea4310b47835e294ae7e542a9bd1b5d71fc88fb1db197a7acf475f0490e280f4b0c9f89bfc59c55e573582cde2b14382775ce2e884db596f11693d6c852e91dbfe9dbc9274689d570742677239b6e8a1fbd0dad270e526535254117122143db4695b89f952c8e42ff9e6ca702f04f81ec5d9f801394b2e62fae16dd9940dcc28c50f7a13b8b8521f589215d555e4ee3488919d0287ca041fe293c904f3c34d21000f9dab14ee562677661934a43dd16ab898c2c78874d0d55ba654ec98080f0c058e9da5d0b99ee663614184ac2d5d14647974bd6fb69cabbcba6c30315ae0fd9c04fa40cd245539b598ae00e69b2410bd5923874aed4cdc4326d3dd2b3e6d3f28cdfef0c0be3937f9636cf722afd20ada3858f447a77b46e5ba3356faf83e58df63c17443720ba8a6b01ffff2d27929316f71d8a8ad14280b195b8d37503c1571ef31b18908e320b538ab89e57ede22af5aa840d4c36357dd28d8bbe9e7b83e40613502ba4c1819eb182305b23484ddaf046ca2528b26bf6ca749d21565b6a1f8aa190769288f0534255d264632877de6e37c4c81c5ce001682bcb3034d7e048f4961e05400221a35f8b06a9262a86fdf4da9e91d437291702abe4a0edc5fac8477e664a3ad6f8af492eec211a0546686f4758b34e0fe7eb775d54208ed5475f8ff59ec32c706aa74301b9fd3d3dabccb6ec4048f33713e5bdedf12e9f5d33e224ea96bcdfbbadeb6ea4c3877a1126d722a65025daea274a6d13c72a0fe597c27c061cab70279f1f7e992380e9ce95f7c4d1e50ab7c2bb3a854eae72eabadbfbe02bc87614cb6abbefd906113d47a9db14f6920408361ff480dc3fb7c4916be26e37a9796e7d4fb53ac521636958dd18fb2c1fab2707ce245c80ceb12e27f69027f675f9341126c937eaf81a7d429c74be437a53e120b96b3e422392ff80c9b1b392f0d5cbc5956913237d171ce429a35941cf093708eccb5c0af53bebb6e749418d0f9c3237b56c6859ce51ad79d20660802b3fc343c014907fa4669129818f8f4aee3a389922df60277015abe9720ed1dcaeec12d6e9a04faa3ed85ddb4b86584f2f738d8f762332fb920666135c792b38490f424c698c560ca53d064f061abbf78d0b1b7076efc45fc78d6cc38da952c4ac8eab7b18941b844864a20b2a75076a7c78843a7f0ee7d76bd9f0c74ad7c9988e8c8a7e53c5ee4060c162fa9eb11592c5e311ddea9b5a9b734017a92343cefbfd14b8a6c9ada2420afbf7afdd7fb1ff54698fcb0982d839bdf1c6c362fe86b0c5641aa7a647a7fa8b7b490f4f25478deffd42b7fc8073c3df88598151905a0307a9bdb2cfed2257fa22e870dbed16bbddeee42b01a5f565d53ff575e132c121b47cd7fe81918b9e37582cbd188fd32f5d8314264f8bbdf387ff309e643adefd990b5697b80841bd811fce8c68368c77ce73783934fbdf438d59be63f4319b3b624c9f80d3f48b7ae227ead9ee4082d20df6def1cadf04d48e6450056a5e795046819b83f9321b6a1643a9806dc838d6002d75e06ccb0722fe88074b2f00da49286373095aa709745d568701ea29eeccf0389a4e10d4ca72a9c9848b35e8279e0b3a58a5d20e1985598861060700f6cadd90a46d309499d642bfb8d9038c5ad9f2764a6713e46360a5478db1ad8c6c2ce78f8d8aa6c9b813e06fafbdef543cccf5f0f3d30bbca8af65c00d50d3d96013c31026aad6d5d5d40cac9db0699a3c2f9e90835cc10252e11e011e96b7dcbe0ccb81a7113f824a0fd814cbede70f3f60b7c1b2e13776888f2db8d30456bd507b4ba52d434c73aeb24dfd233427d8459978b6ac594bc08d40ac3478924b2dd642699524a29c105be05c40541aafebefd5d5a5f0a411f2c41b8fdeb71142e5c3e74f5c4a8bd2f2dba2c5b3d2e5f58d615a72c2b595f9cd11b63ee31f716c07a83dcdc8222a9a3773d6115811e89f4118290b643bae8573ada47283979388163d6a63a5a2b4917de5f75d4260190f38b47ce0d9e184eb0d0d1bbf2f8d0d1bbd6b698b040e7b6a5a3b716a40bd05cb18200a40b0cc4b5a356cd29a1a377955acae53ef73707b0215d84a6064b403301d80601d28507a20c1db55b3c38e0c818e174e2f4aa51a140eae85d498c205d84e607484817b6eb5faf4b1ae9020411878e5a9eb406e4c4119e283840d4a64e1b789e9c88c203e5045447ef8a824647ef9a5303274cc40fe922344538d2510b164493bbb922d2c5d5893774f4ea4e24e9e85d49a08ede75c668b6cbeae8dd299147d8768f96b0edf2b43a7a79a2938cc949542cc5b69b4e61db9512b774f44a61bd6360c863e5adc2b67b55281dbdd74a0bdb6e6f876e41cfb9f69c9b77fd7a51fbdabc60602c88bea8bdd0efd7a10d73ce69fb6e3571fdbcbab0ed7a303776050083784d2c7d753a0a55a716fa8814cb8715bed61ace08d4d15ad656d891dff1794dab40593932820d0552476de1ee8784af57bedc2c20d3d19bd813b3eb69d961458b1517e4f6ba852b966af53020c028a4a356bd60057b587e08793e211a1cc04747ad1557379832a1579a000e6674d4f67276e0ce061bfcb7c403088c461db55b57a26c55e53e9080084447ed2fb603736274bc2726adc00bf1878edaae9dcbe2d4cbe20584108274d4ae2bf0aac460830b0d117e2c9808f5cb4af815960bbab971a7605a88a936f4a02bd275f4ea46fc7e3c70d9198d2874f4eec05d155558d5758faed23daa2a62a15e55056dde55150a2b13af285c784b97478489080f24f224cc11b161881826a71b1dbd4ce29a83919bdea89bde26b9c71baba85253574a5c7b5aaeae7aabc4321dbdaa9572adee158f8e5eab9ea48a949e9638a6a3b737f2b0fa444aee68cb4b0a1dbd5bb31f942c59eeef72e53a7a7fb29c972b4d6ed7f5c2d1d1db453b72b2def57edd35c4efeacb0ebfde05bb61471dbd604e36bd2a5996d62a5b4669152da52a5a3ee482687414e7be462b54bcac54a161a5852f2b54545d11908e625d2ca7c2124f85263ba3a28ee29d4eb178e8faa271a4b4eb283e4acfde95274c57a8c8f0964ae828e6853f1eaf8a1693d338d451cc94a6507b3d987a3da4f492967a4941a4513b3a8a539eabc16aecd57083d4148aa5bc80bfab2f5aba2b197e5c7d21a2568d323a8a559eb3748d3bacd5eaca0a0beb4af7b0b28e7658593cf4b0b014615d15e96989417414f77652cd530187f1a7eeb6bc8c3a3a8ab74aec7e5cf807a6f887e502cf6a4a97578d8ee2ae995cef4a6dc16afd5abdc81e142aae9404d90ae5c90e285452180c87fde8e2513eca4a5a5ad8a7f6cbe57223e07a6cd6d5d54773941446b39aaf32d3bf5e679c75dfe86a5dd6d5091ffb7b83cd9cfb3928e77ea8966877d070fdd7e27bb5bf6097a96f593908dbb22ee7b2fe3a7bb00b34772ff3dc7a1dc56bd7c7182cecf18073b847e62d99c357778bee4ec643f27aa1f64b47619ecc39e731ae4999a9d3738647afd7cf3e368618631cc2d65aeb7bb9d7df26aaabaab4aad3cc933d9829a8d1d1fbd329eec1d7875824eaddb22cce3edf1da2ddb93febc3b0f7ecc5cb536f8efce9fbfbe5aaaa9eadae99d5303d7b9c73d641c43d2ca66100d7d7bdb93a5b6b2d8d6a4df8fad65a6badce78e7db1b6b57d0aeb579cd40a2ddb139e3eca3811afe7bb77a71d6f65cf9d74ac4b75e5c00c7c526f0d1510b26c20feeb9f1fcfbd1db0db1d72d34607dccdf9bcd15d8767508fdcbbc1629d0eae9f5ba5d29935eabd145f266f18aa298019bef8a7bbe70cfcce3f5e6fc8cb7a9598730694cd4fbaefba6d97644985d5e6e575247ad550b1dbd5151551dbd4c4c3174f42a297de9e8dded64e8e80d82d2510b0656a5a3d6cbeb858edadf4f4b47ad96965647ad55948eb668b1d5d1dbafa33b3b593a5afb5e3af7db97543a36d33f63e2a56b625ef6a2cf013582c8489974560d1f20e79c73bef7de7b771d11c5a0b1c5d543121f4e5048d891975448da7285049e1030beaca4ec42071b392260bd1aa68c98c0641c74b128243be8c06146d8900ea629bc37945f8059124358991a3fa6c472241142aa91c439e79cf3bdf7de7bd7c8c2a30a051b5855b250b8348654a9e1e35240861d9e74f1da0191f57b827203496b26312420d5d82c4c5d40487900646c1ce96cb234a16106930f6572803990680ea808856142c310233c700c99d2ddb4dfd1799bdff11b7c41a74b726daf06165ce8bd1074e403b1dd90ad1a600c221b92f4b69aa8a028d15d4e5c36921040820b419c00c583093f70fb0653188f6f70efbdf74512c3bdf7de7b6b9d35c3b4a192c79a0b04eab54eeb7b6b1b9ffbfee567262940bd779f5bd09cfb685c5801108f6d9a73ce39df7befddedde5be7b1cdc63dea77b04dfbec13f3da83be679f98e9350b45c2acc8504fd06104c76b7ceebb8fc894cfb9dfc12f6e7f57b0d4ed477d88d33759d32e2fe881cb3f760a1034b2703074d10bb87f410fdc3631d8553f7459010723ff82c0817e9b3f76ca932b39e060e4325c3810c50db7992706ce679e0e40cf3db86f3ad8a0e7e61e3b4ecda6b8e1000cf38f5d210a8a130e46d6c2813e066ec7862fb0fae6435c7d428e70fab16691bdf534e54fde62040c0743cc8185c248e160b42832a2c2815e973fd6061c0c9b89aa1ce1604cd0c355164da3041507e396f36a61d77e2e90d28a838175c0591bced018df862932b585d3a58885c3295645bb83ae5075bc8ef9837353c293f039668d1741c220c4c389f0dec7c0993e062ec79bf026cc9f9c1c9f63fee4d8d446f810e60d073f341fc2c7c099f03170a58f818b81b3de2c6d50e9cb1e8031ddb633a45f1c96d707722b7ee39853d67a705139d51fb20a8d9e971c0ee7de56fc4efa6cea8ffde9857bb48ecc8e56a4f1d6fae0dda9c2d0b5140c6c0aec41cd42474f23ff346259c4bc6e3475ca9cf29205d8f931d3029b22fb53977d683bcf789a125d71ea74e83965460adbf01975963d7a7aaa4a3dbbf5aba79546e55d365abf7a5a38ca68e39df39acd98176358c830fdf0c73c9923dbca754e5de6c1397e7bd1669f333d75fc4c79794a5de9dca3677a4ad98cfe4ccf28dcc33d1e63e38d024c781ddf81091df3e7e987a7831cf34783cc83bd8e8943fd774e595e3042e79ef4b319e79cf440c7b49927c7f4c0669ed0661e6ea2a71a9ea9d726de8961bf538725cccacf3d6714b6e99f6b3a3e8700fe98f7a0b499473f6d9fe3752ca931c0bbd4523a7aea6ce6c90961ff6c5964f38cb229b03fd31d303d66e633fd18efb2cfc5f5b2cfdda5bd3563a9ad23a9ad0c56bbe19c8ec7a45ae51f6d977d72fc69741e9dbab3e85c330fc6d69e60ff544fdda9c33dbb081be11e7df2704f48f5b13f972c8bfbf1aefdbd77ecd4fae714c10cca85613fd5ab3b75463b1ab08f9e5616470396639e2a9e00ac9faa6561c1c7feb43aaf70cfcdf6feece19e6c6280f641f047e555c2b6b3b774f2782ab69dbf1c18c8487df454cfa29b0db23f771fdf7fea9c984c28b22d440192c3d146bcd069b4ec51daee638f77a79a7930c6477877d2d0d118bb53876d98249b3a7aea3ef638e5a9c33d18a07dd200fa880a98dc209b47621e7170f6e8698254796d38fad521889f4df4ecddab730cd2d17b7bb5f26ed98cb9bf11e5edd2a1dff54ab0a5e3106500a6f4eb27f8c0b982e5c17eca51d6c985e95477741d3dd58ffdf8d5af5f4113a3e3087260cdd0c29a7175f5eb89c96191a9c2d2a57605e1bb33fad78f319260866e3de9a2c40cfafa1d19dd7a71c40d5d4920641fed411f03079a3f4fd78b907db4df668e77e140f3c697d0790bc0db0799e77a4b7280713f317009c02d92c010f9d8240006d81601af5fd205f6d704a2fb7f7707b8390d6686bae565cc9744bb1382a31fb558de7020fe883e06ce8a343705d8fea6832cfaec2deea603ec473f9a1e9079ec2d2e041c6e0f5e323437a0e06683db07fded83de02310ffad0ef9879b301e8430ffad0e480f5053ad8fc22f74772ccaf41c9d03c06510516d625a09666bef541e5e0505a50bd0fa57e348c1aa1b88eec72fb5b1f940f3ea830586bad41f008cf28cc4772e978a42b7feb3bc2353e218b5881dffa90cc3802f66bd8b6c2a6a0bde8d667e4087feb83a2fa2fbaf51dd1fa68f8f1112a1fedb41acb0209d128ce469224c9d90c090f9a27514595eb33b0f9eab00a38f36dfe85875da1b8c68c0051c6bef5a541bf3672d205e79ccce092d66723b0868c7471656406f79319b8652eeddaf202ddfa52a74fb32cecaf5916fc5b5fba74bffed6974efd4d24aa9802824e1df5f6e62ef8d451df5fecfc10db280803a402880c153884a20cf540a462478d8f76ff758382321d7feb2b8203d45afb8ae8f85a7bebfd4eec5b5f91123eea8dc4f1a32dbef5b121ce32a59fe65b9f10ab5f8118edc78c1983c48625ba1b9a70a0905448441dcae68b1b085a505f102402f5411004419d6ff63d85f9e4b7bea71bb016e6c2187f7d1bf6adc73dbc8443a71e85a76eb8ea663de6dd2fecf341d80f07e1226c64fd5d77bbab9dd6cecb8ecbfa6bafaa5e25f52ea9d7498d9abaa1d71a66f9f65e30e6a1f03b359c70ec659e8b838c3ef679a775195c2c1555a54a45711e46c5541999c1fde2388ee348e6bccef4cc45a3696832be3432a3259e3e32ea9dc68827826654a36b6a32be35b158896aab2a15555d1421825655a37b23b495aec2362c8637c4da821a0433be6038bbb9bf461c01067dec49e4b00de75197a806fbd897d05f36dac626e36b338204893197b38f7b780bff700d477dec4ba4d806824c1ffb9b251c100727e38b0382be44897befbd9783b1906f9df50de6f1782b57ffebbac575fd5ad7755dc11de6e48cb9a4f961188661a8aa5895a93b16aeebd7baaeebaaaa55aaaaaa6a18134913b90cf163b1582c16eb71d9fab5aeab6c5d63aa5aa5aaaa0a8ee40c8d88b21c7329e2afaa4c2693c9641f849d1cc331af5febb8e6755dd52a7554b3aaaae2efc7f5fbfd7e3f9a10b411359b1327c69b97b047fb60582c5cd7755dd52a5555d5bddbed76bbdf8febf7fbfd7e691a95a6699a92185554f59a0d8e8e37710ff7d4dc6eb71bc8fbe0f75b7fbbdd6eb75377aaaaaaaadead775db1f9e38de3e8476cf3be56cb341eedd9f3ac5399c71e5ca201791de4cd80bc19c823b7ec17fb5ad7758de59daaab66b43ad32aa9d5ac55598855bd33f25eeff42ef3f5877b3617f9cb3fd92ff60bf11ed3cb4b718f1895a6699a8a26d8d380ceb91c847d4099898e63cc4469b4d04471704c746707c54741141fac80848eee9ced893af6e2acc1cd756233f2f72b8155b4d6da12c4368b79e0de7bef309c73cef9de7befcd39d745d121394971d1e0025219127834d9157828eae255828fa0212249d6236916111dba7a880d01622349a737972b84748d9444b912a602094c09e0e23292831399a12121424f503892b21710c8f2bdf7de5b6806a10b04096f19e1e64092201d990a0253ca41170b92e302f99286f6511317dd45c79b7c5b0c095884291794642889da21089cf1a42b3ae282bbe870e42c05e84784a463e8898703ff9801ec02c2718f9a52d9e73ace973e1f3f070af2913719a2cde39c73cef9de7befcdfd2d66b4b7bff12028e0f101294d84b840c3470a462466201d75d1e90848195a61f5e168ca0fdc5efadbe94910240b8240218265481057b480d41cd112662a6a8c132b28e02e040cd756102849a89ce0d40308520640983140a464a1e2c40510c9935111d2e6555d8ddb38f8dd0af361e9924349d68fa6266157d8802129a5261e32b67a260c41410f0982a2a949892ac817103f7c70c1439466e82f259099c19744244aa9090a8ecc39e79cefbdf7de527c443ec0a0240ce689c90f214d6c20e941c406a3a4bd83eede5efe0602a28477f8f0d970e2a540442a694811a1a4985d53179ab8462852069cea0dedec19d80610d245dad163462a65c9863486c40416748f98bae8a00c39c1a205081c36869430c46be886fd9d00b55445d483912f1e5cfe101a84a65c0ba65aa937b482f0f1648b0c9e3523c61226526608b75ef3de06e69c7d50f53e1597252a19a8bca8c046783283c8931ce913de93a627534fa2829e420d8837bc1e2cf5d7e5f8c732c8aa4fa1176fd093de1a9c7173dc7600e125c39c679c9ba610d1d438df6ce07fe8fd0f65216cc23226fab0142296e1bf1e946d6baf99f9e7e50a6cabfa6199bf181363368ccd50187fffb15c218aa21df10c85f1f7ce40f6b70fcb9b2ca64bfdc1320330723f9bdae21e329cf1bd4df1fb8da52b00cb2f729acb90e4158ce1dd7baf55f3d38e08553a64a96460db10a49c054910cd411054816ac86b193e67aaa09f814c05a1054cd285a668c8a1e7c0bff54df980a7448bc53b42b725f0c559d66a9db34d6129c862b7fcdb47637ceb31c8f687206827571fc5416b61cdb7640a1f99d0c9c1b9b129512331a2862622044d9f9991a3288b6d3254f96a9d5f506948fd9bfb3edf8c330f571219df555512d62fe86d97785912462a9773ce5a4b298d229cc8990a3319d6ccb8a5c766a091794989112115461369d4d0bef531d93142c4b73e2643484cd598589560a2a566a4d168b49a1a265e374cc27066ba9ccee3964d8298a88db789aa33e6e4e4e4e8e834a1628ef8d6d784eb44931f7642dc80e00408c69d9d9d1d32831202084817b989561a01c9826d060b8f932f12d47ceb73720300c65b3a2983a2c4b7be29a214534b01207f6d82f14e4d89e08460e784597a133a39383736256a2446d4d04484b0cd6874882ac02cf18223822561749c523921088aa484ac938e132ad830b168945b8afc0c4326bc68e848899991231566038706d1cd0e239b213a4a30992aaa994cac6c9060a265a7b5d69a82315d7feb63520393309ace86081ebb10416cd04415f5262a8d99265486664db878904d7ed831dec8213a39e186cc49ba23e684851aa1132c37b8932f43dbc90d3b4027658af41411529e5a3ac2535335eed4145defb4292d1ffcd63795f5d1ee23d8023e59e25be2b464e95bdf12a325ba6f7d4ac094e8a0e4031f64b21efb0dac5d896f48fcd6a7c40bf641c99492ababc487858060d207c119200882e08f26a25087900677a8b114d284e11dba091dace7e8503dc91a94e42b4c122466bcf762cc138db4d6201855c453ce79185acd2c91c964a298e497646b4cb2823706b039e0e7d817f176154a5d498ffa2234fbd617d545c443823f01a933f7db8bbc040201f4e2b618e31133cc736dff9219d8b818b18ddb14d857f043476b3c7bf4454ebfe696851d710f8fcd047e36730aa2c5a2f56507fa46e2e5fa10a32d6aaaafb259dd30ba33624f00dc83e260d082a6f4511c9c9da8a28f5a8083a0d8d8826754f75e8ca3aea2bc447dd13db2d805586b1570ab751085e7a1bdcdef78100790531190d308a2f6fa014d4254469078b5d0e584a61774d3179ef7575fee2f3741109402b414ee28255ff6ad2fca298c41b4f65e33ebecbdf7de7befbdf7de7befbdf7de7befbdf7de7befbdf7de7b2fba818fbd05bef501c8e106669d55d4e8257428ae216ea4eb30a2e087944767ea4ffd460791fcd04d80e84e1d878efa5d87922123fcd60745a34a67ad7315adb5d63e1be11721dea5db5a6badb5d65aeb6c0408adb5d65a67adb5a761afb5d62a88ba8a25fdf6740a88dedffa527507df7b8d4714cbba89283cea5b1b06904b48f79acd209819d9863a67af71f634ecb3efb55c5321a4ffe8469a7c281e62987d829e62a1b7d6ca96430d84107eed67e4e86b6258da0f96f6675d0a217dac14a2abe1483f2bc30bf8482396b73b01f6b3d25e1e74001ff75c1eec555c1bf6b5ec53002b1fdb5800d62dc0bdfff6a3dca3376ec6caedc972460df7c4bd088e3e635b08e619c83fea01720485903e24fb17652338f379a96d8ad9ec87a017b1b517eb99ec8f250df3e0fc18085a52cfb62eb729fbb924ff588699272c6d987df815b198afc559cfb6977d3fe652fcbae4403813ee6d86f8321c16112dc6222f8b4d3a7c122a01b76028c205a6880ef95a2344bf48994fab293226c43db1582c261399b81451c59510522ecd0fdb9c6ddae5a75b61956aa824ecd80e5a2ad1d400000000b317000020100806c4912c0992344f7b7a14800a5d72545a462a1e0705b160288e6320866328864100046000844100c42006a1e47410006e8ffb422d49fc08312637e2f9ef1fc0b097c67243cd98517bdb757979380488d44fbbf2bfac67a49e2fd61c73c4f096281ef7e695377fa4502d8c90d91af4f16175627f59503ee554afdc088604a277fd4231aba136b6f69637d4817448e83360478e2681230eb51ff3d06bda109646a40ff044464e099225e80e5efba5e6b0e246c4204dc9b65ae99f1bbd2a268adc7fc1dc7394229717a940b2a944a917d6167b05dbeacb04a91603e49a6e44994c7f55c12837fbc94db18909cbcc5df348d64800fd7469e84af47d0e4b265b15aee96dbdf65106fd470bdb0aa8f537aa839eb7a9843be249d55f3588058f951aff61295f6527fc2fa2aff035885538e4cc491640fcc1dfcb376a73adfb1b30ffe5b2e675b86f830915313c1a2886fb0a1308cfafccff2b30e216372bfd475aa2c94c119cf64bf526615e83ebea53ebd40cfd609c9027d517b4a8a5e6b03e905a2af1511ed63cadee9ddba9f1cb6219bce23de497d3c447416002b438e32d0ac577daf2dacd63e3eb06ff1f7db3d2fa0a300be94bb95aa6e43d21791a438682ed65de976238454bc4b79521b7604bef3850d6c476c16d33edc24ac6784d300fb32342ff55297c9127f563007304f3efc57a180d853f46a7d12b8815e56ea80c095abc6ca08bf07eea123e261379e2e004e0af3a0673dd1ffe9bbfc793ec118d7b51f00790562ea69a474564e6a740dbd1763f608c70b8469ed074eeeee58d3a086f6317871d7ec1db6359a2dbd9461cf62b5fb87f09782308674d94a6e1f13d468fc1f449187c1c51a61e50e711bee1030b27fe322bd347070fc223582793f6a3a3fe07c446201ec1f43fdf4baedcfe1711cf0483218a803787e832123c39df2110db41cf49a81c62d9ee17546e83ac78958dc53b7bb46665315ee6ee73edaba69c84a46dcb18011e9f9018db036645e878c71fbc37c05f947909930f254320d1419d4d5ce38154171bd962910b481f2ed00fdde73920de0a2bbab775df212716a947f55c061cb08bca3272177493d3f558fc150a17a297cb4050c1a0b91d152894b909e6d54f9ee7ad5095eb373ca385cf4b1d0a20afcf5bb81e7944802a383c2d90a7209e846802f224c813104c409a066122e429084f009c454c70acd66d663202815e85c84cdee4a26d9926e5226b0f8bcedbf5e47762b8440ccdb4281c374d665125f8d5440c1187875089661238e33ee86c0665cf67be6ba2a7ea571cdce008a1743e70550beca003863934374ddf502b729eae08013ace2b009e1d29662fb60366c0555236416538a200d0b28086a8cae3844e09cbba78a2f4bce73b1ad156d96606d7478f77c22db57bb8a6d17ba5796ee7bfd4787f6c16e53add3f58442de85efc03a898a29928e26b6c54d5d1bfaddde8100d7ab93a99fba78d03b4b2448dd3cbed30bcbdeabd5efeaf3606ba0d4717bb353211a6e5c6417e0b6ce5dfaffcdb9d7fbff36fd7c2c5057ba5406633dba0695ad2835c4b42c34cb3c872761e213874cce033ae3998618712dc76bf02b956e657b530b58b1cfe395d247973b2c681c353be2088c42a1b4ad7c6cefccf8bb139538818a9661cddd7bca1bb1387fcd9df423bbc710eda7ba05effeac78e44cd8515b9eca9ac171e8494fe32a9f1c1fc162397b9a8dfc300039770d996bf595e1680fbef71084d3c3eff28f34fa2f4d498b18becd03338f7a98bb1138a8e31102d78166d0da3f0c1390307953ffe5bf2d952e1f62dd4f3d035b1db51aef4010b2cbba8f314ecee43a5710069d32585a9616e98c82a8e80e1e1509ab0bfa2a970c9b41626a6362e59ed416a6ce2b3dd1e8adadb78b0691da4e6263e9bed416a6c164e0376483485da727f994bba04223bd4a7052cc4a6202292f4dd3e8d10065a1bae7b3ec215e1512c0e2731c80129911ffa47da7326ab56cc79dcc7644e4d9ccf7d9becdce4e6729f4be6d4cc66b91f4b7ed6cc59fe7adfff710e4735da82404391dc115e39b0605f4b07ba60c4664b0c6f2d925b422b07312f60c74d8c98bd2b23b9eadb1d0c036ffe09561b0824ccdcdc35bc5b2e39d6a7b2d923e04f25802256f6617c71d492ac5cacf1c2e2dab3f29ea645ae3eb985432bc599ca1936d9e9d71b07714a83161737c1c13e8293c7b7a1b5282f062a97621cf52a39fa61a623ed41eb17ada6a13e21fca1530822cf174cc7707aa7ad8adb34ede3db96a8c7c9c3a27e254da4cb3a59014239f008485563fe4acf47f99b55a0afb902dc366651b411301a400562e022c22cc41a507fb30918c75a1eb44b32f5edb47990d9f52a07e6d1e47975b1a32ac7d1a726e3605f21cd82aea302dd37ee70f66b24453597d948131b9ab84ed27934c871c9597681f4221b89e16129182dd16d26f1b55cc230c209ee5b3659e915936713c333d06d38ba385a7472c9556581063b8cf88625932bd5bd73557cc8b4c211dc32c1148323fdaadc0f59e5c257bc305b7d6c7355756676b2ccfd8b2009fd5e6e5293ad84aefc319f991861c74ad4462efd3fb688538fd57093a2c10f68a54d2f1aacd1c7ee4c4787f603edba71d1fcd2ce47e52196cbe3a00e6687635997d78908f52a4e5093a1d2ac2e30d2a30c177dbd561f9b4db804fc43d645491c960de58048f035b28cf07b0717e830f956029f6aca1084f189b1963f8ac8d9d3ec780b17aee82c95c57a560e41083520c9b4ace80e535ed07bbe59bc05043d696e24a68c4d5f61544dbb256c347e979eee10d613fcf4ce10f69bbe83d4e1d79e6274481a21822b8ee24fd4bf722e685f2891e4581d0dbda8947cfe1e036b04e04b77f1e17edcac63607b075f94154d876f112ec59a3ba8ee33e6f5ad5924506c1373964cf91c106d08ebeed1891f81cd4700c2c5b5d7570caa9551c237b7d7a6dcb04a0426418ca169cfb39ecc16d56f8e8406165d1e553c2e6761a0efafd422166cee91e508d50e655afa575b43c0418995a8b9d038abf0d7f46c086f6580d6d93491fb096bdba802a10a2b5ea820f1215219b8dcd9c60a795a2cc0a968e84dc79e60e92d3484fe9c08176b10ac6d2916fac7cf9a59992c343424d13e190635c951319eacf8f8adbb07154bd28f437569ea4ab73a4f732e7877d482405d61d6e549677512626bc257a67ea7b512750898ec9f8388edbed13e37c3650889918bbdd7181bd25fef6241032ecd42c9fd77a536306b586113175b1f47082c1d2a8032caf53241c54756e599355f7ffcbb8c9aad8ffcc6b6ec9a39ce2ffcbfe3f9e5f77fadfb6be498b3f02db722fffaf3df09ef45fce5f7bf67addb96ff5e61e10b134b1188a95a583ff0e1014b99b58d6ca98cea207aca264fcfb6f39144a71b5bb20a916bba5661deede8d5918fc137743fbc1600af0b9e624af47eb737b83e4a24812dce01f519d4eb76c3de085a1ac1dc731f53adf344298bcc50fecae6baf5522903b5c98af35dfd6fbb903123dc28e52103b6129daaa4610b2155f10c7a1dbd56d193ee13313fe68ee30638081c2a863233f6d34802bac0eec83d7c1819c471e0029da0860727603f779ae15f6ee3b04df90202441f3405b051f4dd3026e4bf9582997cbe39eb6c875c7b69ad065ea3ea45803cf1cc16e52b2273c7e3421703f3284d2375831b9d0427941c7fda5fdbd17fb7aed64a549ff2a5437de9323dbf4391b43e178b6c4e03f053787c31dedde486f3ced8e34f77688e86fe9fb059ffa57cc8f84404cce6b712fe4cc5cb838feea377b16a522503311324bdb7013752311eecfb7640e97e406b824a0b4cbd2ed1f70f90c034ae2c37a5561b70dc742f74a6ed2d730933c64fdf7e73ec5c55aa3f79c738c822ef6e4c4a9c15ffab0ca9dd3c55fea3dc78a32f55460ebde5bdf60ebc4ba4bcfeba8e3128f50e6d894e0e52a1e155cad209426bb8abe2262c92d97baf3228342a0365e8f7ff6a23a4f112bdb57f87e2e0f0a88dde70d636203d1cb25e0e5827955f6482abe6263a08ba5a847a346054fb021eaf924795ceb71c3a3c8f2374b7ff2e6ab5520f1abce059dfdd750d2dddef7cd1f120f6dc86a690d23360cb37b559bfed680cf001992321008fa3a84924785f3b643ac2d84626361674f3ff3a09aa5b1c1848c4f711d65a5cd16cf21ed0c2570c59bd8e2b51c8e26abd03a3582a7994ae7c604bfb987edb35d93a8d771e651ea1b211e087def4b9a5213155e0644a3a3c27e36ae324a75d0d604958e4f09aeaf997e09c98bebdcfce32d99f07f69dab13f663cd9cb37a3f948bf4caa38b343566adb6a139661d14323dff528128dcbf735a6deb3036ae1467b69a0f9ae1b7093508c274d506ff5be92ca174b0b79d53b97dc413c9da2d3d2baad3688f2b55a1a3dab33ffbffee94da063bfa05020847a610d49bb05507c096f449d22d7d704c0dd8d271c10af2216a54c44c0d7f3b50c65dddecca13cb7c2b38b2b629d5ffeec4c6c2cc5fd05964707c2dcb81a53c309d42faaa20886117cced9eb237ba2f84540146048e05777ffe1261914def07f9dff33c1fde8d1fbfa561597fef2d6cb537c140cdce2178106a96f2cce732247c2a868baee75eca8a1570d3f68831672934008975aedd6535e299420d25bf618351d2fa307c4330a3c3f632e9c1d1f08d2023b9950c3682e8164791f8668c22e94d66d8647ddefd9eadc487eb34c3a77b990ad5b3e13b0da18308eb68e231d77606b7faf1fa5b6ec309f18e859ca6a59533677706d96a21bc6b3501d9fbefc55c467b679dfda25f2a676fa7ebeaf073cbf6abfae09dcb8e0dbd7c9fca7b658d4d1c653b14733f2bb84c1d292e0f1d801a82e2e1d9298fb02e366e3a18910b4c8163daa1c333527f1018ef7b1816cc2bffac0548c84de58c9f0f081c36abc14cb903d180a3b323b9c7d1f2ee828074fbe9dc0ce37a3f186d16baf45f4e29d9fdfe8d598538538b5a3994f33e95f3ef19367c423bffb17d5f584e562d425de5f1b87429cc6d2dd810681a0ffb4fd9a6d4f8a9fc024c5d6f180d83f3f2b4dd67128df7dae2bd73bd206544ec19bd854b87fa997087287917e638773be80a5a23fc62d16ff81d397180f58c665bbb424a3295b7756a13bf0c12d2ccd14cce58671f37275d56e7f83aa01d73bb67d6fb844e684d9b76725afd9af2baa5705ab390dd95bbf87a1c1515e2351bf564bb4753a080fa69f631b763407e5688f5aa33f754ef7bedfa49d02b6a65fc8890fd9cd3b65bfb65dfb4d8885291af121c9dc489e8e7398666f566395223d372cb08675e7a6e1cf1409f65f2b9b971eb5cc85b052fc93533bf2ff0b14c766be355ff9f05558bae41ed5f82ae74bf8391863e7432cdc9785623fe32fb8e680b4592678715e9af9c1bd97b33b5ab4313f60285e9c273904d620259cdaa836a59900217dcb22aff5b9182a1911a1bd6a5f9383be5f6f11f8273667063bfe078135f9c4e151cb6a5d63d08ede11ae38077c61464af840aaa51985b83acd3194456b29cd8584768a424b694ef60c5580da07f5fc1b557eb70d852e70e46a7ec2fdff5b9277f9d5b3d859e713b52f39956824edaf48f82a1a7fe21069d64e13821fc1243e6ef79d3541983aa9cfd844cd49c965ddec90acb424c88495d88fb259eda191a2359dbdf889cc3187fdce02f3ca86804ee8ab5acf7173099d6b02c842abda5eae394ca34587095aaf1c6034b537085abcbec716197fdc12b2d50ec889fc78ac06f0ee9171d5901166db205fe2f13606136d75c8fb0bd68521cae36c539dbed354c901c48d1a4e6b9ec99f02bde1b8396f144ba16d6ee65e0dcb6dffc4e3c157ad401424d1f669060757b096ed8a6b46766e1ed6750cbfb69b9f90a013561470052f27f032e4cdeb34805ce484cad5e7a2a80549d236677d0907cc8e0764b9c962523f74fbe02804d6e03f51f247136122a17a7596484a75d048e268a31d0cb5dadbbfb8b4cd2f1ac0056d31fddf5bb6e224229491de389c4bb92d57a6c18f956afedb33c967d3ef2579f32757536ca8333c4eb1f7a2748f40881b2e5d10c1d0176ba0857aebf1b97db941e6afa914422c526767c98fb0a60329d2bba62e24d0366bbe4e37fdb13aa0c6a8ad0c237f5038ae3c34256e57ea4f404efb6dab8df55eb99f62d88b91044f1ce7897505070335890e65703f3ea8c973144f92fb5b7167cc772c04ab6eb5a3854ab8778cca87c4b1c4b39a622e642e48ddc7557198b5aad176a3ddbbea021d9fd23c563278443636cb7a642b4fc40f3f9e8071b8364d7f37757f368a490383d903be7d672db021538a44860f064bae91f00d43f14eec002621ce617c178321a7ba967d33424fc201cdec2a5a3303f6bd8e5ba32201c2bc0c7abd6d9c08ec1e2ad0876420228e2653d1d58cc46bd37c2341ed3c41dd85c294d682e00cef89ddda072e1c2f75bd607d57e06dbabc5e68e04cc88f54cb3be648b4443fdd2f47d1a78e69dafd5161a6e4081013abc020b39e610d9ad455ea056ab9c234103c1e7708be0765772d82e67c6fb54b0803a379af00ea5c7169bcc2b1db25b1074f6529c8a92f2e531697ca00a35751a07c0f81c5df7ccffd4e64793c6f057e88616331d91560547641e2916cc703794aabdd67065c2de9ff8cc64f681cb1334d4231ddc41441721fac510d2d949e7017c4a00c04de734536311032d579eca03c487c795c60ae0df45e2840ff7743bb05d474a12e2fc2030ed7ea663b4d05a85e8f92cac732556bc0f4f4d552b0801062f6f3dcd27cb691051a24458e10bf381d23074fd80f18a8dd60226b20187cdcf14f3e32ab9e4b8522f29d03686e1c5acc51f2c4ae82d12fa8684df9f347c98731b4c326472ea6c9ce5b5d40b1990514279598a15f8ad155dccf96091458a2e3164e69779c90843f551681a79183529e2ca7b6ba17fe82c3ed65f29e646770a8c4ba95621be454d65c0b9cb56054eb95e5118d49cf44d0d5b7d08df9c57ab6051ced0acde3e388c066705fd474b25efc2aa6ee816e942c69d034494a6c397bf492fe7cc5457c69bb4854484e21a7e86accdc2597a98092313aa5115bb0a28727d1ce71ed4c583b40e2abf6f560dfd51d64f51e55f19496d538aa6c9f9a9892b3f7929a6ff7cd464974ee2d0ef2587b22cd68a39e1da7d6c5e8335e891627223208401107e0ef377ce94fce3ed253abf13fae146bc51c6c4bd884e31b2278c5886e154331b3b9f51981d4dc1126a70208a3ca781a289145de2f9cda337f14f993db17afd5909d6e53f2608ac1e7e080b2d30fdd59cc391032c6a83f2ce413a265403db96ce68ad3515c2a9f8ff94fbb694c34d54a0714a7e42ccfa0c12a86246e176b816a8418c21083f1bf6355d9496da06881045a42876e44fda61f7746cb79b3a300b19ec06ac043b005bb0eb6222ec641c80dd8d6522e7645755e26f57e462ce838b162d7ac586c8f9db8730b749039b2231c25d0edb336ace2d600decf40d72a2f6787903b4f0973c4dd37e4bf5530629cbccbc8cb4b0ff918dc752d2aa8006e669b5bc080e60fdd591746310e0f5d723d3ecbd7df2d36164ea30eada813762a1c6e385582b6743b937dd558cfc3d1d5a7e30e62c48e8c4e26438e09c9e32b1d790b01370afb9953c87cf9acc0d3b6678cdaf323cedb63153cb2896775a44336580b8a8e05d6b5500e673fd5ca445f8980250ed2dc64414d1991ab4944470193455ead75755a292f919a556456d3c70977fffa95157af631be78d94852a296206d03cc2784013078333c6488e2299ffdd66ff37aa6223c19da462a2b5ff858d83c6cfc1bb092778c6248014fe58171d1531be4278c0f9821603cb8f48e50d9e82046e24fea979df8d4c3e168f91e49f85f15c4b0a1236085ebfffa767672507b663f23de57e92778b7359323393e55fed368f1db819a3ca195f24aa9430b51ff522600667f0184c7f4129f59411f001409144448c3da41b2c3ff8e9982cb57a68b7d0ae45325fe01ab3314e7eaedd41d24a357559908aaa578b191975f7828cbec87839a9222d411c06a10a51157410e12ae42288f22720d3e11e48a74c65a9b81ca72778c23c5fe7d7fc494cf4f51561da494e5efa6e7d4e0089a6fdd8c4450e046145b4738314277b4e69524a46ebf2d5346297ab0588a04a4cd647224aa45cef2669ca1cf1cf234e446254a9ad00d49ee193df4bbabfd7a585e7c63e20fb0a8aef5bb17551b1749dc1c9bfb66f9ee1274b58ec8265e2cd288fb486bbf405a5a2ae2207b81af4772749a5bb2422b12de828e1f9abafa80e2d55492b630acfb76f3f6c610a2d3e7a9565b227df0277d853a4656a4570a1c7035647d0e512968e9e7b88333a1b60037d1adc0d0397aee6ad8f0aecef717f53c03b661d6e12775f16add8ada4d37b9e94f88a07cb726603d691819dfe7eafd050bb5295d330ae7b324bc5965074696f7c1bee4709d623a448d3d517ed15a7600034adc86a32e9dbc2f7a1f9e88527baabb0fbc019dec498808bed2e0e216c1b5151ee5c5bf0e94994a419742f038b39773a214aabe9b90620f90f87f2ba105d51f29d48530f750e29026418fae038c24cb7e129d2bf8d79998f0b4b70048b3b6c5e610356c7356744e5dc4c3fe67a3b19dc5cdf57a0b77de7aca6ad4d11e212489162a44039c40ae97798a0e170be3476e8dac599e8f1f1ff8a3a81bc7435702d7779bb439586b1581319b1d0574c70ccba8689175b714dfc959eed1a86613dbf07c71c5694d6cbe369e667b6f720c8c99dc8d1b1c460f2a68e2824dcc6e26deb1eb5c9d8aacaaf5c0e2a44283d8bdba2e5336973e0ddda6969c05a6bb45a4517aa7dd1c242bdfb06a3dddc254058fe68437f0008b65cbdf9c3ad46817b1b3fa63d4a4f534743d7d65d7d244aa69e79f842e4c728aa3ad5da317a84da03b251a1183d9d7f478bad46c147cf5d8e3eb46eed49eaa76c91484f6b13e1affe0968b1805a17d8335816d455cd6e9980d1a388350797ce662c190d89bf495a116b6b226461f5db21a03d461b424bad123b5a7cc03e7e86893e2022b43284676b7d131755496d41fbb2687b90b9a8459a4b8eb6b8a1f053ec31bf9e11d55d273fcfddfe680b477739b078c0d03e58d04985a6d579016c572ee147642c8439b6434553f284abf0e9366bfdf1543387d87246d233c76f092087b0d90c55c07293928a373392be273713073892efb7b9b212611cd6e663b2127bce39fafccd662ff640ed44e7a1d61b4e3a344a5ccc34b1651874c4dc3b96bf343118b044e7173741357c5a43c141f1d73611cdb7e1c6de6e4e9443d1105fd3b69a4296aba2f2e8c78c93f3c16c1e2dc965798a7243849b4e969303ea2344fb01551a15986e5b662a32f25b022ee72bb4f5b03525bf2671cede714ad62d64963a2eb099cbee5bc24d22560b3c6fff87c9b1ffe39797ae23b0d7288a036275585b887f58e62a3677802fc493a203165c17e8935babdc3ecab7093ae576ce7af805616b0972ccaeb78389663a9568733ca9a84a8a789371e9bf33dc3c3b66a15a988a441f9a893f1ef373cdba8f62e381f1aee775e5d1b1829afdbed6e15c093114a334428be656271514a2675367bfb33801929a9867c9fbaa56a16b22addbb3b5113f3ca2faec193f4d521155665bc3e63a6d8bac229666c50d3b5a4fd74a9d5572d98c982a07a96be110396cd1611001652bb9b62616ebf50fb10628fd91732a3720e83322000f10cf8eeae77a1211255b7c134412d454004fff1cfeeb204cffb20dda4236ec1708edeada2af7ff860e025d43c4940defe22079b0516213bac8cc3015521e8f3eca68819e2338e0bea797890702175d86754a332112b6b97ea80adeb7fcd8ee4ee64d3e6a912def9e70c279abc7da97a7a6a61c0157808b5e6d7cc51482f10508c543c4b062681c4689fa4922803abdacbaa8b1b6256aa9e56c81a18819289cd3be95bce0baa9c878031a94f1b907da908a646c498bdcb75c307f3a465fa010f75eeae118ab5c9485b5519cc8e57e66ca17ac24c539f7836f1c0de0f6b670187cec19d99098dc4c48f6d9d4610a1dbe1c08bb293a51141421cf7af2389d8e716dc034e450602a5b3ab02906ef8cfacb5967d29d9f3d990722091616f4c5195075b74dbe883261b99f8369de27789c2410fa41978c84b5ad86faf66e7da3395e83966877d4030496ca26868ab761252228757ae69e396644581cebcbc6f308c5806e6f058a126620929089c0169168d12a9e6ec9daa5f1bb9c99c1f01d621bb9ce1b8c5d25f05fb65c295e1ef62cf1152e41a7958e953916c0cbd0729afc496ce34373b6143ae771a4ec20e7d4e47bda12bf5586b80f6f2fb18faf644bc85a70e1b21b8795a194cd84d1b8ce4368b5a7223c7496b730b0ab411c04b21b543950e4b8e40b52e42bed8376e33adacd304537a21b55c2db0dc2f5ed86b8ef3528e06be81cbc404f6447528766e3499941c5c3e44ad5398dcc2d6646ac43dc4151cda488f91bdf5a2ded4064811b345d603a88cd04855a73b89b77a025d987d9ccd47c4f7ff8eb5bba1397eb0da658fe7ac7f652e83871730755b9ac42d5506152886132f2e1b1b948a41975948d8377d539f9f692c55f5158a9ee0308e656c331cb0b20b3cfbaa36ee470f7d67381233d94f193c626dfe1bc3e535e5064703906370b143c19c98f73229964fda7200a60df73cf5c4286be8cc471a1dd3b96e38d868d03da97d6f3b60f43aaeaf54b813e2682ad77f53a012ece273980c68ceb7ef33e5b28fe1fe3e10188049e51906d7d8a52fe01ee46b01768aeb3bf4235b55a58f92050a8aa9dd0707516dd1bfa32528b8e297387eaf98289d61120ea6f7114d44ee61ef4a0f45948f75ddd1348164333f3d048c8fa193004758f3c025599729ba9846a4e6d30be8b7b2a4b28928022f24f50bdb6a78278e85455ab614d0cc66e4432f12ec64abec544df43458de14a49a8c818524a296e7955a5e868cc880ad5e0e27ea081fac8a024268aee4d22b16b6ce33280a57652b9bbd7207c946f8267074880f0f408de55192d36cd90fc0807e59184c208cb7f9530745d2b2136adb366fc6019a3c16aee302aabe81f137a9431385e050a24916b4960ac98356b4cd27a3dccb1ca0cec35095532fd843a2f6e22929da897f2c8e7783bc1987ede1c7e9af039652b154d1269396e6ce931c1079a1bcdcd8fc3b36239efa42798dcfba6523086a0dda2911f45595ae6bb43e41ca6cb93d35ce65f1320b2b810483c29e3a3ca5f19ce49c6d708345d85b51bd69020652c66215ddb420dbd0d5fe9aea72e13754d76280f5414dad6db57b972079b93f9d44e59ff16676d66ec182b8db41c08f501327ca02ebcfc86540a99cd24f6d9d1416a863545e421e204049ad500ff3a422012a5f4bd8fa3b6c4a8be864aa803b3cc8eeb6bc4e6a4690b28433456b7f4af32b680e573c9bbffc198e021234b4d1364c482b270194aa9c829ca740e4d81845b57b5d96d4cebb6b7a84c60c214c0429d7fd51090884bfd81bce65233fad1239fe4ea8f7656311b23ebc67b074f83433065c968618638635548e8c0c2cbe12dd3590b1a614d9eebbd47dfea0b5cf8afc2102dfd879d15a150b3742a54666da94c3313298bec36bc9f17f504b0e6c4aefbf2d52965b96babecc9bf7de1fda17639da36115c4e49b9e7040449b450630b7af68d4edb83e6b3fed20f74373af5f46a284c4ae7ad9b0b36367d98a5144eebd74fa556e4c2576e2a6671b790845c335429fb8c3a921cfa7ff185cb02579ac583bd540b38f0bb6f9c4c3c812c604fdd8a29db41ee74fe326cb2e1faabc6857f453b6d63a47626df3ac9be96861b3c0369b8ddcb053a06b28b1a527c3003bd9874d0d30eda80490771af423b838f750d28a055565e53caac817771a6ae4bbc915ae3cc2d0a229895e4da723ccbbfe35e219dc98e86c5572fe90b96f3793814819a7d6e535f51dc11c49fc89161eb5d4197a4cf8c5a2bec838217ce813402e155b2b260880c6dfae570b45042425e093088d3a53ba2b8f658ce30b2833c1bb1ec38a5b4d3f28d8ebb50e21f817215c7b3f8d03cf6d35a4c02bb321c431516419a8018f29e448ed61c26d21f69ed57a2ffdf40ac8f81232775ea41a19e1dd5fc2585b0b41de66cfe55fde106a0c71f827f140eb5d9019ed8d5b261182670605753536cd5db6a411c210147a82416d6e4679cd822561a5942563349a23804840e04b98428ba6a811d931e0a330b1f1ceb949bc5bcce03c5c2136c5b55f26df9652d2519cda952ba878e484860a973e362dfba9050581461a18246e76ace57a1f128ec7198418b37df1d975655d4c37b8485c564b4bf435c84b5f0f3f6a3a2c3ef23d8f6f232afea000d4962443aebd4ba75b6508042c24fffd599056a95964aef3eb455a2ab2ef59128c925b3228ef2729ad4614c73e1e4f1d2ac163cc39ec16289506e079ac9241c00f317b9e5fdbdba10ae93aad6c7d1f3ab15365f7ff22c980bac0d154e9b8d1fe14607eb789f7d7dfa0adefb7a414633bf3e15ad4bede022e0693c5c180abe41b4fd3afef4e96e0570d7d0c791102005f0b1279771fc8878154d639c49cb09373b8733f7c4385939d4d17c0e68c19ba83a702212b4847c1b918fa1cd3eaaeb8475125d212d9b74b938bcd1849e02fc0f2c698c721d54f2a21fed88685f9f737de5e6e0b12d264ef08745e4e6ac22114e90f7d221007a8be2b1af9114a47cb1fd4ad0966c2aa3007108de68946129c92ec1be8bc7de5ad819e1714112797ac15a4b93b869134d3c959e44ed82239e01d4885641900f43c44ead74cc98ddc528a7b8497cfc439db413178ada18f53107bb28702d1378bb0e6435e4f908ea3f0ea338511a88e2c8da015bd772ad1364310a6326d1b181be477cfbf3c69580712385b5caa7c386b9b54601cd6f5fc9c4d19a3719623f3eaca041e8cc6cba0fada6a708c03be4719eabef29fdf83715cfcf2840b633f771d48ec824cd0dfc1a5a5681b47237a8ce384722703387781c60bd40887494785b5054a841f603c2c5bd1550779015127f1f39a8157cd1c315b75343f1dc1b9d982b55f4dd2d2b31c809e366e242f17774412aca3e233781d1f0adb7205840ac90e26c9530307687641a6bdb27133635a3de863f8db1c278e14cfcf9dd9aad3697fcf3a41746150edd7ea4728c52da708599d7dda72ac79cdaa6ebcf558e31b16d77cb93bfa8a431be6cdd6ac79af420d11fc466e2d3302109a5d8484f5d46da134ffe9db3c7ce438d443fc0aea6918fc33743ffb83b0d3ac6a84bd74ed89975499fd4175fd3d0203cb719faedffe9767dc836828dd7787dff24ed9fbffc7e6cc4e2bd429cbc17d6a4f83ae15617d19d8ae6ff90e799be8332c7d310acc9f2b3a45b87bcbad015dbb141f589cdf0ef02de9dd2c384f7055824f3f1ff14adc25a03e2823b91b5dd005ae53ae46b31a691a89aca3ca89ebcdfcca33cee09cad5eb6aaf4978c6ebedf06454a6b5641dc15ca8fbe8662a3ea518ab6286a40483d8210af0bcb88228be070aea2751928926a3fcefedd7ac2bdbfd75d9dd24b788fbfc2ed6f923fe03bd379b056cb2bf093762655e55326e9e10bb12ebe90c1003d1fa101d7f82b5c6f97583139b3f7d5cb8d79681542e6e12c0ac5159527d19eed2967a9fa54d378eea17b188015d9f51731ea7465c4ced306d394e7f75bfa22a6c1cbbd743de4aa9db0735e2bf2f9f7cd05f97d655a9df117aa3af3094d8a193f7eddc39172abc75b6ada8570d2a41ead81888ee706563023d083b310692bc37fe1c334f99737eb40b4d1cf350d7cfc001e9ec7810b3d1dc19369d3b63c06e6cdc0f094d3caf26d9380269d22add88b4677360dc6506af88324c29b15b26b8bbc472e1b44b8f398682460646c03f5280370c0440a382e0236a948a5801a9bb9ab109008185786dff678c6cda7010a919cf6079f2ea6ddb3a5f4dc10a41f5e9cf00deefaf9a2389171db4eedcce667941c8b3d7439b0b37dbb9edbcd8373eb1bd5df4658432ac0445f9642352afae189351197928148d4a02cfdd5f27d0813e2b09195068d38289ac87e951bebf0148042b1d3a8884cd814abc8d4be25cb07525850b92391673b0edf5e87d8b762a4b5a534a0cbbd28ea4603fd0341f30f1732cc20e80232a159d440242e46d53a328f7e3e6460e0761663f2441a06de99bd9635ba6c47a2c0bc5e9dfc91de1dc409712af638638b27a6a08a13d6d1a48421d9e2e682c7c807d2a8b96a666392cab9c19120ed1186624a19afb7542adc35b4884913dca231ca8fadb554df94b528b1b00aa461e2209cf67cd46a366bf5a9e479386e5e705d268ec2387246102e9a0095b0bdd1fa1a773781266ad75ca2894ced15da322d443032886dff68944db2d6a6e593255fbfeb00e2866afd66a9a09b02309d6acfd6145a48b82b4cdd395085b8d3d1586227669ea92ca202c1b5e2b1b5bda4bee51cfd66c0138d062d70390911bb02cfacf0f14b3f7c84919eb6d30fe3a932530eb9f1754c159d37247724a6a7a8608106b76bc784631183bd67a8cfa8f22ef85c79e484346bef320906cedebdb5242c99e30a8f1eadf18117b6d1f1183391d142c8baddaeeda2231b1b083e1196c14e42e6a59d198b6cc40975e0bf0453e0733755d62d967042e82d90cac6d6cd1b728df6bb9a7bd844ccc8165d4a3f97bbc8fcf36e0e1ac55b1cff11e795b3b9656dbd070225e3df0331e405e0382a16673835f0c2a8a75e8361a0b58c7607c4855cc0b2f70e7a61eb35453467f6ee329496911ac00c0226b08ca4625e99a7d22f2ec804794db70d342f975472aa4d015b986a7713e99d44ac2ef876dfc9398487eef0e9dd8da84113e4318590374c2c5dc60e2e767c88b3de91cfd4e2511529bd4c7634956349a550305bb1e65ac4184b7e68caf9ee83a574c0d34b5aeb168799b14e5a5c539b36bf0b694c5b1668f553d3a2b635274ec069a52ebf10b1217b1ff22f8be516628c2985264c852bc7416ac2ab06bac8c1adc026109a91d338d0db4b5c085cff666628278041b1854a9d15f1e245e470a75d0ac64cf54fe8a1fafea14465eb2dfd83eb674a3e9411ef17793adab3b5988f5bd0ff73ee29ab0cc9a1e753d152b5a83633554238457f0ee5b53722033dec41c7f7d3892312f6e10be07ebacab4c9fbab58d6735355344a5a46c5b1bf0b83f4abbd9a5fe6e019881a40ed71402de7d320403ec713141709b970a6885ec7d38358df9d69c096b4c6e79cc59d681d0ccea2328bd199b1e83e7aa208103b1ee4e7e359463e0027fac323332a32cbed6a14cba610e0480385147cd59a64bb0824dc7c1ee0270112aade3760e5b2218ac60bcab7b27485ca4180b32caa1ec350e38da396490c5f8390b7a9a7b8fdc20aada574808e7df0a5d109645542373c03075dd3e449e6b2a4e85130035524c0a500e9e23f5dd2b3049db73392b78c58f156f2b88844d0c8310b1a242732dd58109215ae4d1d0465d4636f928904151ece1531446b08a8abf9a11c416eb745e086796bb925cf00e438a1ce07bd1f1d1c80377a45f0e063e7c05c0e2ae199b49961eef270e9c5f1309c1e66ca7c4172ebb2c9cfabab0c1b8ed4e204416667acbd067364931d04bf63afb44531c7ef5709493fe709e331f668420700c4463cbad318caac7c37f05595edcd196e99d46db361048584f1d48f9601e23609e25831e1b9908a21ea7d14bc33da2e0082cf956e0a591fd95fef3db1dc74df026089a979399969bdfe6fabf09a4a7ff49cbd7ae06eeb182e01341452c883294ddee5a4531ca0497470c43c71658d16e1cbaae7f4cc573f1f585566bdcc98ee47cc40bef2e56b42b3e9874ed8629e83946d749745a8053be1117debde8d94197aa78afd5e820f072c2c650a380b38db98700c2ce3737afc93ef8d39f13bc5570e248a8f14595dd902576daba310c85296a4773bb5d3757be046da5b723c6e359209c13d76beec64edf3a7d44f3d19965784fb0a6a0e7f751bd559b8b54dd9bfa54f28022df43b2083e0762431d757362461b2218ea1d76600a4290394aa5f2499c5c62152d548a59949780680ba7cdb3679b4a7153ba17b5f3b62f71252844d96a0dbc980d8c3b164b2301a30438ff42fda8ff871c0dab451f10052e120856104b0dd3cef3ad2cca4ccdb653b7fd6edddaade8d7c26a88956461477326ba0ed5d85fd8904062c7c3369c4a4135b69528f53a2c87ca67eecabaa035672a6d36410842af8f9177d98edc10c913952671d42660a17c7635dc3ac33ed6cf1f96a0d68d4fa70ac88d01a27124bcda088eb4dbbc5d704443cac00651ea1841fb0d2f158b6a3e1e8b752b981826d83b653ceda04ec35086f65d73a98f4ddb16a4a55e55fdc664e1b4c9553582858ea15b8a7c3fad7b588c32d9719c8d30eec94d55710b1b6cd6418af91b822cd9f47a131097686a9ce335164671879d0f67ffb135e7a1c0b97f1651d16172d741aa82446e4f795f4dbe4ae184ea3fde038927f719bc6b32bc0b2d97806c6fb8791304c76471974a75c8402bf207ee7a6cee4159c7a5a11f7db4df4794f221ec888eb0ce6fc08919d23465799d850e6cfe1faa4f124b6e6087977570b7c55f5758e695eaa45e5b053431a0bf4fcbecd9b175455e4b2333340122c2a2ddd7a21b1bc4f78c198c44816188a106dd44abab91319ca8be7cce96ca8f5b5faf16b44343a4a8ae1a26ba7f9bf7398b76ac19f232c68538a9c8b416e13913f2be6306a3e614ae8cb3a7e7559773ffd74006652f727ede8245de34e25c8268e8555408b176ba37942c038a4556725b409819a7036bc54010534b2f57cb56c8b1321ba08f0af47bdb7bf80848ace1e7810d467412f2a2a4dec6e9e850abb559d80e701fec9ca1233d89a81a1dc2e535ea8006f786e3634b2e191be16724cda2bdf7de726f29a594494a193e07ec061b07326b6aa5472dbad4fa06bc82cf4b1fa01e444c3e403da84fa443344551d4084d022529350511297232051131a554434e5629d550aa1dc3864140c2430c5eb7c21139046b10903a8c01cec02e2f9f945569144b5b974a415954c71444a416a9a76aaa41954835615c5377a83a79e9575d61aa3a5453d6f6b03eb555a7a8536a0bbb0d7c825996655996651984f8de135604a332a122724666599665599665539b10420821c45886d955265db3ac8d732267a45dc919e94237898b3a0915c92084104288337c5bdae6c3dd29ee94ab95467f3986979397ded7aa2fd53574652bbea3883673b118be455efae82467a40dd3050bc28860221666c258188b85b13016c6c258988905bd52980933bd3c66c24c3ef311635d128391432fb5d56ad556a457ea5ea92b75a5703405b54e2da116aa65a4b55aad56ab183361ec3dcdd3e9745bb7d538b6eb5e5edc67663e567abbeee5c59d622cbbb0dbf7744ff7744fa7d36c8fd48a464c1c4ba3bff65a3bed3d65ddcb8b7bbc27acdaae5fe2ad5476dd4bbc5237c6d876b55aad56ad28ec285b61478f6131c66b57766557766557ad9eb627567bd6cc52ecf6ac2dda954e7cad3dd9138bc562b158180583c032e2adf032d50ae2e53d45f1b23d0813a9073dd822fee87bd332ef58b16c95ad62b68ad92acad3eaa563f76ead75e630631c669ad65adf34ebd8957ae95d6f96ad765b96ca9a6134db7b7ae911637cb15bec15679a6d88ff653eab9757ce10f044f76154678ca2a8c6b9b7eb5e5e4ed99ee449a8856a1969a55aaa56f438c59540a6940c4abd8c295535ad9e5413ab9aaa299a9a267ad01562a158a8c691e2b6ba171f0742c1689ae8f2a63a958bcbb5ddd16b3e38a8684da23b0015254e90f2b104027230b4448785ee4887640912212059c1d529b664caf4358b8463f4f0bf8962d1811695cede3e1c5c60ecba73fb3ce02b1db76974fb72f0eaf67d20dbedd30100394fd48b2d7a4f8cf308994862c99886992fad0fc60067c0c019311e3db6e00c924797293843c6a3b709ce60e1d19b0567b4f0783d7618ceb48dbbae118ff9d759e1db5b78cd6ae14d67e1893a2bc860ff5686be9d85375dc61bad0c9132ff5882f8f6e924cf258818cd3f9628be9de44d5fc11205cce630de8bc7b4c0e08573187c5a71ad53ef7a2effe2c5006ba2aff0e209d64477f1620bd6441f7932056ba28bbc36c19ad18a7ff4075bc19a66f12f623eb0069f5a2c0b5d614f4c186c710a1f3df33e79e4eb2197c441e90a9fe88a5bc119179f7a7e13064d45321127c1f0c5477d7c1ab6bec86a31992e3f1d52389a8d848fde2c5813659ef45ecc7b30efbdbcf4563c757cb759848bd63775cdb5a865ac23cb5ffcec8752b7fed19f0d056b46cff9d810d17d22d10aced09c5ecaf2977f31ad9813ace9d1c926a8fb625aa3131317a798d6b747a751e403675087abd5f7e8b9554c2bc604a2d5b75bcf67445db41e02a007c7090463998e24968ce986e9f9d274c5bbf0d3e5eaf2304314f599a8ba045600c21cc09051e3460c0e58e204ca4489baecce18a3944a4c18326adc88618913d669ce0a4c4a274001154cc98061810ea67ce8f19f4fa00211a0404ade4cf472a3009f0fbd8ca8cbaed7f3225a42a6645e449e0e2f432d99970fc830114f32d0cb4185310dceb4e830f366901cf182c66790f02002d043b2e476672ad84688140f41c0a486e8614ed1cb29754ac5208854925413292852494855e1c8cf111f8e88e0c8e9080d8e3871a40847a42cf1b3c40f4b9060092388582235648927967002921d900081a40824228091e252f51c710414522498a2c7116a606487035021a2424b9b8319e347f7751da414c25a2184a3cf1f85d06db00217282cf4075a8fd7f7f4c803fec01be983a24268ca498aece8edd1db07ce8899c5d320a889b2465f9d06619294c562511605920d6fe82579c01b1bc4024909d438d6d434edf776ddcb8b5b09f4ed314d7f200dd23f70264a69dbd45a104b33c19af6ea65ac201a91079237da1038d3de6e4d8d9361d68459933561d6f46d6d4eddca0ac7711c9727ea5e28374a27e5c5a998e9ae79852567da1190a4fb38d3f787a5e00cebfd6143a6b767514c5913f9a6c2a7bc48e9a260a6f7d17458bcad9e5cb50fac914efac7a64717bd4e149cd1423a8b09c8085ea044143bb8410a94e84113258ef0ddc3264a30196203c406880d101b2036406c80d800b1016203c40648b7a94d2b205216c1d231a713652667a04e741833461ae34cd3484d062f612f4306adf693a1c9d664885a294a4d06d9b0bbbbb16f1b3794b0a7b9847d8c1ffdd34a11d6e870f0af0b8335ed989b0186569241839aa6695a94776bef7c610d8c18dba6d36b65c3c33499682a55351b363c70363cd8f0d06aa552363c74d16f1502dbf8e83eb912c9530b4b2ab5d241168bdb1eba8d8f2ecad595e574124b3a25598245609d2c93a145221999ae13fd849cce8ca1870f1f97b351b55266d34307a3a63321bcd7365d27c39e5244ae3d36952fad0d94ce8679611b8ed89df19ab0263a9481a2dbddd938f7fb9a5346447367b3fb32932965c09433b6ce799b200351caee39298d40db79e955313c316e1931bd2a66f1cd2cbed9a561dbbc346cc31de6b24e5bd958288c6c850555048ae49429a6b454434e5643aaa1542c728a4488c8292d18854620468c18a5944db349ab4ebcd7bdae96f1b2f7c2328c319c655ab6711bc7b58cdc96699acec6719a4e97adb0acb0b0b48c2c2e5aa965c58b561a65302e2b9a0ef45a7462369a18ba8c5c5c5a4697bbbd74cffb34f6f6ed4487734e9c2f36e7f4e637edd67456bcbdd865adb598b5d35a6bed75edf4eb4aaae998f9b6e946c843092990844f3a6ae393975716e8cf6c8fc55fded65235ce3645d3480e48b5b5b6d6d6ea161364e1f2b656d3c89693cb907449c99bea2245ba44912e50a48b93974e3efae7b2848a96bca153e4cd9c42de74a576fee5614b248dc4542a39832d59c11a2d74dfd6f29137ad29db141b15f2e65bf1755e9756d5814b0a08bfa4c17f7008d4f412dbcbe23153e36ca92d65dab6ead7bf2d55ab7f5baa4ee1c8cb2d15595ab046b66c445c4cf2a65b522da996544baa25d5927ae92d2d432d2a79533b4eaf81608dcbb74b8afec0d39b529bd81f3555df54adb219a28bdda8875b13df4e297702426219329a26ca905b89239164b86e9d86ead46fce35dfdb349dcc3147aa95566c94d424dd37bfe5a7cd5027fae9525e2ddb24d1c52bca0b5e6c6b27dd90de5e923d5f431281319761fc51d4b7a653dfc87f14e86d6ad055bf016b885570cc3c29b1d7ddd8f1cc326fce39b1639a651ea59452ecb86699576badb5629b1d7498dbcbc4baa936b5de9e5aab78faaa8fcd0ebaead22f4dbb41d2720ed1e38b23f3cb4b198682b08694b9e6a4cb7b5ecb37645987c5e37cb17458fccd14251de766f19ac128de8f919a1a678547fa03def847857c747aca31c688b1fce1b07acc514d2353724652247086b653148433b5d2f8613b284a9ea09b2e47269733487ec09919e9338c3821409aea9c569d73711f2b1cd3918f1c7e3b962b3c52fb8b8baff0f615390717f44b0e2feee23d2fc4c0886ed0d152fdf651ae21561901c119edb2c5041f9dc5e1ca908ff407a489dc133883d5382b5c6699f4ce21bcf1cd6732051279df28e873b132faf1f573f9e384c0b546a7978e791f0eabdf415114f5d6e9e9079cb12e1d00f046c85b97426ffd02a2264863ddc50bd2b1d666085dbbcd50d6da3b28ea5b89234a7c8f50228991c819e19c46584c2f81e04b0cce64cd3f2af4d0e9095221909a204ee6d0e90f48233a0df2d03953e36c99b23267b2e1728af1cc32d8278cfdbad8832f5cd7b30cfef51e76d043037cfde3a3d7201f9d0e5961c1dc4040d663f114d248bf1733cd39e4a5694b99b0282c2aa64c912225caf871a697ce9938225c90bcb93850a9542a954aa552a9542a954aa552a9542a954aa552a9542a954aa552a9542a1b51d3a13eb0a6064e1d162f7bfac5c79d80e00c4b516f13854e7af42b0585eebb36159cc18241789abfedfaf9c1cd66884efa47b1a09f6ee24e47800143768c4d23290db64ae9356469ba9c5396f07575c38011a76629a494522aafe82713dd97f54caae190d7a7cb3c610dac69995a09879c5a69c57a751f975fa71a4906ebd569b8f2a55aa9ab1a8e98a356e2ee35fd0686b51968b075bb1a0d36cbc8329ae6d2269c1eb506c09f594e6d06ebf501366b250be5dda64f69b51b4ad84b2f613f1d7311d640ae143de64b2be19f4e6dc520b6b5b75779a83580ce9921f61a983d536a3f983aa61e9cb7e34837e0432fc187f9c78f87def398d6607f3a8e1a62154ef3cc7d60c73c063d741ab0e9a197ea4f2f553c1dca58a5045fca1ca34ae7f2d6932bde76976646bd3927cc538f524a63ded65a6b2d91de5a6badb5326f276aa258ac562bdb18b9b41ca2b78eb1d8da8d9bbde65a1d73975fbf32bdb0461d166f5d87c5575bbd89d2aa4baf7ab5cacf4c81600da440f575983d57e220501f09838892787a2c3e86c6a13ed2dbdd690effce375e980ff5c180c01be81e5ec7dc6db07156c7560558272fbf74f0e111abbc8039aeb5daad066880e8b3eb63b67b60a8759e99691a48a2e1baf5197a5e6a25fbb3345d7acf4f97de9944bd3aa9bde76bbe81e612f633c312f6325681a184dd90c87f33d35eaac00b9872799004ca0f5050f0ac1d61443205c913628cb285d81e349b734e5a8b1461adbd300735090cc3301e5a3214e57202b641428330312609c23e4018879c2411926d045c82285cc40a18e200e5b3874d98487df42ec24026a230310542d8b08658053ad4463032b1840913133e49aa60820aed874d94f09894076cd30208081121042554474032c40bd489a7bd022904e9eea513751da552093d14b9b5c990169254a1bbbba377476904dab165eceeee467df4ee289148228897fe329372441cb1238ba7294f1701c6880550b400c68bf9024ad474269c26e8be6e0745bdf4e3884a1cd9c1479fde0e8a9a319372cccc13bf3d1a01437903034480e108521c210ad091204e48084267544191cb24c9ca87cbefa52d3252538e41a5a34070c65b4e2f9d005148527cbfe9d52fefebf9cb2795316239837c40858c8bdca757963ca7b7788b7f1de45776f4cfcbc41825144fbd78fc781bd5349ebffb2e72c7227f1da945279361bc160ee3d9ebe1021927b1f046354ea7babb512c3c198ff1eac3c8788bb7707c69365ac8f895692193294c0b219d6a9cd3c7168af1e6b38007f83aa85fc683b006a645867286e4c197dfc283b0a6253e0bcf23c96415e46ced06e330319efdecc1acf044eee28d44dee22f8ea966e3258bbc6a0ce8799128cb1a30055397f417af532df25ae5551395aec53117c9938bd7e22e1e4bca5b791b2adde62e5e07ddc5e3de864ad7b98bb7bd0d952eba48e42e9ef6239f8fa29c6db058afd12c5efbfca014604096e9bffe3ab6feebafbf4ea53a55e3acb19d362a87442209107cca855c2981037ea6c8ccb737118756f8bcbc6aa517ad0e21a53644ae2b7a1768d4ef5c5e1d230008dd28f5d5e565be7ee5cdc65ddd855df8baa85f9b6b2ae8e26ff7621d47e59527aed975e9bd1effe6afe9903b4a7d0d5943de8b3b17ff7557d0bdadf67bbbae5d7cf4cfc5760afa728b100ebaaf65757ae92d2b97bfa6467d2d2b58e193a5404b71dec91b2e7ff139ef3ace37c79b778de8e2733524f54af1a593cba5fc442fafe8d7a6a57e689e4a2b3e5e595bf3646300751f5685e08c485da39e51c7d431ea1716a9d768cdfb5cfce59987bdaffbee5ff9e356f057ce6c9139a7166312d1db415194ce1c9fb031a8b4770118bfd39e75bc3efddb01e3af5f36429d75f8d2e97de9f7c63cf3ce741a0402e106091c618911a440048f253441c50638e4e4678810471821c4112f4022c4c4494930bb9f48c1c10f8050727a2283090516fc9873cee983227e099b223acc67a2b4d284f1b4566d8699a699569b814e194d331d576b6fa51a37356d6a37caecd266909c6bce695e43562e97b0a799bd36bbb4e916d64cc737c3305cca6694d8cc72a695209ca170063ad430ad3427acc159a665da0c3498e658cbb026036c1a1ae8582bd9d71c6730cbb22c8bf26e9a6bf96230629b73dcf44dd3328435990d09ba1dec9232a80fb6baaac0f045931eb01e3659e2e4000f9b2c996243fc0f5b714fd8684344f7333ac119d6e5d55dde44c96ae90fa5d88a7be2db6d88d4c351e56dfeaa471df27348b72e337c8ec33a4cb482336677badde9dbafd5b7cd1ffdf908a5cf7fd54939ac5f7ee59dcbadc3875a8efb241cf723e5b876ec95e1f7bc9452b66a152ca5c71863f476cc442062c7d93901534ba2441f213e10e2831ebf43f6a0072d275017bc410240c56f2728f4a0892d0c010b21b84110a2a0248806551c11e485064217657ca5940aa59e523a638cd1af135ba869e28416a43f6ce2c4109c587d742aa2d9f98baef9db011249fa4e74e8241c19f6c4f802862f3e9a7ae98eb1ce1793bf891d6a38e6f4ce19cb5f8f63ded750011d6ce284e9db599cf0c139e2b767e079fa77361f39d46060f1ce5ce49dbf16bf5e43c6b8f0d3ed603e1d73cd31e6d1d663eec203d6b7b3388ece3b7f3bdaa9a6633a56c25cc3b9310f730664ce7963d9859fceeb1c2b8ec30a06cd3b93386fc77c7a24ceb14f12977530c78e1df3ce31f661afd867d357b20b3f1d752cef64cee51d17cfd9254774970c4799a79fe513e596ccd3bf394bfe74f8c6d88b1a8ec626f09c570dc784e1d31c3b96a79cf978fab7fc7de0bb1fe7296730d73297bfe93c8d7d3d507ecffcf1f477a7a0eb5eadbe351dd8e5b3c35da7d3e5afe7356fef38ceb9cc798dee569cf3a66b5e863bd734cd3bed72f838ef5c8ee1b832066b4818ae761e34e141ea31ef9ed3a7c7791bd8bc3ace3bed18e698e3b0fa155fc91ee77df13bd726f6e969de799cd768cdfbb66c31cc6fe879eced7d38783635e8daa76b7e617e5df3341d9c3f1d1e73e899c3ebf21a7d79186869e084c3aa694f59c50e543ef606e2cb2a76a8828e22e704013118c223e1077ed8a4092700d5296260828eb3410bbaaaf2418f1fa8aad0848f268468a2044da4a00916a84ea06281ca886a072a25aa1fa888a08aa26a820a0a2a1e3cb4ea07909c826a884a081eaa21feeb5c0955c98a472b69f24309149312293ca0602aa14209159ec0bdba9a40a952a28f2ceb1cf01c78e8d3db31f310b6d765305adbdba7c3d301c07ba96ca1caf34c8fdf499a51ad5de746bd36ce6df1d583d5afa633809f2e1ec6fc0cbc5efce9beee657bad1de19c1bcd73e64bfa10d05447a4539252a28488129492391fc21a2154235d900ea853c283c7f6b089121e1a35e99c740a12302822074cf08962c90b73c9cfb9022dfc9cfe4276fd3adf01027e74970707a8e20756caec3993f810248990887bd824090ea0a055e88ce8681294900eba2b74b6874ff743571f36194a02483714e587fb8b21283ce12627fbdbef953ebd24f39cdf2e4d704eccc52865293accd78b55beb394b38894733ae6e69c9392a4973a2e51eda48db5135a25ebce0e1b87738c31ce1fcf8c894125efa0409a9661ce838d659ef957b6393fbc745788e9718ee9e600e508b939262cc72465feac5f380361e7fc7ae779e62b39f3986dcb5f96b98b67becdf28783f30e0af4169b7e5dddfee5fc90f343ce0fd5846d9ced3cf223959f75d7e8b853430e2fbd6be89719cee7915fcc3cf2a311f4ed50a4dee66113288400010504a0c7ad4987628a17c0c32650ac1ecbac6a36a4472d13c919e8c20f457d96ca0e8ac2bebca396be9e9d2b7ff1e69de86ef1d3fc459fcd9a59c0f005d67c3a9d4e93a046680e68ad34549846d063ecd65aa9f3c81d1959eb6dec513a751ee97df265bc52a15a1dc25aab8ccccd304c97f11a32dab81e3d948bc8214b27351d8a92f9bb5dccdfcb4f8a9af9ebc9414a49a308f67630d7e16794893470744ca3082c9377b0633234848821c9e44f3ee6a4fcc9c73482be7693658aa6cb74fa0308ce20f9cc1f861df3afe6c06a26617e9d62cf367f71181cd3699868108934a6d739e7c7233f1a417ff3ce7419c7ea5d317a81f195e9d46f95a5ea3b325f4d47f6d53b13cccb96cd3afd5ad5634cf1f4e69c984c0d396910f93a499ee6311ef6f8e2992c0d939ca1186b451e6551fe5e5e56f2e7c205ce28104d515a69cc5fe45c6aa5aa39c66a9cced4188b3ee130ea773c4f1d91cb24ebd86967ba335f574ddf9d91ead4b1e7c24f47fd72cda340b006fb4e36351dd9639f6921259f8ccec8b7d31022d2c07e390d93bc817fd108fa69e79cd36dcd34848806a077141357482189cf9ec55354bc0b0f9b38197a27449a487927451e7fcc31bde8f4740ba24554a7452a35d5cb57bccd390fbb9ea53f1ac7acc91ca0e892476ade4ee63a7ce4a4ccd1ae6b9a57c9e958c9f921d2905e7734cf78e4b3e858c99ffccc59f2275ff31cd3578f315797de173f8b3546997734cfbcf67f357fb327071deef7819c22feb3ce2377a4ebf0d5fbfa5fbb3958742ce710f9cf5e1d9f7ce91673abddfce598defabd7eaf7fd36fdf7baf432d477579657c123518b2ea377fb16652e6985ba73f60c011bdea885e3d07284748a451fdd6979e0b3f1d8ffcfaf1c81c53a79558bce61d16acb26017c358b69bbfa85dffb821eeadd5b12cf558a39f55f85a9daf79f4abcb68802f47c85fbf6af48ab759bf3ccc5b54730e1036e4296dc10d31e4a9144fa9c778f9878323c5cba85d9a7f393f441a5f6ceff287f30487f535db8f67c6bc53fd7a74ccad0643fcfaed008a997455f86488a752f0f0ccd79cf30fe7c967d9859f6e0705fae89a5bc72ecaf2c7333f9c27a5e83b5afea2d574641f04cdbf4dd3f2b7c352cff921d288f9cb1c7b0e90bcc139a6c768fdab0351ca8dc69b737e8806b099e61dd1693485122b253d564e744dea0d563058d180c7ca064ca4c0440bab134c3a27d531411227f811a404422042142b1128c1a3c35230420bab1d280e9ef0c08445a52082891026262622605202264598b0800912ee2f98189112634720386336160593824dc1a8c03db0104c04c635bd942b79b3e2a1632c39231d07991ac39e1b8435d15dfe8404a694ce19a448d475d396e6bdfc3a0d97d72bdb3a49ed3bd3a9cf09c4bd5d6a2abb679c93b697689632574aab5f6b31b6d6da1d8d6a77e121468fee9238dfd91c3b976354e9362350ce582d6a526adedd9b37e79c9a639a6d1ea594d2cdabb5d65a372f46902ef36b254ff4d46aa5520f59d6ed966304e92ee718c3242ee7c85ed31c670cf39ec7b139f6d29621acf92a0a3bd4b64cda9c7312f69ee7f20d5bd6197dcd50ce5cf9932bd4f42f87d5ef683cc427b4f989f0c58753734e890238331d7a9f244e3535cd0f78534f72c624712eaf48409a693ac99b2a5451006f6a0be0ccf4e9989e705a20343d01428dd3404d93552179d3404854a19f1dc4e6afbfe6e90d04c4b96e03d9a7d7899c0e054a942852a44c99f36ba09fb3337d126387eeab425f157a896b0c1fdae18be8ab183fdd77353a29a5a91435996aad3595b2d65a9bb577efbdedc5e8a1fbe42a460fddfd562b958aa668fa20eb8391758c1e3a68234695ae14db0748a37e83cee849f4e40aca77efbdf7bafccc7265c2a680259d33607e499128765d15895a4a0d0795f7a9637eb98febd6a7469201f3cb69c0ae0b7b1ae3072294daa95f1a8ea6ad95b2a78eb9dc7544c071b5127d4ae3855e43ea307ba8a43ee8eeeeeeeeee26755718bbbbadb5d6ee54976eab8ce163e3ba9b738552ca526bad2dd65a6b45ab953cc953ab954ac5f091e1c86ec86ee81e8d46236bdb664a60d8372f7246877743ce90e04d0e38836550ffb8bfd137e48deb803802f8b89f75abc17e3bf497f6ad777720ab9968b97cbf48e9f68133ba00b1d69c77daddbff367637ad32950a10a53ac70052c4c91052d6c818a2aa8d02a3e3f3bf0e8e183871e807e000922c40758236dc45c7f8035d26d44875acc15084a5b90360e1d9234d297bcb81459808f32d3562b9eb2a8646e543639ea98220100000000531540202818100a0563812448533952dc0314800a8194406854194ac4519423290aa3203084106208000022203023344400bc079fab15c1b8190d47f2efa9d1c9a4b66535c1727dc7b89aba75eadd449990a1590a8447a12eff6c341dc78fa15da0b70c6404e1641f62970fa40081a04aa427462bfa5494886e3305faf8618ac21c4f20bdfed506a76df753fa18009554e93922716c2da9cf3077392b02918dc7408490ef953dce5cb12d64319c31e96c949eae05d4e582c2390ca53364a27b072f1173a695a61bb134be112d7f42086fb8c24ce4c9eac10f1955620045d393f80bb9c45fc65cd69b57676509ead9fd19390b9007592d0d598ce7b98b3a5a83dc3b2cb654fb3bf030ad1b5a052154e932ad7bcd732b377ff0f7d389a9da373ea1d36166356a21ee4b72a53f5b85a06a78bebeb299428b3357070575ff4066db980dab4e278578edbac95e442daeaa3b639cb7d50487e6ee9fc3bd16bad08dc504f5cc973148323d168d2a080ce363d02ae153695a05904375b9b6b61dda26d5b8812674825c944ab1fcf4164a712fad8f78c08f4792eec2ef3fb164d8d54eba26cfbfcb2823aa542723b8e530e22d8fae12bbe282286c86d2423054697842ae92b61a6bfcd7d8dbf0d85c000fd23f9cc5e9e1ab5c82b2186011ddaf77250cf2a3b69ee363d0b329c1a73e4ce86482c6e2f26edcf024f132220d78d9aa1f6f062d06d6418648c2a5653750ac58f0f7304cc4bf8e855f66542d5a70d8cdab3443b5426d86e17e6aa6e89b765b1b5480a619a1f647950bf77560fdd6ce57cdd4f8d66f95ef5b2fb4597103a4482006833bdc9364dca389936b4cc16a173d8a7020122aec89f7ceca002e668a657ce8b877c217332b10d8b4e5d76ee4d5ecf791c0c81a35916d91dc507d6a698020dc54b062e16e874a8b726dd0e4b30346c1ac2511b7ecc2b5d920f7d9318263b20e0fbed6674de9d4c8e7963e2d19d2eb6d26e19ac2a0ac836f4a7eb041c95619b3594f35698139bbcc13a4a5394499967944c16588680a8b16f2c20bedf15ce3839663c5e7aaeae29a52ac5e8e9f318906825f3c364a112570d38835fba95214815a795021b1d87b0d8548fe3ced4b3479f1ba6e36b2b1523b11493673d9213cd04ab6d32e736ddacc69cea32b999b52fe65146ef01bcf86f567ad095be65ad638852c3cd928ca9bf3b46634035e401929bdcce11e19b581b8070f0a25725b8ddd26f13b9ec261f369bfa86a5fdce87bd4fc582e017f88c259f17b1b34bfbd5b44d778123283d58ebdf50ea8a039b83bdfdae18233bc2e3e5c2aa8f7dd7012d6242a254ce620c26e68ddba5b8fef8e05682b48b15756d51fde8f95af43329120f01a070d76b5cff3836aa5d9566e561731c774967d075a4bcb5c89b50ae591a9eb04dc137e88219821d4875bd5009918870fdd5018d7bd9f9323de148e70500306371c865665b9e1506550b4c454d66706a12224946a5398d234fcba1f616acf2dd2cd42b3a9943a24991f7c0730dd224a4cd2fcf28a6343125d2cbd49350ceb7255df9a0b422347fd3cf680a5fd691032d70b5c23fac4394583b6d66f2a677a1845b5a02a3f2da3ec46ad48371b51dda8f7a502b16e21309831b430ad0d080e2c0ffb8a0d400283425d0350cc3cd89ecf0cc04c7048e4043e73df59683f3d54b0ac7c4dc699652158763cbe8dbb2605d7c6b3245f0a42eb44a620f2d5001bff64309ee3f1957c6519580dc6be4f3b9a32f102b80b51ddcd44d5ad06d090e2e08a7ce3e5c2bb1f16fa0f57a1e3574b1c551632ac5983b22c105bdafefff861f2134e001cbd5bb17db6a3ad5ee9a00dac236982ed8b0d09328b82fd0786e686d6d0b1147c6a22eee11b8ea36333c88096384c154542000bec4c5e5a66c72edc8f5a9b77f0be03547dc2041512c64d24ead65787564b6136addfc72ebe163e05b04cbb73788a2e0045f6ee56edf1a13ed2c22ad95bc5a2308f11054605903b91cff3aa303323f82c2792b5f00170535a93d0b3474c16dea028330ad7b1a7e2e982f4b2fa13ba37dc806ab300eac910b81a2ddc30f755b5170be86e1e065a30ef061cc40019d0abc34557c7d43e440350e5f9b8910fa699d45904c5424693227128f0b674fec66459f8dd7d1d7df915de2931af0f0bb8b39e84bb3c389e4a88206f4de4053b241698bd8982b4835f15699e148f98593af81d5e4f85db193589197503ae4fd38589f1c757e5313c062b445009f723d6357f392cbb52818047e7fef0d514e1119166423522805ad0131f89024ac67194cff2698aa36353aac0343ccf56fa3f5ce12f741cb236a60a682840c8fb457804d95422e45540cd825d2861f9ff508785bbc761a8a967e0e095caa65915de6060a88e36490aa5f4eb25af49a57ea153fda646dd1ba13c52f1013b1ef45a67236d8c4c3655269531d46656ef7df469188c2cd4f37e56b31f29f3ffe66554c7a09d1a4dfbd9ff2d5034eeb782646484bf24d94a697fa11b98c762dc5fd74e21368e11e8a6e5bef5a9e71d2489aa734e9325e49f5e833ec8ec67494119232439fa02e4a65647ff436eaa0b512956f8848d72e52ea5a92346bf8ed0312a5b40964b0b3a8a7a1a223307310515ff65c900f575bd7ec10e999d6baa1a8ec46130524f0a0e149768adc132ff7b3645a6c69cb4aea2bb9f9c9da99f1637a03062b8010d9f43c0d5a5fb8a60c22d00f4f40387a7ef0d1b2620b5981d23fe018a0629fd8d6e43442a512336de51b7b69499186453c87dc3edfcdd61cd00158f9ff56510770e478729a0c2b9d4710618ec04fcf0cfb27198493465d9132c5b70927c2b7c421cb0e513cfe77572aec65d2162e2a6d153574fd88ae34e7f8b85c09d734d786dc91b11f12c82bc86f50d1f3a694543c2a6d772a5bd3b3dddb3fca0d3b0a8c75ee72307432bea04dfcffb28f155e200a49680ae43cbcc65e3afabf937b1cf2f05dc6ea3543c043c3687965de42b9ea8faaa5a07de9686722520061b4aa11e128d0fb7db9c5cf2ffe3c02639f16fcb6486d2357e777d7f73f2cb8e08bb757fcec6ef55224ca8c2dba8620c05d43716acf2c77926f5cc23299d3c60d739954bef45028cdd3965ea3279d9f5fe5753c4475c036a4e4353e0004381dba3f7fdb0c82f535753b79adfc24fc8a9f262f46dd26bf668d79ff0bc64f6aa060b090edbe315ea1204046b707930f3e1b1c4136375673a9458a83e2717882a4b4f675f5d75331086e832d7ac1c9441ce7f8207d65603c9f2b622e82e6f63f983494119dba6c36db731065cd1e15280334a48974d2bfac8addb20a4ee5f4256bc88b3d7c4201cb7e23558b9cef0a3ca021cb1bcd0e4539a2a96957ca63229350ad5a5a80ded3831f50907442204aa038356c8749497239d34de3e114d6a88a24cd8bbf708b4349580eb93c4bc9de425e8e6bc601438760038ea77460375b0a783102ba2197763f9861a764d702bc0db53b7b48f7d2d2120dbeb43ec18b5225ad0351d73f408d8e671d25224746b29aab7601d787a9c5d2b6fad73c8a844dc7e06b206338cc0ca073d5379bdc1cbb4d6dd15195c18cc0b8fc2253789a8093f943b69b3f26e363706f6324133f1da439129059c6fc9112080e85d42166d04594b37f39c67910ee97bed58d81bc31086849cc18fad518e45036621951a820e87580d778d931192ab03921296186d40290ab10f0709dda88368b2ab713e5e0469f367880a94d5bc8b116968716d0512326dac13b3501570659427fe29d22cf38a831a0332fe4672a36efdd274673efc690642815fde5f735fb0b826a05ee8449442d175d7c47575e5c6af10a0241edc5e5fd59f9e8001efb777ab91645626baeb36695271568cb0e8090ddee0feb3565113cc33528fa36f5db78b997aee0c4e30e43fdbb382ca160ebc55000ee98d0c17e4bacf82da2719d160e354ca86b283c259c5196d6c9809be4ba07472e2263a92cad16695473562add723a220c178a4740e251ef5671f66f141e1f0a536133f31c4ded8761ebcf7ff9f59cfe316b2c0053c740443d20ae1043d624bee0a1ae4dfc046f963b2ed38cabb68546e6bc4ceafacaf28a11c634eed60149628938e4b1daf025e01eda10db573d56223b8be4af69b3ec47ad2cbab4a1cc6602bafe4154ae5b80768ca1acfcc4d715bf189792b5a68d596ec0ffa30147561e4b036069d41a9153a859887fcd8e1ab79dc956654812b247139456a7cbf995c2c37d1955d4a9642bffa3387af0c5efd57527d39e8dd868466db38c1ab726a9dde42d0561e859e338b6191f0cb6c50ec5dd950cd3a53197574a8c494d618d8fcfd702a4d0b6fa9fd90a40673d2cb00202cff66130cf42a5d6e93cd5b6820251960e7bac0476ffb54c907b9831d81e4430c60a48e1d7f604688ee535d616cd035ffb09af84c0092e67db3be23c3570e8b91ad78f4b60e8fb12ffbcabad5d59fe7b94fecdc97d5bc461011140b7f7adff6615fa07e0569f49e3efa761628d6500026fc021092656e0cfed1b0fd2fc6d8a8ea0962a35e054b1c7cc95a723828d6c0831a19dae0255e7ad9acb160e2f12a4643d8965c06c19877248d875ee87810ed38a4adc953491964acbd02ed0568468106058f7713bcfd842328ead15de3f66d8ae0bcd3bbbcdb093f1fc482a6d7949868f2ab2ac067dd1b3b040fbc531be81623ca763c6b98e459282acfe287cb9104e7d916134de4d75ae5733a26ae8db95e1b8c50d0882489feb5bdc801352c0e092ea7e136d3f75374c44962c067163699b1a08070c8dbc1d597222bfb2ac978b22a64b915081efcc2f18d7bdfc2078c80498b3c5d295b7bc259f1d8fda4119108d8f8708c1be92365d90ad329f0e18176f07148f7000c7e0a6ea636fdf2fc875a49e2a562ea30ea35b1c746ac1c9ea3f13848c5499e568785429055f4c328d3824c6321ee4046e35a81ba8e6aa5fdf6aa27640ccd9a6d7ff953ae474b542961ae0709e903ba7cb12400c00b888ab6450eca3adab2b0c52155222d4918e8ec923eae22fac5c773410152aba671a3bce1cb444643b679752a2fae328458af8af0e14353567382a79c39c72e1c24765a686a039484d71ae29364715852f9c32bd5e69f1aa08e100f87dd87c3715dcc14abcb37c13672d5ea6533ec192025cc177586112ef1249e7d9f9f6157a103ff9802ef3b42c340c748f5b237ecf08f1c0dbb8a069426bb33233df69dd2f05d179bd83d66d235f1ed6550e5343cf6e0cb1ba622e7b07af86f92960a7376338523c2dfe69b5cc7306779c8bce05b77a57c86d54c74c99b00f9910a9d1b56a7d710b1a6860f58f3686f08a34d5b60d015d179a1f1180aeb3c7047e2e687b7a34683da07e0b5176209fccdbd9633cabedb810aa595d7183d63b2f351c26b6c8c8505aaf1a907f06cc27732741eb3ea4b9d81766b73568350c87637004455facc4e2be39c194e62d18171231333ce78339f72fe99e478ce549a99ea4ebf3b1bf52fe3604b062a110e43511334a2ecb21a8fbdf2c635546bcc6240bd9d56dee811cd415d29ec69004128d3021b17343cc39ac2d2f549b21022e9164d2701f962b9bf9a1ae57512e13e71d0dfd72c2223ea187e4b35fdccf4ecebb5c56c783ea46b495916597131e42ddcf61de068966bd4b9147802f3c5ada04295eaaaababaeb2c2ea6aab564387adad366fdbbce9c6ed9b6e6eb0096ea8ea90331c14f6a89a7d3d7c8eee0b1e2101c0b6b4652ebd3a8c1993db12f07f9c2739a18cb2f28ea91167add6f845bf3a7e49729927579130324fa159e897e7cb4599f1889139bd83d83223be707dd1f275299ce86a8a067bda38b5617972460c8e2dfc34ffe38ee42c277ace545795e8afc28e87d5dbc167906fe0e99fe207a67f35f3e67a8e5ffed60ccd844c09fff25e7310f5c269c6a70f490c644a86756544115ae2c2d2e2a15b42cbcdd3970df57cde793c257f456b7181c213783ff04f04295e5984b094427e9892d3c10df65194bae88caca5216b8842da104230c1bfb4e721d54ff0fb9e6ff409425abc01ea2a83beb70b12558ce2a01dc4608ee92ee0d505ad913750a851a714d06064a87700d1e8ea4558bac9092fa5b8b95b6ac88411cd43b66fad766a420ff9cb2a1829a173984a8e73112cfa1f5766d0680b7c7f67124cfb05fb362f7d200ee7939d031e00f28e69950f0012105252cbfc780c73b8858e2c0069b2f0fed2d2efe957464cbb5d3dadc855f67d0168d497f7cd3ac74a64f013d95ebf4570aacb6782eb836db7510bb27d1f46146fb12ac1423bdf02f3030221e002e72a16e46e6afe630d9cb70bf570a088f09b7e9a21d247cac2c85e919bcf9b18d2dff384e80db1f6c827ca1ebc3164a75fbf7e27756de41ab596685d770a139fdf76022b268e5572c9260868da0166999979001d43356177d5f8a165a3f4caf3b82acfb14f67ffd7b1018ea6bb598e5426bf30fca4e4f9593fc395c67cf646655af291ea0ef73f07735852801edbd7b25792775f7257d2c9f681319704beb53de785b085b3041d543a3a1781d4c9c83be47449bf904e91f811820c732158937fc6d47a398ec1d7245e84ce0286522e3e89c6d987983247e0cd11cef499442b0dbab6f529781cfbd08d4bab826947b9080e2679a4b56a3a676353d9a991464a2c674915ff601dda04e74bc9a087640f9b6b34997de65bb270d6089f8cb711cef9a1080c51840479ddf761b21632d2b3f44ed13f393088704cdbcdefb30ef3163154db488f3c3a80bef789d469db92d820344d896470945c80cbe0b90ef51ac4bdfd9b6151bbfde41e3e99d12275701671c8f1a5f2a08cc514356649f6f964270782743258d4133efcb071698d84e1d15097df0aa4edf708be6361b207cc4905315bcaf8d8f59c6d11d93abf28c1baf9e99f50d481070e985876a065e0bae8254d92b515f28b6519408b1f3bc800d4c91677c944f3bbcb23372cc47679e70954205ff2d9c3818cc92b7d62b2a3953b4571c7cd14be18b81d865352aa4911a2110a85fa395d8dcc4641234371e65792b7b630a2d088dcf1a0e7dda004fd5fd0eeedefacf7065ae18bc3f0796d2a47398078c2779676c37047bcba4f852320e68167323773839e58024421d4b721c4e61e7c49c6fca4ff6c1652ea0e46f3b4c116839b6caaa3ccdd485e063b184d0abf028267640d985fd12092cb842fa09a895f14ea8a5809372828fa0100fd6f076e5de7bafc611b3126bd54bf869c4cf6f69f6f8db376c7aeefd34fc18dcee0007ddbc95a1604c99dc8dcc18bae079b0bb1962801141100b4481c3dbc5c030720864c4f504f1e879f82b28f450f38d2c02b0d22e0d321edb84af4a972ee1568936827dd2e614d683031469ab3d1f49b07d29d6c6a6105173c7aeb764e72f21a1abeffdeab28897604317882964ca5f2f7402fd4ad21a0f9a096214884a50a698ea18ac3c32ee9fc9ac3281117652a06f26c0a1dadb87b5e2a30f160e2176358beb8a396693296c29fc85aac5a3cf8b0a80325e2313c8f732a9693f43f2fce3d71944b6d239b22d0ad9cf61974b96e8efb98eb716a8141c182fdc79746bc915bf704702cbf7a655d4c76f48064f0a420d8d913987b53359ffc046d01b7eca485b540ff47e4dac6e1fcbac219008d4f847994b69e0702e93250b535e1c32982a8501aaa014e0f4789c26054ce5213a41ebe4414e91ae2b39d44bca25b736dc0a6a5c007a2bc18a784198559a2a6f0a38e3fd6d0d54385e63c1d44d7c99d9007411ea47aedce6080d86a1d8071066407ef87c0364004fdbe837ea5eef216b148dabeee06ba9863b2025105b21c8135db92fad77e8814094d72437a7c6244c5042469a6b25ea40d5c6ec945944cdcc03f8fc1afc583fd0c49877c3d1a34ab5a3af1d0c2e69435300137583be092536ca998cded924d19505e793934d5381d3a042921103c5e74faf93e236e70de478e6cab0d21462fb18ed171eea2abb07a18bd03d10e21aebc788eef30ef6bbe3d65603bb2675ce8a536f4cffd3715c86a5f35ebfa59f976d61ff0e7b811ce1c5c40adf42df3aa01ec04f6a32b9ad391fdde06ebfd6ee6b0e455fa5a6ffeac9fd27a8c7a94cdc4b4638998ab0faf12d7662b2b1f59a3e937100aafbb121ebe5bcbb2170f08e8ca92e6d7d1b65ea93dce01ace0b049f71a13bd9c8098f1a8416f8483830f8524f6da1cfd9ee480a84348abed8065cc8199468845c07d83e4fe7e073006b5f38d841e5792961fb9498207f045e87e091ed03b9435db7aaa6e0b39d2c3cfb2e6127305f5817357b9e83764f6dd2766abc702d503a1d2e5a28f7fb904a75c9d909a229b5c23e94a5567cc26cdcf1247ee10f891dfd1abe0b0202148df3d650852da3cf061c48b1a175b71bfd2a0dad29d4dce4763e149a380dac8746db84af3369923fd8e419b201f91952d6e967383d86f89cf6ec6297d4cdb3be6e36bf9ef117f261592cd80c57d6d14ac162c98d886ccaa75d5e56a00bcbc866d231aa7493bf3111f0535e720c96d309aee3d1a66ccd5975299ffbd345f0cc97c9dde99620882adf2bfd15d44b32f8a18de521d9d3d6676f8bf667e9328c17e27e8af49f7f6f513b40c0bfe6b1a20c75ec7350bd62ebb1edf398684bbaef6ff98e6d2483f0b26a703b967596e297c0f3329560f74c226c26fcd202ae5e08837d994515b84d4291a126505c319f1d516b1de70950ba98dd0914e9f6b7e5c97da88e036a279f19eb93b7c41bd7f8c6eb743cc71f9024a9537829d01ad5ccb182159ccb76313631090196e4f137826ec9a2a6491e87603c16d4fe8a44f3b54a4a001c6d1147084e5c44ae51018dd0e2739b0cf5eeb9fb65d82507bcfcf368794959a8a46cfc96f0e873086640dc2e96f6df65a6ab120001540e5737470c50dcdc732e673343636dd85b550bed736b66b50cd710866201d7ab91c48eadb5cd1ccb6063d90e22042f789b3d6cca8e61ef99c449e78ffe452a34c2ff2c788b3d6b88c5f94685d62b1e5b01f92258f35d367f325e1ad5d75b297bdd3daba40733ebc360d4ec549dfcf3a26b5eb70b8e653bc56642222d09008e8309501d64a096831d59e4ded63ca49c82bd5bb21bdcb9ee9d9c63a7807efb5e83a9440ffbbaffbf3045c49d88129e3c1813d25cd034385fe7c0406d09f7884557c6fc933dcb4911e42cdfcf08ab9cb21c0b41cc8469412c12084de7a40e82bd809f0ae35d7821d60fdb5db0218f8d99d800586415839892ec06a6a67031b1a7927e0bf4f7745bbfb04e27ab89ffa05ac32f2f8d244fe3d24da11f09163ba05ee0a5ba4635850cf340a451d65f8b7fbb008396bb2a39d5600b6d1e06a488035065f7cd712259a5e885d3a9c95cf052b422f2c5d1d9acdbd578472645c99fa6fcd682f7f347b944e8c1b5c3d8ed6f8150de0b8e906b898c26fe0a42243a0fc1ec023b480162fe318c5361dfd1bf62e233a3640bc163d39e066aa4017bad0d8f7a0e1cd688a9c588d76ac16f716d07fdf0b7f1973f18b841d3ffb53a05039c206410fa75342e9bb4a361d15e00746468eef74af89df1abeb54d6daa709ae44f514d481b78b8c680ad96d579a1982511851f2b181c0deb20ac442bb9005e31aed3765494ad36ab06f02f2bca6fc5dd19f999350a8328cfd55272362c31938d03d9714f0dc38a8135f594efc1ad6d1191f7859b51c6e494953a0e290eef0c4673f2317a6f42415d7504efbe0c360459c3e8a7de9ea30dbd66fba2c9ae230be746165cb111a2d1303added306320e1bb047e5da80b4def5164fe56da12315e7f670961c41c69efa59e161afabbf4bdb066e38f5ae857c1ad3e095d5b2c5bd0368251c6a19041426db9b2401b4a5c0c9a45686d7ecf35413abf2ad62a7d6e3b08d5dcd15570cd755df7ba5b19a3d807727e5f58d7cf198c4c040d2041f225a561304bc52bd53c73a737602052b215788ca3f0a6ba70111faedf85daa987efda5950d38bc47c80465d91a9f8ad4092b899048b88b92629b2fcd5afd1aab98acccd05e0af7cd0f47289f2bbd589b7846d172147a697d46b795040c302f0471498a84f91d0eb11b1e1be50916de373676f1c06d12bd6a719c54cbdc1aebab25841c425d5255857f55f3da31856249ef4d22ac40a0e519d8e94c871225459d16e8994fec04e9196d81f3f058097392aff2f1fbfb0d13cdf7cb89799af05b02760c2eb06f96b402dd1cdc806af3389134da5fc0546883d48cce4a361997c9404a30ad919900f71ebdfe96466c202affaa06c655c3948ac748cedb90007bae6b9da1d622454c410fd3e78eb2f17c09684b89bde4700977d9f672cfa2b0d01997815592cd037f02fd1b7c8c0290d3521b11867bc08e8c91516d04321b59580b386f9bd0637941172336ce028d14f597f48ac6d5a246dcbf7398db8c10bab5c3cb8fbd40a1704ef2c35b71a1f115bb62d6ecce63909958084db749aa31eaceae3159c58416f2c32869de5368471755d689c1b8d4b145f52e8bda8845f2d4a33cf13631a7960f575e7769b81f3c69d7adca2715792ed9a7904c5a04bca71b24ea57c2c15f7703809fb9bede9ac63f3715b4dd72f166a37d912ef3b59142cabd1f2354bd7afddb5ac7d8361f9a736d7c6e1765764548e67e5d22b95023b0e39ceaf566c7698d95acb773a366b676cf4b9bbcb879c83f0505b2ce44e1ea6241db0d5b8bc98f30e0791d0f671290f9d00bf3f2393ab5a288a177b7d706f6e157dafae907841c0e2b5a17d39943cec9463a987e7ccc1b08905075e5198b28822cbf0cf40b4bd0c523155a8ab6210a595d96ce280b07df58e169d5894d6d9196500520f321942c058e9250430f2afca22f31e9217f076087ae483d81252f0c6cbc5a3e0279907142fb30c6a4a35a92733a5cfe71bb4ecc8a773e76600be459441e428e0235230e2ca1d2f3edf7b0e899b9b7a8606db7ef6161199ee822b87242ba603d6563362516ca9e893fa5effc4daa88589cdada56e05b23d454ddbca3cee56018c38116bd395225072c49617d45f07048890a8e8fa8d91cfe247143fa9eca024c3733f41c13e7a55947a448f63b54f2bc4467b8163aac428d07ddf6005c38d708aea6d02772fe1e0ff29b26de989dd014b23ebb2b2d3a83e72099d6bb4b70f46a27ce3bdf8cae50bb481614e532deb77c66e3991080528de336eb845631df172bf0708203892447a13964aa1c9e0ac9bbeb548e64d59fda3259a189dcd35c7429b012f3f762a464315abd380f6e1af4e784a4f041b6fe2e3cdee87b42c4f4d59b705bf9a1293cbe63d683cd29d3eb267965aecdc65db9ef892f6e940cf5bc146cc0675b5f94669473c44d16d08cd936e5bf7bdb1a7005001357d71b7dd1bb7a53a4ae7e5762f26d31f4b77c0c03d8ecad2647929dd7438642aa68ef5521582bbb1e3564aa01df49fbce18ae504090f977da606a04a855dcb190444b7e73f07eb2abc5e1e455defffe063bb882a0938528c8d5d3c2c150ba50236a05031d610583b5bb1d6a95fa8007954dac81a322931c8b559c0b750511966b82c549c008948aaa2d60657807587dce303d0e5d19bf95f01f202fae8e3dbf6385b985b65dd9d07f9e8e1bca17bdf12fc444892674181ec483f9741de1507c3f4645043f921550948275137332262c3a31627d81bc0fb8323c8c7ba536ff5911bc4065a038e66ff909886dd5030ccc2635078ec030640a8c81a0ad69a4fa2e72145ba203820101b8a55d0a76964195ef7b293558f13752f6299a5b7f30133aa96cac547bea1766c6200c28d864ef6e81d516584ef257470ebf8fa65108e20951d50c2cae40fa85822f2b79a15ab4ca2a23a1292f0615d519b370ece57ab9ed9a0dc02287755caf6511f7ed219f2c9fb02b88867218e20aa205e35ba25475388b52258cf5e1c34bad8ba9cf404b300d0058faf58e528fc6b30d0ce7b92e09642bda4140685dc626c0fb209f5b271b100549748d188d52af8f2653395982cbe69be862cee15543d6385165f95330991361f7b1505ba2f36052ad809f30492351c5dbf97e5ef527ad956d3805cb61830318f7db866876dbe9402c0e01f3c3807141013d40edd998e3c295ae88fdcf2862e0bdeccdf3aa27c132c17d3bb124eab242d498354a35534a68009c87ab03e5cccf6631db98f135cfeaec3c9046238b28f9b005692b0e6687c71a2738ab1506be946e6c5f0285c0e1f5622ef502b5dccfba1f523f0f8ca157c6c2a3bb54542ac8afacbb7e1bb15b253f93185d8267dd32c7790191e12c196bedcf476d32ddfd9e67b366692ac6ab5f6dbbf770b02d6df4eab6c923419fa89db94f3f9db13e6b69f8af334537d69c41ae846cf946b41444f50c04ed9fd599d85160aa059bf128e99277c4e4b00ec3deb87fe6d7a7897508cc7a36d01e64727eae42c58cb4c409795e69ec97acb72cc80451d90e2222e868a640d15b38e2c2638d7006799643f319c77c8723afcd9844ad235b2e311d613c48ce3de3d29adb67320d7cb79ba34dc7ac8265b0da041f1bd4c49dc6888d1db667a68e1760996c24c3edec4170c96396c3cb2ada5c8a1f8b64e6954da7a8fedc4021f05d2962d3a464d3a1b6ae80513ed6cc332c80c454076fee644aef142f1ee0c51d59c1b72d60f1e8ec18a2fca0cf2cd1f9f6faba0360163b492658ec7fff6dd7c780e9b8843f3c4e3ccc11d2ae67a6ec812e9172e3e9e5c5ba4dd9442029156dfbb78d9c0f2ee140653586b3ad6cb9f2860d2a2415f1e36035ca19058d530757c805f4443988df4a4f606706ec58389e42936ecbd15a12e49122ba59a2709ddca9a675ab499ce47f4baf674be6b3dae9f8e260eeeee4da2b8531a477b52ac49d2f36d389a637171e851d5d08ac10742dc973c2dc52d30c2ab27d10eba3d88fdf5a6cc774724d8e32a700bd7dad312780c7c61aff7f320d13b4632b9a66b8c1559ac75d32bfb71c4cfacc51e5795bce9518343b920a8903f69578fdeff76752bdd745731bf85de5531682af5ec789f64bbaa0a4b050381ce416326b5483ff49f12ceb5ed24f9724dad173dc59ab793176d29ced170ca06a1fe6b3a5a6ce49dfe323f65c81d8f629c6378760a8877467fe414b79b015ed3aa0bf0291c2d0aa462952b52109970cb2983d2c35f6b3b9786e85cc64f8cb7ec5c9ad241cffff9e24fc8e6794b6beb80b68a3e287346dbb7c05fb184266756bb1cff654071d461107f9fd78e1c8df4558a755677dff1aa2d27e53161f84a932c2527efcaef6e40c996703e2a0cff9de5af052994b7c0ecf9f32bafca0a9f822165401056a078f8a31388bbf253399693078a14be2be529d33003b00e7272ab8b71314a3fc9dc06a4fe575010116a62261baab44160472a7f0bc058dab679f246b2b53739e2becc2e81ffa2517ba20346c138bf031476ffe5c000d1ba0aa7da6ddd7e55d7c17c0e8608102cadd72e7d0bb0400146337b2ddb12ce5c8afedc00a0d94cde77732e141a77789cb334af156a9418f4eda816ffab7459c8318ab704937c8bfc8689e00abcd71d8762d1eca61e11d873f94d49a1ebea922d60e08279d4fece43b5fc27807ee0efc94833a00aeb6c674b89c7f7890f35a8c35ae7c5e59be8171040e94c8832450bb0218ead74eedc67f55da84587d838c3aa22910595aa9a8cb819ec965e7168eb54c50da92a9c09fc572a5057ab8a51c3fea870c085008dd316e785b86da84aadadcbd70e219a10e502a0aa7b9c7a5d0bced6284f1badb4846cd1464f62535fa991bb8233ad73b7eb54f124ab51d45a099b3181f08700abb5a5438b61a127f096466b1daf1fab5fae49869cf269ad2839741cf9e94498a0b5f54001a1a99d1c40b203ad7dc4acb4ae35d5b9c657e85a5ba39a5cc69ff62cf36a388af605b43cbc1aa0a0c7b426bb80a2ac931e2417315ab67ebf272b349ec727f43ae8548a33852d0b0b7a96e7fa0649dd50ffccd5aa6234ac47b626090184ffbd1bd1ae8ca3c8e540cccce36ce7eb9a62d977eb361d6af592cbe319d1fb29575eb379e8557805f5cf76e2c96ab4787324217dd50faf88f1e59efac53002845d799fbd20e6946049f8c8aa99aaba17b6e13bb8a2d6cf60a566c13871a5561c0d91fcc3f19f3c2e9e47d416dae1963f50671d67a4a0403c5d8f710436939282c396ce57f3eb8f94175dde98068b6ea566c5dda3f930793b5d5c3333c45ee5d2273db417be90c49ac74bc754fd066767cd1e5d296c054c6581201de6ca94c11eb66583f8a51aff25f5fd6d0bb5a1f06e42aa9efddf78515876754743b2f49540d79ec9d2a7d2bc5b72aed96814447db7a41d682dab4067aab1f1f82a3a60d32b72238ca6052c2d66d693104f60a47a30a4437da21834d54d24c354066fb8fc25a7eb304c7fdd5bfd91d4cfd4ad845c7873c04e3d149906502ddec80a77d5973851e996a38cf6428b8c6c4bd99b351f0382f47c19bc790484b062eb0e3eb8eef7626d47be3f6c1bcf95a0be4432fc1c80290f55a89c060fe577a59831a12b35358325c0a85650c77f988fede5adda0726a4340072ac99b1bae5519994c4795ce2e35a48a8e02b201acba68b4e5f6d9820c9d06b7858c696f75d98f7d786039b1ca2dd94955abb40dd6b2c3bea5661c4b8c948bec9e5974133a55c0620280ca6798980c6b0f8b6babdd163a635ea49746450267c817db7cf3fa6e727d8f38c03647a504fc8d3e107f88402e3a19bd9d7272a480e5ef4c6ba9b10fa7e1cc28337992f86b22e44f310918280ac4d2aa80bc390233d4439459982c4c1db5b47196fcfb5a5ea7fcb41982fee2ddb8e068ebf6ec96587f634a3b56d763bbca2b3d136e0fc8dde4f0bc7255f90557e18563e3896a35ed55d5aebf6d3018cd13b0c0f494043c54aec9f809595d39d673da7367d556d52a326f0a9e1f1f92293956fca4c224b3066784bb78f99910cfb4267979106e40a5056385c952c40b1d71a90ad954ac44ce6633c1c50c2b927296121b6e2880e1623ab2322776ed89b24f398d824f71e9710690090ccae9cd6baa26698e044b90d3881f6ab81142c7f25c28c42b6c707b24560b879df0f72de9561084cf28a3b21ec1175fa80cb5f13c28550a7d9359f4c2a3514c9a87098220383592f39a82bb2d714fb22f4c0ca3f1cacfe02bd89694da2bc42da0b0990813bea1b6d6e2a23dfb82ae4a9604c213c9ee32a3d6c96100033a567fab677d44bb5b42d107dd661269f6f9bb5cee8d494942b629ac64acbbe078bf0fcee787b53cfd1be14bce7e9dbe6d9d3075991ac5a02be600a76da40b7599546b6da4169de5146026f33db6d7c8a5f9752d072605c2a40058d2e7d00c292faf244b3fc59bc307660e5cd1e56109cea8aa28d1dbae4216b727619bd3a507f076f9294f38a0d545daceb75531629c5adea8ff2762ba2f3832326878a53e29fc7b5438a731601cb3aed7c68326195efdfa6dc34b109fb3516b8b9eac6363218f0771163a8708a18fd606809d7fb4e5106eed5b966847874dd16aae7bd34e1b078867aeb52a6dd38f5c0062e9cb5be5224fef4853054f732640783422473360228a5725fc870e2f43cd76dba33788d931f87ec3852185d50ac551f4932e835f2ac6bb351e8c6c49da2a313deddc0a3a68b142166993df9158361a750c23e3e6cd0b3200bac3648a0d7ccad9bc55ca2b006c28875124fd4324ea8c0b9fd70890b0b46528a2bb9ba2909a8197fbd5d8d4c18050c126f0d74d93a08a778e86510ec2ef419e208cd8778b64ed09ed86b3cd368250da7a15d50b84b230b2212277a9a8e4e16362d7e712fdfe2f983948b44d3899ad75b8d237864da3d6118279f8de9d84418414f056a20e0962e331fe7406c4c459c8ee0b46885377d87fb17dfef83c207f31e344031cd393bf5f143341b2cee717864c8eef604adaa5f885f418bd58942b22dc8406834f8f158f42016fb7a3deee1655019f68d5918c0f07d9fbdb10b5543c08fbf99d499a0217c7c70b2a7509aac8da6799da68cba00dc833330b43e2a6355b5e93942c62f658276e5f99e2e22261755b7faaab9bbffd53b86964f5269290cd2b374f671fc6ffca5871b07466379fc6bbac221006400a127e8cd942a7333066bfe73fe0317a83f11c20a7647abdfaf08d8bdcbef5cf6b73620083af294c60be617f9e04400a73458605802a6f02764af042a61a8ebbf5b2ead95b04a66af0e983e6d710813726488d1324538f2e1e0c66b60198b3a1dea39a2b3dc42f529b3556336953f831b526c13eb695c9ad7ad690255ce39a9f223840faf36d101062a608368bb8fe82394b29876363ee5ae1bc12e454ea6b27d8e5e8057593a1e334d6eea59b5cd9edd265aeee92246fa98c3c99dba9d37dbcdb913e3619da8a3e8e36711b98d3a4f348925fdfee73f92af5293513ef0bb710e42ffa2ea03fecac194395b84b73a5eb908b3402b5961652851b20ee32756cc4c6a82bd8170c8846b4421374b13bb51c3d964faba2b7109a4a9be35b7691c51b4427da620885d225210ab5281e4c4dc8e1f4a35f186a3eb95bb5d4e594ed6914cf152a774451ea032548f3bbfe29fd1af8d9da5996e25680990dceebcfb4d2110b56c870177b64b755f7be45d561d2365c90ff828e2e403fc20f137b2ab063cd0e203ef64f64c1725d2a8b25516adc07cbfc5aaa7a81c7d596f6a94abfeb52fbd651fe750a09a9d828084a195bb6d6d7113e04a2e3a9246803ba81351c62222bea116e4950365e4323436787fca7c168c22b9301d90912c7a8bcc5791cd25fb90f5d3740518f8bb031e38a3c0d1f6aba5702465107b8b2ae144e69cca1b8fbf0d85601f0e2e3470dd210e9df1c8ddca63147c712b6891ce9fb15d8e0ddc55bf4b2f633a4b64591b33ee31e99197eadfc593f244e760ad59966ccf170b8388128f7f6dce89da4a520aedeab88e6ace55a416deaab377b78d0921b351f415b34dc0d973abffa9ab2566fb0a1065ee7bf68558daf636f20ed53f369784b174573a4df931c53a8a0388b1e8e263778e7088167156808c6e025eef6d19811bfcd735ec335fd69c18f7ff79b97f313291dc13d86493571c2ed41fd9b9f7e4336d4b6fda127768aec4adaa46a2a1c0addc0662d2a19ff35924b16bf4de7437cd90950e644b9a6d18586aca6de7b61aed1798fc689c2f900c093ec293d23512ffac7e6232e70cf35ffd61f86bf525beb40a4a431bda0d681b18b698f6dd2ffa3051bd9a367244c670e0c1fa11e3cea68ec7cbfd11d8ab90fd44ac80c3d68519ee5199b1f600eaee6660843b76b86514baf62d6f3de271a39152b7bc043e20913e89e5896fe74309ad4bc1c12e749aa74e71be706a1f9667dc92bad6ee7ccd785e4c0e37d7177e783cd6fcdd5bcc7f2dc45118e1dd6c2951edb4ca597dab966e31da593645ad34bbf3ab2470efa12bca79ff987aa80ca5a2b2724ab517dba568d8b05f8886e9ed7a1027b08c3b6526e9acdbbba6f1bf7078ac3c59a4ca6843accdd14be17bb1dab646f9d585f23aa4c5b123d8f81e5e86b05a0ffade82bb9486db653865495a076cdfb842901cbd5415223a352fadae356e3373ca738ca30dc0eacce09a64efa5fecafebd61e1133b32000831ceefdce1d4091f143e27b12ce2ec8be640dfd30362f2cafd8c228a16816bcfc4f897bae75610c65f19d670c53f27aacfee30b7f0c1d2709c18ae27dcfda41852f483f2a5d7f0ff8d77f54f5468a45ec7056820c69936f4c370e0cc88f5eb0a03e7f18c85e102ac48940a834275397571360d78bbff4a051eff64c919871b0defd81a386c8ad0c29fd2508505f85a4c9cc471f103a37d64aff9ddd42716e210675bad9235698a0a17aa17f5844d4e9aa7db7b152509e682c2117b282dd11ecdb9efcf6c9dbc0cb3e58ac24b57a3a14c09b0f8fdf7d14800096cd0727efc4a7156672d38ff59046d72e1b0d06ee5a4ab0a96c0aa7dbc60ed192420f735ca9b014b2b96549ac0d806eb834e508bacb3eb9684a6e3e45e1c19b455662da03f0af1523578f3ea6307288349078c6ea74da3c586b2438778925a3a0de58021e20d1406ae61848d829790e8dcb64f6a9b939c26681d3fa6b4dfd623e59125302833137e986c1a9151754d00fe24569309294ccaa4115e0689fe4184280d64291151187d623a3ab154ea1b9b47d473da4d60121fb1e0dfc511bab19fbc71665880963a5e5dd77e97a52716c170f1c985fa79f70d5c3718c05d1c2be16f4788737ddc50f92d9128f784a2ab6db8ebccaa07a63d38c276064f16050e11a7def15cff9520801047efab28ca51bfc15dcc96158dc2ca6950c7415623ba5cc7c88a493ca57eaa5174ced1c861d8dbace6af6fef7ae2b829f78b37633966d1d91f45f4369019933a580ad8443225c2484f22c26324e942e05d9466221e495f2fe1492d25bc84ab37fc7f913737463b72b88e9e98982c5abab91e35bb3395b5bf57044eb22e11f8dd0853d352a0ebd3e0e6394096645d7fecdb3d127e3204a60226c38599ad35df3db76812b59817ec74b641322081897eb2701a27bff389a9d7b90b4284e8d008a7d63769eb3494c488987300281915ca5e83e249049334c9b8d759a08180ca2b3d10d61fc1210ea8b689e8f08d3612d0707e8b37e630883f7c6d83d66d81c95a496e412f5c3caccbc4dcbb4c7924b277516dad0ed67f6a9ba92c2468a1678b9c4a2d32214f049b9434d6e3540d8d840e9e13a1e2e0a832082756f011f0d1c211772eedb68d33fb33e3fa39cc6842c30ed425e6ebea8bc60a186273d6a011cf34dfc17e54857a795b3d821e5cc9e6b5acc3120b32a74116e9a2cbab1e8147380cccb62ad30106659b8cb2b316572ed112e03b388e41fdf44f470f9d6c7ddd866661be46d02158c7b38711c05d697261a763094dadcca2c99bf97a6fc31963283aa3b6c58cd02b2a01b3860d7eb0f3f5b7094b9e00634f3dd8151c5dff29c233c15277143a2672d0493589e57e4f6f74ec20f261fe9d0b72ec53839663ae6cbadefe7d0ff4a480ddcf3f9a872646f923d3fc64850f874ca6bb0e89687f5c5a24768518ee24d2f7ba9726ba10e99350e12d7750aeea1d812c0ffb9d10889487788f738b1208dd1ed620530d6e9a3ac44ee377410a5321bb435ba33e331442006cb2a2538d70fb32c968f748af150afe4316b0e926e2f720db6eb823e4f8c28a2417818cff3dfa2e580909dbc4d54479cd9aefff33744f0cb4ee79c1d5d427d1ea97da155e00c3fced8d3d9fc2be9569164fb8c4f66a3256f771ed8a5eab9f03096fe9f2155333a7efb2379bfbf354427375c8405f0d27ee115a8b283e82569f7055ccd4d7c77b073a599382ca94844f3f1db601ab2e9414d725f342af881cfeabda3726386359f1efcd177d1034ce37e450a327aaf840327b0909edc4fe07ab22c87a6633f060196c026ec9ffea085da1eaf8894415822b016a171f1d5bd7db48bc0048468835a38e4fd7d90b50e9d87686ea9a411f9bb0667b5c3e8f7c3b37046c1545f34e2d00c9353be360a9fb91cf8837fe8fd04506c297a4f0046ed08ffab561a583320bf960acb263190cb25b7ad702ed624012525e191f36ef0fdea4f458ec70b1620f45a9a303298b080c271784e6d9ddec71bc22bc0f3284eda955e7c46a3068345a02ed250caaa45337d9de03f05b7a7678addbaf9748979b2f29e225d96bbc0b5067fb918ad8e664e5a4d28719c7a9de7f1c42304784a2c340c93af14e0fc746af1dc34620b5cc389bdb26ed5a232c405d0683f7bdc291b4e4c6ed7fa885c15626e2cd4326b316910525bd987ec65b379733433cd223d7e16c96048e689d3a801a27a431a26e304997e10b30e5c36667d5a4a7c603ae296bb2e289e0d3345affd176dbeab9655a3acdbe225a127e6418c1695967378d8a2ac2fc08ca630465e5f4bf22a16f42420419c533c91648b0a37c12b2972f46f9595cceb857661a33ca1ab45edf37666ac62328e034d624bf45573228567c2f305114deaf9aa62e47ebf9c97987aed0478bf1c3bdf9c727cc45cf6879a353645a8bda67e384eeb7b71c6a043e8dfa3521e2d77f3a5ff1531ec874fe722feb5ba4f83874aae6405a53d42e4821600be6bcddc291442198b44d46399d1a2f9aee3968c1a9d4ea6a0a6ef698bde19bba547be431d969784524fd9d69bf137ebc5b24caedcaf2b4c4220d711deb5ec99e57c6b5d26a0056805c6353fc3dab83a99e0c1b63be602f0ab17a6588eb7aa5a8c49735224e231dd981719730480c847106e1e340c4bc0f251c6a6a0391e9f4f127da470c1a4bfcc350b70043e3123c4ffd3ba0780602b621a82ce9762431aafca6ec6124bd3667667fa07c5fce73ed5b4b7874a2f82934eb137315a2cdcd6f0d1d80f2772b4a3bca0ea2ba79550a4b58850f577afff946d6ee56048ec6d0ff65b7ab8f4576466fa0896ffc2999b50d95e8b2cae1ac7249abe7c474d3e3f0ae1fa2ebefa0275f2b44ee6c40e30c760ebedb5620e096262f3f639e2e85d303821f6765c589457521f3655d98ed8f113e314f96e4bfb65f4b802c2f66c68be34cb9fce5cfc54081997c2cda8b963d200ed672b52fc40058e51b3e366aade56c1fc95a7fd18e2d9f4361d0250ceb8b3c21c967a533f838393f18370bd519368c344e175fed147d052502a530e1b206a31e9df48bdc4b77bd1eaab26e27f3280a1e0df371390f7aeaf68b8f50ccb9377d040689b68494e7255a603041239e494db7492728584c78c7f6307520d64c67d47884497984f652477f16a72e9e2a76061124be6e2a5a1426e42f5c9c63906be7e76a134af2045979fe31449bcc1b46e6187ca6d502bcd7193a1d8025de582c820b60d4382b7c4998952d28efd97fa7aef6626f5369b63f939f395b0c60e9b1fdb209e9cb1f0959b0b394e5d36a23844cfa0ba3052b2255c740271d1513b1ed484d9fe17c2149e9958125e288a76f2b3f99a5b0bc8951a14f477e67a082186749275dc8118c7742a66b380e07e7126311f3af49d8b5810a42ce640d8d1bd458fb1bc72c030cc7f7a62a18b418de4ae9e9b13d79cadee9f3fd8f5438f5a2aa3494835f572947ee71002c732118dcb7f831cc9a2c661e911866763b03fa869b99352ef48ea2fd8359ffe803b6565142acda226f5bfc83887b226ee86945a3271ac6e4319eaedcd16d7abac2aedee2a9c5266bdb231517d5831803f604cefa1a2bc62a06df9f7943e593a74295d43da83e4fe2de36099bc62ae17b92108c6ad8c7ee56775429e7a7aba74fe0f1df084b604d4cf8f6440eb788a14342abba2205e6c8b1caa005f830e7ceecdbe1e108329cdc402ef108e8240b6e7e87d80ce7ec8d0da0cd3c95e9be70594e308acf9de0118e7be863247d42e7155af04a55d8435774164d1c8ab4037b88f6b8fae07d48dae3af4784daa63262d6b69d3bf05c6bd1087ac444f684faf55fec1cde0d764c185c21ae4078e6a7321fa2394b04bb0f2434e7a08b89a79015d21dae244e9d2c19dd61aacd8eb8d61d9df5b39aa27d428ff7b66f66ccbffd6a701fa2551881fbfba403bc6480247959f704c4010bd61f12d5906de87b25942782aee2f0ce5cd848dd8f6fc1b43560efc9941c8d78eff78441c07ebfab7841362f259f3dbe926cef50d64925fbe138c720a1e52f2cf00954d5cb89e9467feb956647a30b4cf0b53d514518c766bfae170af218ab449c15850bb7d4b36b404d2b156a9fe033ce727420e8d53467da936662e851a54b0cd3dfed32e8f3ca62d2007dd8bf09dc13dc656c88293704456938d5260a0221ea6c0e55ac16271f7eb786a708e42af57506856b2f65c5a3305e702e7cde49967a8a9fbd04760c0e5ca294ba679954d7df3ff6122b0eb6ed351b2c73fa1d0fc3b69ac574b3f1673e86c145f8f0f306c6950730fe1116ccd86d7cc534850f19e62e2c64f136e8aa1c1bd5ddadce6f71508a067069dd3a793c282ccfbad2852c781405338a0b45657141040994c0723fcdf6473d1f08ee9b7814152441f7f2c9d92b676e84d4ce8780b1f11fb0b4e771833ad32f5b9a78a1a160ecf9b588a60a074849097527989efd7f8e09fb36d38fc7a59cd86b3b881864454e163b8333d44f68e6e64ffcdcad46fddababa56046587d4d0bdac108ae4c238ab77c695f559af54da3faac0d30bbd4068054ba0514dd28e62795cf9e030be5f8e7d8b6fc2af5836a8e7e704c0bbcd092c4b96cfcc02352db2fa05cd628b9b9275740729e48cdf2758399173fdd2ef47d4631de8022f87106d73809eb5d7dc17cea0395893d5dfae6d5064cf640aebd3471854efd9f6318e36fc948c75d5062a9abe3e7d2541d2adbe59ed13fc6a36441885d342a635e2c825158f5f17d11682b9ab51d887261b84ed55510577584ceed16e09a23bf09e23fc4540774854eccc603deb9a9f1aee80bb6dd2ca801f086c14cf414c2ad456e06a1a5bce391abb0dde1df68da733e08430f5544caa8bfa38ae8178ebb60fc9822e43bf4dd4224f69c3657c41ce604ee98d1395669b109017bb90bb8295ee63a7722eeed855b3ad683e807231751abaed9b4424add97a02c80b4484bf11f748373ca59ced343f0d047fc5821063f3f38e599122754ac51e310022e4903adf91128a4f16785f1c9ac2d203a41e6eb2996267bd955b01ad494854bd5452ec1d30984ba0e2fe40fff7522c1f82511511edbdee55f1b39015152bd74cb4f2df02a48977826e9a80cd77775a4a8d9293bc8fa048aa7c7e55e2d97b374a5a5c7564c298721a656d195a2863f6a91a27ed47b73705ba252af06e794e47309545d750ed67f87a319812de21fe8f7c200bb3e02b9099c08811d3912802e396cb6cbfe28559a98854504e2f37b45d8ba64004ea5aff2715c7b42ca7f33e929b151980717192371ec72f29b0f4a2b2f267abab45c6813ca6472fdb0d4b0bacaaa326c0d2ac90f5a00954997042c287233724b3cb0bccf44482f96f5b8f1893e3c471c763dd21334b14eae1f8e64ea199053c0c4670d92067002773f40c93c11d2a5b52d4e6dd2b20e1b3aa5bc031086159332335f39be9ceccebcfcd269a58f5aa36560944508236590c5a42e9ee8af502ac3e5ae9d03d900da262ae0a4a7237b003c8014e96be6dd8e904c75c4cdb825d031c79044e87a61562878affa2a63a40b0926de5fc83627ae8739790fdde0e7d5505a361134e7a15272ef0271db331bbffe6b9e724a294ef67123f8b023bc78d81f857932dcf3589c0295c907b46cb9f0f706bb5d8eebeb8b8edb31c6476c224080e70cd7fec4be0eab2389be3b0676968de55b2364e50f83239bb39ee23b9dc011610ba7f3747b2a01204e5db83478a00a681db369989cb9f7d253673135ba6a747206080812860dc4ff85bb6907e2e203758aa0d71b75fcae04b444dfe111e4dd1d6867a5c00d178370f5b76b02f1f2196bc392efd96bd9685fd1f63a3ec9a9045f9bc5a2be24ff93d12cdff333271b51a0fe7ae08b10fb50650ade9d0164c41816e7e400505d8ce795a438032b8a5aa4a887bfede9da114802fc6835e68390cd5452a4de5987a1af9f6751b6186f7c5f272407cbaf1d5fd790817c70cbd8b60b0861e30ccf38443c487e5b607a9dc43e5c6c4470f9c7da67f643174c324378644bfd6f775dd1d999a2286a63689d85d9519b21b8d321b38ac0da5d6dfb8072733b576836e74183f209656c7e2ebee9aae4f2c54dcfce8aac5daef5e8d9e3fb6abee7ce956c7f2eb4e4d05f0498cc7526effe9abc562e0cdcc23e8f249cd4a7c43f8535a10fe061f7d8edfed51950a325983184b35b2d0db3f47366733a15b660dc9ac458ed712545965f6e5be5d41e57a277c383a2b1f6d01d43b5c806ddc173f7a112fb920c43867b35801ab6f1d4257410286b102c2880babb66cd82bdf4e23cc72ddd325f5bdbcd2d5e7dd3811d4b4c22138ecb28979f94ef6f33ec381fcf61416dc55994fa11a9b594d5af99b4c18330fdda7cf5d955515369984515246f02b10edd052f2da37df8c9b44e919e92e34a57e4ee6c31d11a596667541daa7f0849bf9915e48949fbea660bd5596d565c3b537879c972a1a455fedbda149cc8e1547f05595d6859125662b412217bc2a1a110063375b4a7dd3d496ca4150b4a50620f9bcb8acc3174da990da40799cf4d948d6c7293f45e62aff96d1af064e91b115e9e9cc935b319b7448ba06e989a4e7cf542ad76cfe40f51190c4d44f9b8cf932372bed1f7fcbff50ce0852211bca2d6d1e2a08b04629bc822e6230e8dc55cf72fc9028fe3892e5304215a4f02482c8e08bea9b3ad1145e236b74f4d26aed3bc149616d4d31416e498d1492467c02577b5c938e76fa1c8b38f248fb856c73a3ff439be19bb1abee98408b48b8a9c684628b50f3ffb967bb76ccac57fba9da16db7efbabddd2056973814b54cedab52dd3b7b31edd4dc4bc14ccecf6b54ec81d50259fbe202361fde38c04ab860d89ecd2b4066f4a9870e2de8afff57688fc7e2a3039e8c919313054dbbf3b7edd4ecdd5d8632e6088715e74a41299f88df1eb7ccf086e9e31469bb4dea7f4572b017129f54d3a2031f72bb53527bc490f3c239c2cf3b496008b6764f47ef661013a62396ba128ee90f5644b65256a6cd97e1c1a0e6c023256e06c668b5109656dd10258a79e989f4cd1d045d0e27e2a7039f698bb1927b1435a0b4cdd0c463a4e1d0db150dfbb61cd4e2b1d9ce996b54f45a55b5a5c42100beef40e62928f1c152b53ea5560b5515a84614133770b1835166d64b248f0f0068bc6168decf3f81a2d2de7c57823950135e9ea19be4de80cee17ebcf9abd1dacedcf7983f35e15d0357c1ac4cdf55e0d97434084ee6f399dda053929cf11297237775190e9a080a83d97b58c4e59f32c6dd4be94850510613292dd9b4bbfe4956cdfdb4e819fb3d7dce263a2d69ad73c88b278f0080b36a48666aba18350f447ec9f11ce02beafe4fd4986aa97bf450914bea0f9c36e37dde23a97142850e7c398f0aa08d52e15a6ce138ad354978294af22535da6833d4e4e60225a07bd078e23f611b054790fd62bca32ef286c0ad648924d589605ce516c48611c66a90ccb2a1c0c60a1d79bb91f4b3f5effe5370bda651b7219504f4f8a983c9c965ba034d2ec8111e3c1924305e010bdb93d926c21b01e6a48ae80ce3267e19d8691fd99adbe3c3108482a97004020b051b62151a7f4bfc6a2bbd0301eb43340cb1b90c80152af0f87c78e8e23b1b82695c35d96ee2c8475508347eedfcc83321fa43792974b9aca09ce123ae80ac96345ac04ac649263ab93f02058307df22f5cec806ea3781b7f732507c970dc9425107c87d67420776f556597fc9a83d98e998f4a890107e8bc9895adf8c1bdea195aaec70c235321b044442cc3cd9aa30156595b61df7a3aa281b83771631c4c139706544712a381bba1092d3aa8159aec5b5a04bdf575b24e19ca5fc9ca86734175c2c859121a3afed18be3a5fd58ca86b3437da2cf23461c6e38a5b14e2d42b181eb2af0a39349cab12ec2b79f25f48963d30a4112c2c60e75e3134ea8f53b3a35e81ba0116be1ba2a342e09dd672ac96ef6618d684667ad0c9582bd041e5227558c70c8b761cad196ea7079e37f62eff7405c4ce94adce06ad25a096e6630d9680069a4518fac54865d4c7715fd64d61d7564e1f8a2ce5d540c52d14f376ff9f564b16c17850feb8e31bd0caa4c5f98177f0fe894c1ac362e84564d11ee28d644928eecdde3de0bca531ee5336da7be280f602978499687081b9b31530c4c12654aab09754d6a08510c34110283a9e1bf33327ef919426fda0d7d5071575ffb1a9066d01f9f91da40ea44dad1a0a55b9b2fab4a075b620fc95e177531cccf569d1cbab8552e56484fd7379685f4beef9faf872abc19238e0e8e3fbe2f2fb73a96e4969680210213f430c29e7d0e07a5010d4f2675c697330224734e4baee276279bccbc1ce8af080261a543ab633121345ed84c0f3c2e9b0d6841b27c3c91d8db0d0076a0408bf09bb2318dc691846e9b2aac0910388f825fb7640fdb408db50dec72de0de58ebf1034a8cca074d226f49b32282c4239905afad8158e11085ae6cc6d68ac60e42074ccbdbbb2278ebe50412a9199b5765b9d060f2802ddb07074ca6409311f953483c25582ab1cab19d6f4d002040d76804d2cd6b6612d4e8d20d79ed359a75df13763856071577c904008b45bf1b3bff9f7479e35797f4ab5d23638544311e4ad0c7b2e2981b71af83d758e87886df2195229947ff8dfb48728f57ab572c6328d90e1cadcbc92bb1e7a6f201cdc9e38deda5b2e3ddaad4db7655f1f694b3c60055eb585ae13581038ca8c7301e5459ab781ea06f1d31cae7e672dae663633b83c8c3ea09e8c15c552f52886969f5aed4e601374dbed4ef9b685cd422c8102601314081fb5f0f7a5725b8a8f943654da366f939fd30236da6ffbbfbbb79429b713dc13a512387c7e4565c3071b55d9a6c19341d4b8e2b7fca417732adb643e7c53876cb3f1e19b54a60f3d04718625e9e50cc7cf31ce72e4c8f13894661ec7e39841c1f133a3cc467e5fd9438d2b8e1376b12133759099543ee49a0cd320c4329afc38643424e499528e9f79259ac7f1348fe3593964387c5c60c9c8dc908def31191b366cf8f2b37e0619e5cc339c84b3d18be3849f434646791793ca9c2ac2f47997f171bcaf958346e63864647e868d9ff13468d020c999548d08737c1916eb6dd8b021ab31ca66bc8d1a339e354366d6f28d52e30c55e10c635559a30267d86ab5c2903513dd731661c60f92c9d1073b7d7ef5e34361d8438ae7c7b08f791862e10fec639e8c3dcc93b08f7925d8c7cc94620f73baf86cbdd83265352bcc714ad80a67be1a11e6eb57f0e212df258b72b658e293e20709a7b0161106a573e0a3d972f5aa37aedcd022deb0cab63cf3feb0fc43f3fe268d0c96675a2d1b573646d80d992a2c9f7b91a9225345a68a4c15992a376c5cd9b8b2c1858d2d36c2b071021b5d6c78f12c343e9a2b54983846d41671c7c9bed699b1e36452d6b322f3d28d276e64c933942432bf9cf9ca99a7c18167687ee695687e66e66974d0bc8e70cc356a68f0bef0b46143ecf13c689ec836f169aa641bcd956ccb31e367b6c854b921fe0d256e54b961c5f4e214bd904ff35186669ee695669ea6c7cfcc946678fccc2cca1059ba64e18cf7d9b89ad9e238228df7b9971ba1ec861237aae41af15fb21b56728d986139427c24a5c7f3f8b27685293a99f91def84e675b0e000251eafe359e831a3217fc7cffc8e6781c78c269c91b063a6b46336f33a9e86e65def72c9c8f7182c0cc3d82bca99d5a55c23ba66689e95568e233e8dac0cad3065beb4f25b95f087e6459235e53734f8902c4b3164c588625235224cf27de49415bc98a70be6a39c19c6052baf5c9e617c2b9c7946565e9dbef0bc316391536e58ddb86135e3c529be4c0c29152323a3a2728d2825d78819071c7ae821889c677e02139351590ae665c437ac68aaf88dfc10263f39a5bcf2d6ec35f3f5b85a829cd15491c2f4b997272852d916d6906d53d94605fb5056a3a645ad0a73e669de474ed1b19a62d47adf9b6bc4a7a912da883f23f3d086e6ca29ce6ca94d71c3ea14bfbccab6ccc5966c0b23dbf209b2ad4be92544d27a5f9eb548cf2ec2907eb664a23bc6220c3ec9190ca5d97223ff07a6a8aa7c264bbdcf908d53b2d28962b122aff75749e3956828cd988d53a1cdf81edac0c0601f939131a298f9a5697e96d58830f3afe0e5c43031a22cca8963600f23f3fd643961e5c364aed16aac325a1561e656cb3c5b3568c8f0cc17241ca560a439db02935d5e4c2b1798e5c34ca91acc295387d7c7589551d92625dba4ca1aa20ce12fcb8729c51e36f3f0431989ff35f3c53c20c6ca71b26be62ba7caa8524a2955d610c2caa8192de5eb0ae72e33df28e525145badd6fbcaa80f65e39351ce59695ab9f2288567317d9e05e31cd63850d36262588b158629be3eca50cbf5af5772fdab3563cd945cfffa976b16656804a31685e9f32c3aa2307b45478a8edc3fdbf299ff6b322b8f2f28336cac828981c744853faecf2e9fab8b6fa4fad64719a2717f28ad77cd7c3f5b945eae7fcd7cc871cd0566eb5def7379097f7c287f8c1498aa2ed92686cb8b8b8c18e02ad6cc0713c499df27c600a6ca71b2ab8bab8b4b0c9797ec8aa91213508e8ff18d1c76e9c245e4c265e4c2850bb9650b172e5cb870d1718a5cb818a9111685e1c8c240ed8bda17b52f6a5fd4bea87d51fba2f645ed8bda17b52f6a5f88400421f0620422f05ca35685493eeb7d315558e496181a61f84d7c1a2420e20c636884e138614c9533c42116b5384ef82e15d698a93af1971d809df95f5562aebcaabcaabcaabcaab43e8797f067940a6df04f4d5151cd58c184e138f87b5060922047d5951c4190e0c435ba9c384795e3e0578d2e27165f2e3c9ba9122550db62a4c21848a20523556d8b0f04490549054d95a5931f0a4d953887e4287e90fc640ec9f0431c230a3f8fe3c8cae3487e903c86e42883e51a1765d8c3700c43320cc5d0c92a56ae963862253131b2fc3f574136c45cae876999af9898d9973fd3325b31b39898b14a88714a16e3cf9598ffd57a568cc8253361ada91c236ab5605998415338f664064ae780035e3a50e5d439bd8a0e489d5d58b1603eb2624aca13632cc3e23f09bf8c38483612da01e7c85e7cc13c767d28b4c3933123af333613da2156e41c634478bc926bc227724d386629f3b3624418872216a610158e8d303c82e131ef5f3aa0088fb32c314bac324e8536ac93f538637c25941253c439f09910d57b8ca887e3e4b1c600d3d703d6c36fde22cf5a1167172ee3c3d45c608a1ffe2bea25e535feab866cc33fbe6b8adff2fbfc4af42292916daea8161ef2f047fc79bd6633d7bf64e5e7d8cce722e21cdf875d531c67ac3a6133df28354ac935e3b3645e922c9fe885152b82bf9c6559be4ff4929f7ce5972cca9959a4e8e51c5f53e10ccaa8da0bccd712fee3e16bcb8bcb2b8cd7095ef9eac58133bf34f0aa408d395659952cd94b0c17e345c638a54b11a66fb43a5f5dc29fd7550f0ee0d0c0ab022d2fa7cfac19ad548951a30248fc240e42684a68871194cec1cc0627c014df5773b2484c7e7e1f346476b5b0cbd59279aec91fa8828b53767a155554394b0cf3571431db5eb320724a6d8ae98b316be0f818bff919fe383e4b168631a21a1126ecf3bf64bed78f1e2bf27ad797b228e78b9cbdb46431b20e2c84a8c56f3daa58b9f2045512260b462d6154d9fa9c6731a295df780449054d9d415782a482a6baa8994b28233fcbc41cc2c2587e51145b201fc7c89cf13986a310e1f865cea218be280bf30b51e550263415231263392b6185e113d699a388d8439830939874c203529ef0fd5c9dbe573c499f3a4a89a2288aa3388a631e49f1b32c7c310749651028f101aab3cce5d3196631cb82a642317b08e38b0281d27194e263ec18635c53a3943f876198652de42861ce0728c5c7f9c59c737e31677214c95114b318e65ccb398759cc39ccf9c59cc31fc91f5f891c5b2c31bf98b39859f945f2c76fc594c819105068d38ad5b298f9c59cc5fca2288a39b38298c9383849e220b0d8827df9394643028c4aa908c6a1582467b155240c479345ca8ab05a2ed68b9f63462c3ce4b1da1326f9aae23839b4b26a71612585e91babb45a5bc629b060c182050b162c58b060c182050b162c58b060c182050b162c58b060c182050b162c58b060c182c5799ce4bb58c56fa34f5ce2c4f959e450e938cc304f98ae2c8e934306b06a5398bef18913e7af4d61b64ecc92592d517a2e7ba630f1c3724d01e314bfb1cefca3162bc6ab6ce3228b32f101b51698becf9ffff38f5bb8f098c0c5390cc3aa2a5114c7711c499224c99a132c168be544cd892e5d5a4e98f8bd9533911866d613a57350fe9114186030c07e9c0a6dca1811f79a9a324682bbcd3883db8826ec1de7702c129be5588d091336a32142be8761187e089bc13cec4b180c068395ef30b01f65e43956c936df68459eb0d2719ce4150993953f5a85ae16cbaafc5cfeab7c57f9adf259ef1baf6225ac24227e391babe41afce528f3f5387d64fe71b472e20f65314cce604a092a54a850a142850a152a54a850a142850a152a54a850a142850a152a54a850a142850a152a54a85079184c9c99f9c6872962fec8549ae2cc57c2647ec2509561f8129f46cc8be3cc77e35b486888b1b7f1e2cc57e35bb9268c297ffc524696e56b20f3a2a964fcb208fe022396b2f2636f7e58ce609e1467fcf83232df0a612081797146638e321a1a7346a47cd8c3be7c998795a20ff6ac3236fe8c59eccb1953eccd1993063f9b6960a4c1cc6fccdef0a4f1e397339f8d995863f68667cc8f3466325f0ba708fbf2cb52062bc5d214611ef6658cf8e3fb6f788ae5986de6eb612f8a62cd03a61896ff82bd287bfd18cec87c292323f33846447c19d37c7366ce7853363e8c2cf6309987f9191f0e989799f946aa1366f682cd985e5fbe663ed84b2c5ffc51e61ba9ced1876394c1fc48d5844f9967c1664ce297e50a619cea884f998719613f9aa62c9f30325fcc4c1da960b2d257be1be55c218c1a044cf17d231591509495ef7a18590d0226aca43a63b117b04a50cb0a6198612800118ce0c454a1004420822c422eacb8a8e2628a8ba898d2c5e5ac76444ced889a07ba78a04b4c8c28c666b1982ce6455124aa1d618a628c07b6d48e20aa1d513b02a68c9a046a11a82d51ab42047f396994ca7f3d0c58ccb9159a188fe2935f449c8d52a314c639144792f5047f1965352b337fe8aa59bd8ac617bfc838463973ebc72fd29ad1b4def534e347395d33b73523b283fc714664478873188aa2388ee348922449b2582c168b5543c2cc1f6ed9d2a50b1266b9e32467a2954fb4f29948d48cd8428c3384d3abd0e2ea2c637c7c1b165b4224995b38f3fba29ce318922fcac62789c65164e11c819c31ce3197cc375a953099ccc33c0ce6c77c08f34a32af877925f3f5315f6251cc632b24cd227ec2602c18f263f80c6346b1d9ab4684f99af95c9f5dfffa20a13a5ae528e1c7c860c219cef05b10a3bc72f84519c6d41c108ae2388e2449922c168bc5aa39a0e60031c78a8c9f3f5634ce32ce312372cc62cea12cca88595aaca87c787dd054d0944bf6b3e51459ac99efe74a4a4a4a2aa88620a9201b849852f59a7a519de302a4826c588094540d5356e30a4a2b2cce9e84a8728d1055ae61b130cec92fca847438c52745a1b0c4302c13da01e7885ccef1b525d78ca11825fc590b676d0833bfcfcf51fc560fa595951255ac7a2881307d6316a9522a4b2995258b0fa56daad421db481fcaaa6c2bcb29a5114253423b084d91e1a58cf24246195595c54b1629b45871c5c596322adbc2aff5e01aabb278c92285162baeb8d8e243962c5268b1e28a8b2d423b08f9504a79c9525279c922c51519a554594396524a6807211db25859596511daa1b42aad942355b6893ffa706298c731570ccf6a9866364089cb6f85e1d77e307d6355f8f3aad20aab54bdaad40e6032b97e2cdff541f2abcacbcaebcaeb896c235f52bcb4645bd9faf0afaeb868952f29d70709493c7b19916ba6e41aabd79497112fab6c63e19755cd00f85de3cc17a5860093f5fe9a7200139fbeb16a0c7f9c321a61a544b655c936d8e9c3a74f8793fc2aa6bca664db2b4bb6f95e53c6aaaa33fc5188f05f46bcaa649b5751859433fc9795fc83877caf2aaed993f0cb97aa707cb95e7166e6737de3ac45fa6b0b4c06160b06cb31180c46041b9f050323638e3131b35a1964d4192a21e28499f9a29c30cf9285368461ec6164b120a9201b60415261d038b24211060bb78c5fd60c60326133dbde173425c26a44f1566ba4f2e1243f48c6679054cca83cc3998c2ca64a6883799cad87d9e2384435264c9c051127b68ae271c668e0c4b3128cd317a301cce3f458915188718bdf6a65980ec689df655a599d6489652f12cb44d3eaf48d5467eb4921666355aee971b6bec7497e8b8cb9621542998db2f1c72749724b2cc35c50925fd67a3099583f9ee478b29efc56141cd9360a5189b01a51ce512cadc8c7ae28a3d014eb3f9c290967be28e3b803d518ca5c98c56279eb85a646511cbfc4e2cc952e2789c571d67a65711cf283e4da044c92bc726228b3202129cac417838451ced1ab5a639864d0948c4c9054900d4153e6b364e6cc1725c88620293f71d054900d3466c47c6b562bc314adce201b4a2282a438e86280992b335a66b2cc5c054dcd3c3123c58c153357826cd0c2847d4d0bf13dc67c808c14f695449ce28da9280eb6788c088f13bbde6763090ea41c479cea9125b4117f86cc439b58f93ef3013cba388e7845ea14bf7601d337ce5c39451f07be1e5a4e51ca297e79a5c715073348a941071f84b072e54ad054900d545553acaa9ce273d0053603cfb60e6439fff42ab49071965e6618141c251361a24cb2be1563cacc60cd94cc9031637c51f0b75a3b50655beb7190308c11c13c4b06d32a3ddb84a6dc4ff285a85a32cf3966549e61c982b5320b8606e3194d97f0a76c955b68baf87a1091e4630fb49caf45162d8c384b0cabf56032b57e1c4f1d514fe325dbc83c13838c6cc3345d6a5726ec6b57a6af9499c2adcc92a932e3cb2de1cf8d2b260e5f8fd3576ef1c1a44e71c6cc83298e23fe159999af8739f3e17039cc8d2ba7e8bb11c67b711cf165a6ca17cb67cd7c3df2cc87c365a61ecfa64e91a64b9553fc1a064cdfb8a5cb2996563e06a7f82f9a2e1e10718a5f5ac962294569c5297261c5e96b85259499b34821aa8fc2f27156a57565cc82c5f7408b79658ab1582c2692b198e8fa58cc28238a315148089919a2187b99989711678832339f1015cbb432655656e20bed20b483d00e423b88e418575b4e8cc3e955642185cf63333326f631313131b18f89897d0c938ffcd6b770c6c89818f18cb162b1568c8b1c59b0d8c7624c33c634633e4818137b1913cc7b8c4c4c4cec67c4fc8c98697e902c0303f331e68c19b1f17d3364bec532636acc781a3364622cd18489f99818a9188c2cc8069c139b8932871929cfd7cc47be2bff381ba7c008c3308419c7cf423bc0e4fcca303139bf1e06f63039c30851598556d92a26b3b2ca2fb483d00e423b08ed305a9155b4f0e104e1f42aaeba903132261febc56f6164c166e3181b3f263e095b3fe3e382eb15c26231582c06fb2021ecf532a612060383bd3e06f631af272113fef23da6a48c817dcccc1734357e0c8cccc3c4c4bc8c2ce6cd570cdfe7c21ee6535be197b299d89af958a718cea658429464283425a4839010423a08f92044256b912fabf2359bf9a2b058423be0305f987c01eccb320cc9307c0fc370c60ac330c427ab2566162b7f9014cb17e403d3f89edf95737e312462cd9e84277e7f676121c6c9737a15585cc9c20a2b0b23b290c262b16a659833f94b9827dfc794673eec7a6101ee4ddcccc1aa6930c743dba1f1e0b0bb35c5d9aad6b92b2f75dddeab8fd8dd6a04d5b2af46578d44d5ed062a51f5af6593b8ae56850ce9e95e0d7453d485d247ee2e814de2b63474f75cdd5b83fb97be6bb0c561020e273886f8d2df96fed5bf5ffdba8fee17186c5b5688bbefe8a85ad53714dcd440c86934da07ef566f5ebd5bdebf6c92a7b9af6aad0675aad67def4d9d9eb7a6e81afcddeadf4d6eea6eda2a4ebbab6edf9be300601ebcc3e38e61f0e8ee3e376ffcdc6d697ab7af9bb379ba655fd7a666dd2a6da76ef77ff87834fdb92dade7ea5e4e0e90ce27a86797cbd9e1d13a3f3dba1adad35303d2d93f38fbd5edebe6826e8efb065926ddeee972de94d77375ef767b5b16080cbab8fdd3edce8611ee6fcbf6dc67d9b7e7eadedfb6dbeea6dd3e9aaaba6935fa717725376d14b92d0d5df7fbb45dc8dd5570b3c695efd7dd2baedd6f8afbb1ed0ea7db403747b79b727a76280f90deedfcf0f4689f5d6ec74717f463dba9f5fcf0e476745ab7fbd139a09d8e8eced1bd6eb0cda961e5c6dd954d5fdd56895c1698c4dd4300c1bddd68dac4b647fad543dc76af6bda046ad5485856c9c3a61c685850a5e6221316102da054cecaa2c80b9c6802721c00f6c503121d70d60baa8c0040a3f9e25f4ee0e30e34fc20b1b380e31f624d7920c2f34f088911a06a7cdc21bc408f073e3e9a50c410142079771b1608348400c49d490a2896a4a00bc7178832950429c559391a908120c850e2a2ab023ae02ad0e4ac18b24ca120832f4e9250ea1f59beb8b3747ead38f16c82225a932a82dc675044540a414a30be6d035f44e08bb38232a0abf2c1848f602460095fb1713ca546c8c6da848748a0a051c293023c07f9fd8081df1727dd1ab0048172f2908a2157c228240f8f8e9a40f8c00a4e1e40070f925841099eabf0148964ec70160c051822ea0810ce52810a3e34015b72bf620164dc80e30727693e1801a6cacac6575eaaeabd42eabe41164a1f11c18cd171ef12b5e4ac1fcb45177f606755617494c12c8fc1e30d0c835b3ce28f873a883c58b9d51ac51063d720d33c3b264114013b4b68f418123bc6781c6bb9cc8e310e7311c62c9c310ec91532ce98956f20614c6296671ca38599246b5e8fc59cc5292c629c5fd80519bb7cc4620e37c6be8c716e2dc934196752ccd8041963326312b0061887b975813164c1e01b18db6885f8079c319931ce210e67f1e5aa651806d2c22307e10fccca59dc095f39639b181b0680b85312098fb28b9e1780c5b0461c330e7d2c00fe027b76d3341d3b1671c629c0ad0c02c679cc39c4b807e7e01686e59c3329047f90b398b14e482b73b43cc0b1193399073ce61c58c421f67c94ff8545cf29e9bf840710baec02d88364208798c4220bc770489573eb1662a010e72c7e86b96af088314b47dce9a183f38b6c658c3166617cc3397b2024b12b8b61139ee520c6210e769519b7583807c93c328fbc23f4d0710c2673c631d10cc59c23e78c492ceeb08b035c8e198f18e607638c314d366295ae8c732d182b6312700be330631117e11d3928ebc86566e5118738e35c669688c331c4f80787b806bb300c632c8479e457c8c22d11678c6f189337720d2c663c8a38289738cc3c7018e62c661107e58c67f20bbb704bcc18e31bde915fac108b2d1163fcf31807082d1f6108230eb10b8b38b0cb752b4fa0e181371071ce64666d6cdad0298b30ce38638c719ce54037210aee52420afc4b08220c2d25c8b89ca089fee288255e6c7125d9169e1404077692a6c29182b0f203928c55424210be2e47ba16e448103c5a50a475e8040581232748c334e0c78b18073fb217b6798167605bfa0284edcb70dad2183c7096a6e49b2d43b037af03420855372ae8b1f4b01eafc10cafa291822f8d38f88b01700fddddddbdc66d69fad5288e94f2ea208a28968be512615a36962bdc01537221c9ac5543be5a40222c068b3012273a85b15046bc0a8001a24898cc728937b11d404744bd98c050f106c6855ca319978898f46181a81f4786c804208b2ff1880d0ca246f002085a5cc0e34da6471017b23813e200b10cc818c2155124d9e3488a8d982118c2222ec81095c3d50456035663052236802df7f582281fafabc812014c962b8c8d4ce20c35b8d01261c81d38a6c7053012e002010b292d2f3cc0e4720c22c30a128dec13ac5ea031d6bc88bc3c205110416005bdb04410455e28e2861ba68e9498ee88009e882a83bb030420c6138399e8088558396c2ebc5841e4ab0513fe18150977bc88bc20eac5328a25b58a688888bc5c45b024d4c712511a8837b61779a311235f25d14c8521a25ead221e391aa2c6d01c314902f91a854810800c9541d707ad22f2c5ca111be2e2404ce2d2a0860e0f666e80d00ac10892072ebc5a34564face7c7e88805135933e32db4b960e2071f84e562bdc49cd1880814048f34a16cbca1e36ae9c22022211cadd70dd78f0b429294b5568dc881c8015944140830e290318491878b035147cc4eb8e385285a0e0e77218e188e0098a9908313ec8ac4115aae58215184100288c2c293cbb4dbba25155030c10993254947474628ba01f5d478d0c1062f6600035f547002304400d2c16c2607ab6a0a29183004094422dc76d88095e4135626e080290c1042014154fdb0a4e6831cda010ca094f4850c317226a8200514c0b13204b821dd01117ab1a6e030f5e3d2e28a8829b7209b0635ca578ba5834602114061a34a928c60448408081b669061c6169f269038a288284d98fcd870d41039a0a5054c80fcb0b958e4036298401209d821071a0080002080b841491220421fc0f018a10cb217b1cba72087218291bd18b7e02d7216190bd715302b32073215d94a7e40884466127d9803dca3b563d411cb9135c836700d191ae38c2c834d112697e32bbb5aadcc628d59c421c6a4c762429ad08928710051e2111284c72c17b8600e11358e3096075bb82006b1236a0013a201a400a2c423ae1b2f1d380210e52a413421e451be24ac45a40544be42211caf318aa85695a81a28b8e0fa097f6001ac8ba8f14bd4089bc1220af6ce9c8872e180c92ca13167d441ca4699838717c41aa2626431b2104726a78543e6cb0b63082d20968f3026f4d112222a44b29343be4823a2c2929c116fca9d108b30f1e645640c8155023943be481fac06ac860b87eb06ab8470062f9033a1d50b6396a80e7ce2ef711dec106fb0185246c4214e28443c826940665613160d2bc96803069365388ab0300c73c80a5ba1eba5430c594b5833628e5e322c9285439658137344dcca11d2b8e0ea3136200ac60817644891a635e405b18b1766b8f72f6580a1c51507b468204b909784c314d190083f7ed8550529c8f9c08c69b14e304149922f1bb0a2033d6ed0a1e2218801bac8820a2c149000120cc8a179b043f0bdc16d5db0822e5c5c610506a844694105144c70a2244911223d786870c30a13519038a20516703b74b06b0f982e40f0e417f5a50c18bc00851334b8219305960c5480021288c0036eb061069c0a25345192c4888a0d64608a26a88a3cb005155830400127861082061802b0822e595c410127ac2421256aa3464432b12e22e0d2850630c0c40494200287016c14a7020a2734312212bad9c8c4a8d880067e38000e5236aac208464488846e3031d145c0858a0d4091012626a0c4038820800d525218c1e887d0eda7b663334346f42f22e00202283490010c303181071041001c6c1880942f7029a020420e8d8c318e188981f9808ea91a6870d244270103a001eac901ae2f61197906e3185906ad06884564223203304fde097530adc4094308ff468fac03d3b066c21c1887a801bee1a2316346688a313018333603cb51ea78c9b8582d929549721cc31a779f717f992e75ab39fdae690bccd928ab4bd7e00e77cc720521dd74cd305d2fb3c5c5e66bfe379aba6bfa6efa6ffa4250ad6e754ff73afd5612ad23cc5695fbd2f3d674f79cdee67ff8f89a1f3e7e4d793bd7029aadedbef43bf6ddf45fa85c0a341aed04a322276774dc47a1d9f881bbf77093b5c5dd96b6b6fb7d3530a77d437093f583c9925ad3d7dde8ffcbaeef6e167d9b9aad6b83ac6bb27c2c2547d7fdea774d560d5db7c9ca6497efbdbc75abbf63ff5b76f7b6fa757ff356ddf6c82b6e4b6b7bbadffee56c5daabaee728187bb6b05f7edaa7ab783edebee33ad168d74e21fb8bb07eedec1631104ed3c906fdba00e84a020a0a09f205b904f502da82788276827482728080808e807c806e4035403ea01e201da01d2010afa01faf9f9b1fdf8fcd47e7a7e787e767e747e826c40b61f9bcde663abd97a6c3cb61d9b8e2dc807c8e7c7c7e6e3e353f3e9f1e1f1d9f1d1f109aa01d57e6ab69a4fad56eba9f1d4766a3ab5a01ea09e9f1e5b8f4f4fada7a787a767a747a727880788e787c7c6e3c353e3e9e1e1e1d9e1d1e109da01daf9d9b1edf8ecd4767a76787676767476827480747e746c3a3e3a359d1e1d1e9d1d1d1d9d9cce93ce76771a37471deeb6b45f70cd797fbbddb2ba144e78ddd4bc6a5878161e00ff37af9b1700feffdf3770f799bb8b2f5efd4d89251b4cf10511e8ee39dc149bb8fbdf9696fef6ba595afadbbbdbeaff6bc9e6edf1d2f6ffdd6db566e96fe85653bd73f58f1f3f64eeeee95efd93bd29cac4ff9b0f3ef8573fba9974e9aa815edc6fe115eefe6e8654fcddf40de6fedd74f7de3788a465db96c8896d5b76c80ca7dc69b4b0c9dd596e86ac190e719bf7e1e3776a7eaf6c92a55fd9f4052a71ddfdc8cdec85bbbb2d2d97aa6fcbe6b4bbdb70a7e1ee1cb88b326238630755ddf44b6d4bb4f4abfe95fd259b1aa06e9dd6e0edf6aa4177232d3dba5555ebdcdd74f71877877198bb97d807774772136b31b11479e6fe37175d836cee77ecbf41f6d917f7a62ad0755b9a0ead46dbc171f70b95cbe9e101d2eda01c1a14c463d3e9f60ece8fd5d172c1de9bf2a0dad74d79bbcd6179add6e5a85ac7aa39ad1bccf91f3ededd5fde9ae126d6c15dd50bd0c04dace4ee35dcc441fc47bbdd3575f798fb0805d7705b1acafe7274bbc9742f5fb8fb0d375d0a775fb7fa3e7ee71e55836ccf7d9b77d39a7fdae89afe6e627fbbc501b593ebee2ef78cba3b0e375d08b7a5bdbf1de9eed83d7bcbb6edfe553fbad736f8eef657367df5bbeedf5681bff4df54b7eff3d6f47df5af6577af33e9ee3adc741cee2ee3a6d7dcdd895577ed6e621fea4d751aa8756e4bd3ed269c936e7374ad9ba3635555e740b5bacda5aabb8fa1e8ee99c61756a55f898a3a3da431021dca982c7f89f1508c8516f2b766514330d8bb582ff15f315996223eecb18c05d7e3ec8abd4b16830843e1bf6452a4f050f831883024e252e643a52c16fb57f95278e8250546820fb9b08c85d763177e16cacf43af5216a321c2ba7232b1660c80ca4fce180015be08a7096b7dd92a6552ca97faa1d7b76452621fc30fbd1ec7665133292cb410fe6b16154a89e187621fc64c30a3a26232165a08bf9c45859f3f540a3fbf645278e8f52e99141e727d299312830843af97c243e59759167b2c837d1823a22141fcd89b103eec5b3229ae26518662f85978bd8c85f225231f0fd560603ef12f2739636200547e0640892f7e7ea5f0287624bec78ec20f923fcce412594a4a8a94388ba115fe4081d242fef05bc02f3e1407409122f543e3c7f0430e5072fdf82dcca0c4f043e30c4a0be4bf66ae590bad99d40fe11f9f356b81c43fcec2f76f81c46b598b41cb6b64d46430beb7ccd0667c1f29a6f8ef83697cd117651625c4464a7f12aa2d292fb880f367d1fd86a988a2283ec69809fba0f8f08f10857c114a53fef10d7ffc20e1188e50468872b6583351c634ce9e40395d2439be380b7df9314bc6d482228a61188aa1288a62188e327f1c3312937f24a2748c7128cbb227a1fb38f389ed2b3e396bca4ffe28ca9ee03899f2ec0d4ff2fd7ccd9e4029c267eb5db327504ed68c89244999387b129e4cfe6e24e6cc515a383751d6c630f397652bfc19cab218700ca21429073c11e1648a419c39e0890867f81837091f972c6f9dd845a129a11dc4f109d97865aa4665e21fb5843fa315ae727ace213ec32c1badf84f852cf8a2ca0b1d4e17eb5def9fbde5ac9993ad6705d56043d0944b361a916ba6e49a307b8c4814ab38ce68956bc2aff160e6c73e4a651bff70ea8a289ee2ff5c853ff83dfb6c1c75c83557ba1c651baeb28280d7e0f7a567fef15b331ff0ccefebadb0e5cc335f58fb80e9c33ee49a271a22e1493e945c73e4858b5cd82ae3c710c0b86a744171e2e4ea74926b70a9e5a5e574c97c4ec6596ae02cb79c384bd191a36cc3277612da601b5680d7d870551ea5c6fc231599b3f8ae8e62fe28594663943ffb13e2670eca2c5a3207b906565e9df89d54655ba901c70a28a538f1e3328c135f29b3380ece1c7070e2137f59d3c27c75711cfcb5294cfc3ef2ca8967f8cb2e2faa98ab0f428eacb135bac6a7711c635fc66440b50f00f97872b997967b61b917d2bd54490102d308d2ca8ab4b2ca02e363245ba4d52885ab306915b658e28fa4d599b7545db5b21906084c9fccd49633ff0d2abf85a37bf9537455e4af99f7f0f59099bac23253e596d90d2a2964a6cefc4f3001d4d8b20508a8a685e9218be55381b830f1493aa903e959c65951ab659a3870f808f1873371cb1983336b9167315bba307d6616922a4b8c17afda1437ad56abd53a811752b6b400b3a08a2d5c6230519ec1a65aae1a9886cce73f9af13ed7629a2d9f49a335c3868dd1c66863b421835d993e73cab49265732adbb23a434d21ce70ca19be87e6d454f8a655b6b96361ba125d5c215931221b33d894158df7f0c7ccdfca3fa30b519eb51a60cabcafd525fcc143648c23b6311b471bb327ac28a518a5171fb187306597a96c0b6b59982fb66032397094256ec95ed83467cc623464a6cc679e38fe2467bed1cb19be4cd9c5c638e6318f7926838de1933125acf7a955e490796833d674720d14262c4a8c6cf302930293cab6a96ca319c32791b0de97dfc68cfc8cc7c7f1396e601c391ec72be5781c383e470e73eaa4b2e16b9d30d87b4c2ce66338727c3c3c3c3c3c3c3c3c3b3b3b3b3b3b3b3b3a3a3a3a3a3a3a3a3a3a3b41414141414141414141404040404040404040403f3f3f3f3f3f3f3f3f3f369bcd66b3d96c369b8f8f8f8f8f8f8f8f8f4fad56abd56ab55aadd6d3d3d3d3d3d3d3d3d3c3c3c3c3c3c3c3c3c3c3b3b3b3b3b3b3b3b3b3a3b31304f463f3a9f5f0ec2051f5dbb2bf9f91bbdfb8f3dc5ca14ab73b4797b67bdd3c287555f20bfed23545e2c4aa7be8ddae7b124155b648c856731a5d37f089e86d77ef55724409aa87dcb110129bc4bdb9e320770cb4b229d29bfed223ed86d2477edcb1cdc7ddb776511d5112919047f59010b7fd05dd95d5b92f30b8919c76c15fd3575af70a6cbb7b83b927f26ed7d5a8caf67e65bfddadee776ea728fb3efa6e8aeed549035da855ff9e6595da0da5d557f5b9d9774d1f18646259f5d774f72fbb0a599dd895695d62e3c4aab9d74fe3d176cdbb7befa034f0d9f757a3dd06dfe04ee7bef7b669eeb5eeb93ff675833adaaf5aef9c5669e87e35bbba2ff46efacf5bd39597baee09dc718f3be631dd2774dc5f76c5b4fd6393dc318ee31b771c823b06c1c695522032c12809378468881221498870484a909088704447960c518204e7e444a25d54b7d7cdc42a21010a02f9edd768088f5551a3bda6bc9548484be38e1ff7705d16b8e2802eba5b1d7ce2e27e4177136d55ffd235fd3d71d9d438cdf114d68b7b0ee7ba1a886360de55afe92f6d756e8afe2fb86ed7dd634c6eb2402af097eaf40e27e62aba7fae7672d922de9a3e6dffefd72ceeeeeba64f44fad543de75b7bbb71ab93b09c20083b7a628abdb43d08da4ea571371f71188c0ddd19465a25d54d70271dddda2a8c63d6dd4a8ddfb084d71eddec0146a3eb69e9eda8f87bb6f209412d436c2be40a856c7d3831ae9ce1645f56ec7de6ebb6d59a77727a9d982aa07eee866aac10786077b3a1648b4737a88081213743d9dd228a877efb4db7375abdb4ebf6bb6e0efd21762d2b1aaee55553b3dfb3e9476fafd63ddd7ea6e6bb6e0bbe9e7b8ee5ebfa763815bf7b2aa4b34740ebd4ffb5d59216f50c96a440489c9cee9a11d262dfb02d356e7ee21bc74b7b242dea79d8408121317755f564d3a9d5855d536355bf06f6ab6608d6e4db57a04d5ead7dd3ca856ffd2f6880812931dcbb640dcae6d52420489c91b04be4116b76b9b5e564d7a59d5bdddda0d0cba3c293ba7733837e8aedb5d59dd0e04ee0e04eadb0d8703ba1804ee0c90a1c9dc851ee4a5b97309db6e13a086700e9818ee341d8bfe568328bba68ffe8e65db2330dc3de7265ac692d2d2afe9bfe9eedf20704d7fc7fe2fe8ae671bd4e9d8f51c02c4b3b304dd4f2991d60fba2b7774c8593d774fddd4b9bbdb7696b4fbe876c301dd77ddba761fe96a70f70e306f4d7b2faeddc09c18ee4ecb9dc0dddd7f6ee66e3ccd01f1ece0dc7603d5dde3e909da76824041209e9da05e62890c2542dc9d8a0db8ab6d4ba46355f52cc0520a1bb4b82a1775f8007035d94835d6a00204dc75a788c2084a051f29d4e07a978296deab813950af14ee2e46f1af1b44fa1f6fbbdf7b35f055f5bcdddc5d27e71ec141775f72330596bb2d0daa7dd79d03d43f1482af7ce48410bc9a9736e9d0881d4426a415af8bfe8f1faf6b5d1a643041f59dc7f9f13ddae6a17ba3ec9ab377fbe67254565782929190f3e814f23efe094d4bf840093edcdde6c35354c28d0f11f585b71c9f8073a2e364097fd259d9d44d9b52946787a68383a36a1d7bc4dd3b70cf276822e5c05fb0491026633009c3dbcd6bb5faff84a6cf64eab7df94499049ebee44dc6472e34ffaf7bbf7ae4e6ccb03fe82bf7b2f13236ccbb67b8554bddd703821e86ef76e171c7247a2a485dbd2da9c9c9c34c7ddb1dfb2ae6e37ba91ceb73dd1fdb24950bd8b9c76922454efa2251b65852405a15b0d04feda9ebb834f369290750395007f6d115218ee5ef3bc87ca7dbb812dfb43a272f7740dd6b848004072e2ee4070de75bf6efab47f65d7d7eb76df4d1f299b248451f30bfed227fd63797b2541879aff27a1258135490042820eb779205ff33a1ebb57277d8404a9c815220e893e70b88b402ee68832f308e8eea31dcbb647afbba69a77b2445c346925427275ef89682f79f5af6589286139f0177479ed5944b7fa68b753b23ab143786b8ab69acdad4a5cdd6e5507a5dbbbf4886ef511922dee3aefa6502ff0b74940320449049050b99be29efcd0fff12eda6aa7d58945712612134c244b9084ee29a07073020aaa0ebe3abd7baf92233c1c79724487bb7b10378d70e17f83a64b4bbfd59c5e6a77138bdbbab4a9ddee1294aaeae0aa7fdfee57abbf73bf6a1d4dd5c135fd55eb9ef6bb796830674409234d9e020abf5748ddbff91bdd6aa0eb6e1ddb027fe7d0a674eb76bb7f6c92bbef70d3c8691a61ed360d3ed9eddfa0db0da2abee7d4f49c9085d8c6095020a7fb37facee0db2233c71dfb1ffee1a448d908c90c41c41c73d05141e4dff041468488438f9475bed045c82a638744d8dec967dda2b10127ce8369377bbbb1d6ba4e4dbad11ada8044edb2fdbf438bb05ee47f7bbdaa9c801454e965056b7f4b48db2ba27c2e4e8752e6d756e2ee87e9b7ebb7b6fba95cc22d35d37f0f7b3efeb560373dc2c72c5ae4e7bfdd32c42a506d1ddd3b9c1d309aa770f35d26d8f9b456096d074e969bad547af350a0ca2ac0e5d53236bca84d3ad06ea704d6eeaee75f3d01ddcc1274050203d5717a17af770ba27ef1a6475389efbc33de91f4f4f70af90bab8cdd3aebb7fb827fdc36d9ec6fdda74b7b23a20a8910fa046b277ab34e4426d26144dd7d408baa64ceefea5efd34687c4a0e54e669cc7511445310cc330c4243992238b1c4516c96291ac18864eb0a62c1087eea774a74bdffdebbd69ab7386a070a8dc6f346d4a51e06fbfaf06e2d9093efbbeaed6b59bb7df35454237ba1241a17ab7eac926799f761274afecefc7ea706fabdfa4a3edea7de465d524b7a5e9dcf497a373d3df939b678dbb83ae9be6f474fbfd17f842e86e378f97b6ef6e83e8ebb3c659c3fd6f40a006023510a871badb6ddd47d03535b26b3a62774c8cfc8cdcfdbe27d49bee24de9a9eba9dc45b53269d1e7a535daa029f88de96cd9da8debd932df2aedbdded51dabeee463a42770a0cea744a76cb0a11f2b451216f3a94618038d9e90e0808407600d101c486bbaf6ccadbedbf690ee7f5ffd876f7bb655f17d56c8672f724377fd47077276efef0c15d53fd63d1dda47baf5f5553a77751bd7b37ee8ddbee77ff74ab81aa0e3ed92ece9be6cc1fbc8db23d9ddeed1a7c74b7fb5df7df54bdb6fb5d53b7a56d5daaeee0b8bf7dbbd59a6e3777f3f4d19346a26a5dca638f3c6924bb297d7512269d7e93a05af6b7913c6924bc35dded11909f6ef551cbbe41dd7ed2ed27a25dd35190ddbf5409b8dba35dd351cd167cdae8faeab62f17745596cdbdc0add36957d54d6c0b046a1cbad1bd57f7655517e7c4f65c1cbbbaabbb55e04fbb38560da2ec0fb76b9bdade8ba64eb8ddd3416975a3b8decbb67b057705020cb26d1160906d793820a8d1bbee1f10d4680814d466c241b5bb89d5b9290a043542b78bb269bb813834059e1e746f17f763d155b740b62e45d714d784d34098f8d06d26406cfea6c6876e3371dd1e4ed77375516ae40d02dd740854fbae44edbb5bbddbdeab5bad438d5af6d5aaca0a418d80e8e4dc23b75bbbd196bddd74bbe94d737ff3a6433c3d412eeb4e81a96eb7ed46d7eda2463c3dc19eab8b8090a046ea5e57dd1302fcb545a851cfd5451b6585ac6c7abbada9eee974ab81a8de453f5adcdddd4de99bfed21688db2dabc3bd2caa995416d87375d17e359aa2ab16d27375d1cf0f1f4c9b154773073627eeb6d3b4bdbbff0f1fffb725de9aaea9feed76e95f57037feb56dd84ae5aa7db282b44000150e1879b3e522edbee159eb7a650eaca3eaab7faecfbee9be63edded82bbb4e73a6ee2eeda4d9f96ae0707c7e6660d8bbba3fbc90be9f4a8c0f3abeebdefba5b855af5bbfbd76a37c8a64dbb3d02020428e843b799f87083ed66d2b1aa8e3d92be6a1009badbbd3ab1403c3bc1770db23f5d939192210677dfeeca02dd8df4d33959535eda2a519540edd213ca6d77ba4b5f3609ba93f6e6e93789972261f2d33da12dabdb485bcda5bc3548e4c87ed9df46da61a2c384d7d4b6444c4a8894dc13ddbffd1afdf66be4be691321fb5d8d7872fc0db63b5b5cedf4aaaa757f9a3b53eeee34732788ebdeeb5fd3df7ed3df2a6f4ddd3fdd1dc7cd9d96bbd3dc60ced4d900bba23c3d411d1cdc7d899b3a3e9ca6aadbc9d57fc35bd3d76dd3ddefd87f1a94aa0677bfdfad027fac0e4dd177f7efd8efe95e3dc4c3e45d77aa5bcf1d26efba7f501a88dbaedeea4672a1b4937aee9575da49b6ae6589f0d654d7b244dcdd6e8f5255336177ab917e75a7bb6a60126f4d6f37de9a32e9d7c5a92c9b43b7aaa6b9761ffd581d909a2de803880fdd66d2b2690bc4b1ed5e616557dceb6a55035ddd12bdad7e5d0d4ca2e5406b3d6d74354ac2f9c1c451427bddcde305dd7f7bbcf4f71b08f5ea6f77ee175c7ff7de9bdd7b7f415eab7befef77da4994e0f0b8d33610ea7df765d5a4fd4b9576f089062a41d7d408baa64ce68d14e64d949b109c96020a2fabba37e89a329d8002cddd3d57abeea3e87e5397056ef4ddb4a7f4b49755dddfbff47d1b5dba4b751be8e2a06bca5483aefa9770db5dda40a0eefd12baa64c4bbfeea694b7631fdd6fcb0277fb5bb7eedd8efd55bf9bfe6e774f094afdedf608ddaa9a1ae9b9fa4d4237bab243aecab247b96012da6aa78da4b2ec91cab247e8cae6d625abd1d66960528f176457a2974db29b5277ddbb3d84b7a6506cfbba7b08bad70d54b25ffdba2acb02713f76fda55a15826e740db2c01f2bc449b768ef4d9ba8eae9ba5b6d370e08d4b7db5e53756571bf959726e95235173cbadd82767c7c785a170411bcddef9b3a296d9efe19b59bb7a64480bc2335d53fa2974d9172bb89aa79fb089456816e3a44e94d8770fad5436c3e3c7bb73d2526add36d23361f1edd4bb2f9f0b04e4a9b49ab433c3d4e1a69a7c74923e9f43869a4201e278d04c4e3a4917e789c34928dc74923f9f03869a41a8f9346eae171d2483c3c4e1a4987c7492305ed386924a01d278df4b3e3a4916c3b4e1ac967c74923d5769c3452cf8e9346e2d971d2483b3b4e1a29c84923013969a41f278d6473d2483e4e1aa9e6a4917a9c34128f9346da71d2483a4e1a69af3629b8594395d6d43e78e20322dc5175a744a0def45d376af441cd918edc9d3aed24a60739b85cae170d2ae772b95ee87edff4b7df26c7c571735c1d8be2aca9fee5b0298de6ea583427e7e602dfb75793c2bbeea67dc2ef9e92cbe57add6e1a88aebae5bdeb6e22323d28721aba7f4177b71eccd0d0ed0635f0df75b76d50c8eba647ce77bb38b3832adc69e84e552050ffba77ee5dddfb77bb0ff5a62ddb7b83fa9d7692770db242782bdb36d1eb12774f82ae4116c9893dc2b24abb657549e87edd60fbae4b94d0dda6ee1eb2b242d08dae4176c8dd4970df2ba449ad121c0e88db6f10f86385aca9ee095995242d61d2e99caa6a2250ba75f3dafd2372dfcd6bf5cbd3136ce26087bb27d96a4eaba96e3ad7252fdb73817a4d5ad9d475d35f2e98a4b22c129749b77bbb3d3a02f5a66a9a4b5dbd89b8bb91119c49c71e4193a8aa161254d9a4ddb26f12ab44c46df7cb32d92d8b844dd2ee26954dbadddabda64836ce1ac4d9aeee0171d2570dfe72a0deb4d54097a75522ba7d9f8854f5d4b92992bd89b81b08f5de6eaebbb2aede48403c3b415917af9baab7dbeba62acee5e91c0ee8a647f6ab75a90a0c0e6977daa477d1cb26e1e9a1fdd349abd1cb2641d7204be465936c944de2ee45457000e2a66c87bf1facee665137edbddfd3a53da7bf5142b71b940a43ff057c0dfeecfb2e4fabbb47376fd53d1d90fd1d5cd3776575cfbe8feeb47d3fc89600a58753a10717771e9a0e6da7896848099252d212763c05bb2d6de7d8f67577683a341e9c9f0e6da7e9d7546d371212c8dd95b8d9e3dd7db7c157efdd2fb96b10d7c4b4f4bbe7061fdd6a4eaffb77ee5f37f87b74d5afa6ede3406da69f1d5a8da683c3635522771fe2ee42dce4e16528aa85a3a25c887a45b94745798ef230ca6788721a68707fb9cbdda5dc0510e5aea82817a26888728f8a128047bde0ee6e82bb97e0e68e2cee4ebaedd9a1e9e0ac4e6c9bebb9bab74388ff9eab8bdcdd77ece0be7f6cd25ed9244aec6e35daa86e79c025afab8fa46fba1aad90aa7a4d891041a940e0b95ddd3b52e2ad69fb2621e180eefee9f5d569e08f1572bbe936caea9a787a824070ed063ee91f938e5573adaa5ff6c53de91f101f4ffaa74bdd356ddfa78ddbaece01e1e909fa68f7d193fefd58a59526096ff7917bbb1d61d2b1ea101074038169fbbab79fab9d7c283153c3abdb6dcbeafe6557f77f973eba5fdd063eed77d3df3da5175ac2b52c6e09dd3d375dd3ddb71b55d3e090dbd2dc37fda56a8eae75737efb75a1dcfd87bb631f8eeea676bb1b69ef9688ce4d7fb79bbb6e26f6bddddce0abdfa4ad5bb5aa8750a377abaa3e5ad9d46d37932477074a01e885808227b850fa68ef1dce85d240d7d5aaaec9dbe3a5dba9090feb49bb41f78daa7f7b600c0f4c79e088d3760ec943e9d4adb4c50ab648c01604d82288d35c28b5d52e12214e1e6ad5ed46d2220a538b02985ac4a045cd69eeebee57ab4b8f4343770e6dd7e0efc7ea7ee774a95655f67d22ba62e2ca862b2077a7f1d6f4a77b5a7a1c9acd0bdc5af7effabaec2f874488939abdd36e52164bb24882c51858a0008b1ab070b190b982892b9c5c017305698595154eac10abe8a28a1adc69dbd53d5d1b04fedebbdd7b57fdbbf46f705a1cda6fbf29fbae290e6df75e77a3ebc6a1edd83fffb604d5ea5497eef6ba734b8f43abd97172636343a3d5fc4d0d8f93a5df395daaa64d9e36e46f4bafbbd1a5c7e1adbf63815a476372d3f65fbd73e8badd5f77464fdbb1bf84bb59faed37c5edbd5bdab1bfe4a2ff7b13d12dbb5b79e96e7713ebaa5ac7baedde3171b30a1b1d20a3036474c008a7b94e4b4b8f437b591609c9df96961ec79df6a6501a891027dfa6ade6edf6dde03fabbe7e26f774df76ab2c9bc3b929101ba0e0df00056b7cac1a48fb0487db3928fd73b3e90b379d70b8e924829b4e25b8e904e5a61301dc74d2805309dc6c9280bbdb663c87cc4608bacdc43940012d5b3c05ea1c257727c14d2d50dcbdc7972f5fb4e4b40c71f707f27f6bd957b79b70de34876ed43d2b41371a54754f44e85e973001717e2bceae6d72f719496e5261c493edfad080683a38e8aa5b2a8e72922224478c8c6054542413d94000dc9636c513ee7f7bd35f2ee87e56624eb181ac4d8d294516f7efed6fd99d7800d100a3894b9089dc4587c203505c91a2c85d0a9b28c800f25b44914514556ef33ebe46f6b7d7d5ea1a857a7377cbea3e85288ea2088a02e87fc8fe6f299c1005e9eeee9be6703f649fc3a1fb0dde8a2c19c28142cadd715c1c746f1727bcb9fb1137a18069c007775b5aceed968500349fa6675fadfe5a76d76e20dbb63b88a6fa686f22596068ae0b4c53a85daa027f7a3b2d2d017fc1dceb7f37ef6fd8a4a72d01d3977d9f7555fd6efaf69cdca5a5a517b209e185369107e143f8bd77b89afff1c0f4c5bd5b15b201a66f8d4ed53adc8fff77ab3939bf5b76e5a537af1b6c9796969ef650b9bfb101a66fcda37b85d4c581525bada6bfd47dfd6bb0c57957b6ddfdaa754baafeb5eecaa65a0df276fbebca02d3f7758e7df6fda5bd77b82515b804f5a64bdfa6bbc7e69e7d7f064cdf366d9afd92aa9776aa02df540dfe7eefdd9fef7803176948917ef5903789bb5f960950b7baa41758546bbaddd0cda4d343bc34f7a6ae6e899a007f6d91d1bb935c9749c71e71d3df8f15e2ea5220abb4429aa4d58da4badb6555d44865d92324214f1b855a3530c96d5997a7875e373dc2a4df2455bfac92ab5b22d7656af5afddaf6e02fcb5453d571725ad4bdc55ffda7d34c4dd6d242217a891ec9765b26bda4112dc8dbaef4e7281da09ea896d5d95dde913db1eb52c91bdc2de2d1157bf7a48cb127181ba557237943eda297173c12425ed060693dc1fab032e69370fdd4f3b897e3712571fb9ba35c8aeebe9be6beaea96e8d4af46f5bb91003512d7dd2971df55a3ebe69d282b84888bea5de4baee6e79ab11f0d716a92c7bd4737511d4d0465921aeeb426d23aefbb62c503b01c5dd9d38c18924276aecfb377fbbf95bbb774ec0dcdda909319ad8e27f637b4e7f836ba20977a3ff06d99feea989013cddfc4d6736b1a38916cdd5e8aadd149501b6c28a112f8595279a3ec225b012c49d96eed276373dceb422d252309978826dbf0913528664254ce0d84d13e0c29d862659da124386bb2f2180256ceebe832a0bd4bfdbadca167787da4cfbd5aa5fc0dd5537aba06e4b03ea5f4ecbbebaddebe655d1f91ba685e15c70907bc12cc700ae818209f700cee11198f5100130fcc726bdcb4b7dbb7d219d07f23d3708fcd70d8e10819ebb19811abdd11c95a007d1ddfe9afeba4409224c25aa9490622a01b451f5cbd6fc6f94d56df4d9f76f4bd8a6ca74b7a5e1fc744f393fdd53bb5fa046350e4a77bbb51bf7d33d991030c21f4addaf4ed1fdfba5bfb483edbbb4b4a638dcaa754b10b881000e045cb9ff2d09205c2ed7cb3d4188cbe57ae156ad0271b82468c07f579db6bae9f7ca0a5152552dc4dde99346f2ee4e77ec9346f2fb972a11d16c760eea05eef697748ff33b4b35bfa45b7a777d59957d74bb3ab79f7d5fd56953dabebaf475736c0b7c37758986ce219e6e77931e42439974699b63dd67d1674f57f7f6bb4379eb5673a9ebb24028f575b5dad3e97db3fbd19eb6d3a3ab661f6a33edd5897db5fe1b77ebd2cd63dfdfe9ab1fdd2d8b03b5995ebfbb9ebcb56d757476cea7d96c57ebf47edd35fda55d9085aa496f762ee969a8ba53281dca5b53a876074978daca3e30f8e85ef556d15543b5efbaee377df64ddf892d7a9a0b0cfe5e53f786c95dd3dd46939e66e3b639f6d9f75d4ddbb4b4e6a1806cfbae2acbea975dd75f53269d6edfdf4d9eb69464e97770922c3d94ba9bd257b781ee6eff06dde8aadddd7bd39ba58d5bd9259b2782c444bf1fac2c5ab3a65a05be1b44d5209005fece7dcbeeda0dfc5dfa4bfbc7262dbd9bdaec5abd555dcd43b5ba6581bfadf43428cdc4beabd7a5a4a5bf1129294982e3444913a51dbba4a3aafadfedee76afecbf4f7bfdedf4376eaa7feb56254f5b2a522a9284e8084e29091192224449b8244a8c94842c41524a4aa244080e8968489225c0e0ef589c7fdd547dd4757fdddbe9ddb9dfed589c8deaf67dfd9b49ff7e3779dacaa68f9444a7d7fd6efaf43477c7f2d0ad3ab12b13ce9ab23a1cfd3d5dd07d77bb2cb00d026fa05aedb6bb09d5bfeaedb23d9d9b6aa79ba7fd2d4be469401b9c95fdedaafba1daddb47f33a56acdbbe96fb7bf57f65dfdbc95559f75ffa6dd4ddb89edfd8dabb23af6655be0ebd21e50b7bc352de144f2afd1d37eed7e5f97aafaf75ab7eea0ba5bd3dfb94fdfd57fc35b5335f7d3bc6fd9ddcebd929224af94b4044950b7937e83a781f0ae5b4ddf35f83aefa6e886527fbffa5f56a5b93b7dda4f5b92e982edebaeac6e0945b5767a77d5fbe70665bd974db23a2981da46c2ed42ba067bafde68cbee76ee6b3615785e55774e012a4df7bc356dd99567949dc857e92fa96acab4f42dfb4b9b49bf4bdfeea6bdfe767aa19c81956d756beaa6b4dfb987d2bfd70dfa507d3eedc65bd3b5d54d6008c2e7ee4b2fab262db5b4aa26771ace9ae6a85af74d69f0c97ede9ab6389b690db2bb7f7645800d775f5ada6e50eb76ee6d35f0ddf4770e078829dc7fcfae0f830f5f00d93fbdbe8f1fe51426eeeea40c0257dcdd6d70e001512468b09a7b0a016a50ef36faaaaad19465d26e5545d23aed8463d1a08b03ba2dce4ddb5e10f843535ceaea58dc667a5dbde3a5ee13db02d120b06d6a7bfacdfd744e56cdaaa813db0a71db0d04eab6098b065b8dc4ee96007f43daedea56eb561fb94155b71ab5ba97e4ea561f6d26fdbaeb92d7d54492bc6e7aa4dd2fcb8497ba2c1a4cda3cfd4b777bd432d948ef12253fa322aad6a52dfbba69910b4c7bee9111f0a75f37a7877efb75a192ac70c75574c01d73c01d6ba16203ee780a29dc711459a070c71ac8e2eec113ea896d8fd4ad5bb5101e1a1cc11d62d160126f4d99f49bd4ee9dbbea5669659340ade7064229413db1edd1e8022fdcdd3b709387c72d14a0276400281cb7f0039050d4113ddc8207aadc1a80c5e3283657411cb09982a3c8400514032a3d1c858c2c403a3a2085a30670f403645605bbf003876dc90257f8050dae7ca25c55c101903dd5aa66d0054b49a97aae3001c2308cd185a70b271ecb10c6ef7c51400b0be0b4daf92fc203f8608b8e083c41f054045a392a34b9820b1073446b2196051b601785f3c36f0bae6241d18d151400e129646c09810b182d3889ab2310c6489ac24eacc00b08333f27780a28aed8043945e12eca08a3c6495518f8054ba67ce094c4962cd34491073b3c71957b4cb8d201952daa64140a00e4c00a2fbd7c030853361003a6284f11b4b4018d0f12e62861ca8c8707286ca2801619093e964212245d3d90889418e402d4e38623ab71a67485c711265c8d2ef0c2774cb11db3f014c4ddfdcb972f5fbe7cf9f2450431a0b9e000363ddf000f576ef8420557bec40e585cc1c1f73e44951cb8c0adc08b170003a8de20862a1e030d270c5d11aae24f1f00820b0644b1e46a0e5cb06082091dd7338cf1147cbd3741000e6601299c7892086ca1c3005ef8e0442290b3e3600a2d2e020c52e450c3011e789029058a1b2a50e53d4290907298529c16e3010315bf731b156e7861053a7b07365b7461920d7159ee87a55a1033df31032a1c100108513eb3020e4a3086891d478f0aa418d90003dc8613a24f1732cc380da90d723812250597a1018a0ee4c004041e738107c200b92a51303521e0220a1f550eab82420f8d2b2b882a7f475e8916047f09e1121546039a7017d01248407c114454ab480242d48ae845b19ee880210626253889842a8221acd8e0245e871855c0c8c7fdb213ba8809225e454083272e1c5c3481003718112587202109ce2f32e0b057c20034250058021251618d9880076ce0e54abe02044308b92180a85cc30c2ac0273257b2119676871119546598201e62285a1f5f2dc1c48d1460d1c1538a28b20495cc16cc020b0e38620c2e8e3013252ab0830f0f8f7b3e40a4050d25bae01a5306c059aa31e55eda011821638a28ef420789ee354017cf12e5f5600322a85c891d212c80e551e3414c91be3664a0e73898f0830a1890227f2187da04608cd0e2c123588e44b1c0114f711e8022628a2a578188232e0d5022ca97b87a48a50829e1475014406801d07d48d58c866aa506ff31c4ab0a9b1c56ffd133b4f08362f39d2b35e30b525dfce6014756cc9e90c26b3cd8e165032a1dce810c8e08511490f2df3d490143110f7c074d4f035c4063023ed3411122183d78e11ab0c08114040425e136726c20891c017ce033c4400008922a9084cbb08830c5608391c71cb1b1a3c444178f35c18227702c8139ccc9082c1713a07979a38c09e0ace003ee7a410a426a844006de6a010d2f6ae0217bebe581075270a206670185413355840b9c84a2022439d470021fad9881012b1690fae843cf50b0010c2e2a41a3430f3500e06290950482f880021e46210118e070457bd843414f081260f1d0a4c586e000099eafbc2d6ce1ea3c3f5d1106f602e719288ad2113b30c7654060c88b2a40388602cb0b02146c8e7d988d0181275870dcfa78b3b07085e32354c19c1a193806210b8f1e51d43886f1b0d8ada081fb0a78743042d9c1fdca004414718102b85ba000395da098b81f21c5164828d9e2fe030bedb2e3e63e00b3690491877b0b4bb42000040bee4fb00001168f70df0d099baac4dc4f00412eaf000c7724194ab062a689fb081bf438508014f7a11eb0e0e1018abb9015c01704d2dd8702443c21d8c25da72895420336f710701848415b86bb072fe0020211a1fb068e45a7c506dc7b3c7962863204e0ae4384251f371ddc732ce58431c60ef71b5138213384cbbdc60a4f963ce8e13e63370de94285bb1943111ee00106ee3057356ce10013b8c798d8948005e85e8e01f3438b07eeaf0bfcf80a50717735a0870d2114e1de4202ca13113cc09d35c5da43141770676514680328813b694405af608cfb382596450dd97dac71c5163086dcc5a90248b9a2ca5dcc81e5e70729dc432462ad1b43b8874a6ea84610847b8e41f9820d38eeb90146d0288012ee7907c24672c33d7fb084950050b9631524801625bbe3268004e084803bbe410649b8b0dc31efc807af07773c94450a1955dcb10c0637f80871c72294246c34e1ee25c038701ab87b076c57a0c0c1ddad64414544c9dd8910a2013688ee4eb57f1420c1dda5c090c99273f715c61003034feebe02408302b8dc5d27c210377cee5e428d009030e2ee4738605737b87b911640e590ba7b900458f1040adcfd661260aae7ee359b265b9eb83bcd0315d035e1ee20e0d059a20177ef408b0f50a2dc5de6c557c509eece434a91cfe6ee343ed7cb0577c7513543101bdcfd468f1c804cb83b8d2eb01051e3ee325bc010c606dcdd746d710030dc1da6f7ab71ba3b6c05ac06a870f7b28a003ea6bbbf9e8891c3cadd5db9199a30dcbdf50b410b65b83b8b0051298880bb9353a84033b83ba963890b22ee3eb2b0440520771753102c42e3eee24a7365c8dd43196cc04ae8ee610e371f3e70f7d04633d2c1ddf3173c3c608abb671ea2806881bb67a5928b6de7d5691698c485da465234a8579eab75ab812bfb4b774ee756ddf268dca0c4c111ee9e6f10e2ee7e830f2003001001053851010e77c7103822e7312b4368e0ee610940a4065c51e34b0bdc3d9c9200228e683091e5c7ddb108043841c9d40c3f5871774c83097ad8b4a38f06b83bb64018a58a11ac58a1e7ee59860d6a2020041d401185bb872e40810a491881811e50e0eed909151db0c2d1c51538b87bb68209408906891218b9bbf8e30724013ce41b24dc3d1f21124104347c40030cdc3db471839614f04065a503ee4e1aa0005eb015b73282b87be6e2021ca090c143087edc5d3cc012ab875e14be2edc1d5df7eb6e271bb60cb9b720e5516c1882515073c729c05b602bb8890154895bcce0ee466e0ea0a65ba15c4c440419cc1aa09451032c9402e4815a8773daeb7801ece3ee98080d1ce8ecd0767074bb4955f50f13e12138c61d014004b9bb0fee329cee0e030c2d90d73d15c1f7bfae56ff8578be03dd9abaaff33f64bf6ed56706284d58468c1f2f70246cdcdd013320867af0a91bdc8f9c8908ae434a03df6a2e97ae3f3c341d9c1c8ab3d3f6cd0dd96fda3aa17ab7355b500627779e26dcede6e280ae0c1bc8604306d1e66f7ff3b8dd6e6b9a2839b142802e5050b799b829f07453a0e6ad2af097c6e02ae2ae731306d7711b65d7b47d812e30edb9edcefd36928db22b6ea7ed2ba506b64d9b562255efa4f648b7fb76bbdd744be4e3e3e3e3e3e353abd56ab55aad56abf5f4f4f4f4f4f4f4f4f4f0f0f0f0f0f0f0f0f0f0ecececececececececece8f0e8f0e8f0e8f0e8f0e8f0e8f0e8f0e8f0e8f00405050505050505050501010101010101010101fdfcfcfcfcfcfcfcfcfcd86c369bcd66b3d96c3e3e3e3e3e3e3e3e3e3eb55aad56abd56ab55a4f4f4f4f4f4f4f4f4f0f0f4fa5f3a8b46db0c6a11ee598526806600400000000d3130030381c140b86c3f1805898d46ced14000787b06a7a441708b32cc861ca18c3003100000000000000603411003bb009f881cdf5c9f9cc01e584428b6dc4a868b92c785e5d0b7d03c9d36afe363b0ef41b93f7f97d7edc28ce6504cea222ea5587a4d67d098d44ba0467a98507382852b74bd386dde2fec3f01470cb9e5b0e2e3fbb6a095fb5563c0042915d82fc1c3da803f77bccc5f9974431dda69e11728d024af61e7ee079076fd04b3b3dee4248ac20bd350a49710f6170807f3e24d626c3642deebfb3bde19a84027fba0335001ee14e046adc8b15bba3ec419df51de8f72ed3f10024fb0d432bfdde748bcbbf3f07cf79588b7d1b0ef06a9d7fbeffa6c2bac88f6fedc5a2bb5baa3b865393a34e6f00d8737a033714a5a3360c4a2c8642b792e3eac76c167070df9e4159af1a40918db014f52ce10c72f67d14a7c4ab734b2719e81efbfa7f467627b37e68322122e291011ff6e4168e0de9f42d2b8ac566287778c2ed2230e7de863e628a559f7aae7eea2348c4219cd34bbe917aeb44cab8798b4d60792828b32eb88a0d88c6fa9a83d9c4db972dbd1d8a32589b0c43b5b4b47a26145e3a07223f2bbf7afdd5744c1b6f686f7f80784c86c478757c49c6fb74b364ea3a9fae43853ae2b7ac0309e40d42d093c6a806c6aaa2d1bbfdcbd238333f89049844e56cc150aa8d275f3fe87e8be50a38b5dc76ff62019d1291e0bec06dfcd7d0d04b54d573db14e44aad3d4f4b66acde7731229e486edd830114a20a80cd485fd8ee73e3bc5f3a4272838be327120a4c5fa8a520defa11918dcb2a49d92c22c8d2f36febf1e100a9db2e05332b88494a7410a7c0520c769ff2bfb9b144a662015c0d5912c4d074169e54a0e1b48483c8b00e958f1154a35a5729cce94568c1a23c0099d468de0052208944d5b75ebc8b7a58a4d342b0b680a5564b4a2c6e52843a1f684f51cb0a3d0a423f9b7c8e5424528a8b53484ab1eb66f9010afc9171915cc30c9d4c03859839620383f6e08d10390ff7c41f20003e3e5003f53916f3cc91bab34d3826855fb566039e049a1e97c1e0d7c513f511b4ec70b4d62134252c322dc24d7ff95f7b561232eb7afcbc6557306afe61e3962f4ce8be12ccc1b04f32d219813c867153e7031ff94453436fa0f3442240d3a7081da26f0ff64923c04fb9fb310a5809a3ee15b7d163a67fc48a2c28a4080109cca3cb3f320a2dce019458ddaff6c2f0acd0b90440481133ace034c219a01ed1ccce00d9cebd3865b8e7a0dc1218dd3ec703781d6a61ea6466695045d0cc245e07863211c8d15b0161de1ee0b23f2e85f14bbc8e03e8a659cd22b71aced200861d723173315636712240ce1941bfe07bff0af7475ac6530f004edc98ee932fc57bc06d74f337c4d38ffd6e27c809401323f2e72026e779c4d265b6aae7f30afebe1facf0d5be5e8975316428e273e504e457f6ed82900fb840924ecb126d47fba8b2d7a28897e3b9ab94fe93b8da704602c9d74525cae2bf0b470894cbc0b7638bb198ce8767354aad04a790bb1e450de7a723eb5dc40c71a61c9e80d11b98e99726283d4a73dfcee2a88fe084c3363a02662c16c00d32cd9ae7b07747a4a835b2243eda76b8741c803e7b8606ca4ca6c160911d949e501dfdffdb5462e354576a5230aef66cc3e6ac50ffbff1699cebc5c44e01c5343cd82965835e3850f9a43628902c58c5c0c9aad5c52726f23f965ca39deac0d17b518d578c056a3383491d6992434d011e22e94e27b78a4f668a9f54c5443a98c32855b20c330f2a2251cf1c422ce4404414c0bfb8964f3771d3a8ba5910937b692fbe1071d1ff180df23eaddfbb97e1af4d35859f06826ee10c54aa7144c70a8a4c06b45e5bb5d2b2ad654db163e3b058cb80afc9b8636d17f6dd059f8b708331ecc17cdc3d8f42604571c5a04c724e6f27490f214ddca0970c9b0aa6d31005decd17f8adfcfc8171fef8e5cca92a916404d68c458d1742a5d28ae6b28adb852612a83d8814e94adbb9dbf8fe896390401468c6bc5fd375e474c075271f8ce6f023be3213a0de1011f8bb141b27dfa9c218be24b653ea13fb575b86a94568ce14d2dd2ba4c4bd3ecc25188b93b714c12db924becca202ce648773752a9c7305159cd208c85c86f7985d2d63707d9de4978c1d7aac38cb8340e28400d1d4004d5aeaa7e884a8db2e2360090e8d036810bd9abbaef47d1ea5c24ec2859b41b0e100a642362c3e0361f847c15bda016ba91eb6b172c0a372194ebe752410bb8a4f20327336312791a57df001edc452ffbdb99ad55abbb794f00ea8a60bff38181a5d54668ca2a9f3e243c2b385f30512b2fd7b0aca8d4a3ce787d802f827458450e7906d370896ee11ae4dc86c64d670306f0976224dbadb3435621e1f53930eb1beba4b2df6da8b0b236c3d696ee74577f22fb07b14d0f0dfd72b510c20ea62dfcf04cbe8b5d5dd740cd54fbe5d0024c0f4f8a82434fc26345e13050e20d431ff791941d1fe96583750e1c1c856920418c8e476e3d21dcb134449de9b3c3843a91be24b4567b352da5da9829b16f6479ac0b51312479bce5a735da236b51223bdc50740e9390c862537c000a4b3e43daa09ee8931a0158ca747b6b370da3b2ab0e4f1d0808f56953ee33464485e923adef02347e910a3620d808a4160fdd30ee35df6a04c53d460db975133dcc64155906a1b0ab139a79f15959fb97f8202de9deba1f32c59b64d9eddf84805f168127631044b23be11b6beef4e131793d53bf4f8fc395bdf5950b77254c37a370aed8eb01f29afda0d53fc2df8f5ffacfb901a516ce2733139fb9119d8854eba549c301439b2fcd1c1aeff2f612c07e77dc481010737840c2a603ac01d025d90764fb8d65bca64353434f7791f5ad8d055037a8386323a1427fe0554ae7118316fc158b73afb8402d5d2d136b4eccb1c2112016e55f810c6ec37b3849ad3f89370ce929bd535a57d03a2e5932562235f99e8f1de4afb23f5d25ec612969c50d3b366acaee7429b24bbade6d92bd8368af90db9495cbe13c2293f558bce95042ec047d7252bfa1cc47078be86b68439228b87c247386f8584b0c24ea756a9993b8fc8eb9a9b51179e723c996d9a5f85e6da008fc1cca0d8eb6381f3c2cd8bb30411b4da07efae024f9c5ddc87e94f509f6064475cf6509bad91d4379f8ed99836da4057ecf8f16f659132fd5f145e017cd4fab094b30272f200d48d02071c15c69716ea3168a2f9b333bfa854a522a68e277678c6897c402b4c1ed23a19c97702cedb55e5c92d4eeafe6358eccd4035c81458e87de100b9495a33a020dbf18bf944d4f0f10dc3d77bbd81ccb1cb6df26fac0a20107e4a886b348369e30d555a8852cd1870ed0319fd66beea5501d84d30c2221194181b8c18226d164d205138d70a35fbf55cc2728510906f83b5f72c1d6387c927e754754053a2c506bf62e572398df2a43bc7999728badae3c461b69b2508d95d285709132d1904e8a2ad83ccffc2d1253e2e69f13b6136b240dc55f3ed5c5377862a4070dca253fb1901cca9e4693817ba5aa3914d149f6d9ba662bec68fdb7b928d13b5c80ea573aad31e3674320d829c000bacae1230b1073d7a6134de672742732099ddbd26336ce10cc69487c5974d96c98d4522923260a4640415a58e39a9030d8e407ad15bc0a68f4b1428428beeb2b8f9c2e18e1d4ecd8e0ddf24ce3999e254a304f88154ba028175448523014793a67a9a7245f42e6b8cdb1d32481d9f172e9d73e21425caffa0db4dd18c4934617e9d795248f8ce4ebd560b6e95fb79ed39a964c0cfa35ed1b717e810c1dd206da5b8492c5b976b22e8732644c36b0c66ab684da48346f2e2ee83d29182bc3019c06be10cb9078718ca738a9d24b85be1d1abc36bc45f24743e0cbd8fa48c4988202216f9bd06af4714e1e79021895f8d509c4d47758a30404d30c2f2bddb63f8c82255cc9470d17f0eef0029400e003bbac196a6e3991918e658465b2333f918a9bb0a0a14491b96a802f077ca16d3092e84fd1fc4ccf433d20feb005ab12bfa8dfeb113531da026eceaec80420af96cb0b876e6206289a336a9c8fb37d63861aef7bc08586bd8ee48e4ba187bc74cf6c5aff28d182054259d09f017fb28dc6481e83fdd871591b5c67e3470381870348ef4f596774701eebfaad2442ff5b6a9f871a5b5838b4c003ad9992142808b6b251356ece725481fe642024360027043c21241ee05dc44d64442c85391bbb2f1819869eba06ba6466e466ae689bdcc560a4807f34fc1f62ffda5bc5a5f299b33ecd600b06d9fba785b37a73b9a82da1e00d27b976c9c6b878c43189a20604fa9529af5363e2110fcf03097661d3bf7d9d0ff47ab190188767187160643fd7eea63bcbac8bf1c1e60a819a5089a98294752cf4e01121103f51415d0e93ca360c055cfff2a3e248f45dacb3a91641e923f0337d4c2a0d4c034b635336ef0add826f161af8ee20b437d55feb063df4e89d828dab8c90053d3645725903dca86019517a992c1f57d9f1cc5237adac4ad9a2cb99d8625d5ab1b94fd85429ce494268227f92f111e6f3264aae486a6befb2c90f696baea55d42456a17a92b486293519929f66a4f825d8e0e420a5afcd336932826c0443357090984cdca7f11c56a207ec5e16b6e75711eb3f1d8cb8ee738fe77b23c193c5d97abfc59d0500657a8a8ba648dc96af3586e2144977dec32fd4e3fb98f4d8860f9221b5d7a1fb31513ba208fc75658f4151411cce58b2411c8de49bb9eb1085c9e86fdbd8fcfe3d73ca7139447671a6389567c51ad600bef826e8ebc1caaaedd69dd11e8abd97c6d2dac5cc177c53df8923c76271160d5be18755db41b9c0d4e90cc3ed40aaf7abf92407d73c5c1babea6efbf1a37e84746f1d787503308bd3af9b2cec96d4587d65f0f5e44fbaf5dcf612673c103f14e5bfa90d8f4967510c1dac67e9c8725acc94030c07261968b31585c6e46a451a484c4be6b8063648e396fe4105022c8f88ca6269b49b473524bc2f465f5bc8f2acac57e3c6b27ba6693a4e8b3ec28aa20119ed7883cd81a36b271c0bccdd99f7d37a0e2f4b6dcbbe261bd4727b592ac268085d1760154db473335430f5c552d03b6c6ccee0da6fd37e59269396b60df7fb54fc3fe17bb0287eff84ff0d532d52c6f93f703edb7ca192d40f2ec2826633db1ce95e62655f9a33d93980a3066f05492997d8dc127604ceea03d8b659a9ec48148d707da7be2b2a9daae37fd48d9e223a3f9600d31793c97f2288aaeae17248431368f460aa1ea0eb3fc16935d430aa02b20bd2fb54b67de60dff556550d1b062b00a1a3933e4e8183bdd0e9b5f9862651185bb7eddc63e804301432da5e8bbdc3e7ee8dbf4c32d1cac8707c839272be7c22535fe4cceca1469b0daf46db23dce6532dd14a45e4e21e1f90f55175d89f37275c3aa904425bc3b6337fa3dfedd63df8b67c8e0350ac847f081382b75f09ef812d08119dcc6549b5ed6a919546f781fd5f05b96e78c1a4296b82105e430cbc23260727788eaa22bf7ffc16ae5d1c51a065f1556963e52088627ae05c7be19c5056eedf1d30a101649cc929ff0675210e75d1b6e1c31e68d9d42071d5125ee6cf7938d53fb1e716428416f189d1c3357beb9fd55616383d2ab09338b2e53c421c22d4176ba9796afb1a6615f2411944908e6528d9c5be19369eeadd0730a9211ab55bb7992835126a6ee3f840b57fea32114a81d151388a410d808a67cc90732bdcdb1c1baadfd03ea6df4f2760afc0fb6e3c79fb1fd0ef3ff3c27db98d8fb46713d7c49d7b56339be68ca8918b2063f9fd31a9986afae62bc4a130d1a5b9a6f103f3bf1f3e2fc932084be3e760729c37be4c0b1bcb9f0c5f440618a5edf00b83e6f2536865e56589e3a46d89be3c0f17054287bd70c20ac9be2dcdf53ba15fac1d476ac2440a6cec0b8e9fe7779bd0f809949f19e3c9b70794f9b9f910395278fc8bff0570b32395464d849f983f1df6319ae2b6ac70cf9111ed8694f3266f7759c323b53713af4181e7eca9e294ee5025c7f60693733513cd8c00db3a55877c8f6b4e9ad37cc1043446a4cbef4aab596ef105972d5f1f1ba9ef8e085cc9d158ecc7c6f0ab7d1781b8d277998edc8c73bbddd1c36359abfa679e205d6a0a026c8c79419655d8c668f600c651a30f58ec18825f3f3a1819ec409ea80bb5afeb701e2e4215d537efdbb2f2a11ca6b575db33f8e51351f3421ee85e76ab88d5174d3c7e7a862f23ab916b9d91cab30315d0813fd45fd95fca9b6c4161d49a9fe16e96b01ec7772aeb69638e8116d4d45a4baaec1de94a723bd6bbe075f38d80958b3ee50bde9be96c2c2b1bb62775aca64011ecb00e5589804e114850f606dbb4e29474e7f9a3a839ba9987929a0301d2afd995f053077842d64fcbf40ed0649adf7756c9e9651a7d6e294b756c163270f208322074ca25617aa8268b9da27a3f285b2be42cd639a4102d4e16f85f7cf49b43074f2a5255c1177a4ab7178066442b1bb4b07a4088544e2aaf880a1ec9891864f05f7d90f48e1f349b11f3af073969d75641576399dec0a647b956ca4da28a6539de15cb79e8bd61ba564ba23ccf56ab958ad514ea73b43f2b404c4d452d23f4e49a1ba97efd573d17aa3944c7705f39d5a365e694ba9545728d7a9e7e3d5562199ea0866ba956cbcd62ca792dda16cbf928bd61b652ad515caf52af978bd594a24bb02d94e3513adb78ae954673857d785ee327acb53dcc8b32daeb673d9c8c716edda796b84558b8edaeba68d04a253f5e9daa9c07aa257db4e05d613bdda7622584bf5ea5aa9807aba5fdb4a04d612dd9a6622b09eead6b792c1b564bfa6950caea4bdba562aa896e8d7b793419564afa659620aa0344bd424e4ff819da366433463a1e5f290ad38227857cf0a5234b6c2e9878ee23c86af97a177c1119eeb581b51d323a528f72945e916370df9b7d70618eb8655a2d2c0e52df33d57926f61fe56d9c377f8fc2c474c28b1d022974a6d717e20a5c7d67c78b7087cbd47b746e404717381d826d744d84eca09c95932096eb5f59bd75dcf5fc59d9de7ea159e40b68ac2788c0c69748ebec261369a5ab55babcc287bd79a404a5a54812347269492276e7f434497dcb5a654b4e113edaaa564b9441fae503d8a8c13ec8130ce77939f153ef8f947daab051dba9e172aeeed1046c30dd02bbc257a87124b4a359a7c9c02ff60396e3e1ab1c4cf20ef51c8ecc1349cc151144b6aeceaedb6799cb28379aae9ee4eae524026ec7e1e52dbf45300103be6b84b1836872834312aa24afe45e8a4c06301070700709089deaedc00d4461cfa490b9f624e84d2c05b94dede8af0b596b6e2dab444ebb635e905636d70a1d7ac9a44dc558d54bd40a1804129f98f1a2230ebd9044a1ecfe1ef85ffe45549b0575ea3ea3b9bd2f3b9ba55cfe4ebaa49bee2b2dc5b9403097530eedb24aacb59824bd4498b7c661300a7ea6a335051da4b0654d8f9c8b9a05988b9e1fa463ec1193da13da1e928e4a617aa70e9847eaa82956243a56039412c4cfc0bbef6604f8b02f148b8e439e627793ccea5f6933d1fbd935ddd09abe1aa82e6ed9344e21ac5cb7ed385b1593746cd68857e710f52b1529e880ca8788cf3a289a5dcfaade3b0d31bebc512229cea5d078481573dd9e1debed283b0c89f4c30f8e7421c80e89d2acf8bc89b849ff36f2a9bfb994489f1f167d0d800841ea4e06a947485b56fb6e01f58a5d4da049bd5eb7d898dab8757a8c4096e8714a582603f96b6cc73fb2d6042b088cb30ee98b1911363c44961493e273db6ca7b88956fa42972709c77d0ddc0876808bf06ba6b99fedbd34d9a799b71053fd78682727488af049f975e240f65510745d63fe2f7f23793cd7ed292733f945f7e9596b22c825363b1b47b3260bd0280d54d76c0bd90387e842043b73ecde1d1c107c0cb3733717841ebde0b87b18cb6a26164474343b4871aee50d34da36ba84c80a9e0f6e6c14f8bb7d20d9015677f98a0199e218083aaa90ca1fae40dd36115b54c15ed6deb1eb280549a58b0bf55289613064882a982a493ce8b6e77424af73039de9dcc15d8ae5b030fb5a0cde110fb4dc39fd26b6edc2733248be7c3583398c1f2148ba58840af06c51f3d90aa6f9c34c400a3060384e28b90e215a5ff287125ad803d1b3b0bb4ea30d3202f4dbdbbf501303f53d16d45703d06cfcf680d1b3b79e6ea1570d08e623146517bbf15bf16378986bb32f1d729f3e7062920965472675f2a5543879d3d95d6b395fade2598ff66423d7d4f16052535bd2b302d01040ef36da10b02dee9409372944ddfd10f486731f6718900748d8cece73156846c614ecb71a772f9d6f5b36947caccbc433aec66a3e997d56a5e5050a074dd69046073efe83280f363db98cbaab0199be0c741a620be46df1fc56192ce04662aa6008dc280933fc8bec74591ca57df01f00ec4b7c4bf8bf61bd5c582a5f36f3e0fb331092377faf1ceebf2de00449f44c64312f219479d2332376a8a6f0b45a0d183a9a1ffad34c4d132b0f0f6793cdab694a36ca8fa9aa5828c53375d1f0195ecb2c213e6a8ed97c494dcd5174f88969cec7ea3fe70f1630d87e86353e1595326413178fb9233f2bd5f9d70b9b5a213efeaeb68308d3c75ec5a40de13ddb43f15b166eaf560f6f36b6b705572c4be615f075efc2ceffbaf8de4ad92992fb9e8c3c1f79e4aeac1e6caa22c9badc9670117e059e6360b80887be63a2723fac0d22fe83d94541a753ee17331e3fbbf1ce24b0d22d409c888c933ba00e2e4f4b099115785289bf06d39d593ebd27cac1466b9382c83e8815ebcc33a646b600c995de658856893fd9777d56b06c025db7ab3c090956422213d6b6d4cf513990e7eae89821089c890ec0c2880441d4d187f62caff93d505c496c4376b6e9473623d19c74d27cc7ed20f4966b7917fe4e6e55819c1ad2e2591367fb6bcf4339a791103cfae2c922b7ae72b7755e52365c12c5df19f92866c87933baa197da2204a392efc8b9e90b1dfa72520ab6924401fb185792676180f5116415b861ee4d19dc4f7a04513e79bbaddb6e28cda1044a5dec14d856e661e2cfb056fe2c4c3ae73b1b68ecd30e8af80dd425390f80748c5150b15d51c91d45f4a006a00c7c3df4bc2b4267fa208a20455c8c9979a1a9cae890435cd429dc72732776ede3fce299c27fa31078a8292c275ca5ffddb49e2837d1b2b6d395ee06cd453cf214c3a93af855b82fc88c28f8f7edc2dbab4b18d68f52067879b69281a64b24e4e25227cd2f40a31542ab770a86c9abc2c275b00d1af956e02f53e4cc647d1597e23d3c98ec89994c12662bb9d1d028c1a570ea42b6ff1f73d50f74ee1a2b0cf83974f52c7573d3ad15a34415b4b378e0193a0a5336fe86e8acc3839175a2659675e0e9769f47fc93fa399e40a2e7e7489f36f0a200f165ca90bae606cf9487f4343f21ad70c05a0f901cc861ac935b69a6a43b9b3ac2872c19dbd2c9996731cbbb8ad82ee82a47e14139b6f3f04f27af9be9c21d9a9a31ffa742c60ddcd9fdd3725e779fc9756a25ac18a0141fccaaeef2a04cfc05ef76886fc5523fdc9cc8a0213ac12dc17c37e2a09626eb5b24442adb3790843f9aaa4d23ae24bc1e310434c776689c2b0bf9a82e46f5ebedb2c8845209ccc7727ea4cc84de6ea630069860db0f983c02435971587fc8151ceb9b82a91604228b0ccad02ff23d3920dec8d1494858dbd635528b0ec5e98ec9dd3071ad5c5a80c1d7ba28d8d6c7cbafba1cc93d59586d19ecd6c141bdabaf9c2ea3853264416379de653d05d42df906e9677aa618d10d3bcad1f740b3ecd34f3c042fb494766832008e69bf18ad5aa27bf4427ce99595d62eaaabed829cb26b4933ab7f716be6e60c5e13251303faa01ccc18b3496d631055d98530a172991c3befd5c6a2d9606d5d2636d1a70e6549511dcb703f12f486151de2f8377cb41233bf07199aa3e4381ca1764f76bdb50fd0c8359d72ab19c94856e88907ebb98665850b9d0c85ae6636a2c2b3907e45c41222810d47782d69dd9013dc52214e461ef6ff0cab0abeca3a7cd64a99934ecb33140deb87e45d63e15121b4e42554b80a43375361060b92ebdeb2596abe5b4561c065c7cbba891f9f48e243e883b1691bd1eed8ad621657f25aa74b3cd9750cd09424bf148a8753ac6c94771c19b5101c834f36737c3d1d461d54c0e413097ed16501c4cb021c80585ff394bd39599e640b4193c4ca25f05365e6ece0246e25fe83751fe876816e57c4316b2e88026826b4c0da5f6b6a77e69e629d515ef029341bf69fe57bff65bd6f64a6fbe1a134a33c8ccb9f0a526d44520ce2316738e7f2206daaf27b01c71dd08015b0a9991c6b5cebb6b4ec53b9235acead35c1c82248c6a84ccbc62d021a84decad265e52da5c3aafa6fb1bf69da8622a254ef2e595bb47930fb8be3df0bff9f8e12181671fa8ff40559c119219ecd4de25706c60b4880d7092c3444da6cb10b83a2b0490d13c37881deaee23d8d7ca27b1a639d627e19b7cbc34ad26699e5f62c31501a64eba4fbcb41d5105da929a137a767f90c2d808d8bd7e83784caff6411b952e562c91cad256a905b5358fa7782fe28035094b5857d6ec6c539274fe6e525057b81a2585e7e3ef505ac90c7924da81b3515f5e2633b9d6968161daa225a45f74429722767797d9a5121af3f3c4f780cb49918ce690ce0b5adb17e5905e4ca09fe81378fdc8d349d35320dca366a8a3531fbccd6af4033241e5065fa60b845231f4dfabeb1d02bf153cf64fe9861274ce133141e9aef0742e6b692e6b3287be47ff18d45ec91aae5e78a8cac169560ccc4242c764122aec98575efe2621ffc341cf681c9d74fa6880fae782776a7e97ad2718e9ae0631c4768cff17b19eaed9eea522f25eee719b9b00cd5102808b61e509cd3c24b303395cf4f77b37ba56eb334db2de23d503bc75cc40082023f1941cd894ba08d9294fa99ad9d077a431c9e3d4dab310e9abd865d741276d364376e34cddcb8ffd60f4cd07167f28ed996d053892cecf1ceb8b6ad48c0cb8f5dd271bf1d79b91437b1cfe816cc16c45051545100b54a68d34d21fc4ff829dade58b4dbab336eb5c878b01ff86cf1d3bc60a1f2a57944405cc65070341ec1521c9337357b1100f7b53e4880bcbe1c2f11ddada23e1302ad6b899db03bb32291766274fcdac9be96e9302351e84bba7805541e5d93231f47d7459c25ed670aaef4bc4bf00040fbc77f432f8cfde28ada07a0092fd6b16753bacbb05f97f8ef6728cd2048bf4b865e7466eb8311fb6fb6f0f32e761b6ad6a5697c0e21fefc1dd0e353b0beb69e72fe336f6403b2293c7f274512357dfae6d3971472b80683ea439b943b8d1fd45969b83f35a2f7f6fec92a23c7ba58123b13182919110a88839115bc990ea5201dd9ff48e8abdf67bcb4757e8385383c8840adf2bef866a9a3729bf92b247385fc83d150dcf1c8f3d230388dc57c3e87f6f67b5652046df4175bfabc59a6d992ada91820d1a9dc0404faa365304a62fad00c7067bd9c75741a9f22544fa1fbeec6fc23f40c2e11bcb3d16f4114f64930d3680ea15d80259284d22fdf6b35d5f83347264176742e31248e3af5c1dee1b48b8b218a7cda2124e486ea2a6c594cc1ca6ea5d0a0f1fb0ffcd0cd6869695e8c112e3c818f202a0283aae71299fd81d5e61deb5de3bcb470ed0a65d099f3e083c0b6e6ded5689a6ff0e0445b3667db15c8e62b896a177e6e915fe065205702dd20b6216ae203c75ae88df41f5b9f5b0e0fd25fb11da7c6b1f74d0a566dfd76e1e1ff0e0370e32c43e97e47580fb2b8fbd7ed941635fd70decf276ad74518ae4f2136d7d5b7d1db95fbb106ae4ffa174a2987b0a423de4391332f362f738b5180c8fff79ab133990e07f3346227049f7127b8db22ae831f8685275f6cb2b13d5ea629496a2f4eb865db29227eba31f25802776ec18a8709da680baa502d9438286dfb48be22a037ad6edbd80c902fec198ad91d9fd43b299320ea39b6c82646425fc74e302eebebeb2388ed88d22bd7df960ee9b311c0effae65780c168c8e8221eb4826c30777e9b4300faedfe86dbccbeae3fb839d36ce538b465c5ca4c0b43700a04951cd9f4b931e699ba349c61f1527187a43af52b0a79f3e745b172d7edc51ac54479a961ddf91656be5f7612c4207421ea70a678d7726deeb2e42e4836d6960358480edfe554862027d5faa19ee8041efb89094258f031b741d825540e12a40d1a4ef30fa6a41a18e633e0f55dd01f6e5aa5430c40a73ff3578382841e276cc68abb371aad8eedfea425d5652de42e72251d1374124b9a48480e6d8b136b267a46389e0d4adaa1d086b71e6f23c7632d770b7a6e0f4f8f2f7e053b084df03944f4f581a9c8433877ec2551742eb740f059f0d5011db31dadd675d3330db1fe5a9cb94bc461c35f9c0a8aeee811f7c4b51473f028030a91e6fb0e2ce0a0ffae393b2679bfd6c5a45103f936c744ae8591df524bfc41eeae01857ca55bafc087966849e5e8b9759a3dfcd70bb9b0b413c743572c3b105f2b997a003275966c9a8df8248687f193c67a4dfa55789cae11da6bbd76ec71511e0b0278075d7ed11d9ddfbeecfc04b14bbfd0183a4a9642a2e7e1ee362e57ee6fac3a4618f08364200a511fdc46a3c2ab56f1673fec009ae2e4873cee80155f3f93b8f9a4bf20d2552bc602c1161d34a07ed9fe56429e906da20c5fc64486d38342e64d8da93d4ea337347e32afa2b4a24400772ae453fa2588c917c4f3bbe50d4d46aeea4a553c1a574f6d6b4fd21333e9b90ff482e3641b8d43ae274f0f54d94db59eba4aca07c7404920bdc79d6c6e724a3d891ae9f61605ee91ed4b2ef35a6c16ca7e18b089d35708439c9b1c9ef5493e8b9fc74eed47e80fca078b191b1967f14434fa29e8aa52ac5859ef3332e2aeee4599da63a63e76da6abfb1f2854edf94be556d24816c58b64769ce037959a5a32761de1c8eb6b991f7b0bdc4cb8dffd289691cb80fc41daffe79b41f80c482d1af7bf0ac4634720e9fa1cb8cc0873922c30d8be3f62dcde5b12e945b5447f47230ae2dcba2cca5e0aef617b9e22516079f58465910a976402bb0cac7a15a34f88360a8d4ecfead96dffb62bd959f963af91cb5b0fc2d5056f31fbc3eab05f6ba79f60f7bac1cb5fd795d77730d6af9712f57a6416dd5d16ec4630b05b8f93676193f930e7178244305f13d81a6220c7a0a750f7f4d0794c97a05fc9c758077ac7e4719f1abebde85a8e49fc0ae954bdf4fdae88a3bb8f0393ed2f5ad88f2971051896dcade57edd480133ea98fc4bbadc6ba27426889b33029f6a026398e635753c32649c429e7b90d53d603b4fac5f8fd86d2bffee25b5f8f377004c3107b5777f730fc7c7aadb94f8628a56e60e3f78d1861ad3b6c5366ab2907e6d1e99f741f45432b70d06473bdcf73236c915b4fcb81ba5123f27547d0d29cf78e886fd13b831818bf5cd99778b58ac2cf0dcdfd86e5244a618685cfeeb7bf6ec66f0e81228b82fa45ec441be46c25b66f212122720395860dc85ffd43d389d97589a55a4d1929bad9b7cbab1e030e4c57479d0c3fdd9646ee93a9722342d13e42f9f7a47d19b384b79676c4f8b07bee8bbb2bfaf3cd1cbc33be0af0fca39fd287f9bfc394ee47f61a1afe2f3f8332a6b5cb31c9b1b6efbb36b38d1929a7eec70fe48ddc17cedc2cace3eba3f44560509e5e47d63b1d9d100f7ff3663235447233f981e3bf5db346b6bf666ce5e0608f51ec7875cd9808499eb388c2a913c010df79d6455308c7c6a19af5e0f54d766ccfecebdcac54efb4748f335a41226656591ddd51eab63b18ed0c4e9586c046bdce91110c574053a4cb039cfff55dc2dd1fedf9598dabdc5e8dc92efa72f7aaab93860547727948372d17ed389e3cc893934f528d00a4567533f7541bde75d51374f1de6c878e86ac06631802a5b2ae1a58e2e1bd5468f922cf63638a1921b1f54f4bf01a15ad27948a91233f76231eb1a7b811751536c0680dbd0fc6ee61924648d0b2bc5b704a1376242111ee4973c57af61ca5eba0479a11263164673af979394662542d11fa6e3a62153b90851988cae16bd57cfa13ddf1a0fbd03b033c3c698e71615704ed7e233450a4e48ce4b386d965312512f54238a5c5264230bd0a9cdca0a864bd3d3bf4637dbf34dc29e53ba58e630c7f5a641cf003d6c0c85f78eb84b01de0b3225eec450698cb3d24eb5276853fe87d069fc2270b6452f0cfeae1998ac6bef93070f30179270e24a67436bc60f17211579c62ceb0933806959b9a8eb77aa07205da4483b96592480340e010f78f3a0ea34d09506e8099fd4b959c8bbaf638cb6fdb84600982e20d2832c0368650d9a6c0a8a3c1b1e8fe8704cb4e906b450d903e11baf2b421d6114f3203c446e5afb68e261e0e36604b1b5b48c712e2dd70b0c2be087bb6efc758a78f05961ac49e697aef1e811e780302198e1cad6d288ca196617f07a666d9b4ac1b86aacb272c54b14859e4dd584cf0d04386d484ebb394017a087587100665be201df52d1b0af6c19e11b9f1035ab52db8e0f32e418feadf6cd818419148c38268c3195c7a94b11075f806907b35d54596989dc829934373fb98123d108c9e1e04def5e3fa079ac71db3fd8632d7e90c46a4d95875c71e2a869b51fcaf73a0ef668a2d7413bc0af48f392b43c0cc688d60e421052461e3b49b78706c5e4a26230cfbbc3e15185a0a000507abb6b1a6a5250df97cfd6d3861c09276285be945c8e544a38027c5d9c34e55650cb1eae0c84aae8480257c784ae4aca58a7c4fc646b77aef6128fcf9b9208d7230bccc666ef24a3fb68f04f2f66c0d82c50a2d6dcc5923fd92bc2c7b2a6889ce86f7e477f0487cc6e73f390310f4c561014090fabc74c8c7ef11b6d3bc04bb1f5a5b7b62679057f8f855cf40f07d786a40438ce702ce649f8feb683abb02689fdaec22689db13f615c83d99a27aad0f1c3f2d5d3d3727ee5e3f2aa3f838241ebdab722b8a04119bf5dfb3abb3596d60b8d189af750cf5644bcb5e6edbba5716ee45415bb202ed0c9c4e09d316a0848325d3190ed99b420cf66bd9ea413143cf0d273e5c63c0d337286ed0a760e99b803679c63ae6b725f8b054a108bab184c0b6b00591c6c3d74d1295a961a77d432eea40347ebd0ca4ee3c185b00d509a60ee53db10f3a54562639ca38082698afcfb9e3486e9c0940c14f6fe69a17d72ac4dabc530badacb505edcc430358f09e8687ddb3404897f8fbaaa81c9175259fd4861d04ea4db0fc4e3c68ca86f425bbd6ea1cc422feb491a3d570604080174984260ae0414441952ba118d794aca2e4fb5a5c08e75866a5a23b9802c2ab5fe4159ed493f23ce3a0d96b29641d2791ce4b58040fd6a3b16aff23df2caa00d1808325a4a8b16d296cfd874ea6499d57842a503c7441fd7907cfe37291aba53619840f669dc875160ffb54676999c8aa6640858670227abefbdccc0ce28eba73aa954df8992656e2b3fab92aa3d17bcf08475242c77a287ab93a791dca35c6cefb1f443fd5264ec941959e8ec97201e6963cbc89403ca5d3e8e11d29ab8c2fe78a29c27f314b35f21397943c1400eac9d4a52c1a54349f6d57e5ccf60fc08ff281778b9f24fd776ed835d1e1124b9a18b487baccc9beb9fb4f7371945d5251357cc38a01ab55948d4c35208a20f24d9471dfe6bee83eea7490d5588a3bacf95b15fbec5288a7db7e70c12bcd7965871b8606a93bb67c84bab7cab455e0d2e33c0d97da497c49331ab98e0a6ab1a909169531ba00360b701403598e79d1f057934b2e62ef64efe38bb8580d779cdee738ffd9ebd8857700e0f88ff90214e0ee581ccd9cdcf8e1b7ab290331c74da9739b510342305eb83c13b9ed67f2f6cd913b68e6cf5478414038051267e2957845711945d9603addc4be182999ce82c149da8150d1557e4dccfed7d80a761ec03fac105f4052a34e3a289fa454f24687eeb7f3abdc3d4bfa0ab5174f5c14638ef5a2cb26f0ea797fb3dc16e734e5fbdea286c37a995d6266c8613d329524d782510a26792218e19caba00e6da5ed0d55362efc800e77c69ceacd4b89cefeceb34cf88f02cb8d3ef1a0b93990faedb582c6457215d83dcea692074c14c10d847f6a3a13567886b372b61e0679a9827d9923cafa4bee3e9872de0b27d03073c5a5fd788a43d968ee639eb1794dea3072c185214d34ab58ffd9574734da5440b1ee596611ab307131e5e8870c18065f25beb8f1486382646ada7c4661d4952f31b664249a51b59fa174c50d2e8c3bc7200df3ca47fdec0baf0dd6de7d282dac24b9c42d7042a9e07b0840a8676a2e7132669c674d9b6bb1c72ceaaa7a776877b1fda9873e150ac5b035e456f5882cb7931d439352e6ca61a20c3d0e63a39c01bae0a8786baece1d8d90a0b6b6834a6af4751b53c8ba592ee4aad93fcb2d16b066961aff5df75f7d1b48f9c7d28a5c50f9813f801c0f3161ecba2a6612f8a31ba45e4e6726954a196c60b05a4cccdfba0000815c2dc59e198e484f9e2beb0acf1c9169f39d5d628350eaae33bf2f0c4f342b01962c4bf9891d97df2bdbb016252a1499d50313f5df0b7e7e7b41b6da477517d81b9cddee9098607631bc0f553f2f77d1bd9e6300f28294e75caeca1a1f7fe860868b52ca864e2bc071114dcf84722edd43e4d600fdd54d029ae1c3353f8ed6b4e288d0fce1c12350100b420160982d89cb81486895161d94603f395a6a23ca0b16280408d7271169b8a7f548e0aace3229d4f63b9b551cd2b1ebb345398b09f77b52070c0e635b39661bc8fdfda998f4498b840471717550d92c447d54269dc41ef6de8da4a3e8fe0f19099a6eeb2986d1c5a748f969cc57829056f7723d03bf73159d4b0d25d0bfcba1a9bca3f08f5776549fda10f99468d9d7ffc531afca15168f8ea41d52229fd07b687fc603247b2b4f97ff51d33ca0cbaa75ecdc1cf0989a60029c142f23fa0fb6d2ada45df77d50d26fa52dd21d6f9d1a5b4629cb8a94b05d2222443b93b4c37e4bb91631c50a85336f5001ffbba5f91d28948e581270f540ef00f9e25f68f4b881cac3590dec4ee289f28f04857fda19973678739e5a51b3d83e01fe44893c3f8565598ab19869e6de533107e6c9deaaa1925490e8b51acd86f4d0ce2cf9938e6af8639685b726ebd6cf6f7d7f8a636915eaeced305b2d39cfd7fe532b714493b84fbd2d3c081173dfd423721cc3094cc47c4a7aa4b1840ff5b214f178d565061e1bc2aca86622d3dc686c839a395c33ffa83e74268b6f7bd34a303b5441d0b912a6f03fbae282623554d43499352a1d8fa0b5cd663d8aa90ef4de1dd5bc4c03a5a23a3d4acdafab732e79a78407fb9932ec464cd69a61ef94086c90d93e8f949a6fe48723b0872386dde8226264fce0cbd47bbc2dca190ae28661b4712125f1a0c05ef3dc107861d5a1bb8c6870fcc3711bda5207d533d5c4c81fa1038abb841609f676b86f7256133e71426f75def8f86c0da66220e2887c07c68407bdf9833221d419bfd3f03827243451f792b03365e24c61c0fea660b2b0a373c67fd48ebc1a7088da04d37460325182fb61a7b9ea5e1f26b66fb8739cee6136b138b2d4aadb294089bdfcc6ca1a3467b1f7bd2fbea506198ad01a46d3c6f62255ea2243bffbda21724c7b46ac50c21bfb160052c0bd403a2c82b969a048771c6b405afc5e96fc2855aa17ed3027225dec33d9256b4de23c5641d564508176651a89309a3433d8e267ec77e25d668f98444e49ce37e229c81e9453dc3ccca2e32e974213c82cd33f0b0f451002281d86157dd22beb5a414828858df46f22d88e28a2d1c5bdc47d94a17da36c302161c3ee549ed0e18ee106e0b4469cc926b92048421bea3b0e8ede51e1c6f26ec46b8289537fdbe8d22a68fd2d09902c4a02f2af68c4e66be4bcdea1262aa62ae0f89b0db734b00441a7600a380ea9820b0a01871c1b287b2f3acbd0f4330203975283ecf30035c4167eaa3f70601c49f5d87c83c29183bd2d49de08323ba6be36e26abc3927721c3f61e2f090dbcc446f9f658b78b97827384835143821b0b69098b520e24038a65eb30678b9ae3acceb268d684be855eff5b426b1a641c582067255fb75209b2646f7f9cc85e180dab9a176d4854af27211507135bc109e8613b0856fd0502ee2636783c4e500a81b737440000e910a052df96d66f67e152d1cc4bbcd09c83dd05a623830f4dc6d780a7d65ec980ab85e2da8d3055dcb77266123d974a665f9b6ba1d5afd34c50e5bce850a4c280ccea0358f34762af5c6f1d055b34406bbe511ae3cdb55ea384adbc0672a0031cc2bd734d27cc0eba53c9e8227a151588ee49474356b8e80788f46146345a96686bc0c1ab086e2dd60dba4c7e5a56b1f7b4763383ea2fb36c740bcd6806e145fa4d603ee4d4de189c3ba71ef3d933ccdd738a9aab868039525e49bee5de95e04d7f8d6a20b20f039eaf03c9066898f18c8db84f2ccdae10932a02711572ce6bf0e794897504297289eab2839c3132a320c392e8a0c10b3c316e6551e09e5f7d5c15d76666023f12f4eef75ae6e372d6ab77171a7acd5f2204b086a3d693fbd39d34dfc353e4c38c212402dfdaba0dd1f7f32df842439f5cc57c9ffbaac1f33e8b936bf4a8a2665035f488fd3db1c032b6412978c3fc408aad8d43b1a8dd2e6cf1efd0c6ea3080b9a8e81a66f5378ccffb4e571093d37da71ed687615d3cbd29e9a7714e47f97c48326796677de894f719dca88a2f84ad9fcd0218592e6b89e520bff803837a4a669c8f9b906562815935ff2050910a21b1cd502b6f22b46da1d4d8c292d32eab2017291ce6fb9888a62ab8a047cf1514cb987a48c3af48ebcd302fac09d429988d7f8a828514ac2bf477f8bea6aa56e32ce6c64f818aa2a96f6f22e7460a5477e6961a10214a0e98f308e61f433e65e952e779b678f2c02ce0146126d9bf8611025dbbae07064e4c75057f1c77c57bf13b5652e480a2b4867c4df0a2b29230a356df3116a2977df4e6b2b9ad656566f69d177c9982b06274098bd66b971a16f4d89e37dadce93e0e8337418ac4766cab5ed06cb17c3ee38f73c354937ef61fad1c985c182726e4a297d93ba14f86dc4fe2eb5175a9479826d4aecd6a2f20a9d2cbf659b17ca3bcdb368e91c9580cdbd2fa0d3eb9fb4076f3ea6780497d48016411df4337cb59ca85e902944c12f83a65c159c66770c673f3a170cf4c7aa33010fbd9fe19d038881ab38f78eecda047103f2f17b6725649fd951cc73ad4d663500029bd7bb2c5ac649713d6d12d20fe6c9e4d66b95c0653c8b14d2224d09ab8ef907d607306c394656d26586db416ca3073bd00689a75701658ce2a77223dc9bafb8f2269a462340717ea25e0810c615ad4e2ca802ec546b6c85aec6ad14042ce52653c03ed7996c2e3c24e7a6a8214657ddb22626e1545bc0902fcc0dde0a810a0eaf279a033e676352e8a9eddfbb5c045c26ca049bf6e36f416b5e225cf3ccac4c7cf8e0de6e08deef47afe8d5aeb82f1dc2d045f17c2fea156189a2ce569e11a16faf623e0353fb551ad0bd3dead7145547fb9d5eb95909e116c07fa59e802f31c1a0cca44cef6239d6fea4d9812ed4d2c5ef901f3930fb321c66a0db4b20c71ff47c3ad4ffd731bdbef8483fef9614a2ba2c96fa12980aef29ed7e0627fc5ed9175217241e555c0404913c8412644b994e53ae0e36efb1b3ad83c8dca478d5929475b52a16b356e481913018f489c4188db0b22e5729f95bd284efc974369ec094ae0958161d4fcaecd9bfd51ebcfd3f79a9ebca4f629e41e89e69240829288e8f10fe63ff77cf805f42a3cab35c7d4cb68a93eacc9a7b728769f290294b73724be9361bc697ffa3414ff5e5ffa8d2c98c4ebfa6f5cc7dcfa037de4921b215c065feaab2cb7988a8d69a9cdfdb79bbd66087d043693385b25557985a1cdccc258bc1bde77e8d3eea07b5617b84bfa6659724fa9d92b8b8ef149684fc900aa78d791f52e2d4edc4d2b9b7b82a6278705a7611b8ba5c0228ea721259185c021cade2fb766609f2afbd27462c312de60b5bbae44299d062c444fc4beb4e68ac8112d75240f47327ab9e65b1a2b79457e9534d77904dbddff65ef3d24297a2b5cba6f9a7ed7af39f807e4e1e557f2792f1168c64348197df735a62bf8e5b5b77293bca9a82fd1d359bbec961d97179eeed3c0785ff27632c3f9124f6c2085b1864065732711cd42853849701d00eafe97c2f44d632534c4728e85b2bd61e55568217318e99d853459d445aea770e323d9959d0a168a868815dd6eb1bf32bd6816b8b818b1a7ef93a03834e51eea1b71442ce8594bca22b4656965aa0609a37ab8b1411c184105a9815f0a87ef7a325167d047f0ad15f06386a3f590061806242b680c2d098321e6e97433125279a77daadcec353fe6657d4ce6c00712faf14d8851f9f5ba7130aa91cbbe05eb33469d18177dfb50d87365c2a420df0067e3375eef9cdc280a92fe20c16cb5f5d5e4d52ed6b448ec300ae54b589d5785382a74bc82cf932678da949e06ca80cc50f31b7afa9af5fb36add06492c433b1a9ea6aae3e6b832cdda243e485c27611b0cee6f9c2a3a3fa0449f8062e84ee4762f4b22dded84dde8735cec93ad14dfa374c1d41e12e0cbcfa2e25ad0165ee2141c088378f426f39d3f3524de114bfdee3f2897307bda74eff8881b18ac60dc1bb9e5b4bc7abf04541e682ccd52829ef098cdcd4a8c168649581d7c9b4a3c1f5041df1cfbbc6943a69d36bcacba09bb0a023809e69c309103aaa0768498461ed694b4f02a9954fee6e00bb10c9249878fb509aa9825342aef59ad461753067c960d4873498566c4efbd67ba7dd29d6e87f00c87374ad6d7e6825c8f4df8b2f4dd6f23d8cf14809faf0ae6866cd948989f5b435fe2f974b12416d086b10f5b43625ae56afc32eb549e8d77afc27988788401a14eae6315cf1b212b85f44518b8abd496d40920404914791461cc1449e977ad01038055bd1111c643a853bcce2cf49e82002251af6084f1fca8f4a9ebd03a28348500204455c708634b8754bf63c32c81ca44aa03001ac3d760648cc45053e44cdb665d6456006ca8193f7402740f18d1db031130412880389e2b0cc6ef408f28c13754c04231ad003f1989ed9308b0ebb7a54153135459b5e8b5ef959c94166aa7aed9384fe58e929ad0fe2be4e06957257abe6a5110f3321a251e7dc04a3e2f837128d38b46e769d6492e4b1d28a694878bbddf07ce9774478ad5a2d7c6bb04166ecca5d7eb85de1ebaa171ef99e300d9d1f754bfab1b96bec6e6b68866e714a7442cb34860e2ddc3ba71016756d3001e8e5c94318753f1c022c4695e6638ad0ae4051a172ec7e2d14b2c7242206cf99cb4132376bc5b281a394578433eef96f4ac100e3552f6246f64addc3aea3278234b6f70b9bc2753a565a03b46ff674c4f50e0f4aa41167d045e604fe9b64f244be88eee4781279078b829f1f84444ff1387863f0dc0e948f6310f5e8c82ec0b776cb1567447f08d1584b702d03b445a5ff4c03feb58fcbe6096900ebf4d665632ec361e1fae1d7957b82e0402bfb84da61bc16f1aca2e23817b19b7153ac24902bf5951fd86675010e3847ef186a110a9f2cf1d70f81396857b4938d9c14b45bfcbd4b811f18185caf165dc4c97f702fd7f79393c8c77fa0180d87159797d5700ef3bfe39df18d8f19fc17dd897981596fe76bc1c110fad6451be6ff824267d2cab87703bdd28019df4321ff99dfe67a1a79d17c90905ca79ae93da162b381580d074cb7f5e160cc42f7ff2e1b76e2460f374054a42e02d9c37a373c70bd87386ed6856277b8455dcfa02c756c8cb957a94f6e031a0ddb568a184b9c92d0dfa122ae3a69d43681b27a1f64a6fad9ff0b7fe5d9d7fe6000301c0a970f428623673d5cceb0b9b031d9f7d531131a2a5ca3a55f64ad94119d7d201fc83d76539428a2fdab897641d593772ea01724134c9cf310baa7b01397511285b8fd433069dcb7adba9db5fe506cf3ace6c8f9386f30829d3e2a650280befc2be5f82606681712f1da8be34aad8a7ea52df44b02eddbeb8cf8d0e9b709b93db97755fb57b813815959641f1b3a9e5ef007da51c7cd81f4497b3c5857ed5b7aa1a7752404c2715cca976f1d0d488938f19a78fb30d0ce2175f86f4e1d26b521b7929e63c253457445b53c529c3934d509e1a9a72715064bfb6e2b36c8af905c4241a819cceb16b98b60023d104303811b1c2740970a84495a896fe4ec5a380d1a2d2b1774b84528584b0ffad06deb46694ad7864f3b372a4f4f7b646436545eef012901f129c07635038aa56d70737f9f413d89d97fb52924a9444d68124baf791180b818814ee235e0b7be5a51d69f75ce20264b9b0f78e99e36b62966cd4d67b6264767b1cb51b56496dbbcc48195952ae23a367e1e77de92ba291e9e36e16c150062b1682ce454e86a47b755ab3c1f60e471835a83ef621e62cb8eda28c34dcdb11ae959a3a8bf00d058553829885517ab4b16b14c4645b14c69e5c827428150666a4f7c6393cb3d115c89e3af23361d586c654a41113c00805ab6f74fc1e232c5f9cca6fb62848b140a842d20974c67e41e14eea2097504fff9259b8522c109bd46d83b84c2ad668cb7296475baa0d624d24a14498fb08d1d6c7c77abe602e701815782d90888f89fa914fa6f7024da75afe468871102f75d85b211c36b48d60e85c41b8990c6cbbc113918b3435f367567c3d566671f90c7e0deb0afc0e8eeacb90e11a73dc6707ac6dd73ba70de6909d81c24f58392a2c960d27568cd67da3fb01a2fa0d3869d27b51ebfc0a40f598cd9c58b9720cb5e6b58303dc2845e2d7af7646315df0466636a756af3333c85924d42d7d7796acad9a3dcc7af16901da104c2c80c29d69ca5a0452287aba4a742b067340d737cbcc59c750ef32c8eaa9e2b42dea321533de695e1c1bfca426bc1e0b66da87680270c90a7102281740404c723a13d4b044b265292a4db82358ed136931d3fd2fef4085ddd0b3e1fb8357595c93d447922f43073c0d39f2dc4a6f5f74ba2b17c492f93162c8092c6c52df48c4468210dd0900c2179f105168e651b495f1c7d21049eaff4201c3d266c280ea5c65583bf0eaad920c342a92b6f4052607fefcfa92e059647cb790842a23b25757d1624b71f4d55523b8ff7c6b1b41bac79ab4ed6a4090f46d06f09c99c53d96216facf16adaa51143d62b496290583e8327fe2ae51ae2beffc0260488d97b46ce6abdb43f4e1e766245b4cfc2373329e3e2c05fc7e0d1843eb6374680857b080a261659e8bc23b63c7cadf97334e8c9374c774799d1cf8d0c6c02d4a8eee140de3b9a15dba4b18ece753b8e8592aa4d9e216145bbc6dfb7391d298652836a875517528705443329793c2ea1300692582b291d97692466e5a7755904befc967618d7569a098a5bc41499b2da2b87700e8b51d1206b6b05791c070a591af98a03e88cf9d0926a617f8fdfefb654ff02aa5916c976d59004619c1cd0df09ab4b3f78ac8ab18cf3ad7b0601f6095c501c43cd23ff967951a2a9cc3a78b7632982335776b71a0677fb9987a01026dc44cfc6241c6f4c36fde3e403b3286e0ced744842a2e743bc6e6cdac9d3c321502112e0d5a4f34347f10c74a774155a22b126600c9a0407d7905d80078fb42b43ad40fc84caef9c0da3dd2c343d6e6897d43588dd3a8ce5f09ae28859d63057f9b8ef9e39ea464cbf571255eecc157700ad2614ea8f4e45d6f679aab5bae34e376c1547f956705e091abaab233e5926c402c1786e86fc1d5221a02234e42e6cd4575d571b359f918e62dd67d8db0fd2ecc1c332fbb2c01678a0f0856173915ad45150e0b62e434d8174ec839077d7528d3352a60b6e41d407fbd3b63bdb420955ca148c550304faf626bd482d26b070d0106e82b49dafe9b7e1d2c47589c265bcf605a70532bbbfa7dce51af921e7f27bf23835a0fa8e78b44e4838f3933756afb696ad3f37bccc69ba8f73a9f40492e1665802d20d88503694cc464957aa123b2be9b592ea81386ee080f9aa620e17f16d7c16e5316b7facc91c6a4c5eb1e5f4f47a5fbd12fb9ad84b3d2c1aaa1dbb9d0be73fa63e299af3895d2af92a23881431ea4a7e9fe07cb76ab60cfcd9de86c36e2b0521b46beb02d5b2ce43cefe1339991ade949e9872b66455482ec278cafba5a5a2e8937147552b406c2da258ccc534560e4f7ad6a65bbcf03d91414e7fb13baef66ab583b16d91190d2aaa6e32c2547b855ff546b8601769f121f569aaa52e2e341ad8081ef616e1989ca019eaa9402c2d514a070f8236cc6b5189d2840e063008303cf73d529b40db8ace45670572cfe7309e2de6fa7610580ede5052a379178e54285932bcf28bd13c86cbd866bfdc56a15f21f91d1cfd5c2af7b738f2de4055a4020f4f603e6f76f3e8cee07b08c217bffa8c90b4b2949f297860450a9a86507f7ad7ad44ddd4fbe3ff7e2be2f52723fae0aa308146a04dfeabb1b3586bda1b5511aaf77da4ed414901c83f53a7bf1ad209c96c28c71a22fa4a4c951342d500a1bf5a32c46c44004b66ccd529e57fc42dbb920cce8853960e40a05cf94c9566c251e69a43a648922c304c8156647ae3212ff36b14c03cfc20abccc6555ba22c8db2bf96f34734e4ec73d18d0402a37bfb1b9247bcdbf6cafc7b1acff21d485bf5443f000eacebbe8a8986b29f329dab4e00fd486f80f39d84e8a373a7bbf3c4b530b7893197dbea98c5323cca6d058948e188a415b8ed5af6cad0a79c8522d17eac5b784892082e02888e27906efc0ca3fd42147c81d8c6b996895fc6d7ecae7c88d31a55e2b48b29a61a1a36d9d9f225aa24bbb1c4ca0b6eab44f3bf1466852cb3c9c902b8ad0cb7bdc7467823a4d55bc355d48282438f283e705bd5d9c223afbc93e90146af53dcb75a33d77b0f681e4b29a3f0a28179532c86031903133dab0039b0670a171b46ccc3fb6a53c3268c12b3dcdef1a6944bac1295b072f1c1647661a54815fb5389c3fa33886de6bdd254df8261756f6b563eb67c95221b541adbbca9a7e9e99c5ac492e6fe82460526204c412a29814acdd71d099b0a676a5bb531559f065058081f6fa84f5c7c4346dac08a45c9d9c125ef5c55a051dc5cb0d98f009b80a87433b5820c120037b1cc36ccb38a6d13440643e64c4f90d4979d947da628d881fc1ef14e5c00da6c927b6d2017145ad034a7046e83a8052db7304a2740041ad4dbb8326299237c2d8796905fc8e6413adf816861b1bf2123a94e8540a708a89c0e11136b9c5533b30e4a936a4c700db206ad493525a8465905a5c935ceba995905a58935ca22a89dc867c2bc7ffbffe06614d6da03c9d37be067883b32a3e1d29945cc37e272f84ae1343283038a5a1d5b20ef255302d199606edca097471a3c876d0fb6779857f7540890965247a4654382d5df5fbb98d94098480f3bc0f235d2120f51cb9d867197fc8e21087f9547ad3b1cd21c2e5f520fea9154388008fa83cb1245a5d5dda05e8211db0ed9b1936f123e8138b563510e776d6db5ee55f1d11814127288e59dd2ee29c0830c6a31ee43c656d848300ae882d15faa09713d6666a243e020194cc48a8d6db7c978681cfda4c3d73ca3622a525dd66aa1fbacd5b9fc9b33bdf907e3855faf9277204b7b996710d963a7eb0d57c271862ea94ca401faddf417c74e838535429d37f3146cdbffad64e82550b794f96377646ec899a9666b666b7faecc8e03d329ddf80c731b2626c54636d6c09a97d59cc9d1a02f3fe023be594f14d78a4ebaa1a1e474efcb113bb110938983a40df70ea6814e4624a10cdbee158547b33064a385d40fa7de158d40b62f128d6c2c54f94e0e574041fa10ad329c2c3ce74ad5cec93359a7aed43913ba1e961b7e96ce29bf1652efca3ee4dbbca929f6c0360c3336f1ac5258976a71ada10ea94182f16d4eee0089b6491c90228f0911eb54993ac1be681ad5da2369c89be7389d9d9177204f3dbd4fea1bc8eb9885690168a88ac29fea012db7a211478a1f8fd8e29f378fcce386fe51ddd1faca20cc5f7a746ed302e22e907efa712f8d060d54fdbb7a4292dec6f1ea5a2919702d8e1267fa328e59d8e93cf17134212f2e1c7b0cc27f0e905ed8b20229e3c0e325fc60ac97798259678866a0d552f821a42e600170641282937f1efcffc3715a2b15d1449e02c8752960d02005ec7efc31d68a3d841093af836d00f6675e615938304e4bd9146ee69df9ee6d42124f5e82486389c930fbe8cf521986e9ae92f55f43cf936920dc0cacadc4cb046830be82091e87f1fbf8f6a8e93d6b53171738043a717dd37efd302b0126a06a6dc9e7bcbe1108694f30fb04fb55d742a05200744e87880935ceb2195906a5893527a8865907ad893527a8865907a54935ccba195983d6a41a6515b44eec26d1c85315a24c44f56cbd005426375168c09bc99c42c9c01e9f8d14ae2a26d41b4356a431ee7b94acf15bac49748539c685c3e0900d53719110ccb6bcfb4945f26c033e8e015ba7ecc907bfaca903a58dd734458d8ea31a2a120aaa90eccf9e705424e5c79340dec247cbaf0394bbf502332fc89a2d154979df2396b016e37b8cacff2e1d1615099d19183142bdc7c122b3266e9df4dbb00654af1223f32e1a5e2915493521c082dfe5d73585e0687f2a9244098757ca89cd8c1cdb7dcb9c4b6f1142ff4f5ecb668466e89226127e584ff1b7da2eb00ab0c7411027ff291733cf4193e7d3ce9270a94812c04ae85c1f79ae3fad6a80811bbd7a6a2f2e47da1b84158012495511759ad254757505da85a54845a273964b1c19d798891084fa11cfa8b080442ae2563d444592942f97c1911d2b46a9c48bb25f0c25481cd8e4ec965f6875f31c4e9e00ecf22ad8eb73d75114c995d3d789761ca7820c97a4d57e6808fdd149b759222c76791945a28148b62e7d7d4dd72fcb2c11580115a69d31deaddebc2540e3e66cfe1e45f23658c27e2ac0339632201f1dcf25229ea628125179dc438d12e30edc34d74a7228c58061c1405246befc305f7c44ad1870d1ebae7c90c4c0f558b913452286abc0689e624e3f6236277bb33472c48c2ca75520c517a035052ee0256d2d4044e525a4a9cd7499ea855a6abb0f86bbb8e7697f7061edf0e09546eaada8252905fa66348140255633440bbc601791e55124339ac4004b258505b47ca6415eef4a723cb79054720b94cc007917da41bcda9b47bbf406947f042e20048922293ded7108b5a3b0e120074b2f6953e4605db61e41e372c91d01cd43f8892e30523622397492e47849f5e04b172f2747f4caba1b1e48e9770a2cbd8d9483458d32f334b272701a7a7f472b60dff5f09bdbdc27bf3acb31969e46df08e2b626f2d5e5e87a1d618eaf2b4a27735c048951b43d8ce6f07d6256cf600f96a85c6a7d4ffbc2ff5be85f2633bd71d0e8e73caf51b08b98af3669c9e70c635a07ad756f1600cf4fb7ac134a91d05c498e1a96e7a03d2c1fdb627e4e9777751c1fb80d11f72ada180c5773629b42b7f5ebe9ba116caab88ea8cbb7598fb24d060d0787aa000efdc958ca6d456e1cf1101ba37b92cab2613cf6117edbb100a2b0580dd7cc86860143a4b4b37dbb206984232ed3725ca5f0ad1686eb4554ea8c2a15d7bd06d7df01eecf737e3ce797e7d9eaa9dd6ecf3b4958ff3dea45cbd780c3ad81093243c6b26994ae2e29d732013e7af861196519d23f076049e3ad6a3b3d394103d4f28fe8832119f5b6fcfffe30f2e0fa13d40d89454a9230227b4a0f7594017209941e50eed5298cfcc971b08bce77b2453df1f2725426b16b73de3f8bac88243e374d041b17d6db40a2933ea2c76cba324c4a401f2481750292b1cc768dc273d247fc98cd4b5717a1c2e6a1cde9bfd7b4aa853379ed40482b556e5f2d7b45471aa0a1ed1d89cf360471217a6c04795742c87160a6eb68f5eb8161e31dcb37f4f22ce965cb652d09a6be47158b204ee2dcf6f2c37f90f6b9006803f68f70c2b1c772d263eb0e5c3008e6ca8b8657861842c31ca7caa8066cd1740fef2864d870386190e1cd5487d9290fb31abd913688cba94b0a75e543a0f6cb4ea77d54419017d0bbd250ce6f44a373a01c941f4cc25cb5929002a20091fd79ba791bfa0a212050b7a1ee705fbe45131caa3503df284593fc988a3ea122acceead9d412bcb185c2b6022d441d89fb9edf2194a8f60804b928109464f3dfd3557b42d6eeb59ab5ca36d0fec8ea59a76278b95ea8211a173114a32699539c66d2211444d92a01301b746e4f25a7e3a8b5e9e5f1129eecb6c61f05c33ff48c4144ba45e00393e2b4e23ad9ffe67c634a5bf6e9c48de5a022899f9b948460a991a87a189da526a30e1f4b904b4d23507cbe81ad2fb5bb1886ff6a229d2331b5ea0b587980dee45ae1656a3a06e0e003ea936fa4598ca676b3933c2999059020cd90b062d319fa58338be25853bb05e5fe2d22cddba013999560218c82d4e16e6aa620468495d4252bb8a93d0fc3131fd8a7c6de155107192676011fac646597f4d53f2c739053a35e05240d3bd8537bb6860b81d515a89d49488dafbc6dc65eed9d7d09f05d09ca402c483a00eb5a34ad939f7ff66f0ad30d366e1bb4962cca5ae7b83434d878fbad95eea6ce7cd405a174bfab39e61b69d582f85ff70f6b15dc8e59dcb003bf27e5523909fddf7320b1b200ff132707a450215591493d922aa404a07cd8b92bcfe3bd054acdae6e41f61f1e8b032ef4c604ea444c3e45f4e7a3e7bb606e27116c0df35a9f3f8b1a4458a4f04ddb3624358460085c355e9a26310e11c5e4d682e9c1e75247e742d7c5306eb23b48541942b14800e3553121bc201fb504b9f613aa1214095077d61de83bf147a7593b4edd5824059bd89b5567a22a7e6884d9bad88ba67c6c258602b632c896d2f9858f394a127288daa4fc82037141d2df83728b537a43a4e4ab6091375f96b98bd6880434064400b0540683f7368840ac78e19cccca8bf756b502796a0f7b64b58f9f7190c169e8ad5554ae01f3015ec27ad3a97ce91cca170de75e4740e16d36474ea198453f0947d7a6ae9339e5f0c0a943b740288790055ee9c3cc9fb5300e7683a6dcce047563814999bb01f804f51d56b34fd401adb4c3020003cbb291aa299e2d643692c1d5853a5de52ffc7bbc38458b21e7274e78e1b5e2526b6e0dc1a7e594502c8ba7c263e5d20497aba625349aa491821daac5a39f9b52c76beaa36f81065f043c8c2a748c6ef5c28893a0822f5d615750b2cb18da82d8c05adb4b83227ef874989cb3cc1e2fc2638b1daa7d2b77d7e20c2d2e8f2f8264c9e0967009bb927a227a114b9aad22af9232403cc1dda1defa8a21da5107390a551df9f19ee2e0791e18b47430b716deaecf107030bcabbcd42df09fc89003f4422c9eb7d00cf44b51b70dd3da1f3e6a9696688939d822138b900b399d39a49182515f7dc195e8492b1f982680dd63f69bdd51a93dfbd836c2ba7a05fef5c4ef078a993e9f5506798dd8edb5742a1c79a8bc9076f19d36cb7b862f842102db2aa12b3c42145179ee59c456dc18eef679f28adff289e31fd89d6c9f2386dfe12d1e13e56d4f34cc0c0869eec1c01e3063ed243e7c6b1b118553c01eef8507d3950a23b56242c45a86f2a6eb7e3b49a40fe39b777fbccfe85c63569b790c2642af9858bd4c119deae86dbc1e4efbce1b933fc96adfcb007cacdb6a954d7e318bb6554a1d8bd7609964d554b21848b8cad06d488a19304cd025b2c6f25d01803204ee719ea7d37b70c3f918a23cb0c8d71c8838826badff2b8f5d8c96ec43861067ff1cdc125a8243093558ee959f1dca4392170f86781bef902423887ae9d663b957fb8392ba755373b194a04384812708c3abbe1dc092317939227b89223a210ab38d3bc881a0067056a2847657f2cd0135bfa8aa8a22c39394e7d74b5a55fbd7f3d05d314fb761f867408aaa3162fc3197ae28dfdd5fd750cc84088fee5c8ab29520607e844377c49381ac0f120342be2bca6a485bd2fa339c3c5e86cba387c2fe688214d156a60d679ee04b6f7c0efa060a30541b07685973df3bbc84db0a8fef3f785f57205e4e196503ee81e41b1e931b43a30578b3fa5bdb0d11e95b8c96a0ac6562f1c5adb59cc6d30b33496ceb4281fb132732a9f16f4be10f151364d31398bcdbc5df948554e5e01f4ea0b476fa6caa7382785b2f0dcadac164870bbca55947f382d3b2420137f0910cdd1c9b7442b7ac80938a7c8f79ac6a41bf074c0982cb74e37dfc98c866785516cd58047204b6ef854dc2b56cb19bafd382ccaae5e2d224467f092145b5136be2c1ea1208925a2f65cb0a86d1431c8a3cd9f40c534494b3af8ac54305c8b661ddcc446f7dffb5fbc4cfe3e8af9c82bc89d84906bcd140de491d1a24eb138a3b331438f030c9082f2e6723a6764f496b5c2620d97e818969a651333a24ac1781e582ff185d9067b61c2a64f1eaceb6c8988569432c862dc32c5d210e542ba8b531882bb5ca5656e8e0c344a9e4248bcb26568f3dadb58cb8cb020ceac40e857b589314243e5761cc5ffb1d4ea8e3433d2b1c2c4d7b66239107a00d2ac8d13d1fcc412576d84244d3207df63b7f7982d55eac514663310213e3e2f5c89c9896054959a8ed6390daf762e0f720be89e13fbf019566698bd20f9672e7744fc142e108844153d928f81c4dd4efa90bc2460573781c95a3664047173eaa5ff4bfe462f6056d17ea797656eb21fe6613a2fd2f16f9bdb5b43458f2a24f81517079e0379dc49456b499be24fd6eaf01a5841142aa21868ff3ac48bc197830b15cd94c1f446da199a479543c802f08fa2100301b32e398f09e55fa95444cbe26c754643cf5443eeddd0e7642563f47bdc2068fc5dec009b84dd2bbd23fe8bfbd4b3743eb95cf800847b1450fb694c05d8aea41f6eadafa157c794f9658cd136ab9f48991c243dcda9a9a3f60beef2b38716ffa71a70cad1c2fd73fd7509664efb0cdfead0938301d2d4aa660a4daba9aefc17bd2beb5925f8805e1e107cb16ad0fdc39c97fc21f8ef32945ea9943eac7783f89319fa00bc52645b24ea583d8ed72fa2c1bb69ed860b6fe36276a6f60b0fae282c4784e04e8caa4822fb10b043c835415950bcf13bbe34af5e713661d695642ccdf2d5c595351c8115ff06f7487e9511b4abc44d99029ef60cfa04cf1f3a7222671b0687d127a43f3482ee9232ba9b08ad4be5f152174a617c38a34dd39487086467358686541072f1cf6c3e8a1a4dd20499f3c5612c01fae01148978d83fc5482c12e27b2a80d7c2d0a578725c797565a19a0187bef5062c86473fe783c54a45ec1914150f389910ccf2bd4b62655a9d68dfa40986fab217bd1bfcf6daffbddd1183cf3a30e2fb2430c539d1c7eee38c69e0101319e48d20c7c0038e3107a6fa0ded221cea0267a781a445ff8c89b1dd6b16988ae3c34245929c40fa16a7a23d3015317122b79ede077d270fd6fc35996b6680240dfd0f3a159f5d3adfa9b8b2e7ff54a1979b3b1e193d3a07b7168054b849d1508221ebf653dee3b845441343c271c811093f16342b5c608b5828844aeb699fef7230edb9b72ef00fe99936172375dacca97dda668320bfe122eaa2e6489536901c7664d4dcc4633ece5ff054c26b12b9bd06d2f15a888d8a0cc060420c2a8db30df24745e334962d79e62913adff036d36e3a4438a649c562938631eda2e3dbfb7f84813a75311e02cd61b2b35efa310371495205f76349ef2c890f2f509485481879e346653074995f139934357497f70eac5462506349521fec789dfdce40eef567a1dd79bc796c0ee4c7f7046cbba717798a9ce0632984423976e2c6d989ec04f0f65b8b5015117e6ea9a46638f224991320ccdcb87d1c35a3194d600951ac508a53da6882f0bf6a7bd666c83322e3e0f88c1b552a98cb03f11d14857639890f69866311e6f0e4c42d5652525c528a7d909c75a950358b983d450ae251f14958ae12f9e8dfdea3a14ff9dae7844c5009cdfe78280e88d2602e29c849c5a653c4d7fcf90c361f21993a02d0ff126661d8e41f77054c4bed052aa0bdbccb5080369e17254fbca854297a6372feca95aab05c0d654f9f4bdf0ca233404d4e4830e09037a86867ed522ad773e95375fd815a27e52d9e49290623479ceb618b39de0df0b7bb4b89cd10993a98b0f6eee6795d42bdbce9e714303ea9a3745e1886505c539a7a6dab1fd283784e284c96c8eb4c38514dc1a459cdcc7cfb36f32a79265026cfe081a68d7dbc47a3b3f6df15e0fe97491bebdf0a6b9874465d9e7393ff98b10a7e934fb3a07fe0fe96b74b72e778a1633ea975d2cd0d1710be10e2a6699dfbafc3d98c653efb322034c274bbe007cfb02636b47ad8bdf83d9fa8bd8c97b3af97b0cfd83e45c09feee739c8c985422fe3a82b0abddd278054d925323cf12a7ccb0cdeff710c9159538e39e56e3e5ba81a15b44419fdbe32b49885b293234f7270b05ffb32f81c77fccbf19f7535ecfeb7a9153a220143033498dc0ab1c0278d02874cd3bca658cdc5cdd2cb06fc825e16133847036476f9cb173b383ac80fe8c983bf9082471deb127cfe134688ba2ed791557a58ee1f0887406cc6995c683f05ea4a38cdef8c5e8690084a77d4625fad604c3d8f91e0aeefc3de70ea40b36e13f515710cad4ccb07a6fa6cffd22699eaf99cdfadd377812118ddb315cfffece6020618eaea66c5a9d8bc100f485a88fec0bc86b4176d63e686a1621d6d66bed35c160c4f60ce97512d4550e299be3927caf295f8be2069586d14ffefe83b2a0bcae88a7d9ae1b5df655cc0a6f2b1c3955a162530842c92a8d1e5ee99f10e4ed3d28be3d968bda2e5942bcb84432198eb0c3562dde58a13ac3294106109697f02225d4342d0728ed28bc94e0d3c61d318aea4b29409e554fb15bdb6924c212cc3de7c4189dae5646b0797d7114e14fa5144dd1c56d7f1463d5e36f7270fa66451399ceb7baf56b8b3f06900637554daada54db947cb6bc2421fa461b5e8645b782ef10828e33c362c9d6bdafde7a6a9c4a35f16d584adb423506e0567aef067b77d669340718b05059cbb30bc568d9991a6a254b34a980213587cdd4bbd2dc038a91492132f1fcf48c998e400ca303601cbe44171168337449dcba4e33d4c8ff88cd25b70a6de3bbdb15817b312b91393b6f50b7dd3ef2a568bcb7e7a0cb6fe4c58767379b9b006bd802d8fa7ef1474a9571f43a21af5bc460eb6c90d7479f1b31daae8a35cd8ce4257f6647f7002eb66cfa8a49b3909762eec5ea06aaeb5d3937e544d3116075a52b62420f6422b824d46ec2feb00567e24e64e2aba14d14b73a2035f1e683d8883d2fb9198c342b8a2f135376681bb64763bbbcc3488dc6a4ed2ef24e316527e243962dcf67e731043c6205da58dbae531048cd361d18e46b2683f0daba910b20117195bfa60cf7c410dcb139327312d954607e4dc46ea9145bcafd1fd9d6d2ee265d3f149c216dfe7fdcded8ec1f8bb66ad4c13f42abf53083ce32017739c08e843505930ada0a0e0bde7c8e12a0474bbe2506b05941c76ba7fb7b9ea7cd4a64a004a6a453c202ca04a827503839e491c314a31c463940c98127490f928c91a495a4851103e06040968e8f31cd137bb6b57b15d9afa616752f6c81de77d5b2dcf37f9e6d8de1fba0e34ab96a6f2acd2f87cd9e787a7c1f87ff727cad6387cdefb1f5367fd9ce31e7d90effed569e473b1ce92aa741dd1fd37bacc9d9cd762fb71e0d2aff47b96a37cfb32dcf9572d2f368b749fc45f56fd89a3cf1ab8dcdfbd9fb964eb4788274a3a0cd33870100c0c10e1c9c18e1c08343dff0811b88483f59e5a7d8a26eaa738b7268ded79474197c4e8bf9c9e77d8d8a9c9725e77de9a7781f88fbea976ad24f71deef9ee5f83e516c02a5e6fd5497c1cf8961eeced75fed53184aca86a8861d0e149beda3b82a4d61d8ec41a1566e02435ca98653a18d4db0944ab9c9e66f882d08d3e1a92f5b834f0d40dd11c79ec83fea9848a65f44bd77d2dd447a548307e9510d1d918ce946b2c391211c49e30811478ad4a431aae9007e3056a9973a3c33f6f2f69796ec9d99e4eeaa1413c9e1f1bc3d4ba826c8881f2323604646b48c8c38c04890ee48c21cd5624f844261f039cfd86e2a8fb27484f413afcdf359194789634f24dd664c8a3d915084a36389f4514b770e4f04bb5b156bbabb8d70cfc234f6498ce7d7694b371e9d667b7333513132615302ba4f7f5bad774c47458628d2a48877e895ca1ad6e9344d8c68724a7894565062a2c443c904eeafc9e61675946c614a1db4274c517e8aa5d35f9296ad0dc237647db47adfddedda11c94a911e91a0905044e22032860812ddf581b0f4546d9d168f88c43022d2349880868e86d10c777447eeff36918e33fd74feb650b1beec7430f4ffdb64062f6600bb6389fb22ea8d66207577835d96a14c576bb15b2c431832dcee58fa22ea3d49954a0df16234c4ca90537743e991103142ae0839c5c0c6046228e2633ac658eed59f683d0f7fe7a21173fefaaa71193f0bb4d9abb545fefa8aaded36af5f9fe5aad4e59adeacf56539ff3cafd7d4d41c71ee5dd79eaa8b62ff2ff4be3571e7a29d94dc6fee79d3e5b1ac02caf15105e93072e9421620c702ace8eeefb2dfcfb6286593317da2f2c953e3523e2b6f7ea55ee5a9ef5dfb37043505b44001593a661a63a9460a00522920484718fa18c120064f150c5dba391c18402f576bb3c9f360707ced2801613091f75834579a002e3e4a4047614209604211a0c7bbdea345f4828ef3dcf0e3e07024ecf0dcf1bc899342a15628d46a740034b01c40c9e8051ebc1046f7c2014641f80832c52808064641988c8204796cbb5796d647cbb0c753fa49d648208f364fd0e62bfd644dcff2be8b5595df1074ec02132e30e9ee58c2a45cffc340b420460b75d4424e470e4f7f03cc61802c2303e4e63e68c482162316aeb050c382afe08215c2d10a52563061947519654e645132201d2bc5a66f6213a64da2ac8979a18df95f22a18d41f15892dd96274bfe3771a56e9aa18bb2be892927fd1461efb56cf697d2bf085b6f7e919d8e831ed3d7e73aef0c5d1dfedbf2e9f53541a1d14802a323a391094d431333b69e7b9e8a86c6e36fdbacb4865f2de72deaf26f084a8fb11ca5830f8f4f904a4aac50bafb841ecd8831c3cdcc50001c8c0a90c5e32cbd0ffa046d96f7146dd088006610c00204c021c0080330c100f26800308c0430a66dbd78de389e2eafcddb6c9223bd65e7579ba784cd1e7b1f0890763118dd2cc6d3253e4d3c12400fddbda44702806114800008310a005077473cbbf9f258494a12ae76fa17d912f714662f4872ef893e83543863a48210231572e8ee00f44885385241480a3488dcfc4e91ab336f13f6428e92da6fd7b6acb7bcd93dcbab8dc1ecf5e65fe9271450941c3725a6d2f3e626f374bdf7dc83b1d583b1ac79dedcbc695b9507cf1d146ad5e1d9692978f728055005040e9404e2a51bcfd70848fd190191ddddaa119012460000d3f25aff6b71a437379d262300748fe7dfef562300e400a063defebbd508052f1dffb31b9e1d47371a0a8d5090ae1b82f63e10a76aab8f50f02ef64e9eb39b0d3884a90fbd1925f64e6cb68f928abd9313c6e88e2dfbd3a3137630c18f91095eba3bfe0db7adc8ded813b1421cbacdd9daa098474343431361b4f517c42313b27477f43cea6464426b64c2936e593a3232018551095fba5bb67e5402964cbf27ba4f347b4625d87483f33bc744de631eeb7b8c55ac97f2ae0976f7931e91208311096074b7cc8f6152a865258e9425212fe238a49fd7d717d2793c3b30b5b2d93e4a9e79e6cc3c398cb76a6beb83f0bc3a28d42a8542ad34ceb3a311c87437952a1e89accdaad10859ba23473d4c39cf2a71eebb2dd317f68c431f7ca8ca7dcb85984a15ff6e351ac1d5eddf65a1d7d7d4caa6f563bb17ccde78dedc46cdc6a8b17434a1478d803f56d031f644afaf25f9fa2a49fe87107fd83f6cfe98e96e273d5384328a00eb583a42faf1532c53980f1912350dd398dbec960b854c9e373799ad0d925c9d960b71aedfe5b0082a229469226ce9ee48842771a2502b2df644a9d47d6efe0e0ab5b2b181d91b3f5e7e20e96e9d9e19021e5a0c418a215019c20166842026ce6e0b7149be2479a631af5a96e7d157972deb66d8cf3a63d7d2a2d93d0bd3cf484c085608a1270841084215317fccebcfaed6e981eeaaf4afedf27f1e18ab959a26dde66a9dbb76f38052f7ff3e779fc3d9da9b43b0c37367a80609ecb9da87148d427ddb94548964f36d535238dc8064274908041600010442023aca929b381e922c9a9dd7b015724aa4e74d1ce9b3863f30e30742443cbb1f0c60868f33f8b8c287a987aab8c994bd1063ba031fc4a087aa987c80419bf7e8c11e507437cef4fb2eab62a6cefdb5d576afb0db03883d76b8b9070b3d00d343554c25f96d53b8fa42f273cb92eabcff72fcdfa40747f4209b4cb93e0fcee86e1e40c18327188738cffb52787042191994b93254c533b69b9bb217e2afd7b2489e298e553553c6a63bea01043daad0e349c71c5ae7fea3395feba6f9cda9c70a79cc9147961eaae2393cde8333e55aebd3b9d9a9f298c9838487197844000f23778cb903cb5015b7d6e531afde2fb22eeff1c83d08dacbc5becb38b4f75d7572259b7dfe25c598076ed3037de68e1a76e0c60eb0f4509589b38318ec88419bbeeb329db18365870e75ec5107143d54c54d60e8cfb2d964bab8d2f9e51fbc8ea23a9e74cbead411840c196ca252059c377b9ddc0b91f9820c13646c3c6f6ed8562dc6cdbf8ea9d351043a66408795ee1eaa62a25225564b73fd22df689e9fcde1accfa22349be5f54292765909ea163668e2fb104c27e0e2dddf1dad6cc1cf27e6b0e20ddfd1f472728470c2afdeee7e7301139b2c891a49bb54d104fcf270667771fc863c972b65a3c5f337274c4b38b434c8cc38a7b1ecdd8ff4eeec39d9e89a326729fff827215870b3af8d2f67acbdaead984e7ebbb950ebaee9e5eb7d579faab03520ee0e8ee38f3e43c0759704873203bdecf1e6dad7200e4da56c5419758aded307d79ace47993e4f5655738984077f7c5a9548994fab629a994e74d9c148e09399ebeb2b1f9b629291b98bd4965fc5ca61f7d2205490d610adbc0eccd1b366ec4d1717a7da2993d6f9ab8af6188ff5b336e4419421b73b491a58d1b66da283246cccc980e7477a4f9cf621a25d5d2fc5ecc8bd1645b69bcb0156257c5e7e6572dad4433ff77b9daaf5483cd4f5a9f18f3bc79adc7bccda50b7fb27b6d015b88f16228200c3fc20813657def04eba187fae37913c78b780f5ec485a4c9f3a824faf9f1288f488f79d4b317be378b68f843bc26ed2514116811818e0800600618140003e80b3fbeb8e28b27bed821047f84808c1078dd4ee35ec41fcf9b96fd16f5ae6db1c2f0f323252197f5483f92e4db9d8f3d7667a594c751f25ac7b2099638f44037c9237ef2169d5e70f1a2025e04e1c5932e43e8c245975817b0cb922e0b00011810740182211000f9801a1f20227e9f29ca4ff18bace988a37e8cd49f96a5c13f0b45173299ea8f9fe2b5a76aa108917e4a24cfd24f25923fbd366bf7bb2cfd14f34ed5bae99452c1664fcae6bbd7b55d88e9ee2eb874778ce1f9eaa2eb22081761ba3d2f5f2e94e06227d3f96d2a2e82fcdd824c473c555b80516d8145b545d7718b9d2d62757a8f1664a2165cb4b092c323b560d2ddd6470b8f116d340b6521a63b5edbcaa24b776791a5b3e8ba3d8b9def5659d434c962860b1c2dc405061d75fe5b3636307bc30a31476285788405998e1e163b60d1e522eff187afe8b962872b1270c58c074ae081ec81ea81177c1024a5fc6fc55822e577c51209bf47c1cf6eb64a2951279b431fe804bfb56a51ef40131f585185153d5811ad28a23a0c49dbaa3c54726859d3a59f72ace2882aaa74cce1c1a1020c152f2a3a2a945011e4546da538cca758ba96e4718bddb26d61c014774ca18129f289f3ac4b20a0d2eb2bc9311156f87da6d7b242213959d7e59a580a3ca44881143f483124c54c146644e125a84271c5e9312f0a20509001451728948082080a295090c0012f58060579112732c58bf894fc42b6f5fa0ada0b821c10c2811736e0c606b244956d557ae77b381b3035c7a99e70c113603cf17ae2478b10b480d11d652988f4f3adfffcdf62cdfb2d2daf65001af843035c340093deb2ac10737fffe609fe6585d83fc6c24432302603626420890cf864a0a69be354ac3b2fc7a99c90e2849026f468e28d2648d044144dec34a102137630f104132a0cf0a09fb3dbc9e594137767278562e9443a5d19747297a5295ec485bc880739a69e51a895c6bde7fd56a378941d9e9e90e4ea949815624c248561b3c74da5d40e6e22f99052c1668f6d55fa2dcc794f9bb8a934f3cc71d3cc3387942dbd9c47e7f7ac7fa55a1ae802bde97a6fe69993b1f6d9dbb279092cb1b4444d37ebb9e7b2f4d11db3b4200b0c5bcf73584d3a3964659912b3d4e42cb9d2ef025ffa025b2eb0d34db9bf9b941e637933db0bb0d0dd4ac040095847256abadbe624cac412479a2fa124b6742c250176ac376425e1dc5f4ac2214682f3ec0ee7d90e092e4858e997f56e9060d2b14c919831724418d5115b3a5a9b8fe8baa3fc663d224977944d8e00124f7fc19bffdbd25e5f51a895c679d608cf88231618d39d1f3f868229114cbb6c01222c80044b1b584680050a2c56ea0dbbcda31efd2e17cdf7fca590dcd61cf1bcd9a24574bec20ab851012c15b8a188378a10a3880a14f12a82060ad080022f0ac470058e0e6f26b9dbec25d2dcdc8467e79e87e57ccfda18b7fc6ec2267c3f56678c05fe05431b33cdfb2c3795be887a5303ff89a4578cb5f75813e77a858b2b563aae7e7e827e635d216102694c000313d099c04cc76b89dcbf4d2447c31fa847c41d4434d1ddd1e4784e980e112409e42181342460250ef1bedcc588ca75ce90430e91680e51f1efb2044856c644dce56a3fafe1a3b0187274562a1c9d20209d9cd37fb37ad524354d4eeaa9ac3861c587ee8e325b7ba563ca43b4d9271c0dbaffb71501383a0e55a152c5ab2a02572250c33da9863942a04c571504b66c10c8618832dd1d71381c623cbb21be0cf18ad3b62a8f10550826dd33082382000a2249f705020a10088895111b2246145340ac04981d300cc06e6026bc82f0aae2f5a40808455b90bceb3e3d229114f112294f2d1e414924250d09678a474853543dd251f273c8a237e7e93d5c9d9bab52d7fd4f2239c23d6d51ce7e353471fefa9a316dcd2f9a9b12aefefd22ebd884ed2bfbbbeef71409294a210c4178a53b4a2e8f19e532b2057294bca423965ccef384e3aaf43e38c242e84ea1502b1b98bd99db47b944b8605e585eae3bce7bc20f67749c3f0c01c974e4eab49eed5edf77f9b379aed02350c7f6c046611de047055393a849469f1d1f139febdb91d23defb9f999a434d13addb4f298f1176e9712fd48c7c1919e37711ecf1b140a8592138cb11c63daad481eedb59f9334ee51d956d8c7eac42caf8a913701cf08911a444510492122c107187494ac106bdce329dd84e4088d03c9231a9ef854a70782f37aaef315da18d136330e6d2cdb7a0a717d9e3aa3ab5a6f3689b16ab9f031e8dcbf10f739b41a472738ef6bda9d9cfd6ecb87208da9fb3885a369968c69476bbaa3cbff4e22a30e8f4e4b63ea2725f97ddeaea8abe94ce7866955b9c694542357182e697aea9aa0ca55e3b9ba3b7e2b8d96958e527a97af7db57628d1bff148fc948e3153cc63cd6c7d7cd2a2d6501513952a1ebfa6fb0babed569c05381bba7d43632b62abe98e46a663225f6db5947ee7aab4e48ab14cefd9ebd7e2b0fa7056185696282bed9111531c162ce6b0bc239e2f156b86e9ee38b98c265077fccf626fd61964ab3cb24cfcf0075a43e7eae491602e9da0e3c8236492881dfc2772554ac4fd571d3cae24900e4f4ff3d28da996ef3f2ba475f16a3b3e1aa93b6a40a2d003c0349edd0336d01debc449e1ea8545956ab0f9a552b9fe96425243eb2f9842524308fe68da17d6541530ddb14472eea9fc41850b2a4c50d1a8b8d0430c463d70d1c3aa3b66ecf95d9a0c35ad24ff63137493e45e92ec6b859fdee4fc7dcef6482965ccd4710cc170c5431f3cac80870bf030a53bc658eea727a6d213d28fa3fc143f68ca988e539648d143ca0ae2571e8fae6afd59ddca562943524c33bead51b888124594a2283274a34ed6c74ff186969648ee363fcb84a9ca23141a408941a9e98ef8feab5bad8258e9ac8aac5e103a432805424542a6ee8ed7d23beb6fd83d8f12e5ce4541ec422521d24fac79762f3b3b5c1fe3bfa10d3ffcc1a58cd52032826c5029c81d0047b439cefbd887c498f799565a5fef802bddd1c43dd108e80fa0138c8030508dbfec8d9f6209574b49bee2e9e1798242e1bccd3ce756f221b5ac37899c2496fe3fdc8f0b3e73f82ce19343cf1e3d51f4ecf4087922832b4f943c998926ec81f64127da226db5b0cd917b1d8ff23d524c74add6f2676934b6bac9eb77523cfee6b67bc1e6b553d32afda4d344c7f4ce22271e70929dfca0d023273c3bf0b10398ee538c3d91143f45cccdbf7e8aa598472dc9635eae33e34a4ba70f22edb0ea6e1e9e3178409eb873831d27563b2be83046c76aab05c30f2ab2ac29254f941c0a8529f761cb9bf8feb78a57fab51e91446fd297f59ee598ca7804f577bacc38e4a4737f6fb646a6ff56ede6f86fe89ed784084dee68724593294d00a023860e918e4dce0e72acc8e17266bae30b45282d69ca3c359cdee289a5161d42a1a8fc47333ed99294fe8f498e896ad86dcefd9c757e45f6e35e936efa1a3d2f6c7125120a15db50a8156bd6f91ee6ea7f0e2df716f4e9231c2eba3be3f8e014b9211387647e2ca9084589a53c9e37b265c1d05beecd4de806bc41806a072a2daa1c9500ba23aed666c72dea8f9f687e3d8efa6c7d220bdbae32e911132b980cc1644a47cfa3446e739ef3be57fa712a146a8513b9bff75b0df8c0a8015334006c000252605239d5a5663ac63285551e8ff8b9fae029ca589d5faaa1a4c255cb42a15ade0b918624572d4b52a95022572d2bb4b11bda9857ea4d6e42891d9e190cb3d4407072aa253758e2654913dd5d3a42fac10173738bfdbff961c9f17c779ac0f93906e70bb3b2c9c1f902e74b48c90c194a8f0c654486428dd0dd4e3acb218c1c8ac872d8e98ed26355e5dcfcdb65232efd14a9f453960488243f242965495e7840670c28010340203b63c04f191ca2c00196d3b298baf42143e2bc13487acbe2ff2646f090213187c75f5f8ddcffecf6377e1a12a1b834c27d7dc50c87ce6e40c1bcf2ad0d8a6136713c3eaddcb0d39dad876776833f774367367c89d7663658e96e1b76ba3b5e0b640358830fbaaa348e67d6008b355c3b1d8a0d28808401f78160b347c6bc96a5d25b99e2a73767bf29eef3cc47b868cfdaec88921a32354cd4f4504363e40646ae18c1c1c84cecb68c7dd210e313f7a5134f67d14bb7472cdd1d2bdd9e157722906e999d64707a65272327201d8f609650916731a6dca5262fd26d6ed9cce4848949777c7d35cda88ac451a4a80850772c559a6be8d9a25984d4ddaa8ca68c8a8605dd3dbb154d161af939a3210dc9fa4f60b3a7a704a63ecba7640536bfac34a5bbdbe66c2bc98d8c1405c9878ce4117f5a687df8ff3222615c9512f1ac053322b08ea5d2d421b2c3512f915c45a423f7190d61a2ad3440d11d1aa141a361c6e6589d9eb56036031cd8facca065869de8e1b9f1b82a9bc19b4c77c479863f3c3765c042063b54a9673204e9f8b215281b026608ac3feacdbf4376fa5acf8674770b8943889688ef739b15c251214cba1d16c30d71b63e31cc208620babb5aebc5e6a7bdbe62edf535f5faba803da05880ce0b9d2d204a8eba02b0c814102a60860c063d3218c6c860a0000c3634f7955a1fea833f2894d3445ac4a59bb86ceb741a1c3d6b13204702509025e04a4757493fb9e4a8749a9675291d4f8ef62043c00910e0650880e100651c60b236217f29087b211ebfc89f68e679fdce77cf2bf9a5955a1f827e48dc5fea590b86aa17f878e18aec8508bc700a32a6e3f43aed4d1c92544c5fb86dd314b96a6fb2204fb2202b642ed891b9e082ee3675e64214990ba10b60e682c721099b3d3a68de6459cfe5590b741fc8b316d460133635d81caad4ee50698fa4e2f985fce4955a1f5ab8226b61b630d3420a0610c3005e18a04a779458cb8fc3967569924b62f0968d72c912e722b52c259db15044c6c2938c051ab2157490ada0816c0528d90a455678215b4177e75aa9ad5976253365a33b3ab2aef50944447e76f4c3e8003365cc70d1ec497a6098656cce028c29c095ac003a590148c8082003024441001e022c19801d03706200b028bd498bf8555279e290749a388442492a8e6962d87ae227a789dcbbabd29f799b2eaba599004001bc9005808c007859005a5021052a0491a910aae0590a45c852b8234b810b003a4b818a149e74f792ce52284096420940b8c88048200302cb80cc90015161041900ba8e4f44bdcdb4e3b1143e2579ec4e8c7ffbea2dea9e37877880a0370f0d31a14a7f39a28d12dd6f6d96d2441c1f12b937553af3fd0d4ae43ed36f5ddb9aff596ceb574bb7cce344a494190a6664288091a190050a2b1400704208b21336d0dd419d9db0d3fd513762c4a39fa4d3c4eff1138dca87486bb1e3f73a37ea811f95a69b88f17b769a90850941990908c84ad84156821b59091628c1ebe85977aed607adf62ccd668dc8e6ff7cf5e3f3a08d484cf1088a4ed7e74cab4b9e8c043b3212329091d075f74f6724bc30827bd616651afef09ebd3c505a34f344d3b5b037b5bcc7999a6836821ad90856b211e40fa0b3114859b391751259ffcb7aa7785f5ad8fcfc9475e98f35fee8d21da7fd32cb8b9eb53efa83e88f27dd1d7dfe38a1085e14818722a4400430e27759feb53dd647fee3fb3874551a63f97f164bcf5a9008980831f811043fb8f881449b86240a25a998b2b5d7b316f4ac05b9d752f3be761f855a713c54f3ac053f7b5bda7b39dbdb65d8fc529f67d0d55225522ab5b281e2263765fa4aad6ce67dcd560d363fadd22f5b7b2f2b65935ad9e0f76cd07bf6de57c17481dc94f271d38e0d47b5fc18889bd68b4db042c1a95450ce8ecd6405e5ecdce7e6e7a97176bbb6a5e197067eb7c33bdeccd5a69eb338afafdcfc0e0c532a94522537a5766c3248b2d93e8a86af65e517caf53f7c8b2c38f3e4b46a29f719537c1ffc1be2799fa3dcfc8edbb22dd26299eed83c1014a7e67d3e5eb63ac787956e3e1ccffb3e08d320ecb147bb97fb20fb60017bf4e191ab4f74e7e7262c577b7c19ed8145665bf700ea3d50e88198eed8032cdcfc1daff454c31edc406dd6f0bccf0322784053c606655e50268832347aa4a147187a687a8cf2f8230f2df2c012f1bdd46326ae7a74b662a64a27f5fe3f5abf7eb7f2d8e1168f87b66269b3e9da3c96e0a1071e4f8cf050e111a4a31462c996f51f1e531cb94abf4ce72bf6f894f16cb5bcc75fad7fce1d5feee0401c9254520f0415eab63c4b19367bb84f457193939dfba952aeffd91e146af5b2750705f8c30e2ceca062070d76b450070bea58a20e9b3a8844991f6bf73daf1485a239d25b96e4712379b5f4889f4ad89b3f92e452c6580e7e33b34c8f6707fe83dc13d9a24a534478ae88f0cc64b6b0e693b1d24d264977d33a373ae0989ee7afaf7f9d08cf3ca2e30b1d5b383a649feae3900e524f90d2318717fa41732481f3dff421c273959aa3660e20728891834b9503ec9e33e6e1d07eb16ae5f021c2337beabf49532894e74d1c2e955ad9bc07862b9beda3c4d14529e320d9aa8330d46bb56cb5261d7489b127721f1d28e1dc4e6a0341154f0edec8810972b0440e94e4e0d42d758ce8389128597242fa9151ba4eccf359f9ceef482e71ec9db8131d2fe24e50f53d0d7739546935a582ca4d97e354a624a18e9b928444f3a568afaf389871f5c05103389270151c40ded0c11b69bcf18137a6bc218334b96697e98bb3f5d4a28e89ae7db5ac733cfe5ddeeee7ce453d49391ec7446e8f1b52b8a1841b366df0d1060fda88a20d1d3a5e696a592ecc16f4589daaf8d5ba262be6dd8f796ec24fb337eb837e2df79872f2afdd70867d45a156f74130d45c9355f417e4e6771e9db924e4a62824ae86b6d3b48cad37264b8f61d29163038eee188b8d2d6c4c894fd8b8b6b58697e69e35bb35ac346c7e6b00ad01a4f37fa31b7ce96e2837c0d2dd7d839d1bccb4983062b2e09615c3a43be674be46628274c7fa44f6b21c5335c474776335b8740c0c3d3f56037777b785b62863ebc191c617691091462b0def8e428468ab9f9fa0d5cf4f101a4ea0a183469033c29c61c41940feac7f218facb701183688d9c0c60c3ecc186386968e9946e96f3261951908a8411ca18de1d42056832361d80883459823c2dc8429d100b33ebb85dbcd365644b310213400a2c1cc0cce98010866e09a010d32c8810c9088a51229c66e3c3a5bd6b1f5361914033131a0c0280641c48006188481c1153058c1808617a0f102235e60039ea03f0bdb98d4a4e7692dab0d711a975e44e22772c11c2eb8800b72c014010c18606031b431f718cb7a72bedc84bfdb425be4d772ef2821427c7eb8e1bf2df8410b3ed082b005475a904277b7ece954edaaa809e6ead3db62c1976e16687916809505350d44197d7497b1c56d2d0374dd598677cf8ca76a0568e4d50aee0a90a8600f158c11ffafcdf7c1f9dde6dd167e3e3de92d9e50053d7da4804b47ccd5d9d109ce2f56a7f55280848c36c8f8d211086a7255ca714f460f6414d1a4fba876562ea4d3c3b3729da09f1ee93e3f9ea3f3e4890ac7756e847876b814921a522592ab5257a55fb47487ae2f5d7f4982823ebadaff2c56a1c04b4714c05000d4f15a47828220fdfad609c0589f13c0589e89545f9de004331d71e9affd481eabd3045e9c621380b67afe989b5426f031cac499e7185a7abe5463482f65a395a0a7040b20c11b24c842029b8e383d907ee2fdd3742f7578a1c24b125ea27819418cdc8f698dc9898e8a81de3b6fc6d8e0a83140d82042b7a34c4d2a459d3eca06096bfc608d3cd690630d34d638a37d899b7cde772f1898a28c0f51e911ea58a3238107dd6d020965b8ffc123debe68e27975f0901b8ca0699af66d30ad6bd0a06a68694705fd0405e9780c31c4a086901a6a2451a3a61d755f33e2282338f4c01b62cd44b54c0d01f892254bba4780401a5ebadbf3bcf747a8693c208da0349274b7d5be0d9629f7dab7c1b4cfd230401a24a0010434cce82e69247794c951953a08cebbfa50285c2dc528d48d191a0100eac9d9a6077a87e70e0ab5d158a6deccd7f6dcab43f4a393d9a0cb6cf024b3c1936e47e10409dd1d2839d872a1d07dd6bc49996cae6de279130707855281a3360a0a854269f5398e761a05a70662ace16c3d7c3bf00bb523bff1582ec43785c39123478e1c3952732487240cc0e1061b6a4072047fb64536363636cf9a3736cf9a2cbb42a164703cdda664425243b803c773ff3ee52c0e2b367bb84a89aef561e63a5fb655796cabd2ff66fd6f56edd332f5c19b1f85d14ffb3cbf6d0af7efb1429bc355eae4c158d62a75d94fcb53fbd617511786cd8fa35aec7ef8c3b5f489369a533514d97cdb14140a491197fa6f69457f535ec05cea05092f3578f17e8e4edc6d3eb1aac488414b250611621809638e309608634916c64cfc6a3f6b2f4bc6587ead945fe9e726495289e04b77144196ee6b5b52707eba9b486722902d826ac31618707483d1a5bb27088615ec604cb9f3c1f02fca74c72fbe48f9024bb7b55f4cb976feeb8b245908f8e8965a2a95ba5f69cf8fcd6b06d9c0ec8df69a17a7f0fd6c8b3498176e78c1c50b21aa6db5a417345dc474f1d21d87e46bf9b1cddef421144a52597519ea920208bc00810441ea03643a9630c96dde2a8dd5196b79ac4e99250a2533ec2bf5b6d999843ec0c30748e8228f2e34d0c5d6854d7724da26eba76732dd78546d1c174b70e103172e6c4146b685175bf4b0c5095a84916971450b2add1dadf50941536cbe3ce679f4958dfc47f37c51bb8a2592635ce77f3ad569686868a8fd6b7b3cc6bc96a67db555d36c4b2885a5601c215caa376ef2ac6591565960e9ce02288b202d87dc245dc5a54cc45cba74472e30e70214c50271e98eb3da0c0b2e58c0b008d21d63e12bc874bce2045764b9a2bbd6845557d4747ba08f1d0f70e9fa5f7829e701a04c410fd4bcebce0ef491435607b80e64e900ce3ae0568421dd58c105c4b14276c796752bdc8a2abe54a1255601547213a98a1a322a64d0b1ab54e3709e1a15413e2a7acb97ee8e5bb27cb7dac2a43bdb12a4a7d081b539d5e1b933853685499a384aa58abc2f90c2082974900288775ff4f7c6a3f062eaf0eca854f11805d85124e98e02484321a6e385424b08423125629cadf581a2a62307fac88fbd42e180976e8aad7300e4400db701325f932840d906bafc06acd81d6f5edbdac0cc743cbb27c4f4135c9e98d2fd04490b192d30d012765ce52f632d41320d1069c0851364e03390394102275a4ec426c4644d7439599c635ea5afa93d910531d17ccf396a8ba6db8cf3bbf2c7f07dbccd56cd7376ab31cf23c7a99ae861a28fee6b7d985fd1d36ecb5e88395b646262cb646295315104037960a04b77c7186fb5167b8c88949843200cc88ef3315064093696a0c012361eed5ececaf5392ec4cfd9ed5d33e767d5782bf5389c38f7b9fb93e98d965d334f2d4f8dfb1a5e30e56383ffa3ab6d7a5982b20089b17c275844ff0260fc0248342d72eef17c6517b0e102415a0e51914a88698e6aa618a7c4968e4ac0942075c44924912589ae23a659123348c0814497e61edb0c092bed4800c51ac71409b7f7e598fa115ffa082bf608efee58dd0832d1082ed10820234e8d391e8e5ac0cb2c50d3dd5185e50d2c23c0c2b9b74d5a432c2e54808cac025d2ac043055628628c589219d327327ad866dc8ab1b62258451020a3800fa0a0404ee4ac7579b5d3e48f69e61e130992527a2a97d2a7f49f2b3bb8a281ec8ad015201308c104b098c02a53c7dfb5dbfb804f2e311124c888b8828816110a908018aece98ad0db2d8c7a3047a90c00b999532566c66c52602428840188cbf66fa723cbd0d63ebcd08c022a04343008c2e20c082401108c8900dd1c610560c8187d8e9f8e32819a5c461d18f14e9a7183a0a4a8ccdcfad1ce2248498385d882e42c084482284b782f0a2bbdbce3c73701049bcea00d107105bba2b10b1d81ab13262e0bcbf4d2f5613e507c8616436181698fbe97587d707ed8b05af2311bf5ea4ee58e9dda46d12144572b8284cc710f45994a53b16d5cc504c777c3cbb96c5212cf4eca5331677cf9f43b065c262c2cd72ce795f2d8e478fa583b974c4df6d186c5cd371e220f74b7c90a345795ebf497e20f303978c2de83f4cf9210489e6839e33704b83a0db2d0f5aeb96d411575afbe80a66c2e657b574f4e64633bd5b56995416a2fbb7c7b725ce0ddfcff9f1160a7da9ae2dfb4050ac69ada24964ada771b6dabf7fed566473d6b45611b8d569c31cdafab29c67eda669e0173e101463aa7d61d5346dab2dcd5e9b3530a4453a353a0b74c49e3759f42b75d5771e90e64ec84584ee186399fe5b26e9c25f9e5aae8f534950a8ed8b50282e630dc42edc247381dd4db977316957092d2fd1f3288e3d91731eedb6dc7aaf85a5e3acd37aee79aad6948ed77e6ba6bbe33f98f87df65a549fa4ffbb3ee6b44c208fdce476bac31a32cef3c7b22dcc86e9c6a45a6afa6c0bd2f15d777ea9a6fc31158bd565ac195cc32cd31d6797ee38ad6473a7bbdf6702e9eea6587e79223bd7b54e045399a4bbb34a33cd8eee8e29fbfda779337b4099077079002867f8caa953f5001928a7930d4161871850c58fee2854e7b45eab9051c5872a33dd314f93cd5e5d7482dfa44206950ec450483ea633e8f53e92c802f570460f5a7a58a2071c321e76c0c3183c8080872a3cdc300508dd4dd4d91424aa4c3175479bb363225c0d5bd7ba12c9d597277794128de52d7b5272423d764daff53495d2b49a78bc34ff7553d89ad7b6400aee1a62eb596f62f799bacf3da6f3864dfe098b82738b9265888af418654a77e45850ca7c129c5fcca0bc000aac3b7a9cf79b4011b20ad3917b0f0c2f37c31ffc399b9d02a9564d74c7975356b61aeaeea1ce56dd910b5974a3af5c6d26f4a53be696d096f6110285a674cc8492740bcd04cde1003c1cc001073871400a4062004501b40350fcb9c18f113f36643f343e623a5ace7bfa728eb2becbf324fd14dd9443cf62d6fc2edf8c2f55e56a6d967e2ab562ac073fbb9972784eac10731cbdf158fa2258f8f9df094eef7e91bd1c655196e3d07e9dd90b3f8aad073e572251964f0f3ea31e13643d5c22f737b42d29e5b3a85722b5c7cb7a7cbaa3bde0a952af5af6c48f3844453e09e3c93f09d21d31917927085a4c9be09f2f27649c84619d34c976b8a33b7e987eee519b5de6d06ad2f3f6b5dacde70e11d881dba1059e327884e88e7fbffbbc979b541ee79dd5ae329e998ed89b96e5d8f376c84430cc76b6f40e9608a4b39d6f8785a639ae4a7ddb1414cadac0eccdb7c1740875f026707447f0b76d7ead0f5a0dfcee8ef43abdaf33e36fd2244bd6447677931a9d385a678aeee6a1339d263ade1d256c7e513e107f41c714f6994e8e96ee48ebfc7260310728cb99c970ca74cf3bc10c0707ecee327de130b921d3dd38eeb991ece64b0341f14d968e1c8f63dadd905af6e684ee1537bfbb13d4325511aa9cc8e48e8cc9144caa749c4c64e8ee9835a08bac014870f3bb06ac90a564909a22d5f9df99c3736285d897ec60490696e8605326b3d980cd8d0d094a62405f6e2a5d22b1efb26d0911622b6d7df7f3c955ad5703b1b2a8f453f6f3a6758a660000280053100000203858301c924a66f301d17d1400005dc49c54cd4696c789cc29648c21c60000000000000000c000287b4bbbf646a6a0db77e8ac574fb741fb4c581aadb6453d9f7dd35a3445814188d4fd87309763c28467ae107a8e6910039e25835cb72ac6fc3cfcbc28bf0b400beb2005cc7afa1fb52db99fb706ea7cc0c3958e61ecc31c08f746b5ffa18c439cd8540799cf07e1042d0eeaef5e23f9a12b1beba05b8f169d6089931c1a5eb6f639b3e11c6df33372ca3cf053bac7e084c609db5802a120e7b4625d38b1acf7ad74047537fe3cfa14ac04eddeff2ae8323125ad4f9c3ce97a7f76c3c6dfca060ae601714900fdaa77a0caad0089927af59442129f412b53b5dfedf275be7a0c75699b0d613810565c6b69ab506e35637dd8aa98e2c54180bafa91184b4e0a90aadae9bf09f65d20d04ee886d4bc515e6db6de97ecd0156989fe4f2f282567d1307dd0b9d66a11e7f5a435d20fb524908a9e5f97d8440980e885a8640d0db39c14a82c372812145f4daca50e2593ef1e9e4f3868bdf3b03d24e12ef02321f7449dc3514764370ccdd1428cd4c04a7c172d85dbe9c4151376c4f556eb88c733e0ba3576791d7d47b8f70399e5f714f0ffadfde4bfeef8452bff5ef88eb2e1627a824592882e769657882b4a5a362f01ef1f050cb7dd7812c599a1d0bc450c0fcec18082691a1f0daa58f77a77a481743d4fcab8d4a4902952b19f00fb39b849c1769d203c846442407b4e9d7418e7f7035dfbf1c7c6b5df201b8fc37f469003f2e55a6c863436013742d741dce05b6d8253744c2b4694760eb489cd8f5ac4a6f34755e70da9ba3a244957e69327435403225d1c37f1c241c98b3aebf36f5cfcf0ba8fe3f2c45082fddbbf51052ef4ca362eb068a9d0ea3a13f157b149da0940ef51e4d44efde96d6af48a10567a9facdd0b2233ec2de6f6d7e8042ddadf6b68f530d021544f29a181d3cc76af22a313885ae8b64f1c5631f31a2e06552ceea2326359b31485447c6853188d23046b0f82a171d6b5ea416d01abe013f004d15e96ac260c05cbf50af80427609c23625665fc89e2fdeebed34609fa79ee2608f85be1d113c53b755a163a8a805fdcac13c474df7c51020d3b7478bcd32c2b7827e0a3e955f409f10afd51c5451131bb8c4d6a49b0a037d1d54ef3fd9f9a017ab4f56ccf2ddac4ea215a8c77dde9d0d1d34a5b0cd954e773fb57f24d7f858ad1a603d361de91be987b24cdd7f65bfb482c0546a2e0cf7df62b2f53d5081ac5a05413987434e2b4625e3399a49bc0d8c6f3157764cc47a927a78a0be9908905dbd68402dc60274979292c1c7c1a53a20a1d2c7fa1feab644888ecc9fc799014378272dd3d96b82662dde3758197fd7c4fff304ea113a73f37a2eee1b1a70eca70aae4c17dfb4f1320aa93187bba2456a7442dde5c569dd8c68c39858f4cd92d82cee834489d3dcb011bc345f4898175794a296d0ff3464c05563152041216713d8327f0579b9d87389b2c0b3ea9fcc040f15b42c0d05e019040a53506002e508017500001cc8002a03f1e8f2a1bfa46f4fc320cd8c64a33051b78cb11e0bf11f1c153dc2d93e951810885109810156a522080ac222ea5037173db868d436c26e587159c070e798d830b12c1303988d97e1ce97dc088cf8bb4fedac31b1a5bd9b88bb84eca8ac6c66afb5265273cfa22e8f82c4c3b50c09c29b6e957975a6e52fc84c2701a17de2beaa8f88510d5da025c3769f2682d1340ec42b33a81d962e60e7cd21244637390c0faa3b6f9cc85b02fa96b8c05d06f842753bf1027dae5adf1fd3488705400a883857497ec6889ba67b02109918ab32d9fac2a58863b67fcf41efc504dc47d9f366dd8786e03844d2cb5477e35cbf082c3ed6d29f868842f4710957a066cf4ce4484b7d22c65208d5938a869f561a17deabbff9b0be5b8e6c0596a6016f9bd8b158532dda9feb460fece624b76fe8e577d4aade2fc4696338dfc80431cade9b88c9134fe115da9e597b92e50268b55248f3c711a5289badaaf583778c7383ae40e894695f7577a76d5d0bfb5d1bfa5cdef93fe88028f0137679a86609b8be4b67f19d337f2197d042fc7b2f0c5c7321449b6bae003d0e635092e190ca29c61836c15bf72c93986afb6968561dc0816809367843a6b17873d83ae959c9e4fd8c49cae84dadcc9888b2ff20e51209d1d63346c24dfdc998f9db487108d60fbbed4ba1c37c083645e0e0879cdb1428c7e8187bc464bb98b05d90c675ebf19349315f1dc6d91dfb7429a98e0b7bc9765181d198c5059677f75d8e229fc0c73ef5345f97f883e6931b8f615c34f04d5d705a26ccf5a8d75941d4005f277affd11f5a7e4df621ed8d115213e0b1a690068bec2090017d221def378537a577f896453901e32b08cf906c5c164062d1fefdc9edc38538143da37bc52fc98d018274277a8c977f1e8c539264bcd526f47646e8194c95c063927369e0bd39da75e5c9c93913b7f276d11981fd9620c1e2aaec6d925e13f1b4232bff62b75e36a756911f88a6b5b80f4b777d4b71e583c6fae9f3bd646dd6526a78944e8280ceb103dc99a944092a4e5928ba1ce3902c7c3246ae7a09d4e34f97d1547363c304671dc01298c582059493fbf1a60b93030e71264094b37694b57747b5a50af1377cfc448df774cb49c5da009d783fbb95c032fd9c1173be315ddc01f3a672b23c29c09c867185742c6c4cf3c16e7682d75b09525cad50a260ea55a67ef74c80056f3b7c84e4ecf10228f22c2612cd796e565d3ee169fd520b3896e30080cd2818d114df7d313c0f600562f32ac2578ad3c941bbf322ecf4d2581b07a53d433436e92e645555471f54bbdcc40b5011dbe848cda144adb9d08b88f3a14aa62d189bcef218dc45eb5d5aee888360891a3723aa961300d3d9e402a5fe8da7ada2c2d07f67729722660ed8d7f4c0f41d77a07e9c4247c9c468c7211d75170a11aaa255df63e2e588f49650b8249f0a73c84c4a5da0c8f853de17c761d85f366776e44fca06dfb0206b5337120a04a805462bbdb99aca3067fb715cccb3962a2fbbe1c585c5cd6e5f1f43b7687cc572e316cd408e7498e07a1f1e9aa7641eab9c945dc29b1e2da3d72569e822c9e7e93d700faa12c38c15a750eac5f21c6739d5c6f7a35f793456f8028405ebde0b76f996cc2ec0a3b6b4f86d737afc11239c4dde06ef61623fbdf9d1e2c81b018eb2eef01e250a5116e55376610e9a8f1c09e3a0008fde113d20ac6c3d2925bd042d77d57897e466f0e9532f05e34ea2c5dc17c15e1e02c942061a70a685c16a4ca1c052106d4218e33f25ec58fcc8443fbcdfda4981eec9c1ba694be4332c87eae674d004fc1267f97e24a073c6a379504b4500562b8f695017577699fdc9ca273b2cfdc85166a236d86752dea452c5ec48680f274a5cb50c6e3c2b0fa7c5d1c8cfab2c5d80ea9e342b029dc96c2c07db7ca3175cb03b4d9c879261620140e357f1c1601c91453091b85d7cff17f8fce48b94dd09a2b3adcb5fdd5c1881dde1c030544421dd75c8bf1c8bdd7b8b66eb98b684b179da1d2e8ecf8a079a38d488aac6207236cf8240ed1858f5c36e418cf949fc70918c7182d4e31628ae7971a8c61c545828923f55afab25a2d6631a3b56a8615174c7d749a54b56defd69479d58f4c0646757be8b87c76fb59154e9f9aa3c7b5e8fe706c8948cbc3ef599d58f3ace5022d9ae6cb40204a4842181f163f8f3b828be161e2019da7b635348f1c13a41ccacc9ccf993508133ae8f9e414ddde7025d572bde692e7c98c1a39acc94014ffb8b08b226801a52a1564a8463bb66f30a115844759fc0ad91eb45018e83e8e02c7038bdcda21fcdc2adb347c97c634c6ef9bfa156004317638b961dac77ca40ee3bc6f5ab85618e2f887393cb266cb8f95dda59f3cb03ab7cbe45dc84506e5105708206ec417ade4efef46129ad481f0d77eec59bea68652f7d5cd73555efe46cfa879781c1d68d198967d043c43ba399a9afac1efa26702e6363606e60b7453c08b0c86ade99cc11682f98400d4b9d81cc3bb2ab400145f863d7a315db40cebaceceffb98ec0fe85b55c670985d5a1a862e5c1bd81d39e1f4cee51ec1e5e81620e8ade4b5c54e662f1e789663bce3108dd1cbdb18648944b4f2a893078a3d7c6b76dedf193e8439eb9ffad62b48941925d578dad68a68dd11af3065bd40a7c02820287f113488d62d4fcb714218d5911453fd1bfcbb1f5376c075eda9c53d2294b58467ae779c8953db32bb797d37d03c7e5386a00a70eacc6d250e346191bcb6b3ffc14a7902586f7b4800320ef7cae5ab624e40c9b551a93441cc9b85d82202688891841a66a13666267ca5438dbb587794865b79e9b129619d97ec21429fbf8774854e1301d3bad4e5586f3ebf768e20e3dcf6ab4eb4f277ba76a1a286b3c081b65ab00164108512a6a913d777734eae84ec13f58df9060fd3681ae36f3245513b8064d1d4d4b4af1a7e44f0e5945c884728bf193a7e29093b09591e6dbb80bea877b4368297495f4f4e8f3d736cd4253bee267f58bee6b008bfea1dd964125f0e1ffe70fa166669bda2dfb080ff2a2c18052030b96293f2e9723f7e022255832ff2ef3bca53d461b17fc89a2f5c7589b2fa1a34b6457bc98ce1829cc2e0ee546873f0cf6fbaab55d1c414d4b582acac502857438d951bf6a30276238815fca3d2cd209d8a5fbb55ebd89205256895b935277a1d4a505869750fc1c5667324b77489811dbffd7d4c391cf2c09e49978e26fb1018d0c54019d33a70fa83463fbb8c9fffaaf136b62cdc80e7ab55d1071753e9bb152942e4a3d068329b3e1be8e7c05b11e4fd37d4e07f8f932e095de0faaa4a49727ae705720da68567b9286e8bd5548a3499d0197215bf927135ef1b0a3c518e93ba712837f44ee538fa8bcfc7cf4618da8be92a6b7aa5156320c6d3a49301b7d09c68736dd7b9e80d4ac0bbc25b6db21c5b8fce67ebd08caf43a11003fc127ab01a18a06e177a0e409377cb85adf0bd1835657af5e26a3ade958bda6c1b4156a4b357f3d3b0fa09226b13154bcf0178f74d8c9267d1a8ee1373c6968bf5176347896a052821dddc27e66e1549d99e1e59cf78c4c0dba3b7e0ae741aa14ee1b278d8628b1fbb7772e2e147674796b3f5da7f1b98c16e135b8d855a311008b354664e07f457ae63f2c973e42758da51eaa79712990c045e75fc8ce82df3c62979534db22eb4fc1979fed33d27b6859b7b41bb8a365f4254434cda5e20f3944ffd55187a74d338bfdf775e2a3a6cbdf0eaee88adb2c45ca13312f9db8f7ce0b07a4236b3591af3cf9743c43caca33a5caa1ab2304966e4a361a1428c4ae6c33dabaad85b9b486c8dd8d397a3b14d818cef2899d4e3cc142d35596bf31d1686642d9d3a946b3905418c397545db2b05886dad491bc1920bbc1428135f1721a50cdc2843444e8cfc0b69fac46c6761f548cbc98f749c1a4584e58899cbf9301b20ae1b67589949a944e006e95a12744df0147ca2cf1539381baeb41cdfebe2880521b9cb6afa82f34690071db4004fbc32f7077c0d975dcaecbe227c0b445692e4b95449659eaef4da711fb46c265832cc450561dd8a0cb494e5ba87de22509c28b5aa426daf66d9760eab81fb2d058d3a7c6b26c4e55e46b251e3db0d28c8715c4c698546545bb91429eca6cf6f53a74fe1dcc505312ba3314e149597e57272b82ade62bebcb1bd691aa7c2a5d9ac014b225c404df58216b3227511061214a07718b836e0ddbbcb2883728f151bbbafb76b5d21acf7ec88173ebcc68eb241ea85cda9e0a1812d2fa7df2dc9e9efe1fb9cf3f0407de1a6e921d7cc7dced0c0c1ee90def96f6bfb2407f6a0bafa4d8f1672e78bd9c4b3e8a18908392d740f9e6cd2d3549a1650fe017789a6440ea81d219edec28554514649539bb310924fe0e3d75b3330bf414b8fe4d177d8a25ae5fcbb22ecd464410fbeffd5e8f9c1ec37ff66cd33a3bdcc6076d1b29a25df640e5200ffbea8f72de7660cfc156dfe1a2616601c2df992c2e4afcd6f5333444b9e16207f0602cdefc667cb2578e88cda8f6e1cdebf76c8a793c3ac0ff93a31a4bb640e1956446f72399f93036daf3160f5aa50ce23e775f6b85a026470c1b9917989c528d8959bf60a598f0a57ae85779f8aadbdddc4cdd8a2d170a46bafbb650eb50cd98157c43e715a8bdb097c031592c10de033944c191277b7488cd130f3b25c75df178be2d3589c3dcec6134ba6944e3d521c74bd31440ba7b1db2b2e42a7935fe6061dbfa07af573356aa6db2c6a8ab8d9468e26d9d86cc08ac1ea3350e6232d6185472b191dbf126f601ee57d84b045e8182c11e08879741137028aaa6df376b601d03288b69929fcfdb091890b3e4e3b9b4403aad2e6f5c33cb138779de711764ac4b085ece66c3ee55e7f35e5179ab1b06625a878a02f0a2860406d81808696bdd40a3b39c6eae514be5e5887b0878a2962e10190b1b330598570fb5c361c5c75a5f60997e30e1ff70bb6376c4c769393479283540501f476f459a5912308920391797db5b43fa317329b636d8f89fd4c0a0fb8474e2afeb6103767f8ca72090c71abb77fe0c76fa36870b82b6f58407cbea1ba2eabe66c8c7c0c6610ad58dcab4e9e822e9be3105fbbd72169f86a407c7b4e81e0fb20f69421ccca3abc53697086171f59a71e2ab899facaa636a407ba997b3cde12e1ea11a9a200a3c2e581ddcda562224b034dce52a23e0a541de8d4e237118e9d75b3dd03dc4d150b4f37d864ec00c26d7f8390d4dc2866f2525c00bb56db00899e771e101dde498c15d44fe9e1c364cfb71bbb7d82761468842b9298fe92f31a795357b543d1aa4007e079a4d63bda794c4b84e7eb58f4d196f0a78dbeb11f2dedbacfca1e7efa224844cd376f9335de3642486d792bb20ddd52ec6e428006d60283c1ba936303c5602687451757bbe1f24675530f654d3db5c07a5b120a736d25fcf365cc0db3bdcfbd336dc6a2f7865a2f88a82bb2e6830858ee0d55956c860db08605a292f8fe37ab7e365d0df07823edd935efc5bfadfecfeeb174b37e36bb60e6090bc85452ac06ba21849405080756272244f5884f5874b2086e2a7afcad5e12d5a41bb9f03b94d5a7414dac9c04c5cfb81b994b46b29790d1a27b5c99ce257bf5bd76bbaa5f38622ee6b1a75f8012a27d7458cfb3ea48737fb61e1256c189dc2693b633e1fd2e360110bd8de7f8a6fde5e025ea4a29e2f3b2242f77d6f6503078ee48c0e0b70c8f0dc3a6561870b86c72e49aeb39c995404f823fa9b6f22dfe1ff321d459bfe4b7b781ce41c7e0c9b3c172748c2cd6094d9297a5e013b54ff4247e73ce51975edd5e4829e8c83dae3a3d01f5b2883186ad02c5fad8e2437157cb24bc74ee70e5eeb84388d5f452446cbf3a5e6c449355fde317fbd8f83daf8a9474d335b12f1ae8bf36699c684241c4bb4aafa2b3aed0d7b1baa886d5fdc1cd1bd931b20bec6ce98264a7baa3e616b3892f304e20625e3e275677e5177f0aec3cc63904bb53a841146947723aa5918ce4ff34fc1cd9f8160ba3132ee020d079419af94f30502a1cd21b53029baaf84151d423faaf83cc78aac3b1329e0a35cd6dfa6e16c01199c00a6a08de2ec86213f8388a3c860571f02c3d42d269ac4616931e15b88a55e2f108fe0dccdaea60b9d45b0fe81997f26be0411480f1444597f4612485bf02305ea0d575b0c84553bb522645e1f2a9b563bef632ce8f9c7587842806a430f1801f6ea386554f46ee03acac45c10bd8b3892b5e601fd0209bdf782e594a966790869676f7952d3843789f57128a8a174c22054820541a3573c03a711e7f4069d240c482224a81e031daecc3fe259063516b6b78bede3f1cda33f8fea95fc3e0835025f47a1032625a2dec60e22340d804167f4be6edcd68817102f0ca9cd87e24f631af887e3a99226fb2a526fb740d54e7e9518155372dca7f8db31828bc29381eecda0107072fc3cbcfbc56c773ca31f0f4821298be704d4fa2da64255689cdc415be86ea326735bd8b8d852dc911a31ff690b09295e7a6e250d8aaf29125ddbbc8060f38b81ac714a090b8a1061c223aed506cf48586335877f0a477a897ee48b7de59449e50157150420a07c0824c1dc8b49bb757627fb6c6c2e408e21c3fa17d88341cc6e1b92c90b7f9bab2b37071ba57ec4d121d9e010f5fce992bfa065b0d75a3cb6f85c204735007978a96f0d5b796eb0230a0f6feb191117bc911c25f8bbd5db928715d53844360663ac56a2e6de98618d31de5f37bd426ab9fa4346d44058088cb8dd4d9bf01971a73580c4c0ea3dd4783172746f403fb90216408566c72d4effab13e481b77e506173a67cd10fe288083b18f9374c14c0c0946bed69164bb963a60f0c079c7c4b4a1babf36d30213096c5ca7919ee6cc4ec0e307b0efa8666821aba1b5c32975559ebc63e1b665fd3c753b247ddc860bdd06e6537b50bd88a0c62234c0dd22b5b01218bc5641d9a992a378be15da51e18a4e77fb877042729575a8beec5a7ed31df2139a9db4d7aa8e32405d30dfc9760483cd1a55a8bcad3845c21aa4298b07057d4f086ddcffb254c4f42e86da704dac56a5031800fb89543d116c25b2ac4f42a5385898d4d5a6f256280608fe3b9e2375ce5167c138b3b71cb85a4547cc6dec8ecfb84e143210e8b06cc324c2d973bf9a2575ff3b3af90b7012c6ce1db2969b40768ea915815565faf033ca4c9166adea763649b0dca1ab76599bd006c2aec3caf998eb507bd46f5b1f1c36ef7aee90ff8b22daf6b645762add6250aaaf3a6b345a3ebb2930723684373f153b1c4cefea9aa03b676413eac30ee38d4c134d36a8fdbf5d7ad9e11a35f57299942f5453ffa6fbfbc7ad2420f89a809201546e43c4ffcffc6104cf5f2449873c8ca4ba22b10b7a8b45ddcc7970c29631b31420b9647f60a0520cd9cb111e218b1be827847053c1222c87598ed149fb8e1636b917e884b75b3cda613e86de86b253d03d99d26558ac11751e03e41bb72939497e65f65f726c63a8004f18a769eba47b61a739d3a1095bd354c586cd3f6453658da2b911b431cebc890a26b2745ebaca92720de18c6bf9070b3d9afdf98d6337e10d81ae99742c36c191a9f7afb5c4ee4187de12fb0a7826821297ec3dc843675d41832da87a924730d9694029b345a7748f779e15aef478cac6af737921824dcb012b6f9ed67e7aae49e838610774d33f4422112a6df4f3cff403493a1f1e487434f61b8b83baf6cfea7470a2188bd01a1541c5dcc0ad5d888ac0100ceacad159c6ca6a7f486915e3ba4262dc686d15c85d3e6b75be2a65cc3cc27a3e741d0fdd448527c9cb60fc6644e8e2c156c45f204b0ef8e975449f5b1a4983cc05f55fbc1309c238ccad80f1292de853361bba539004b3de706f73c30f6946d6b4e03f8c73018d7e3696573c2f29416dc83e1155828e76db4b20b6f462892f2875e2071833e34959dd507d469b2492168d62d0e8d2ff82dee2dd8c449094ecf926cc8346993bf9bc61f67240117829485c8971ef45ecabb7eac6cd26daaa37b4cac58141e38ab8b8705fb978dfd1c1d41664b4b81c1fcf4ed068477cf17bc182bc2b8de3f1a16f38e6fb76d92461695a5fd67ff581f06995a1e9f5a2eb2846673d6626601cdb46c0b541e9fa151b4493e4d74583126ddaf49eaddb4c1308a4e73cdec928bee94c1f889a20e9ef5aee141b89c0efda5a3f75c3b598f43893357c0dd6df18d4e62e6a04f77328e96d313a9868c8dadfe2ddf5413b9088527b491c4b782f02d73ac21fbbc62f9331d5b4f02f9d80f64432e331634b5df5028f3787b881d708bdd4703efdc810a1a08b2f7c8a85b099d1ce7de686a3741ac7f6dbc596e06ff288b95d2166eb4a334266edd8bc16eaae8964b249deeb1fef33ca78d5d0d59ddc9cd7bd99b6ccbf12f317ffd0a22ab91df977e555679f7ca48488cdc0fa71921ffb63e2ccf36908ee3f124c056d5fd6f4c4328fb152a480964c3a483f8b0ad3d8a738917004c1d10a85137709440b1e4aad05659872a6665491c811b0cff94c9c8ab4ff449649190256ddd9ba7ebb3b48d6a97ee5a84b4e172642e505d16e6ac81f29241a1eb353c07cbf353fd96bf656af5af0829aa19242102f4417add93705b734b6466c5d4c187dffa705597e3cfe62b1c6af3dd122306fd40256685f7933b40e3428b6433a0863cdda79badd241d1f5131f359a01c02b6df6ed166c3b2ceb298c3e21e17fed932678a4796d1111b39f96cd0b551ecd14fe6123252f7d4ed7fbe37886df674f7d772d138e0a26b979f83b91d9a59da2b44c45e69cb8f907daa71727c072eb0625e1d8cc6c47bde824c9d725bf0420b46eb9976476ffe45ee06e3f09ce949c3322989138e21ccd3dc7984a752cdedbbed02b465d3ba17b725752c9e3e86b9b94aaf93d873bb778f6829f4a23ac4690078cf8b368ff874c05b69b6a58d9d75b987cdb77c617b67ffc3bd36900e3c5aae3a168e64422c59f4e2206d00b851821bd98b40bbb9927607007c5e039ed3313643b27633349e05386ad7326975cdfc1da0eeb5aecf038975592655eb49ba4257c96a0527cf930dbcee9e78931d387a5437e8c4d4ff0e04b4aee891183ce2112a50afc81eceb31a5c7eb25d76945e23103ac369a40614cbb3a43995a956cd68dc584b44ab83c89b1220585ed86d27d53e15d2afb3e6658a2a05b36b97da99962141ef01d9ac503b9c69484227a8cd80cd6cbfb9cea9fa3294f9775e2cad63a75a5c0c24f0522085b14d6a620c8b63562c5d83c0cd4d087cc9f045d3b4db1e6e79ac9344de48a54763e0883a18d157a67b2bbb0c0ee5dd1d79ada0a74e7a153cfa0a381cb7545cd40b4bd1ed93e90421bab28f93ca5a45f7b23670342588372151b92fc657657f32e90ef77ea93d7801057fec4b481f807a4474320cb36605cf2a2eb78b9a68b4b4dc330bb9cd0a58657e5214cb59c1435712ec836a83236f85588f44602f89c4c11feee3c279b284dc03c925c2ecf5270e3b37c27ff56dd8535f008186f05aa843230ccb794ec430701bd33bcc9a30a59f192244c92af1e82e6bf22153a0641b73708f17fbe1ad24b39a9499d166ea658f57c10f075570df14c251aebb7b838835b0918e139904940d869279db68b65f54640e362254f3e33f00ebb03bc547955556ad74832e6649edaaa5033cc19d1148cf36cbc6362050ae01d6707f20e96ae53ca1c27fd4c310450d25d05549959806db0cc2172ce4a104e76063fa1c08d56d7d2449e37d52afe0f917c6e08d73153f6c3cd33bc0753564ec2d9481e6fc579c915841754b1eea700a2722271e48aec2427791605ebf55606e55362b3a683c1ca6cd9628f4c614e9fb319d634af569ae96882857bb9ebd8b4115dadf1117b4ed9d2c59ecc478f2526570db5efab59320433a3ca25016fc028de681b60108f3464f86f5d9f6b9381fa851f1c423e403457a01a7372c8c49c1f8076b6df0fde364cd4b018b1eef648be14e2b94fc22ce89b64e2d25ede5ca682db9b6349bd64fd87d11034fe419af0586ccbb384bbe58749a277bd4438817914529a0b402e19efc8685be154f5215838270ef989f64dba8d6710c7e7304afd7500fa13f1453982039c0569bcb080d395b76824f58c69c516a13772e989443d524ddee5d5dcf78dd17319207dc0cc7f64417f4f2558d4773d656a933fac4b2b1950bb32d91794093c9457bfbf1e591c3ba2409f0e10a220811e65cf96c39d8de891f9dd545cd7be1c5173a6f2ff431b6de8bd95390303d150a515cfe9ed9f6d1ca3e364e8db059a7e28391e8058c26e88d6a7966d4f52de52327785ab31a8d6843ec0111b8abc5cdd558838223c85c8267d9c0590c96a7e54d65a1b0d4c87f07d41fde8cf8dbb4343a674c222baee81f25f69eb1aa2abc5701d686e0a664f1c6b2fe85cdfaf1334317520ff18268cacc536f94240bbb70c2730d34a29c305172c4eb13525809441bae32123b90cf62ce8d4e926528dbeb46ae031772499c377a96782b58d023bcc53e66efd2c2f388cfb19c3e034083653e63d80285b46c991596675307bc5cb7706cfd51ca4fa423e052e5918880ca6609c5211b6bfa3e181e69aa3e5db954cb8018d6f096831e48760c108c2720657c14f74bf1c5782534045f96be8c8a8e4c7f56b74ba6c6df926e5abff63f9a4434fdbbd77a989d05e0662f60a6d981e262e2cb398fe246149e9dd5a94e4b180b6f75cc4a77a575f4002b80543f7f7fb86e431c14cc1f95bc6f42ba0beccec5eef92b2dd9dbeaa4c0becc9fb8e7cdd177e23d21d987201615df6b306d01a0ca90690e19bd559c2b4e30f0b9a182065fc71e5dbe1f8542ac4a7bd0375210ccffef34e23e7ee1797e742489b1e2873d89fe4d66d0d76c4b7ff4d4418a1fef36e730fa21e76d99504c3adf0c5ec0d7cacdf1ab84d41575477d4e923e2c96215a6d85c77707c2206450970fedd0b0a17ca06c211eab266132083b8c43ad03933f210499438e45d8454b1fdc4b854eb5894fee5faac63604c07280aeb872b4f0ec664d592050774c0353dc93707967310de28193b68bb1154799eb37f28d199505af3e959162255a6481278aa9383f6940cbaa6f4fd2bb0d6e06230f2207a1eb7d5f91c43824e99bb92018f3fe56a7e1e53981e2173dcc93f3d8401581a0594b1284938ec4ead61165c52967e9c80f6c1e2d4bc997565e2d9e249cfcb785a8dca77909db6e2e231ea27fd94d512cdedfa1066001f07cc4bd22caea4d6ae633113b3961761092878e35bf9209605ee6d0d6852b23b717ea1436db8d93afec11056134cdb83ebb109e9cce98f42eb690e6aa8ebb23cc2d9e64481ae3900ef19ac21208dfa3f6419768cf535490b36b0c713d040d96e1fc9e2368390a34baaae333f94e90e0ff7c7ba4e3ddf2534f61fa8af1e845935738bd6095a8803ff75580caaff5c8cf1e540b41af369131006f5f48b1013a5dd206e860e6537bbc35fc6ab827c000e46fe2cf5c6af5f4eda9a85cf772057baeec2e2aca16785850ed0e359512aa1e25fa3cd026b5c3eef1a89110a980aa5708f42e72c13903f278c3143a2d7f87f567ca1e7fb40b0a8aa68fbb7e184116d9053f483445c56221a9be17f73f501fec3785e86c8c75b677d483c613b370a459ac11829066d09ece0c22c3287a381f7b9abafd1c018afa361eb61a6b1ac1b21b229b2b01a2cf90a3c7a65ba726ccb207e20d9610320011d1b997f18dc63bc0281bf11e4dc3573510edf900f14eee1892b8f589adb71288f22be880501c2a5d0f185252bf0aec59b87a4438d4cddf350c5b16475ef82734b6a03ac3902dfb9c79e07c8f3bec53ce2acf73a566e739ceaddf0196b2458617543a0c99980dcd8c19ed49e07c36c639ad618b8b3d3f44634e964c978af285df8c912d909984b827297571f6937a1623c6d83f8f7cc7cf6490dd823a02bdf3593a0b6a587c469b1e7f6a4c296392f278632b54b1cb6de909f15f7e45da1866a28e5392542d9ce59b9374ff553fd7af5776b3ea0bcff35b2d65a9510bd91269ad38851b5bce6ea7a38d3fb0db944664292515282e85b05fdcabf0fa4849f878fcfe9be77fc65862bd0b04587483b38521256a7060f44cf75eacd01f377fc3250c38216714e24ebf03dc43bc04ebd5999a91808fcd9f21d6b34865e01c1b35dc2e0339d574670e4a9c9873aa1c28bd894af9b00b165257ca4178b796731b3c3c8163152ced0b7e885d3f87283ad0284611c9b60d731ab5a8b99bf7555317abf1becb5d03e498f90b41d50c1b1ce7ce7ecd971fe8554678bbf7008bb4ead6b4bdba9856f921438a454726d21c01a3fc86ecdd316a854a15bdf0834b1407362021cf941d8167f8654e7a9432ea272c009c3c76c5101186a7662f16a3724c868ba6692e016666c83af8fc2d423a0370f16c6c426f4f37fcbfa7cab1fcca20006d9a982f6cd449db555bb066b2a785c0f8478be62f94d38df7f01de34abf0a3eb208d6ed71c2e1a62ca37470289a2d54a8419827d5e7477bfe5b83d809be18a3529116d337efdb1cd1e89638c3de35b0efee2ae0957a072bb6bc4db8e1f6e39b84cc52b9d8ca646a69c42474c475793d49f2d4b932411dedff0b25aa02c10d5720f4e34eefd348fba02e2536a071d4cba1805dfd37f0d229fe0726d75d96ff47e4f4009f67d2c363e9138685e436e8514ee0f9f661871bc3f52686d55179ca417d36e69693a4dd1a701881bab4edd79ce1c51763285864d00b730474f880b1f642c2c93b582461c668493017a881ec120b6922009eb15c52fe65677ee3d5110d2e98596175727d427be9d6e6b63b4bbf3db2cabf6afe8023415a3d988715d8deb4bc4f32130ead91bbc6d0eb7e4d5882b9fc1d07bdf6f4fc46aff44743c7d45bff46c6668fecd996b52870bda0a47adadcfd57311568512972830b2a7763af3b4e8e9f0c5aca1063348bd59ee79f4009de46e75281437a4832744f2802fb94e462b9130a9315b70315228503388dc904976b0b7ada0d2f4e1a87e81713dd722e0c0a02bc82df5156a485bfe468dce9e7cf429ff1d37daf841282915fda248ebecc84e0904f45f179731a0e4a128b754f49f07973ca22003b32e8140513f63f87bb07392b66c4a3ab7fe96ae9844994f5f6217a586c7bae1c05376abb8f01e9ddedf4bd60a4a2a8005233d6cabde6b151e641873bc0165e1bc8e0ae8015a822fedbb5d0a81b2f0b92b8337b0c3477dad510df72459fc9e5acfbd0fcbd299dd31cef01ee35fc46bf42b75b66a4c9893f254d17db2ba6113e089278e4c05d60a2cb7352143a3b33b6f2562b30276d4457b0f08245c4381884be8f73db855b521f09b49663d9ecf891be393c72dbf34fae698a6e094070d266b6da594f88ffdcec1df478066a6155b08ea4a3631029fdfed8a5ac7bb878af868242282058b100c93c3b2ca6f83edd412ff9370beab5f2b34d53415b8086d6b96b6a9f0ff78445f05e072204b718111b3cbfe2c937782106fa71ba25125506420e1720f3e3a2313ca2a171001a94b8803eaed5e91e92e650b2058b5635bd5885efad62b6755f7686ebcecc811c090d7fa1e7ae72001bcbef0be7cbf50496bf475e9b0fe3ea9b421e95a25f91d41356b4179b195c7ae3e2ade650b644ccedd1444ee36d208050215e7bb24725ce82f417d829690a3351f44e8e6a8199a1cedfe1a2fafb437ee68aa8f3fcbfa2d08708bf1d0e80118c951c2dba8607d949c48288586fc4e0a436130084d6f97d7c6d22b70be013c8d17ee7835351202ac8cea7ae4c78d2096ba01be5ace292c61bdbe46d544cbfd499458156e0ebeebcb60acafc36a0ba80c04a301bc46fe0d3361f693d49137bad3a2ea72d246bd3f1d220f2865badb1d847de521e9e31d043c836a1e4612e45f146be8f20c42aa0deb766b2677152dc170962dc2e009c3464bf8f69d4dc7f21e50077959d3497b9d0f7fd2d692aa40eba1b63cef7ff71810fc8f79fc6236395b36f4de8a15d22efa74e492bd5dd66c01e8ecff7c7134c0fc120b1324f23446a3b83bcf1086680f43ac6f827a66562b7cccd93cfc68668fafe400ab6338e59bc0b8dc3b8788629dc703e1a19bcbc0ed1acbbf3882e4573b807ab559ba215996e047e6c3e6d1126fe38569081660ec0023c63b07d7c4897191a529fdf2c5d41f5b3053d094dfb80aa0b93792903234f4c46373be30137f03aadd888bbbd605dd5403514020dc3700ef3df39e291c9fe2422301cd01c3037d2577a7456ae4a546afa5eebebad2d7f8e606cd58373e483d1be21d48e4803e8eea7a9166f4a46329757c0e9a4021fe2fd0902c396abffa545a09fc68d7d5d8e798dec4e5e8013fee09ea98fcdf3c92c0283f14ef63f5ff23f426173d49c2e2330684e3f2da87584157b1b6e0203e29b7cd9fbd926a81c684fae20051c9db3dd669f291ac0bfe5ec760315acb2d55dbb87289117b9a95a038c549078f91752067c9bd928b44337b12d04f016c66affcb09b2fdddfc21744da654ed08a829f6a7a60b686a0237e40dec9384ad7ef18a31957e3183b7bdb305b5f7498ada400d0b80a2a3519ca3840aa69547ce78ca28637e82843c9ee43cef6cd5aeb789080871e7757f63a6bcc03e1a0fece844300df834dffa0de4bf99765fc4e1ebda91352e4888d218120fb7c700146d9de99b0352d4bbf3e3f739da1f7420bd95d70731141bab7179c7929bdc78d08cdfc9f60f16bb6d63d62f95c174908033e059895adfaae65380ba38f12e954e7d7c1ac4acd4f7a1f038f5502ddd806293fa96aebcf51b9701b45b664f185e014964c4c004085f8109971664e2a7a4a730854d0cc8cce163b7d50cf12453c49a86b845efeda4621ff63851582ebcbb7def1b78adc5773df09dc23e7fd73f093c257bb0af1ecabfd7b02edaa45b4e8d21ce14238146a46a9e6e139f67d8eb01bd4722a3c3b02c583e5c1e01f2b166eb1ff72ed166fff83873cf08fdc6aa5ac75e95949eb7fa45052e56e1cd49fd5cea78685c068c62e199bf56a4c34f0615e4fefc016e396d37d0fedb6889c21190fa9a92b8a48ac14592b9da00ae7a109299b04d3f8d54641d82951c913d93ff6c8c1d423088a8e42aff1a3d83f6683d07ad3470a29d0d9a896e6808bd68a04625bc0142233b43ce0fb4aaee0a6cccfbe2de12f60d1f3b8ce3ee70cccd29a980cac04ceaef119c12e79fdbd63a7e9022988c1a7948b707c399bcc8e4c9b9a8b46ba0cb0bb94675096c3d6f6cca0a8d301653eac68c8a8d8217ba67cd8d293bc686aba53606ca971edcb23ebfe61696210b27ead5a5fa2f991a9082bd8ddaa1a87007c954a9a5d5151e85fa7da2bfc577b12744165484a32c2824dd011797bbe85d19202655bc303e659a15c26c506d639e949f92a6dd47046b7a783e020f373ff3d61e1a6457c759cbb70e3b206b44a9501bc8a1a428b3128d729c1549e72fd5bff36f7a49eda42d92bb5feae3bec9b508e1d739d8efd6c13486d08703b05f6c30db754a66b4a45e2c3e2fd40b73c569f2979c3dfb971fae08fa14e2b1cf3dbc47a83cddc0aea77f2b2c4dcc3c4f99af2bd100e43e67a4fd4b719eb656c25ea1496e8854beffa97122cd3c69d930cf33c8e5665157ab1520b8e8e17e71adf8df55b60fa9f8880fb299d1dea26edb82295a7cae4bd5c724855dc8dc93fa4f00e6f8e7b0d02c802505780725bc4dce1100deefcfca883a8651534b4728fa347004991efa86495b6f8c1528aa910570c51756b9e1fc54c9598cd2190774e892c9d3f1b703ad40024f833f5319a5eda11f62948c0f0f9383394acec44d818ed59ad64cfc62e17de70b85fdf84775d47fb70424848b34e2bbf6593f3e6e321f87626dedcff01833c7c95b3f27b0425249f5a98c6dd597cf9bfd5ab6f6ef4b87de2e986fe49cb27f4e4f06fa3156f824fc274fefac3590cbe4bacfc65eab1167d506f4a24a8723873bd8b2be2ac0f17b601067567ad4bf23a67e700efcf5fc19875b14c78b0e8ba5695b8a761359217c67cc6d90273df0c70688bb27eb0ad79af947fb868a57b108cf1097e3d2231095de7c65c3ea7fb4aa5442d3524b568029b2cc52e884a9f52b1fe88c365b31815f443fcf76a481db304018d6b3e472f53d33f5bbd2dfb3c47e661fe774ae4d0a05c13821056f6c80aaf3d162755c707952d8763564f120d2a2de3e211aebcf8ccf0ce4c2ccb9dea0bb0b5835982a24b618229b5977404f88377ff8b35231ab5878d40f0c1854538929def8003d1cb617f5e69da7bba85d0efffc4b7f86db1313d2f1637c39b397f078f9c0f1b36ae290515aa56b94d94bf766e886aedb7131f991640d99fbc05fe6e94e2d6997ff7a68b73d662ef9b2ad34e17bdfa4ff00f7cf43d9bea33d07c2634f4d01316fda0e9450f159c0c42dc2bc58c9334af021a03a9f74a66e4dfd2ffd8f39353ce5028d2b396220054a208addeb6547460d69058c71a5a89431ab2e9056113e401a7565cba3557e2801aba216ae6c79a499383e2adf4c1a96e20869b87b21673e987bc33f18dd130972787cca9c4eaabd6d14aaf261e85470b5bf13885e55018784382f270175e6e3ae12060d633ab9922515ef1a2667a83fc14f897b63d847664fe596d4e55c9f7960283cec0ed35162163d8b9a5e4a8ba9a4f6c46178131d44cd1a9a1135a70331ab9369a1002a99d5ac01313bc0ecb39e85e759fe82cc632a262ff2e1194b2993be8875fca36d0716892f86c08ed5587682d056f9bdd8a4b25350c53969185a8113150e16b87ac375bbad83e4af2c0aa82b2e22d4f5c902cdc46c1ded7403506f34a54a13dfb95670087d01a14d10466b6d2bb7bb00a24ce0e0eeca962a210471804c2ba3891b2ae88d36dea5ef9bc37f15e9d91d26adf470624901c0e66484e158c2a34f126e7bf0eb51b82f49ba0d6620b05688903f383ee8c37bcdc51962eb6f4724bfb592988179525732f3b02a60c1da48baaae3b2c702f2efa531d5eca2ac3641d54495d53f2399fc7043f12ecea2ee433a3ea9930406a69731070d18bbb9b98f92587d34643ad46c7284e8607ad1b06e6a0fcc7b9ebfc3f1a3a6e1fd3be3810584656a277d1de47a180e66ebf77080cbe448fc7cec0aa7b9ecc79370faf21ec4cddbf63ead031b0ab6966b9640d8188bc27355c3a941ebbdb94079bd709e149bfac6f3de89398ab76515d5b63ba25348fb4b06e4378a11cbaa4e0ccd811dde97fb8accdf9ea40377fa1542a7768a0515bd4afe0be8c6de0c1b54d215851f14aa4c38d304c9a5ded5a7a394650e37050cb0fd26382686ff61496e919dd17072f177588eded98049cce47c7230a87ff5b94e063387caa82be58f994d1621fe902c04af19d056aeb078f59c53c515980e474811a94a51629ea1c448022906a395f80d3af32820d0d1bf741c3dd53ef67d3a8bf39ef63c65730998efd912a6900585ad6b5e0da43bcda8e184ba527388c2b0ece6e2fb7fef8d13c36c3fdcb954a8d339d308d9aee42bf57258259fcdec81973004b7a63dedcc123464df297570698a0b3bcde030a5ab17f4771e5e0b125d0e7ebf33a1ee7d7c0a824c0d1ab9fa3a63312192bcda6b466bea85554c70dfe718a03e128b8a6c55e0b2420a0238a08f698ebf4ab2d2c8d2ac85f68fdc6c3719e8071847bdc15c6f97d886ffe1438b689febe2bb2a60b932b76534c5ff7fec9f285b5d352c1860a959ac8255f03614d629d1c76cef31ef80a3f48bd636b18eb0e671a86afda40c1ba7a8558e7f3d11f204ea7e4246ef584cd7337fde0e3e4a2f08d82cd41d1e2bf853ecd2ac77e2ae88489df5f860b0a4b41aeb9034c3cb7b0495ab15741849f06548d5d5bdefaf2629c3f4abdded3ff2f4f1d77426dc522fcf7b7aafafc02239361d896625155b87461ba572c6432f98a6dd813c03ff685432567e0865bec92059a57cc8e5beca32ee62354f0794288a89c6abc81f787a77a7faef0ddd88ca4ccc840ac1a843cf5846b3a589c788190fb1f7e44c748aef8182ddf63c4d750f8125a48d41e108cf03f5f6f4e567b367ca2d22ed02e2b347c535fcfdbf5ec775400606760b0b6f74f7c2492eb0388be4caf5be336032ae9d0120793a6fff6eab157fd9a42241be67a634956ef79c676dd3d4fdf4ca4fc88c3d1c7920be8f6781c1a987d0fa69b98ca0c9ed1adc8af9b5ced4e89261fa30f9c9dba1de3e5ceab13c070e4beafb268c16a3a95c9dd8a3b30ed902669bb483c6e6671285e4c44a4a74d765cc9f28951bd62a831d0511d173744f296e652414ab858c467c5fb3a42cd4b3cc0c386d06ae004ae5ed2e41e34840b3ba51933051a3579baa7c5512f5c881c52ace6d1371b7951ae07be075777b760f62cc83d52e16474cbcff08c9e9276f18e15423005536f130079028429b2d0f1253314972c9ac6924b27b000b1a9779e86f8cc8674466a58360b4a2947edf4182fe43b091c7571df4c518b68417e4f154e957913f5a3d3c97fe8551f4cbba9452d790c77a02c0856eb9126c94f99309e4fd248c5d8b88f407c313129f94f9c5dd3bf6f2e4099e079599239693697a8a4f1c78e8194d946cf8e8c8862c046738acea1b8c2470eced5d5f06a23d2da6c8eb8e59658bfb1d46623b3e87d508684759126d428a939c5e7446bdbf865aed029df164b43c4dcb9ae48bc89bcf01bf82eb4f88590e7427a5a8b006d482804cb304c5600a46222303996993ed36ac7360122d0538e86d8a3156d0d7442e5817eae3a72fecf4f7a20089ebedb47d6e29bbbdf1064537829f2573427dfab98d8eaa161f5d3994ef938262a968df82bb1d1c139172af91dd25046091793bf1796a498ac3904f56dd0e33c8bdb2b8c4eac7686ae35aa9afac62dbc8a32ad47f32733a219e2a7aa8ac786e930619745ac125e53dd55c33c979dc24903e4bdf599e59eb606512ee4fd49d3eb39745e5212709966ad6cb69c57796f261b333e054016ee735390b839e6a57a649a36ba988bffcb8881c9e4a4c8d272673a4f4d41903328e05767c8dd2fbd5f7d840a8f30d9760737943b759f18230cdb569a95143ed88bb6a59f277beb726a746d407f84d51cdf7cdc8bf6aac1f1a41c6a3977c0b059593d6c8d11c7217efc7feb56d2849ab2aecf3639fde46a4b3fb1daae25ef2d84c72f997945aef4d113d62e319148f1554770a8b2aa8556f178a782b0e1455b0e69f088a226d83bdbe6a6bc36b8f08dadb328d191c326f79f24a5c979122f81fa4db7589e7ef2a5b37dc197443d90f3cbac851d03ac5b34a03c5963b99b51ee7ad282e129db390d449d06f95d99e26538fa68c8b98a1cd9b69dbecbc40cfdacb3f0cca29b6d5edc95dfd2ccc1e318f91d7af11cd35010d91bf8d1f314ff2ff64b957695a5e74126d09a46a25366b3300ce983bb373934557edc8fd4d45714f089a130208221993647df10830b4bf76c38ac049be218916567a88d452fdae54ffbc733bea4b42d0864de3e87c11cd8d047aefb0f3293c1207ca2fc9c016227fda11c4aa86c8d81474915f2a67efb1172446f8c4f9d1aa1b3d7c293192604e7a49c3ef5e977570e280ccd106672c2c7aa5abc1d2b15b26581510864f94e2e0fcb7dd866a42363511d32319cd1e383c0be55c82b72600a8c369f2da87e8238ac4f4c8186418d4eb6166b10668670bbcdd743d1e810882184442b9c8d38dda6e6fd852532b6849c4d42b1b32d88d403f17a54383cfcd6e65eab87f6373907d53e0f73eaaca3809d5f3215db09298a9aa823e16eed2e16eb196a04d5a789663aa5bc9ed602833c1a13a3d06969a659773e4cae1705d45299957b401aaa65ecb9a8cb79267a5ab2256d0d667747872edc57ccdac2891489fe75b930d602d8788d5c3b4fdedfcb227c3a2d4fe36a0b39fe603df1e3a3e3aacb057b212940635ce1660b1b3a26054a96b3e6bc142c92468d8f41ef138f5bbdf167cde3cfa21286a810a5887343b192a1f7364f582133fbee2bc386b5556f1f42d691986837c79f27415e1ac36cd19aa8584129824146c5d7c041b8ce091d2648a87c7572ddbbe55610544a82c6e2844d54ee7bacdc59b2fa7d3dc3d9c1fcb81dd4c73e73f5a5c483143ea0e87c03e391a9ff2ffd901626a2aa6356728d9b2719f65866485d03d56457fcdec5ef393e7d8f16b3b9212fb024bd55b6d1b644f6b3168512908e1b50c075a149110c5ff5e6500d25d5f64512f6f9d3186809594f08bbd560702b7a8c2cc74be59d5932d4f8d865d904b3d9239f4fba35441042a6f6e978e633a1500f8ceb801065225e4da52c85e84a91fa4adb3d8c672f3f8306026cb9d72ed2a7f201fde9cf8b9555875582f2e7d5ef28f37c56110cddc7e9bab307ae0661febece51655b8f6612027a0db10a50fc39d090889a038d394cae6fef4243693e9353df726f51c1da37cb3bebd7e6f5858437f66df31ca3042da1aefbeea6e331130ebddb9435188ea522311b85e6635b287875566be55574a0245293ee2b1f55a36cb206b19f5866933d26a7f3b68ba2fe8435e1993489b355662faa67c1e49b57ce5e371ea312631311d9e51be339850d5a2355011cd9fbe5935ce6742a930cbb507a6fcf9b5b2e2e1bf7f0684dc0a2b88bcc52f72a1418e307dbf5722d7494516bac66dd88d304ac1fe39e7f621790636961b2cb35c0882c0c514b96ab1373a9f2dfded895510f243fd2f9ad5199beddccc6d1adb9c49ccf4bc72685c65eaf883133302d7692391aa255b0832f9fa288f639909424c59c3c1979c61cf8bc5dfb03efaf821b29377bbdb0b70fc63a6bd988df5001915f2f145a1c630de88444e5c12fe073f7aee34427ed5e3be9be703f5d890cb9beeac504e9efa9212d97d9018e7358c17ea7bc3031288667681547ce996775eb5503d32edf62cd4f56e3d658ea170f7e97bf08584345f064439122fb8b201fd7e6db7be9e43a7b3b8efbb3cb5502cc7de6860adec4e1c15bd02050bd9307b848006bee3f9e27edf711b4036439635b19cf10325646e3472d67836430515be5e967201725e00b902a7c93921a65a806e64137e8a3b100b928156d341a18de3451534a6b7637129fbbb077066fb455b74a569f23b222f4950961ff1ad7b9825276f4af9022ce32070224b23eafb60d0d494ef628a9db74e2ee41f9fe464dfaf2916331990917381fd168dc2dcc04d839ef323e8a700eb621862532d3b81d535babd6c4bae23dae0d6f412e5e9765324b0416d6b73f5909111e0d3edf86422b7a51e5c709b02adef36ca50b9d5dee3d21354bc11febb448d9d02038acfb22add53507685542810621a5ce877b76956546d16ddd814604c2dc6bcaccff186a6a7acf50421b1336c4ed8db254b1d8b051b2cae50833823f5893ef3bd82f74596a68e484eb4fc903a949f59047404334771961a7a8549e361059e03449e759144509fec5cb9014ce2d2b92e90676eac5a3e8cf7663554e829b5b21878c6125213d19b733cca501771a3b2ddd0673870a882e3f835588bedfd8befcc0782f8bc88ab502c3428c6214210cc2976c62d28f95233f9c056f628f39b8238fb7b92afe9fbb68485f701e16559d9a31849f9b9eb8f399c0a34f1b010e1b105b02151f780d2b846732d65a7230333885a1e95308363743a2a5bf234f9a47a2a67a218f0da723c1b981c1569f737fdb5a0bd3442fe53f2fecebf9f145688e57a74417da40af87c2d452e37ecb3e883774fb30870ffdd0d6bd47014b4a0195fda91a44e21b2cca6765f78d2e528553831baa0e03bdaab7b3aca91cbeb562ef7de30e195d9d8b701786cbb0c2f3aceaaa78ac89a46b5c3ab91a91a0a0698a521d60ed09130426af810d389b3a77918d60dfed650bc98a37d48cef73288b75118e887e700818ca8d3bd920057dd1bb3937c433801ffb02e28459a44ba8787d39648c7aebf13260b118e7e2516192404757403b41d2732969ec51500032aa35dcc2d1634d104a471d18961af42a27ce32cf1e22bf04b015caca42ab066d955f7785d864128b95420b47a6833869ecfe8bee64481de508a4198dc0be6bdefe61af52c3c3c1cd8df0d52a9a44d533ba9d4551c00ccfb56300c206fb3912b60b573d6f4eee919494dcee36c7b6911e4f9993ca0e3298facf8af2ff5ff24c4c25e71f3faa2dfe4c850e40b831b221a2fa8e8799d04e74bd687225a437bfa409cd29614b59331b11a3f53cd23a5ea7d53bb9230c6c5932ce4f93d50be5fe414b54d07c38f2d0ec693d076137b8631695ccd9540b698993fe018d55e7f44ebb74f6f93626ac3d09d4c87d5e578ddddc23dfd537858a71426ddfc7dfa8b797b9bb39d0886bb12fd8f4dc8d499dac1cde7fbcc502d76bd7bfaff82ee4b2669150f12763fe3d53e38d0abc3826a92ca19bee9c2b1409d6e53f12b39d8c830540a704112374f94d9913ea4cbf0ffdec9bff9c5e98ee7ce03805ccf3f4fd315863c187fff496c219f9f665d7bb3886577a8b1901435028acedf72d6f5f21ff2333861af36a591bc34f444725c8e739d919b59b2a4f2256ed0626e3b058f23eba20a31f7adc557323e8a63787c457bb3c67b8e94c5ea3cfadf36fe6cb16eeba6e340b1d49def2c1278db820c2089dc1c98303be9d3ef5313ba274987dc4fcfef0666573d0d28336573a206a01c8eabec7524716acad60feed746e2bbe18b7f32e8cde249c76c115e24ed186c957cb452d4c68ae462b8b9b6a9e9441d2d5af033ef2f61a3d6471c06c1dd06723af7284b80985cf0b2ef2cf9742844fb84a5ff787487c91afe7f767bbe7c18a5182ef4f99784eea6e4b3291ebc4e104a8ca71a09e6e297d04d0208dc953008d743cece7121b60d63f15815ac7fab2dda6170c5784be28e93da6770b8e28c483794d6537cb586680cd91e646447972381b60d12b8d924133d21eeb9ffbfb5f7ad489113c3e0759aeb21c34bf370f3f034b5cff953cbbe533d3d5b780a35cc4dd1d53855f4cb68ef174113a4ae85bf69e1246ebae79fa9fc8fd33443b5bd09b2b6623fb4f27c52e7841c2aff86defb3f41aef7ccc48ed334999071047e080372f3844a680497448c5833aa69ea6e55bb8dd8d3ad7b55cc282fcda1e847483235494ebb099ee5d92dfac2dcab2865a6d49b61215b416c5c814ce4d481044b4eea47e5a0a56edac696c46b31fc08ac58e12556f43b779af83bf690f06e4f026a0533412417e241aa3dcc86f202131b679b188a51cecd18b78ed16604114ea8ca056331f7f96556d40cac94928bf47a20800d85218072d83d8fef8acf3edc8d68acf8d9b59aadae4a8ce2369c00c9a86f342a87b8d26195e73a30684d68e9c25da4bade565808ae06c73bdb551ef0c4cf932fe1833dd6a3fc5fb67619727a81db88f24633921a5a5895ae786e495f6c0769262fb98f9b6bda626688230fdfe2a2a85fcdf91c1d4710f06beca78462c3417f1999cd7b536cc724ca482fa42bdc0d6a5a55c8755f5b367914e61a73dbe966c94f9debcd31b39673f6106fb0b5d012d2ed0eda893d2d628ceda2c14ccd2e38e9d898e0037db928697a3169e8dd057188aebaa89a5b498b523ffdbaafb995eecde49d5866d708b1f785ce9e048ba2188c1ffdb67bf44337332d9c4373e55200eba2b5a59948127f250047e2f6d1c3dcbd2d15631a79057b650452c7c9744afb85ff539c011ec4bde030be29922dabe596880bce8fdae453e2ca7a256d95d602cc18bd32def8c8689a48294605a2c1767c20f8332fca9f15a7f635cd001e505176b81aa28bdc16e65910854177b0cef3aa8b5b77a8d3b0968b1b16ace08c3963106d036c01c241b0f70d07c2742f2ed88f0c4a01f01650221aa93c63084a9d88a2f36aed1b58e3169253a9b84ad7ca96f43189accb811b8aafb15dfd14def96f14bc98831cf7dc03e441dab8d7c4f16fa16807ddd06a31af28af6fc5b18355781375c6ccd1bf55b5335778d534229520be1a297f10c27f6ff1db50f4a1a45fd3be69ed0a6cab7ced89f42529bda1037858cdfd6f532e5fc08d8c26385e0da8452a7d815bb401a1a7f506e119b551491f5d9ef337def14e8a5dc4f034dde31d12acf7c637eb63f845467f428de28c99225aeab6159cb9f886471e656ab6ede01ad6857916093c988bbc7c7c91a15d9219fa2feb3c77ad3122495e2eaed34a73f092d7f4ba5da427930fa5cb10dee54494abdd3fafbea4d9201cf8e91cf87ed398e104d26f8c57e7af1b90f9cbee1d3b3946d7497286a0c29ffd5b32bcbf14d4918765c629823c54020e8cb28c2126ac28d99421d8eb0fff75eb8e995b7f78127111988314150102fb89f9d29a5ebe0c52ab629b71eddacc24e5044f869c01938a3729f762258f69a800e75ec68911407ee2469b4dd385c9942f35d1209ccbf083e1c5fe61cf3ada7a8f072e82b9b4b2a91624dbbd005d6454d7fb887b57c3bbe2bd400b9f63e3a70ddef8b86ab5354384739296ca3d04eff42de759006c2f476909ba9a31ae9a10adf1c9e86828b98bba0e60d2e98a287c2053c25f399e2291886219391f895001e5d0b8faba481ab8fd55cd6ef9477efaa30d2c9c82a6eacd23bbd9d348d210db680babb5965f46eaa92fd91b123a441e81510846938440b3f043fd5c59b47e3f0973b7d16fcfcd1f52fe4da420a75aeb96bdeba770a409f25a20a8df6bd3b75387e9530f3d16ad1f68cbdb3bbd609801662af8fed0a9534eb6931c20f6650c4d027d1df2be166d76c97dc287afdd7277b8bf4f4db7a9cf63ad73275e40516464f97707405c003ad6b3e8f9a822a333afbbf7bb079dd22a0d62bf35f33418aed1d44774241fe0b00dd7f7b95cd96655a5bcbfcfaa4ad0847391c49f424c03ee70da2d825442219a0c6f4f5ec1b19ced768f6bad1816787f7e7d9df33903f18ee0dd379afe61e7829a03ee227d136b9b34d73d791c8c2b18745ccceace1ef2fb33925df74cf0c35a01a805e1da0849f39ce0be9875a2716b74c24e8c4f9083ce2e40d6a8c79fc744c5854ef1a61cb92d10ecf6fb46c983d83d48a3836a0d006c5c1d94bb580230d6fb0059c9e0c597857729895a15ebe63d134bba8a9b2a30c46a49d68c2a8bda1492000a4c94ad8593029bb2316b495fa5aae169183a6c03929afcf0bf05b5fc1ea133cc2dfb60bb40bbc2e77fcd5f1dc138540ff76cd474ab617f98c4b906bee3723bf578df26cdfbde641d4be3ae7bdca2c8764a98b1cd6e25a3e0891782412ebbcd53619281596aced082811e33aaeff613f4b495fcd9e9c1e74c26694c0060f03494b9183e51f12a4464097af3b3e07e958159a50b7a0af8e2caf67b7021ce968bdfd120b42e781314ef2b804d051e9ba77aa7399ad6c9403127e4c78ce362532a76b850810c997537b2965ff775d742f8be3825a95655f5b06a3b3361e4e5b49030400ae6fa0565bf2db0e60165fc11d5b827ab85f385889774e23d8860ca171f4366d92f33ae2a62e536553743e5569d8771e4d53170402dd3c5b2e6a5cd313fda0cfaee08d34dfeea1cbfff22b5f097d4d377ef5fff55b8694a33af0498fdf49feb04ebf7b803df2b56f0f2fe8e2cf15e53b713a55e0be75944f13be1b0045a2049089c088e572cad060b8a82e6f6e6027c846a47e273d86d46f8ada053da611577c5c1ae340a4cc605d404fe6c0a92a6552458946597c6c1785d50bf0690af422c77c31ff18254ce4220317952ea0aae7465fe4f39709f8559b52df8b55be8064566a3b4a31e6bbefa29c1b5db825f4eecd45cf48490252c4d09dc1e7e09a1dd314860a71b7988b200cc6f798ac64caffc6cc814a78077b5f925e4141bb1a96622a944f50c8882e4c045d453c06eb1115576e6af8c16c6ef430e2d9872b37a7cae8b2ebcc37b62ad313765c5307e34fb98049e1aade16160933703402ee59a92e45713069f3ab9a25725fa4cc39ad90cd0d5d8e370d65ece03ce7f90f28232e06fcbfa804343c7780860421e2031509ac4f3b022a11b5b80cf9075a3c61be445a11a7dfe0c92bbf341295975f3ff6e330b4c89be1b82a88a3430a49465e07b14a9a2202fc5a8859b4bb05320fabbd039397f52cf361073d7ede594ec06cc5e22e5092d66a242e7bf9668d785fcf26a34706753825918e979cbbacc5aeba2ebbb64ff4067510ae1941e74f5be91bf37d51d7f9702440cfef9ef8a63424660fe399f96cd0dfb3590b2049b579901da763d1f46bb1896abd32ce06a031b19d2cf6b8ccebfbcf3800aff4120630796ad6939e47797cc200f694f8ef564df5f3a7f04404e9fab3c5aeb4ebe8b76938039c88d7de7185be33b0bf9c69e15b251d890376e521bfefee9bf7d980f56a56ab741b1c4fde33eedf83037523400a4de62a2645fe369a32e35067928539c91fbda4ef5d2b3840d45c0987920f1b85aeb25a47c27e04ebe833dde3a16c86f3ef95ec5aa11f2f9067748e563fbda078f2b22483e6e322df80634dd5905beffb8420f0b1cbe384a8e984439313cc75d6b9f4df37219d987d16562cfd549cd69608175088c9fc59ffd62667b28279688c16ecb0f5297a65deede595ae1fd68e1da6f8d27c765091e56741cc407a49ca802fd2237b7232500c806f50f941d61721a015858be1bd0d7afe7457f03f91166f64ef7a79af6269be9ea366c014e64a70e6ffe3e5ed5b8bc339f6f65cc410043ad35b88203f68e2e09e7527ed1898e45a71e9b1fd22d6ce0a0916924e8c51c4070fd2e503966b02c03cd13e08e19aac17758e145aea83257bf68bcdb994ce8948c0bb8a9eafa1c6e8c757e8f085bdebbc28d1ad083f546df4a6d54762ee69b77bfae6c2577f2b93aced1d5a3a867de497c984968c812f8fa8635068fe072ce0b1c41814cb2cb780a8d3c19d322fa2e11295920f1e620a1f9dec75f79966ad1b18dc58533e1d991e20d533caca2a7d40bc16c11e9a43b1fb13f49e05a98ee276d996be432b3b51cae3614af8632c818bb81889992e4e7f74dca46583d8874167e674d6e1fb6b3d3d430d18f66b95920447e7ff336d5d3ed80b8ffa8f7f502c097304914b39bdd00b759564a720e3162217c1c3771127d582c0b97439fc6ea2b9d6265e8677e12e42930e7fb7b92cd686ef3e2f94a1a4c84322764c494b396e8b608ba6b0377a6e0d6504dc4e0bc90d959dba6f9012d7e8e83a94c6257c199e67e5b6f9de0bc97fea8b671e93ae83aaa3088afb035ebc4f7a7def83fcb69de3f9add8b62e115204ccc2bb303af36be86fd1c050fc10ec17f1cbfdea9d0ee7502cf668e55403fe9c57de2b7920769f80fa48dfadb7c3e3784b8737b39801b743aa26c408d533048a608c90e8519399105062914e35446626ca188ef62ef650aa0f10cd7efc94854e2a205e55877b1f862c3cf6753e164a73417f7086a2e81e6c5fb1dcd5b080f22b4a58f7eb7ef10bcebe4a084afc210a7ed693bab77aa2be3aca5af77b44b84bfff1c81dcfcb2139b66d3890e60c03dd23e2fabb46c3f612b0bf91f2861f87504660ef746e862ffa2ed91fec685c56c89ea9c521f1c42aec5faff2ef7a871e50d913996b75118876565c71b6a48d9b835ffa5e5db0a9c57cbec0f48babb661bf4d599d31b46a39e443be1e9cc0dfb15bdd16122f4276aa01cf59be55ce65c97454334104988ec47334a880730ce4dcfc849cf6c15e33e97033c158724dcbb0fbbf24b60e721aea3926ab00d0320f69218ba32fb5243c2ac70e760f4740d7e09b34196c9291dcb6dfae22175dd4bf1e8b79df72db3b9d7fb263917bc9c4103dd80303f57c971168ae23a5ee2d48c6efadfc329c045520b63cc9d226928de03d61d7448293978c1d05f6491a5598a1a28edbcd11e473e44202b0c33f309418c7165ee57adebe0b295de8e362f313f58ffc996fcaa06b0582800349e552e774a4952ac3f13c9dce08de915f2ea08457b5dcb31cc64d2969555de0ef80989badd60ef7176513166cfdb646649714dacbcd6f2c462c54c36521f330fb92873aca87710a71c8e64e04a2798505eedaffb2866dd999c85c52aa0c88ec568bea512523841760e8195e09621186ddf83c6ff3f4e88db8dc74730d1033d8fe090c3b35d54389bc83f7a899121dd27409d621864a6ad9cbfd8e38ea9d4cd4713ac473896235e9aae5e4db787c566c9b62c2c2fd11ae6d655a7991ab5436afe2da336c45a8ea4117a8b19d8edee3612d2f5d9728aa199b630aa5f9fd73b6075db3fc70ad8e47780cb56a46c405ae41f73723fcb4a511ad095433ff02f4f3b1d257a24313a79272055e95f983245a0bf3080cf8789e893c83eae0596ad724cc7a5e1a0f9ee31135ff60588fed9c9d667915d5482d53bbab1df2c8c136853e01120f7d0667f9e289ac99d5f5d8d4dd0e9c94ad9fdf5cf9bccff7d49d0b36327c464fa3ac89ae29f63d6247100cdc6d8f3584a1165419fea726520008a1041cb7214f03223031d99a82039221ef8fdb0f03bfc2f9bf297b6c3f9b621fb763b4541628df17a1beba218a1986f5a59c2531ef8c3a837b67df1300f4331e39b26eb75662632999eb9e45cdbf3159f794405a97883404e1a7c095486652e545c7542d7f948a3e6ce75f17ec6a76be330b27dc6ef85acc378f2fb5f064b7717d7268c40c9a285276deff54339db1303ac5ae40756cae2a4fb05a999388209b2b0c0fef2c06290f6b14554a2c1a805e9a81aeb8ca8095045d7494bb1864b86205485a387a8e2f273a99f8a9e0b30938f3743329dd20c12e8d4751ec8192b567c7a47a62cc87b38176a22bf60c126c6b2872de4c5a6f2b3bce61baf7f8197e0598b0c331a69f32874b94d85f25582c7f909959b1ace6cabc35ad459e241d86e389e7b71cf4e97358c81fa3a40d79266006e7c4807749b4a6088e2d7a3203df39a679b6ea1d46ed530d5e482bac38c2e36f4d4055014106cbbe0a766456b973defb51968ccac69924b383c07f8490c0b0299dacf54efad1fe20f9a7e417967640fa6d61a33ae3b9c7c92baa79d573ef045122214638b6dd5edbb751f551f5141be6b8978bf8f80cc05606ea9e2fe5321474106a3b5cb8d9385cbe2484b100e6eb08c1160c2d962b68218b861d08f009114d7e5f120fd1d5c9197ffe97a41f822a4599d79fa9cf5deaf250aca38c44efa5ea6b91598c2d92e3fb171a0fad61025dd3c9a9332b35759dca7c56162e5b31840a255820d793ac0181fe5568d6a45498ef410548b9cb92a202bd310158c7c73f09711b24d4296c5cbc8ee078b775c2ea92b9b3d58c147a27b9b693ff8c970114b4aa86dd182e9dcadd60806af31337afcdbf389519da5186138b719f9134e073412d39d6fca82831a93aea31f540d767b5c5dcab80ca402b659d8e300c4414ea80fe265bc83868dce3964945e70b6b600b142cf4d2c46c156dfed1da6644b139e5b59418e6629a0565affe3b207b4b9002756744095ff421d7dfadc0b301ee706a71e6e0890b1f4886c87609d8794c6501fb5fc1b3e3acacf7f4ebf50836d2e154e56e81f0de851fbc240b0d8e013518c20dc485b1cb339a844c249fa363e1c6a04301f7e474cc2468ef5f9a9dbc67ebfaa4473fcab5832f341443ab6e1e42cd6526408eb3ba7887c3edc0818cce9b86728fee316662088acc1f35af8fdf3a0480c8222f5c738a897480ab7fdaec306e6c3a44cc8027923beec21134b5bcdd9c38828afd94f933fe1faa951e5c86a2a6f6e47e908cbda213f07fd12bb22136a8a2bdaadc96e3aead047a30250f646ee291ece405e20b850303900267c9543cfea91a6f18fc3d461aea9661535d9bc43801ef5feef85894f017c4d567521508704c18c4a9e2eca4f7162298ff2dbf70263158db93a31171e852996fafb3a52b2b211db6ec177bf196489704a0b3380aa5be3a12f5d65702f577f60b300571430a3b3f93fb3e62b457bab04d7d15aa8e5ef900362bbba4a3168de33f0972e0d0ce3650b9625c1e5adfd4ea790c06d2ed46b43e289f20fe1e6944b5af9df8c09c43d95677388459453e0bbc4bf5dbcb3f7d52ca61eb5a83254ec28119e19bd0ae8cdb0cc8f0369a9f69da99b08d8549a77c4233e2dda0ba73b8b628d1dfb7b65dafe727571c63a0bd9dcf940270add89051e76c63feae840ca8adba770a7713100d70584462e42cd65e5e283e4ddb65a81028d3e56970e2e0f4adc621774a13dc2138153fdb66dd66d853c223bc2bbe07d3908faf04f1f20a6fbd3c7b21eabc2f8a3771c481f16a8a530f6ede23542ec9391c71a106b8b2371eef86cd2f4070bc62994dc5ab96e833e624639c0b82612617ccd91a0bd13c0dddef0c432ed9040007a0c0a717fabbd4c8ba483efa98841b1d89ff13d3f9edf08e64dc3bc8617a025e8ff2da514b49200cf4258cb0670421ef5bce0afdda797bce532ef04bdec98dd9c0d6975a28d00b37a6e864e2afc8a954a8c8ecee51d41e0d08b6fecb6dfe76d22394ed129c61cef996e2aad661e1369c0d84dab2a279289d680305c9fab29e447a33fe57b7104d40a772d2acb69b54295e36bcb28a463babee3c837043d7d18f22c275d4fffb2b0b2e92ea2704bdd3a69f237b8fd4439a9b5709125735f863ce885fa05662a8296e2bb7d0986a5dd3bb8ec67f6c4f09a4745eb49e0e82eedda41e96bb071cdcbca420844d2ca840f1056581bfc55e6c05457194f1041faa35c64ae90e8394cfba6e10c71377c03d9183c1d6d5dbb5418d84c208387ea72575341faf139cf6e955d6d37c799bda88ee871b90a3fb21880cd6fa5686f9790238fe6ffc166eb487bbe90fd7011ba56223883dae9b6d1a8eea8dc533f182470d2c5c156e99c0804e4a3c935872822731a18a34e1fc7072fa11a1f09d88283bae2603e7991075820a5d4f7c22160732b7626aeee5f30afc730076aa0b2f38e53f6c328e98c1a947d9f293c756e1cc58b04742c740a90e12fd86f147ea1ef1c3b8eb74f054ade4aee461ef1d0e2fbaee60ed592987fd6120cd418bd24e165107e919b9067322d33d6a0d9afa0d6835c2c4831c0bd88c0e241b10ce266108b91db5a6a6f9ec1ebbdeb2c9dfb6c73aae3071071f2821067018ee00692782ae8de1f51073b7a6190828edf0446644ec42d72dcce4ae041aafccb75b61b53beedc2e5c2b9ab4dfa5cb22ff9bf4fb435c65af5e2909149e8f61f1599535e68dde7f05bfbc654347ad8993f62f404f77dd9a307f8acfd422f6e59fb8ec0b13fe93f1ed3dc2a945d39d30e6be0eacbc5f33ccb7561dede3706f799b197da4bdd1f61fca77b7855c114f34c3274ce994effd6167248754781de02e25e1efe1dc5810837734a1d508c9e352afa07a0fbfbdf73373822031ba4f12934f8e826bce83c1c9aaafc1e4fb9d3f1d11df8f77a7c34251a0f51bd916c7582994f601a38f6f5c2f22cd0c04029d91c50052ee19e9255c49614e7809e4176728739cf9498681ebff95f538b3fcb643a689e84dcb54261af33148657a18280214f7adc57ad9c7e02d9b61b5c2f2aa9430e670275b5490acdbc20aaabe088a480276a84150bc731a8a63727d0173a58a85c11edd88fa7b75832f85be829b26616c8c2f4b0327e5409ba4a8d689bb23b06280f66585ae7eccf26f1a2644f90b26fb4706e9909cb57295884fc233d187b795a4f3338e1b289bd23196006a7e4dd06eb4d49d2be8053aa9a458f1540d50f002d43e62dc3251e709afbc36b18943d71f6d966a3a6d3013a96f38b30d994f601484a5944e42451c7744b1fd167eb1241ee83a55ae38751bb7a19a84a841d263d4ce76b098c34de6469a75dc0ef2c37214689b6825eac4b598820f6546ea4174381187e3cb5afda660196a11bf002626d7e97bbdfc2e4b8231aebd4aa5d9142f82bcc94eadd2fc74d53a2e78e02583c656e59b46387f24b1d8807e8f1cee5b22d4d4dc404121646d474d5e01711ba051ccc2de28e70188bc760b718c4bbc90b74a100da3c8e0dcb4e7fbd2234564a360ccdf73478eb5f5adeb2c51c0554039c8eb31011b8a3b1afe3583857ee0b534be2fb2063f9a3712bc063228a821919b4ce6bc55b1966b0f7fde83b77f7d7899d820642306b00b1831c4fe0186f462768437f97d4fa3c70e4b1fb834ac6188947e96e519a171f14af7a876281ecc33b12df61ebff86ead196c37043115986d328206723df419f77057972785c2fef110c5d0c5d8d642e6c34cd0601a3a3ec262809839063648eef80badd190db47048ba905d6b2bd2572983afc043e2f86401e537751d8081b2a97056d7087af81ed6e1b0b080fe401566ba7ffc393cb1c8d06177f9a4d5b495c5226b5866042c8eb13dc6fa00156c9dd52ec1ae2432e00b9dbe605612c2bd962067e2d803256726ed1ded0607123d5a7e66701601f7a9e1e6cac1bbf57e3a5905e1c7b90bddead7bbbcb3e15b219be5dc0cf644bed62247d8ea208e90f27a012a2f87449eb98c36de47b2ec6e2971598cceab04220a601496db59cc222efbe4016ef43e4e5a8a581cc469194d357045a9adc4cc0384b738eaacb18100e420e230ec02a8e23d0ef53a9979f64f0ff273b62c4abf1d9f4fc6493ada9104b7d79063b3569126ac7d2a0ac3048dc9b08bed1012e0f7b68719c11bff233d7acedb885a304790433d032e675125615396de5d8b20a191a673f3673c06f786666a1e52d8a880c168e4be1b97243272b6f430879f9dd875aae4e7edf57706b79a53bc346300f61c5ae37f78564bd63be795f1a8fb4fab91a71b2d66f1d07680fcfc98d40585e8e323463b0cb182f07939c7272c836b229cadf1a7d212df9496abe409013efe2bf3a16fe7d7ccd36af9651f0cc000dd079eb984ec8009a6804e11fb17be55bff3ba53bff3dc91afe6345fff03f52569a791c01023b4314995bb3c4cdf678e5b497775d1fca5b81e54ee5123669a11ed0be43b0d07f2a9d805e68f83884484ab70617bc999d29781c3bb289e9b9d28e903480cab0452b009dbd79643271b3cafd3a2b109803ed23d2042dfe42a49c58bc1304af06e0c9187e998939c3d0e92ea15264477f5c6ccc66d990f8d74face478ddc1e22b63244e8a1bae160a1ce01d010e7ef5fb9319e60b6d5ca82555db966a0d0edd9864ad19caf3f96f65a00e74942c291edc947575c1eaeb02517d872827c7c8027d3663dc0905983462096b7f82dacc7519e98869f0baf71800919de328ef4de4b02c7f5186c508d1afca3c1f5b65d3bdb034298d9a239912c9df052659dad509e07624259a917b21bd1312ebebbd2595e01841e68c0330d8db914ef2233dd0f49fa037d70df0380dd6146b7b326428a70594a10f988c9685c2c6fd7aed1ca22aecea35a13ae1024c20d5a69e077b8a7df3c5c2dab4c548fd935c580af108dadb7f88747f5f903cabf8267718ed75b74e76569bac7932174457468ca4d9043a74599abba75d0481e0f888dde6beb0f6bd41a15a2384f9a724cab61e342414e9314264be643a00abb606471c0091365794dbbecd2272fa66ce0bf38246a760caa7c93d3b4f731dac4bcd35e8f4674336abaade333d162bc29e2f3f2519807f8ec2ad88c4844f484ca3ba016dc1e383c18381b7c3c3a5df29e6b71c372daf7cadd59301badf1f7cc48c8a8325eeb51d3546de6d396b51c850601d4e1a9d8616f6e526a2f0bf36da5a9b23b68689d5433f7eca94c336b71b22329df54bbc99af5643f1df1b7374d2f4ceab37cbbceda4602a83d6bb29ef1abe9125384ad9899d7de4c7d5bb840c471a9c1824174cdd5c0cb000dc53aa4721159424eba4c9a5ac9511fb3a719e37711f6902871f62ec51c2dc41214c1d0c04f0b6d0de117480b7fec66f55f4a9bf9f8dd24b3a90f17b1e3c7bf71ff461a8c16dcb96c05462e88b8d9f76daa290da09c54962b29b6151fb443658310569c5aa64728fecbf95e244c9f86a77b45d5ac67ea3fefea98a3d29562d5ae172da373d298746ab8bfab0af62c43b93f88a806ac715dfb72d0364c6c63a37d97ab6c42c6d20834c4b318c67bb56ebb2f55f8cb335fb9724f48332f97db7f2598500a02f0aa52662d65896460fe0ea7d766698734852fbef581e88122258f696890728729da935414710a9d0a7855ed9d068cfac25c912a405043f1c8a517de02207f22974eaab2ff006c5e217ed1353fcab65a848819804622783737c91ee1f37a2f9dd467ef5f6387fd513d397d3c0ecc5317da42b76077ca573229568eccfb150dc2cba060ac88bb8b6ff65c4ab7a43978c5ba01160c585840d34c17b2bb300068a1c8408780a301efa7f8bd192160fe8c908b3162235ca26a071d910489be48c5bc7cc7272f5a9f7761f4cef03f06e091d66b60e2d6828327d0c2384c603e8f247b07361ea10f9b6ec29ffa8bffbc4627c941b898d32f9ea11de2d99ff9242d5c885e76cdf6c09e452cbacd1ff62f648dbcfcf4fce29b6705a466fbe11b1b71b21272452b2b9488a4ba762b76764d1e61500e89e54218b7d43543a554b788600200f802c2d9400a23e3850f4e7b7fcd898be197af4df34bff35325ab74bbbc2009e61a1e3d0e05c6634ba744655ba0bfa09c91669fafe00e5df43c3c7fe8837a73848a827a1bf16abac68bb46301d1b17be8c152948d42e425221a84b63d1910e3e58f24a69b39350ad84c4e3e8e979c0ecc4f08afe1d350d2a8e8c4b11ec0a13191afe287d6cc591ae44071759b8bac06212d9fd6f303ff7c40aae7a2a78a2692f477169a652a0311c0678dcfb0186743b0c69e3b637f44c14b5e8978982a2583c39bdbb852364e3d3a23652920a65ca368600781b3dc30813ab3b0ed49aa5f11face91c08d58bf32d69a502294da5a071fc95ab8199873a2aeba39e43823f134e388209ae4e31ba371e79e93fa4dd768e15c59b4d390e798ed643e405edbe020edfb03c5e541949a0176f14b2d6c519beb22411f4cad5669723196368edca3cc7009224a0ed420242fef631dd5fd54fd9117832a37a95ac2791835ea5e7c210fe7580fe200f5c6b81620e70aecb7cac5d51badd6a810a77d45726788f902631991b97448e55639514bbce489135359460d4efd7491ef3d7c31cfef9361a0c17592c8c0bb64eeb7b0773bb1f49ee7acde52973cd57893b12318220a943330f2cb28003e58248802a5589756e18850e053cd67121e48b3bb334a11369d84960b3f7fab6b584895ef3763630feb2e481d946c631ffae89c3c54f25d73de8087bea896530a1d44f974a2411bf0b3a4809e49257b760081bc82e49bfd3aa1bb67160f4832bec30cb0cdb9ab115b90063f999783b8a7bc9ba5be973923c24557207df26709eac9f1b6a3ed452a96c5fe5c5561f1d8c786ea60a0f5b706fd1af7f03d5d5bd0aaf3860c1a7de7fce35106e94c5b38b3ac4c1967d3135a260de07839bb320210bffe0ada0c0a066ac0bf391181dd405fa813430d2cbfbee3830194855ccd3ad5a1f9c36615185a341801df021be7d0986c75e508443c46516e1257f4f368d26de760690898885d00ed0f71777b309cb2ee3ba79f2a30b085a63ee6b328e7645eabad7dbd3959c775c91d0fbcd5268fd80cae0a6710a8226dc90cc847c66f9e81880df2c389f179ba21baf5081e1017a6f72cb43610d25f36ad8d32dfe297a533bdc966e74d880049211a038ccc6474a99f1fe333f28df2ce4f259ffa6e3aa84f3c34295d2d51a41180cbd682db640b99bc49a8fb65204c3afa02de9744e796d484e6e95ce768a0134c6e336a08958024540c3ac767c2a9ba35f17482641990ebf228d6ef4a40017c275577dec7da01d696ea89fc2711b7bb7be1f71f1dd542297b238dc6e6217d30bb5314a6097ca52524a9a900715d760ddd8410b4ff2495c6fa4963c91c7564c01e78169d221cf952c64b1653ae5a46d4ee97a73e977a76801fab1fb17dba6db4146d806add212b87f2b8867f5a881c65242ad450611b39d48ee64c732cfcfed3cf3897409087f3f348c76bb61768f3c13a1f96cb5053e69dad9297fdd959c297df8002a0e021ff5799f24ac2f7a7c5a7ae0de0324878ad774ca1858a886e974ca6188435532e8978d044c669a2b965781e62f6b5994df51875a3a8a386d434c6894334186364ea2a4bf2c95601a659188dc292efd3055be5f2060e1065fee1320ad39f9dcf7f803f4a2875ffdce85a62826c5e3c4459ae355b28a328777e31d78f1d0b654ce033a5649e412a6a515deb4a19f43e7ff5cf29abe0f3350942a8ca9cc58cea208db18e48a07e900f68d747d688c065ca3364c6dd6ffc6a704e26c987bc2dca16956e59878722ac3a3f53e1dedb1cdee4ffe7369533109838cb32e9a701b99d1dae412782790baed9831a7412ecb6472c3ccc002ec75082a0e22a612abd7dbbe1c133d53d6bacaa0a7b81333591f3faf5cd20ec96feb3a0cd591291b9f0cd6d5e5b81780f2866bd78272a8fe7d3a8f166d40c097365af760d183ae8e4a7621ca6006803fae06bdc81a52d0e70abba313b1a5d40d692471b45b21125732498cd36efe7e8f8b1391538cf3258d6eaa9120baffca76d8b2c460969763b57674e110fbaf4856f7791d7c78e3131ecff67aa64e60ad94404084e4ad95c4d71de8b7fabf1b389381a250d7b99f29af5bf2727aebd31e2be1f0b1c2462dd0db3a20b1998f1d409ce1a15334cfced132be0a5a73a8e08f35d43752efb21072d6720af7b8e2cb8c64620852e1a31b2fafa2c2b3964c31344bc94e7bb23239b42d4cea514bd028d4248eb5f8c2e5621f263b08e75b62680225c4a5f032f857fa1807c5db90fd0a71ff2311c78683df6ff6fefd34040f317af0a16ace124bbb61be9d49648af37aacb9da3c2af852a90bcb4d72c383b1d8eb02b901369f77dd97b19b6c7cafdd0488ed19cf537b79a0b078f9ca5b9baa8c6d29ffaacbe65e1d439893d02e640482b0901c1afc48393b9b1e581d444330ca2893028db489c0edb8fe564417283859122b7d20b8dfc3faa7f1fee80d3cba49c7d2b09592ea5d112db36ae1d4e6d8abdffcf7e304047b89d0e951a5e595d6065ac427452b02c5a3518dc52ed9268fd5580f67596bd2c3512075badc168014aec086d6d38f8e6f300330cfb34bfb9f54fa85d631c1f6b3dede8230f70655434dd880f990a9d2a4c5851c26bebca8847f88b013ccd68c6e40bf8c62a67c599a106bf075b7e4f525238b447437194b8bf06d2814618c305a71ce5c29982da709c3a48e489f06484688cab0fee17cfbfebee8dbdfaeac9ae55f2d6d6fe242cb7df78da1500d8d9f99c1f769f6fe9a99d6adf20d5efe3925357cc9c3cf2b04530d35a25fa65e9586090f43e92bd5441b765d1914aca7b833b2d4da5f62bd88e9ed393a88378d2833f6f224f057040c3e37ba611c6784ec9154204544fa33dad097cc5e748c221b9f7ff9f6edbfde6b3ed22d4bc8c13d5575cfeb624ed0db4deed8878dfd72b55213bb8bf3c3823295faeedecee512a2cee2160c3be7ddf3f9ea04d01f096acb29de9b8ace90e8c8932afe2d0ebe97e341bd2161b23b4b8d2bf2b14c136f54d20c0e168159e3190462c1bc82cda740a2cbcf13641617713748223e0349f554acb3723bed0c70fab319c0232ec768947114265cb9318f1387e158d6e844be22136479e76f569903cf33ca79a2b719bd669ec235d6c2035ce89c82cd32341c3a3629e781d290ad71299244e1a3b4feb5c917aa788735a2e30e282cf1dd5acded54f092217f89decb9bf94b1e84cf19d30d98f39ac82baab7c825f7000db4f7dc8a482fe28413e753e6d88d7afd481218f8e57df81013b749aad946d055e815a78a2ed5e8f6b19e04fcaf6358f1398ee298ec3cb5c649a663e6970515ac626b16a3d329d00f3348781b8668b2ed506ff42dd2d2b7a4290d84d39f91c45a31de1b68068df42136007d200db9c991be0ef70f930c6b2e34b795d4b72a9c41927e4816171627162fd8e97e23e0bb375c1ffc86471b0db081ef4ea3d9b32752267ab6b8c25d0e6bacb2ddda76005345baaf3e93a053ba3dd5dfff423a1072ba3f9703b525f384ee24f5b19caf672d18ccd19cba25526818209e725b446571394ce77d30ad9f93f7871dbdfd7a08b1672277fdbf616de9cb73b458bda1b5133294b7805213d6bdc119aa7b732d3c9b9ecc0d4a68ca1d061eb02f2634f7afa7b05751af3eb056b1992ababd75ed9bbb04bd31b22222398f5a8944779113e20deb9c2253ed78aeac187f7de5f3f429dfcbb2a70de4fc007dc5900a9cc5b3b0032e01dd7288bc2247962f9a3065bc88f44ba3e3164959f6f576a3722ac68a9fdccc1697f25031298c5a08ea469d7730f03d3665a77dc432831146de3de052417df3bf08291c1b5dfbc100102eb7a13f5c00e0ec4e2e4b4d5dd0de7ff5c30ac1c83ab8a20442ae5db75dcdd41e84de2511cef75a8716f29de0f91f088d28ee0c708213cecbdc7254cf1ffaffbf1b54726c235017c5dfdd5968299dd570d3c67088b696d039a3ffbad8853d35cd5245552cd1e8a9851fdbe019ac81b9313de7c7e15cf85bd8d385f75f425d1b014cc213ac677b4c6f74620353cbd2f7ab9e60e3e2b3f9a1e19ff5869898128b639e10115e9dd219f032126fa30d11211a930587f52ec9748528da208df3b58807bf1022fb2f8004f8bd3196dc1f312c9d21132d5ac73fbed09d198d83b989361113f00172ac9a77cb029b94cb5596bdcef87234a62fc0dec4f789df3c41a744d6c723872b025c54c313430713b1fd70a15d679db85944f182629a67fab933864c9d7784260e57808edfeb8e9ef30f1c8437ff8d4e49131613e2f05e9a6aebdd7927d2b133ad5fc38b1741bfdd5c027f90f850569ed1fbe35873d00ccf8ebc4e026dc7beae6102af57ca0433300b45e84cacd7f705ce12f6a5baf8968566b52e95e0b1c6c6654b7a068c86aeda552bed86147aa90ff3aba67e813537b770b471c49b29d2dae67675046e08d212f6b889039180522bfe7a96be8bff0273eb15d4b85b104d17b24c40197355b99e0665efc23a4662d5745cbcf2e90fd439b749b111af54241f45ae374590f0b6184ee104f3569be268277d847fbead83fdc973f4313180894d2dd43431e9a08979c25f185b753ca7fa5d4d2ea8118f83e584ccfd677019f8dab0cd8e629da573c7b631ab12e1f842932d6cbb9a474154800decab19886eb2ae93177d1f870cdb3f7f631e8b1d4a830e136f610ba1422f37d364fe6ce4e288396cc7fe350713f27fa6cdb1cc7c874c3e8ab9198c0da4af8887f62f33933969754b03c5d9a706368a67de1d13c7279750f413c02884349e44d9b05e907ee982e5c5e362b8bbe661cded781cda9b89e90ab1f6cd348d113b5be591ef46eb1d876999e770cf5d0769f449ac687f7d0c9b91d6eaa4a17ec36935bdafb8a57184b4c0d0627811d077135b918e60969b75e6008827b0416388da43d47c6286318fa55e36a12d405532d994f3ea14287399b954a3bb941f5c8f02bf876de5db870588223d18647fc3143d4b381bf98258483c4f9fe39d658a1b5f502eea69dae2fa06916994320d4b562450b233103708bca124065c9feb98c066e3f2426d20b137a0ea0e181a2a5d553ecbac1d448ac9e9e7bfdc655c6f09ab545d35f222d0b26931747d4197ff50f5967f6617949958d9c3a69257e3a7a19c61954569e02451e91dd34a4688291e1bd2bac9217d702d966bf5433d6f95a7f85f810ed044d32585c43e226381a07d4b7a2cff468641fd4dd2cd0bd1b7301709873027f3c243bce2be152033832178897443a476a0cad92337c8b711201cb289724995cf3070000aed9c47352e2b00d4d1190b4d4205b8ce6670cfdc8c4be75bae778e58c9bc0173af3bc458321c5571f70cdcaac409bced61a3372a370df9828530223a7fa27fa823dd239ef04e3b4a1cdef8de9f67c18abe486983f91bced84156e60a44271c2eb3c79524d4611d8e0a2a5f0c38495deb7d61aab87d6191bc6146ef710d5cb9b69e9c937e74eae2ec3c1cd83429c655d3a5e7b64e1f928283f00f13bc773c7807889323a1833b2c888072c6e075c21bc4f9a3b1c6c6ece7845d73518b6b88662b70cf545476616e930bfe2be2be7389432af407fbcdc7b8a9758ae0fdba1f48e9956638b7445ef3a10375cee2b177f982564656962476dcc1c45dd3efff860d08be31c5b627b05f4538ea1458bd07f83e9e9dbec70deca7b8beffa440911ce91c6470981219c250e5d23c94812a869812bf5f24d45980108b79b504949999e99d9b1d79ef286e8766ca4f0a63d866f7dd0646fc0843ffb5d6f5a44eafb8f013df8501821d27db4f9b7257f072f161ff93c6455842ef1624b233f3b247fb7ae2349ff368d2e50eb50060ae08cc48206ed8a31a4035c9530cfcf451407902d499eb144c09005212af88e8dc1a0ced24eec1b5cbe7ef8f2b725e65507182fb01a6f338fb568050adb282873fc6d21b930f1057e6108bd0599d7fca0ee21af4ee2f93a62e42c79ec714851320c0213225b09b157d8392c5b4422418a048378b36cd63fbb1e578c71719885a2ca8fa086914317ea287cd69e6f13a32f52fc59f1df707771ebee460fb24634d2e4a511d81992fc112cf2e1fba79695d27d634640b90c2943d424179cd2481263addcd9ca32aac03390ced9df6b6d97da398bfb76f635552c24de1a51c21b5334b5365f6feec4aafc786c035e2225db6f40415d6cff3e6ed62ba36dd57b7e3c4c0f80f9925ce838157d037a7b47a53befbcebca1973562ed16d634f8e4f9b975eae7fff0b68850c37d8453f0c2994fc4efd82edad2fbe86da68b50d463dea26fb5b4840d8ac40b73cb13dcbd0dad481a607360b3d31d9e29b3fa732416a244753837f1061e042e4c341437b04fcedfd021f7f595034672920b892dd0178503523603774871729f8f8dcbee81e9085eeca8144e97a64d14ccec63e6c0c954303b821e6f8d0385cc00f0b635e468ab4008caa531060f8b11b850633fb1786d6f7bfb13ebcab0ea8b9ea4807d522bf558b84ebd55e179e33e7fca5dcd4b84a97ee8bfc648373a279a9c8d61b3620eff368fd2afda40347e4f6ead0065e0d46d7c6a55eae8f7d649fa965420ddf1d852633ebefafdd5cfceac2e486b2acafa30290a3fca8c51ee06573e7a0ef87a62d544601bee8b4b73467486f3671392b26c2154a2c681c44f00371cad0ccfc72742afb42ac2fa599f8e7bc62043e63af80d886d802c96a98a05563993da32fa643db532f9d6aa06b1cd45effe3fff3c2861306525cb17dad2b40060ac8c5e351abe7d8fc176ad1c7278afa9aeb393afe8485bc55bd1babd767f1b222e2e6470bafc57dfb14e9fede623aab15210e1ef24cceca6fce66735c2520e86f91cd060df5b84be20c9f90ab687461b96d471ca7a84ffc9bbb989c4dbad560367b53c6a6addf69163e24bbb281027c7d53a0c7a9566f970e090e15247edb693d326c85566a285faa0333027afc2694fd7231a7dc0f94dfda1edd790dddd42736ff958f7bd8bf527f541ec393c5a64fc946cdc2f5a7c746b893e8f698323d56b43bbc1ad60cd4b5897e161f4fcb7ff944c6a056710e4cb5b7c435d1abecb9b992affc46a7da01a6ea4b9bf1bd983e689d699b48d3e91060fbd73ff69e2738a431fd4c7dbeb81392756f34e91e672e62a848e5ef6d5bbcf18e753c336c013c4a7293216f3e3905527f3b00a70c46380ebd307b74d1791bc4319b5d30f46df41ab2a2c64afcc8474359f7f71fad85b31e3b7abd7e1f1726f3e8cc05aa9114f5aded3adb8646b71bb7cc4d57d589ae12762472139620098f365d09d49a7ba9859afe78dfc2633c8f3ef45dd0765b015b46fdcd347394be71a566f4a31cda52e8a7ca4816c22ab41156855ec1375b0f41860c876b492d9f948840fe0e31dc262081c6d3b5c1a1f0981bb22c29ba364f7d60c6c38db2beaf1c828dc0875c31aed30f75be8448dedb6384ae836dc1baa9d2a6814280dd41b8b0850a35ca229ed898f794d79f18696f8e1fbcee021d28ee4b439dac9e2b1d3973bc72cf242b75f1b63eb36e5b3125d7d27ac1a6f0fc3cff9a3eda9f9a1f3b57cdcbd3be51e47d20e5db2bb40e12da77bd261bf348fe584c8c2821e1e51e40e325696f7be6b20a4b8cf5000bcea0748507c27ab2f32d4f2ca942935fbbf9f91cb94d00824b909177391e8678958f6e47b3d1fdea6aa4c476d150367e207791137a43151acd69e329fee979247c637be493e66bb702e48a89bfdcc6921376f68bec654101296d0c5075666e06b4dcbb0821f133045b2eb80ca637f7ab7d0a4021c0d7aadc27732bb3a250ef18ee5138467f94343d931a268d3a0bef72846572b1a33d90fe37c3eb9e4af4523005114ad8949423892eb35ee800c13e89f9323caf024fd6452310913b2dd8b202e4ba47521675669149e02298c295f45f5e0e7779333bf5db3a44e96f9caf4e6c7063e73c417fc349f39e12e99c2b29fb3f44afcdfa312bba2ca096c4599de99f5e5ad2a474d4945415db70f506f39c581e09a1ff4dccea008bd4f80807d0539cd697fcaeaeb22d6a0b78182619e2a68df15008a74c61381bcce0bea0b3da332c480404d5b45187538d8015a8d11f5e5be36585a860903153d9f01dc4f028a56d431c0f0ff719b9aef76851c254b1edd8c4c2512ec1f92cac3e7c0a6595dba2a4f3f0b4cee1d24590fabfd9488fb6468443d053b1d15c1ae35f6a948fd26e3e0550f1c6d3a733469f080dbe8e3bd7218137bf6fa1f315800eb308d3f1dbec7a3d73ddced8ac393130daa514de948a799ebbe21a61b39d45d96c744d1a29521d9af10ca5668ffe640767a5b73a91b7a243bd7dddf062b75764bdde084de3f64f9728f658a3914f50b29bfdbe67abfc3c42f297e0ffe7fabc5af21258ea04c5b3668c7362ccad052e150678c584013b8d274278f9d917262496cdb0f03a5abdc6eeaedfb9eab432c8d9c354fbaaee60c0b9ca3eaa05ce2facf73d584bae89c2647deaf309e92f61fc9e07e6dbb0125b45fa871ac74f772a066bf26f8e4a662a810c5f0a0f0029071593eccae35ec2ebf77ed65dcbefbe95f721b3f2a9129adc84d44017f86ee12f6b9817af355d4b3bcc238a4249252e1ff4c780cffc0deb9c5224fbf3f3cd5700362db57f75e1faf9e9c986f235eae737ddfd385060b12af32d42b8f35daf44532407950605caec7c8ff40fe58dc360f37dcd84a8f455896cee7a3a4104891408310be000f8efe23fdb9647082f1cdebb864cba982cd7e33c6f44848ca2a4eacdc2aa519721eca79d701082f8cf0883ce3d644a8417c061067957acdfd4f6539214f2bc2ce3baddc7e1b9f41d97f4e07a3ac00b364beb2a68f6221e0f5c347296da8687f94fa9d4eb0dd0f77cfe0c6ca1fad0664e5b1165962263c9b0bc3d16c09bee04f7a200b2dbd8a7ac2aed9c4f35494e7a8a9eed9b007600fc78261ab018383b535dc7f31ee1ff83c1d5742685044ed81df9f627effb2704022a7a5f2c2ca51bf7d7b6ffbc81ea4bb8b1d0bc7fdddf79ff75fc11245b3032dda40cdfd56df70349b1ae64f4a74aea2dad5c3843eb1a5ec48aad8b393f82b9398f28898d4dd1b507b46e6a0da41b38a6fcfcef45b819495d570f96f20c9ee30322547449e6990054d71bea4fbf11bdd220a18faaab215096d476238afcbbd945835451a7811417a8c2d567cff7c390b90cd5f7e619f561f6f7a0d4f587afe07e78b81f50c44c6c212ce379f1ce7663a4130b8d1fd3a45bbf964b99f57d463cbfa70fc064c964dbac1c9bf5c6d4f7895faab47cfbaaee3ad582296ac5ad454a6311e463298075552c1ae28cde35229c306c21cad1b104ad9f23977dae5a58ea5c6bdd5ede96e97cf8d51c70ce2837503f092b1234767a855ae117461a0d058740af9a9c04e0bba1a03b58a563e8d65bfade3f24cbc8b774b5677fcab7ba15b89545956221f2618760cac89935f604f6d04c87d7c5d0cf508fe270d824cdfc5f229bb46cffb318e8e88143fbf6dd4d59b67ecec0a82801abf1de456e7eca679c2f735ec8ffc4524080fd6315ad75fc884af2d3cbcf92e1e0c77655c9b8fb4e6dedbe00f2ace6eead70c1e88ec41c2b692809510409b10ab2d0affd56abb151c9e7d31fbed3b0fdbd12a5b960073441039ebcbbf6c29b008c1f90ced7eb4e3cb50ae1a4f38a7369b0e5c8d931527f1d5e9040f88233edfc901bba5c5e63fc055bae27662b62f617fe5f11b2e764c99542d0ee0284601db30f79fec23e4f4767cdcb725deaeb93009c61dbbbd952a73fa2666237a51d86d0233d5190b0a408bc2034b070582658d1041f0f99ff33b978850db5aade7a0d94c44e5e387162d219ee34a16070bc757bdbcb598720633efb236a7f251a7e3db05f127b57a8b1b88913c00ac3bfde07e7dba210b79c706e0651e86290577a468eb007638922c5a12bdaba5c66c7d07183b8de0ef59f3ce97425996238438d98893b5bac550ecd9bba63d6a3b7728bcfe3147e652f51a5a7fa593cbff24878b25d1dbf9c2bb2b956b83e4318ee71e330cc0f954d8c3750ab1c70fa3bbaf75c1fea47c1d5624b84172e57ee9a3182c47406d2f123081f4f127928b18ef03d46084179072552ceffbddb9fdf4a0b77e67b6418d0f7e727cd696fd3682788f6e8da46b43538187453dfaa8e6609579a20b9f184f456332faa84c3e290f1835ed460189b31dcf064fdafb0fd5e92734731ae318a43f27464af8b9323ffdf22ec8055d7343e94211f9e71c352236dd7974d8a2403b2a530bc55f3be5e098a2eeb9e378db4a312120fb0aaf6d08362c04be3e17da3b89373ee2161c9ca8994b5acc4615138855010e27b17d2148af37db0ea1d67651670957f64e4ad7d3ba8a8e3f0287d0850f0e92b308be4338bf51081ceb046aeac69a561040cc481db8d531d450e45ad7ad9eb13a96a55f30d2b8bcb2a8fe9731bf82f82314fe5dbf8c246ea9145bca1d292169594b649ad61db0b40591a727e01f71a716b814f8af453ab81d47f9c9cb112b5e7a191d6b9fa45b097809eb094038b1c6110b980f01cada7a86629c50385032c3157668412b63d2b8b8139164d23c983370d6c2ec4577334ac3ffe2546a17b386b56882579c66f7459adcdf1c4a345c1856ab27509e501c5d6855bbf6472c293969d7fe88524a2f0c33505d68656f0b5f86a3d54c832aa8c8c1840e4fa0d45e948522ce91c3132714057a5f44d2a1505225b092c53fc4775a2972ef61cb0ecd99c102830fbe43ac1287bfcdb138e270d414f732ee3498557488a496982f5ca1f3278a4f9b2ed8305d70c31db53453845212677597a394d317ee5efaa462809c9c9c5d585f0acc1d159bd181bba36052b08bc582d56a49170825e42b0d1524e5c6920eee2928967228bd31978eb83b39e69b52143732164b76133e1a1fa57fdbf97789bffc77dfafb729547f4edf6a2b12cdb9f87ed78ab65bc321ba7a0d7d20ce8bae5a2a90b6c09c43599ae2cc54f4bb96e25415fdfe6bd1ef439b3fc9675f2a6d592c8d035f20cda15f7d4b957eca4c417025538139900a08e2c8e88a8a0ab4b792a3fd9af8301c0bc2f95fd0bb9abb4ae2d36ed07d32290ae8dfd6f815e504d7fb35bda591cfd616fe63bd6d3d5b49b4b2b8d32ab375e4423af746e3f1d9dae1ee1328fa61166529029dae284b3fa15664555820e3eeb5ef33ad5e4ba96ebd7e6e86907ea4106d5b29141cb04ea1ba15949394f44e694ae977ad98f14e887ee093ef830d81f4e670786d0e6718fdac7e2be6fa2048f7acc421bdfa6da5377f71ce6e9653abe11b9c7f57269b5baf1faa2d05415010adf32703da016817a4ddfdc6dd63983f647eb4d57c23fd063a8efce890b18ae61edcf7ca7d661465c812f7195a24b2806361a6fa0c3803ce20994112471c271d9c74f0d9ca19b68995824347403f45413f43525c4a54a8e0a729869ca4f0936b0e20e5ee65f85963f226898184618af89ac2836d01e1e5ca103f2041d3df27d6f2620d8254dbb186c3af637987e887db3d8eea3063dacedd71ce43650b397cf4bb0ad59276991462ccc2cde16b84757d29b5db6a09b570347c74da2955419a2ab8a1820754c04bd7227d2002fccbb977d16fbf2862dadad8b57f72f8e815c34c012985957b129bf7b57e652a7183894ed00774fe70edda9cb98456863893315022c63ddf9ec4afb1a8f5d356983d57582cd405664f6af7389e2ddc1d67f738550a07f79eb96382bb8bf0824f9dd7e449eda8e14e5974d70f778f4ba1e60e169d31fa9998f49739dc7081eb200011ee272e2610857a2a0b4f61e00b8f0418c0031411e804d584e58e5241acfbd6dc33599b8ffeafb9cf84a153709f6923b428264b94b8a37a7eacd77da6c81dc583dad119693a77541277d4974f647ca372dc51abd30b50402141e1dcb8a35437a1ade455a259123fec438ca4ff89f6c37e63178e868f76c6625582e512a867debcdcb7ae64f822495a1cec081cee6ebb36fca7e1fca2f3881394f5c4e7111d771f02348d68601ab172ea22635d41961e7f5bca53b5fdd1b6ae72ef82a30669783185a83187c860bf96edd75ab65fcbf66bd97e2d93edd7b2fdfab56cbf4c9651eea930ee5e372e65906921dc5d86b74c88cedd41d7425610a212f2011bfe5f93e12d0380cf205db8bb166ddd1fcafe6b3348933baedd205fdc5d16c4871964f7b819e4c85deb1765417c76ee1e82cf20d47d028183b4372d9b40ac7077205f6ebade26101ddc43dfcd64409cb80351e97d5f86310442e37a9c3fdac0b56b7f60e1de847f8869f387107afed85e770de78f97bbfb787e5cc639d9fc21e447ca3519d7d3c7163e7d34e1bbcadcbdc6a70f2372d04c1fafe9c3c7dda78f0f9ce21d5620ef429b2508a1e4dbf97672c48a223273841a82a238aaab042375e4cf7e58d1c360bb12a84837f4422f0605dd27938272ef22c75c14fe33d997d2e2a1e2ee477c8e1073a7ac1a133947a8f1215bdb23dadde9e961e1ee1fd6e919c0c9727a37d7b5cfd383e2d3ebe134e3da9c47645c0b45f8c02a56c02a746505bce95ab4adfdbe5dcdd516fd9ec9962bd5566b4b3f5ad534db21fae9b2d2cf385575ff733ff4ab75ccb8a6a988cb3feaa15892ab5ce99b9f1b6ddb5cd694ee32ac54db5d65207d0245a8557325c39aeb7dfa55ca64e9ae74b5ba856526a9a6d9fcb666ab5ff41bb75d5901c12a4c6ff30ea23961d5422d51ac766705fc1bad0669b0cab79b519a4ea21f08562b5a5df9536996e95ad66dd14f147f6852ddb05ad26fbc0413c2413347cdfdd3f913ed2e45bb4b164b27f19982c21f8bd5388c63b715b6c8311725ed8b83f44d02fab75ad73ebfc20f75785b2e3148e7c7edc83afee72cc2b7d9eaeca5b4c03f02e9ae53e8f7391431b9d2b408dde159e9505b9d512a8a3b3463aa63e9b5f27d1f5df5d0150f5ded5401a9f651c2f3ede8e8f0f0e432a4e1834a98d8ea4c14411c954cdb0f47b5dfc662695b05022da86d7596a345bbcb24607e1087b34351d0d5f1c39121a156705554f4c9136a6587a2407155569ad26cde3fe2b765bce93862dac5368816a1df4ec4b98d2f5e699cdb7477c35dc35b3f3b448bd0fc4fd25dfeae61962cc78eaf6554afc2b7f452bd6d9de5aa6f424bef8fce366b5b9de11db634d5b2af26be68ebdeb43257209056aa7fe8a76d754635ee5a91bae867abb3fb43b7b6474d69dc400dbfc1163700e394aee8ae9cb16e0572175fdc90e081004cac11866f10e1fe4611a12f8684c6280a14ef8c37add6d54eb4e4f7ed6a6e776b8977b45620fa51f94162b198ec94f533939d92ead725d5761ceb57c3303ff98138aa1a4681f28078479b9de0b36445a29ffe6cf81bbb70fe17b40eef5bd8c6ae8d5d1bbb76557ca1704914ed86851548015e5cb0b3010e3068000ab761e3071b367cb06143179474ba325382cedf1a4eb85316aedd96ec37beb230ac32d9877fb190fec161090b776f14dab084d9c860c86520c4dd7595e1b07c5de2b0d4315000d387c3128622418d2eee78c976edc3e0e88b3484b8adce3ed7b04c76ad6c97e496344eb0b51573b4adce60b0f2f0752993e1300d1fdc1d8d34c2d7e19f0470033265d4914cd3174300992ceedf14fad15dc9bc408692d12153e34eab2f20e3ee37dafeda0bac98d9c18c0ee0c881cd19709c71858e9d33c23380ce706263c6171b33846ccc406263c607333898b9c11ba717d8a006346494e1eece43918422262b4d467ba6219e20a306f719378a66dac051e9fc25e961a2e9f4b63d4c234d271b23021f886fb486a9ad89658de24db5ed56102477fb54820bb8b86aae2f1b3186701b317c44b106edab1f0bfd60b1b0a1a07d377609e1da9d016d5ab0450e0ddc9d66a64cf8abfd5a0fd980d102f7274e4aaaebb6b3276b7807e50b8d21d018814c143e4c4010a42bf0c361b2a3aa5fce8f76d75d71541f4581f2f480babcc52858bfdbecc3d93d4e4577ba71a5773a9cc559bd4ae15572c96432d98e14433cc6543086ebfc859feb4d872e88b95f000c369acc2f8c98628cc0fd6fae309ec8373088705918868dbb0b71c1670bb8ec7294e92a8a55a6ab0c0c19dcbf9f8dbf5b23deb7de5e32d94e7f2ec97d7f0c655fdc402041003e788c0080106a6cc604c0e7181d9f639ef81c53e4734c0c3ec7c8e0730c103ec77cf139a6c9e79814f81cb3039f4d3b7c3609f1d9b4e3130a263ea1e0c1671390cfa696cfa6187c365df1d994f3d99483cf26217c3625e1b369033e9bacf0d90486cfa618f86c8ac3e7140020724388101682d078b042510da8ac30a6e0756bc54092e08e282b50f90906dadeb6ceb43ad226a0ae8e275027144757c71392c8a012edd368122a03b802322a109a5e7c31bdd04d2f62985eace033406c4e6546e023f80205028a87bb0352f072d739096a48aa0719147cf150169a315190729dbf94073eeea91e3f5b8dbf4f49eef964d4459328da2ae5b4c46e46f7ebd56a0afd7e76ed0f507ea6d7b17ddfd26e68613024fd1b7371f176658b189430b1583c4c2c96256bce240102dc6fcebd2b6937a39fdeba8ab39f2f6fd1ae722e0e3399b36b2f5a24fb774868ae35aa35cead2a4d443f7a6f7b77c55ae2ec549adaae75d55c5f22c62fa550e8ee3229b4f5250227864450c331bdaffd8ce8a8402b61f4db61e9ecd01adec938477f97227e9d9d15de9666f75710d4da664c8ef606526c77cd3978e77ca1a537394cf091b86e6bf38774278ad5eaa7d9f2f58f28d6fc4c43b352ff8daec812671bfdb4d5235e812085329be527f1d656d7dcbe25fd44223f56dbb5384d84d630255fb45f921889e2189092ae22edee4f7831104e5b291bbb3676e5a4fc389a35dad8855dbf3776dd1ac4548bb0d0fda39d3896a38d61d77eec9a52f4e0ee40dbbe5c452176817897528e7415c92300822770dc5d026e801b54b041318117499b6338166211469638b350d63c14be766f84a4df6eddbb60b76da7ec5cf7d6bdabe6219e1e251c0bdfa63326aa9968ebdea5776f145e6c330e0a6ae19a7ebd70ac08696351ebbfbfeb91bead8dc5f0df157eb875effa5ad55749efde48877f1f0bce445bdba31c76e99bae452fbd75efaab7d512ebc6b1af555858bfe8957fd77e6a1ed2adcfb5bca4f5d6bdeb8d3226ca5ff3d01f6951fcd9bd91b6a28b59096322033bb8fbcc800beefee92ad2a7a9edda550dd3515791e877b3d91bddedfd994c4ad272d14fa8457e3c3d54972249b350eb66fc6d2f9e95faa3595d8168e0c619a96869b7c434b1da19ddadec8feef5b64466a52632840d6350a3a4ddde76ebb1ba3676856f7b22ecd27da877b722cd4ad8c6ae8d5d4219138971051419e084c82a2ae8244b518cbc42990e25561c01468a390125dcfd96434ab8c0fd4b05befcf005cb97c341a258653b07184934f9c6a2b6f84a6865bfeb9689a2bdd1747594f902582c520bc5f6c366120f9849284d249c8040988904f83a851081363c0263dc3d0cab9e95ff352bdbaf65fbb54c142d2eacb36b4b197e4a0c784db985d7300180c8dd842325a8a0dbf09aed81d7ec16bc66fbe035bb025eb34fe0353b075e5302f19a72c76b4a2a5e531e6004252e80281ec2f600f090e10c77285bdc6336b812b2478a841e59683443c06ac414f9707713aa38a104cfdd5d1566053ab846e5ee0e8405108ab8d3079ca065b66b334ddbb101272b7eb89fbca8c2e711eebb5bb1f8d5ddcc1d85863b8a8ce78e8ae26846061cedb258e1df5cd2873477d40bdc516744b14203ba107d8b6b86d1fdc5f0de6b6703741ae0ee930146306005f79c19375066cc14c5608404dc5d57c7eb53e2a8bef0c3655c7347e5a0881a14b1338b28c3bd88298ac0c02ca2745ae4d2f963b1f402b6dbca996ee120a98a409a43bff081409a93310b04690e888383a42aa22ba09b0aa4ac8abb560c6a01896a4a5dcc47ee8afccb24c2892b614a2292a39559b2ca6429984141026c70af371cc3e32385bf6d38b4450b148ae24ff8afdc87ff8962fd4050ac3844c25bd668497624911912d5fd0199c831fae55aa358539a6dc617db67ca960868eb7ab4757ddd5678876e2b57228c63e11d4a12c53ac2308e85ad4ad6a0fc4c493929b44044fa628b5d4238b60b2b1e1fe9b65eb755cb4baeda10d3bbea6c8bf86124c6aefcf7b60cf0863b5947bcdd6740300d90330008840d063022b3627d2944e14f20b6f804c2e70f6ab8bb53d67731dd1ad3eccde7f4eba32f865676f14aa746bf21a21f18186af2dfc8e6ea48e4cb27e2c30c3ee0e8c9cba8ff14474e081b660f1b50a20722f0877fb201aa0c1419b689d62ff650420f352c1cedbb98a04b08669724ba0cf164459facf4776122b3baa7bb2f3b512c893676a1c67047b9c01d25863b2a0cbdb1a88dbc92a3c0f882052b5081172940c109ba4099c01d5502771417eea82d5051e820c86681308e61c1ae3768404602b79230ff727679538a22813b4a0b774f392a0b771416eea811bccdee2811b8a342e08ebac21d050277d4077c6a81626a0983f2803bca8a14aa0a771fd255a40fe7c77269ad453568e97d5d524b75158968e3dbdaa1a5fbb5de160912a1d60ffdf66b2238fa2dcd8ef862dae70fc7fc9b5eabaad9efc379dbae2acaa21886714cbfb5d919c56f6da8f17172c373cad9d1aaed8f74f84042103ac011941e05e861c01044271bf8cca44e3704508534b841c31734a4618a49830ffe51bcf7c374fe922861cad234b3dc90c506a717d324540a05c289357050c01805b0618c2e409c727071ed85763a7cf1c542cea0e361b86df878fc11874951acb2574d2c3d7276dbee7cf07027c3dcddb95b21a7526d7571e860e022dd37f76db9863828f72e771415fae58e9ac21dd5e48e1a935bc28a4ffb81fb687bc271fe172e76bd2dd75b904df5c904029dbf67ca252d092d6b1d1e9e9e5c9512b6741f5ead7fd4fa4b9c53cb4cd7e6d432139194a59bae454452966ace956e9035e728de41dad697fe27b295483faec65e8798f6bbf05dbbb02aedfbb09f9d43ebe0a5dee17d9d5fc86f7ff82afd76b7daeab695a7a70473f8e272f4ab613afe6de7a8a67dd6397c71f48f740d53a4202a42f4c311c5baa3a2e1b65f665b498a033259c243791e578de8a79588c2fb5bb44457e9a504e90a74145620187685fbb5cee123d8c6aefd481bbbf43f9156baad91a69385f7f514afbbebf029dedc7d846975a0ac1b6da4e992ec92be0b7717f1cd067740ad0dbfa53b4a0a1a77941251b8fbb562585fe8471b0bef4f4e6d02b51e662dcaac05b983201305c124fae120d9379b1d91684d03d30b0abc8ce1d3cb1b3e8340f90ca2c6671027f80c62e53388273e8338f21944e833881a7c0641c408deb401408dc7011ab8f704cabd274470ef891fee3d81c4bd279ab8f7c491bb8b1006146e13c608dc260c2fdc268c32dc268c19b84d1871b88d189edb886164078f0f0020c6c73d3140ee8931724fcc927b6240f7c48cee89c9e29e181fdc13a300f7c424e19e180d8899c23d312570f71f36227c2af7be1ff7be17dcfb44f7be19dcfb7e70ef63807b1f05dcfbc44c1ed4f0c9030e7c76c9e1b34b083ebb08f1d905c786c60df7e9460e9f6e94e0c6e7ee3421f400610665b8cd0cd4709b19bce13665506e53e603b72903c46dca18719b329fdb94f1719b32446e53868adb94a9b94d19ec3665766e538607b72983800fb838f1c9a5c72797229f5c5e3eb958f1c9c546843000702f8c00dc0b93e35e9815dc0be30ae3827b61aa7b61b67b6178702f4c11ee8501e35e9828dc0b8385bb934013020822f49815e8c26705d2f00966874f3022f80433824f30457c8251e213cc8f4f302e9f605ef009e68a4f303a9f60b6f80473009f608af00926099f602ce0138c066a4410a1049b119430c23d2528e09e121b704f090fb8a7c416ee29f102f79468c3bd2f3adcfbf2c3bd2f1f0f9a1d3e46f049f9f4f9e1d367c7a70fcba74f524d4d08248400a406041a2d3eee6909724f0bcc3d2da17b5a4af7b4ece09e961fdcd362847b5a90704fcb13ee6921817b5ac0704fcb1aee6de1e1de9620ee6db9716f8b12f7b6ace0de96977b5baa7b5bb07b5b66706f0b17f7b60ce1de1606f8288184113c1d250881c13d21ef9e109c7b4274ee0901c2039a1adcbd1a78b85783e75e0d3fdcab61c88e1bb8f8bca1013e6fd880cf1bb4f079c3097cde3086cf1b64e0ee1ed0cc31e0f049c60e9f64d8f82443003ec9b8f149068f4f32583ec90072f70f26104ce31ef6dcc345dcc34cdcc330f7f0e8ee9e8f1a24525e83840faf41e2f31a2488dc3dc7ec1182f700c187a8009f62037c8a14f02936e15304814fd1043e45307c8a68f81471e073a4f1394e9f63007c8e487c8e29f81c877c8e517c8e339fe3f63966f1396ef16903801a11524e5016a8923a7970022c3230e08b251d6f0902ae1794947a967896469aceddb55002af256dce034ade77ed48450c0a6f22f68363692a3a0b6dceb90096e583b9005b6247296ce47c26a56421e50bcdb896034f0a096474fe5a20a27a2dc4e8f7b9d7c2072dbc9a786561792f9f27f40b99bca432c4241f7214a1421b9e4d2c0577b7712fc98897f4010d0aa52cd7085c55782e301fb55debdac145a5d206b072f9a82155f1ad90721c9d80b2c28fc1883ee30d3b72e29fbe7628ff4b57913e1047c562c540f0c559cdfbdaa7ad3466a2b6bb7739f95ff4f3d7e867164b4575ad456ba5b2adcb51464bbc2bf16ea5ab2eaf84551793f2e91bb28abf6409d5f6c9f07fef07fa604488505df558c34234e80b160b4a3eb05aa620fabdb8ebcd4d0edefb521dcec5e8774b0dc6e8a7e9f875d7d0e67edb52065dc16b55e0ea87e67258242b92de55d39d488e56eb6df5cdaeb7fdbaa49fce2a897eb326957e5640b00a58056f4abfd56a068274ac9fb726cbcf3fd2ecd6f8daaa0dc39bf314d500af091c9fb1452191325282c003500b98b922a5f431d94c7e48c39d98eb10aefe14512c89eaeb462b91308ed53c248a3528474b4ae1a04213ebf71dfa64ee850a4a8cdcf3f1c2f3d1c187c667865268e2f42385d07d8414487026485096ce794c8a685fab91e12d29012e0ae22df1f19490f12e9e12225cc90a3a7f40783d6466e68d7b3d48f4f4e0f5cc9cb26a1e8f073c9ea2130b3b68b83bfd700d7fe3a7b5fb298b47820f4fa7c63d491a49b070775ae9d1a7f78793e9c6226c63d7c6224c27e907e820e3e54440e77839d31d467b266fd5c451b54bde6aa4daaec2cf388ca36131498eb74aa1e01e0a573c895158808742170f854d592b0f05276409f350f840b4b7a053171c0a30c38435b3e321a1320f877477e91f9e1e21422801881a565a78aa2fee1a8a138b85ca89a74ae11a22f28900372b0f8177440a0fb1ab65ab40de91afe21d49f28e0c1d7972c4c98a866fa50a35e2002917dc5354dc5353dc534bee4fdc33e285bbbfad02d126b63aa3f543ec3aa561040419b4b6e209605cb495e59db0f24ef0e1ee4e593988e8b0d3c10b6a30c60949112759886ce15110094f174021c0dd4f29253d2b35ca03c7828893903764b5460a8cd33cc2dd08aac47334b185674218cf841d3c1356ee620882382dbb18a476938f7ba61bfdd6f6336abb36e32f73ac86716efd60d7ee8dc2dd1b6197eb8827a4c6dd91703f8110c4c7dd0b52e3bec40322e4ee3b71c9128e7dfe92d0bc1f4b783f8470ca5ab14498399dacb857c2ce5ddf4a75497f57db0ac783078f1e9546e3c183470f9dfbcb92c95c492d9894a125d9100b3f52926230998c95031ddecc0f7f7924b0e03a7f399678360b50792334c0fd6d9524ebdf5837f04640a2dbb1a78df94972fc7d4bd96947e913f0660ff76c3cb0e1e1ee94890438800d6aa5489524229cff059cff851cbec1b00be77fe1435d45cab588e915bbf516d3fa8f3416ab7515c97da648c9c7f28ea58b1c7391ae22e96238dad39e843d2d042fdcfd948a3916ab7fa6757f1b3a951e0827782004e17d5046ef1e67cb25c944990414dea90810d8e49b131bd8e632011198c0db5186fb6968e6ce5454149ffefde2872d01a06c5244b491a281edda21a83894524329a470dc532a9a1d4719e710a76edc5347c0783d52ee35f72e07783caeb8f4cb277890dfcf09068f460dd7f9f368a6f0689a60c2a301e39e3292f274ea8453aa486a480ec30ac5a371e2d134f1687c1c0a5d39d916c8db91c5dd3d45c4dd695243860cf17468e19e0e9dce9f9f705e8e2c74fe92f430c15c49ad58904b298a8cca0f12beb2d38d97c389ebfc9523a62df97472b0ec725e4a872f1f05332ed21629c4f94090ea5aa90e2bceb3d6cc1190eaf2b69b80335b13dc3f09ea71dfb1fc70b6fe1c06afa5598360cd9554fd88615be7fdb6b7c128bd7607d61a66851b5b5d3116ff374babc0926afa6918fd92885a44f45afa7db66f887e5ad4f4d35f2b69346c0b1f87b9d6f1b7fda19ff831fa695beab1ac5d6cf34841ba754f411cd57e0deac76247bbc94ca4adcdfbd6f161a1115947198b95bf9d4f2727c70636b08109e89398a6e9a63d930c573fc762c9c2efe200434d449a15c51a147e0d6ae03e96a37d7156c324ac12269a036477a212260a2587f630d5a3550bbb62f0d78ab55ad28f181662cc8228fe847abffea14b3e2831272108c509d8aae15ae99ebdc6e0d6bd0bdcfa2bcd367f6168c79badc4b5cffde01917b011f3828d8f8d22930dd41a39a02a4a59d40a65e93ebcb64a921cd852d8da6d254b80b64c19a4f1c9e003320853a485272723f74f8b62a5d5bfc5eaacd421933d7231c08c038411c18d7ca6f1725591216a34f138995869b8022e811fd408e247338d2fc41aa03161a004015074fe92e4cc316e1e863fe1e755b89335c7867eacae1743b1c4537443627d21ecc2d1f0d1557a71569bfcc559dd25398e56bba7ac58a20af71415ee9eba406a8a914625fcab14be7baa293506d76e4b144b977b4a0af7541429283a907a22fc18ae7120b5040ab8c0420553782a8842054ef88ff5a62ce084f3f4844a98a4c4ec1ee79eda800ecfee8d68ee290dd45d43f75413bb376221c5c4ce95744f6520b4b9145fee290c3ccde225bd7f5eb7de629f3152d8b257c93d15263fce5e8ce49e5a6248ac347b6fb48f89dadafe48ac9666c332caef72a9ebe7d8951d907bea028ecb98680754c7aa4ba32952300dd3f014f79405c0b88fe5d0677cd335e85a4cdb16487ffea21d2a3c547aa828a1b2840a132a2950f1a1c2c3c4628d349de6e96162b1787a98582c1e2616ab8709d76e4be3da6d41b97f94f1953a42b97ff46375e92ad28ff5d65d4328badec2168b753f1c6919e73ca435a943d14e81e284f64bb7ac855698ecf8ec5c5fbb1003ed5cc9b0b50b311014283c3d4e787a94f6caa774e2c3b353fa94bbc72961525a9e9ed2042a488183a0054d1a1ed01a189268683c4081236ab6446183660341506923c601f757c35d02ee2eeee0e9710387cc09777711926802072e5c76d061071fdc533db8a7baf080030f5b78704f71d9a265071ddc5339e090cac13d8583c7051bde82d3193738087a5cf8785c08c0e3c2b720c2dbc209162c1e0974208190bb7f2450799eb15fb8bbcf08fd90c5d6c28b164c5a88b45be64d577ae6260b28b2609285123d03448333c297e1677ae36b837baa861401dcdd8824add70feb67bb9ce5e45656979b5655398ae9077e2045422965e91afe740e6ddff6cd4eb4584767097d4275965024d482785f3cda2afed00fa7b3437765e97d431d9d1daaf3eb34eb553fed5521840f40ffb8a2dd9de6d12388a5c12a6c5538719f2100ed6686042a28400511546c2a5a70f78b29155a7ad11f6d6c032b6bf3ea69f6b15898c6f95f10c59fab9493ae527d6dddc5565ba48b6dfda28c8366b4cce010be0c3b14057a05a44d404b9dd02be00c0d20ecf53aa24ea805430ba4f3bff4c6224cd7302df28482492808d2af866fb1c7797fae4c3b3487823a545b0d83d1d77596a343ffb6f50d597576324eceacd42a22e12562a9d6252e6d765ffc2becda6552f87b0794ffb5abd077cb4cf1b6f2e17cab1f15fdc2bfb9af2bf9059135073bc106d126f336608303b9e26482fba7b1fd59cde50fcd98887efa3e2ee3d7d805fae972578a0449cd3d10fdb46e00e18fd5c5455314619af8c06316d000165710e03171c4ef72db291818c38230e18b48b9cf4ff6e028a919d31a0e1fbb724f84712c27b5b04b29fcf05a29b0f06f2e81c2dfd72249510a353edaba140a3f87c33bd4d2b550fc7415e9bbd85a6008cd9285864f9f40a159b2501c95b6df7fae85807e9ad2cf453f5b85edc49bc90ad2aaad0641aaf54ad76af8c39a1f888260895da08152ba62b1e8cac9686f30b2223da194d21928a540f4c31ad6dbded2b17b5f4ef07daddefca50577a754eb8bedd16e5bd1daaead2fa49de04097999c191e68ccb0b400c192253d064c1b3b39fb61e1dbaec5d1ac51161293c20f6d7dd53c2463d1b29e00eafcb517da6d7b4bbcd2a178af32ae7dcd43f95f5fcdffe5c832cfaa4c66bb9656864f2b052d0d06ce40d0666b170a053f16b8ba6df2858a36b9bbf6cbf8c9261f0e88445791401588246322504575c6b8f62a5d2bb502e6feb25860158af7b5bace72f856491de6da47412bb489cc766d1590b26afe9c12e7b37dafa2bb5c6bde3aad6df8d56a483ab5d6ee9986603889c509cb9bc320d55640b08aa674bf6eb5284e2c67d44acd432b9a43ab80f45ab028f61a1292b9202529066b158156c02a19e7f06d55f3d3f0f75f6c05fcd9d6f5887ef9c78c592c903ea9fa70547af738160bbcd8e63ee733ddad36be7855da15ceee713290e96dded9ea6c05fcdb26ebb81357165fc991383fabf7d69cea679908c509553ae02642e4efd6bf2fcda9a8586fba3eed975af41bed156f3adbbac97148f7a1885d443856732da994c15ea5f0feb4745881f40e6eec40e65a1ad21dc26427bc783becb003b9830a876abb7a1d66a0c3163ea384869fcb2b36b8e17e820116b2e672e822872372e821071a7210355d694c5e4ac31c82e0a0867b0e5faa290b0487965a30d710d190508a862c3f3878d91f0e44353f2be39c87bc1bc6e8e2860fdc0006875e6b84d6f00d3adc40a58a24395a1b1ee0440d6fd46083bb7f7963d7c9fb6206a8c989d28470e14e5866ace0308648030ceeae077073fa8b5d184b0973558a1201c002811d26b664f1e1ee3f8486cba71c50966620fd253ed31f91388602962c4f60d9c1001b9fa4209dbfc7e53ae6de55c2bcddef9ee8caf097cf1218ecb3e58c7ebb4d84c86789d644accf12188c86bf3ffc58a0dfe33013eda66bd18722567d381a16e911fa81dbe7dbde382afa5aac44f403797a4a1004b7c622c5207865a5026b58bfcd8ab41c27383115a559126f5a9128387efdac2bad86737418887e39d7d2ba845fb56bf5bef957f4fb4090ee661fce0d594715d5f4bb21ad7649522dd4fab1ba684fea8a63434117bbc2cf91fdc372d3f5665bb84dbebbba3fee07378737c231be6034632cc3494c718ca868128a6384d29511ca22315d19a1a28a5216b552657c623c42e52e63c586eaae42b876c99ad3381a1665615897703ec424288b48bf0c27c6ddedecabb1e17eb2d1713dbb127465c9952b1fd055d0e36a4c01febfaba46cbfc6bae697c96a7e59eeafecf34dd7cf389ac5e9ff0052d892c944d15623c06bafc6648c376857b8a76670f713161db26e3ddafdb92623eb6b9dad7d0ea1048fa5728fc5c43dd68f7bac24f75856dc6369f7585ddc6329c03d1618f7584deeb150e01ecb0cf7583b70afc90f770f32029019c49bd48790118e4861857b5284e19e146bb83786c6bd3141dc1bd3e3de9822f7c6dcdc1b83837b6382706fcc03dc1bd3847b633ce0de1831dc1bb34613cabd2612dc6b2ae25e53907b4d53dc6bc2ee357171af0909f79a9a70afc903ee35adc0bd261db8374511214180fcf0e1a1c0b887aac23d140bdc43b1e15e2ae55eaa0477b7a929818b1a9f5c10f1c9c5129f5c08f9e462c92717379f5ce87c7271009f5c7ca1d96133840410721ce13307067ce698c2678e14f8cc81864f1d279f3a88f8d4a1824f1d493e75c4e05307cea78e9d4f1d5a4c181244002ec48423454c884284cf2848f88c5285cf2860f88cb2039f4601f069e4e3d328e6d328fb340ac2a7d1123e8d46e0d3280d9f473a7c1ead7c1e45f17934fa3ce2e2f34889106ce61c2ac3e750199f4425f824c2f14904e49348e903113c200104a7f1f1c30b904f2f493ebd549f5eb24f2f04f0e9e5079f5e8ef0e965099f5e3ae0d3cb083cac6ba8a49478d2a2648806040000000000d31000304020160ac562b180583c29f01b1480015b9264b268461aa95118530819628c01002000020000001811038326cf4a8963671d8f61047dd8931adcdcca1d39038d160ec8ee2cfa12e3031b5d8c426fafe237d620a13a521e824cbc6159218dbd21fb32c9c49babe63bf8f1e1cac5d0e7cab9fcfe8774ccec79e4f76b3ee90ff732aa1ba8e76020e7ff5b4d386cc0dd79907f62746abbfc776c4ab946fbd5cf40fad7655b5bc23eb09919f9e355251bf1f4c429b3a3177f81059bdc0184e285bd505084081e2884f67affa15236ac49a8f3cb44f21e405c06817cc25e730a2705aef24e75ff8cc25fd63e169f8af6c63bff763f62c1cc5fe5faeadb6638fc7e5cefff006e116d2917de96daa4d0e7a9b39ea78e6e02af4b9a20a787155cec1675b43e8c885f77b5c4ef719f9cf9c2b9d7d68e2f1b61010ff18a672ee8395b7a80a8201552158a36febcfe932cb586c48607aeb9a5375de91b8b99fbf75e6754a23f07fa57631e48a3a71c74ffeb5c49f38bf4ed24120c7479a35e322e9cfe13c401987221abe16936716f9bb85b02bffb4a8e23cf5e3cbae1d006122583fb7cf42f9ad532520f6f6062ea3ed2baa3a8018a848212acdc6da613e430ccca7de62fce60aa181b024f145ca6eb98e830f7c546377d4d345158336b40c1e752ed9353802523c9fc28c5fc3cccd2303d082f046b6243c727317333d56c7d85c198602a0d30bb1157d3b9ced084dcd95cd9bdf476a6143b79b61942329f4bc03d0cc84c02c686a30b90d6ce9d91d01417c6a1f441134d215e3273d2ab01807197d427b7576edb43cea8ed3333f0f737f771a3848ec4a59ace76161b1c83aa347ac86bba487b064e76f1d99e7a9dd95178674bb78e6048da901c433286a471f2ee690e8d9452c7cd600c3f63f46472e6417007cdfb6267f54fba225b71ec0d24ad6fdeeff6878ebec73a3b129cfae41ff164f74eb9cb123beefda7b7981f0fc33300f283e4a1e7f6b22ce28acdbd03e8b6309114a5d727f5098cc3e071100d7998c67762133c56b59c1d3b8caf7b7b1c856af9482acee7c97504e4966dbff1923077476bac48ac7ea277654600fd513e5e7fe3b5cc0b48dac6014d98e0346a74fa05b586850285edb02a8b8447c8d22e6894bc1948953c2df8c36648fa7f9e82fac0bf68d477af7e059866aced377e9c4c38f2b2d128b9d23ce4865e5f24e9cafc436b77424cd8c6d83cafd316cbe1fdbd0a750fe043b50f94ba038efe5f9e25a0def07ccfc74f55cb22f92c4c150c1e764d56cfb8ec8afe5ec33dd80133cc18a797581256a55fb724c5a465aba58b4c6e5f2622f7501b0c717cdb0bbd9fdb1fffab48ca4c5540e568b73d7bc94cdc09fea139f45f6fa0ba684a47fdb6550b0d1def0d06467cb2f7afa3330c9f32ad5a1b396c0e3363816db87cd3372d34f431b8764dc3aea17e3bcc46706da0fbfb32fac890634abc2f912217c817cb35b70dcf6a562b807f01862bb3cc4597e43a73e32cac8b5c28b1fb2398023ed03f169219ab34684af28d04fbe7417d9a3555f49c2b2b18f18438c4ee14eb595604d4e57c8f79f66e8c5ba3fbb606d4d2e152189fb7475ad85e3466354f29f598e23507bddc710db9d36be40a7755844794265cf1267189cabcc16f2fa8cbc42d3e3fc17c1c37bdcbcaa92b45844352eb2af763524dabc188496b52f59f916fe333726d7d79cb5166aa7c96dd9bc16657eab67c801850ddc6788d7e042a22ca02b3a4fe9ea79b1dd36b7d6d23b115d6f57167c7588b62095e7cd41a62254138bb8a3e573b7b332a1648763faeeb39dbca49c9002211e8b07476d4cb7c479e36906a44cd2104cca48024585df2e32a157c5500b34f04c362428f35914b64715603b05cec24d6658d1fd1f3980339d51a0314407cc144889ee297c0cce7e4af9b0046145836e14616e57ab20c63a62d6bbd01df045c69bf173a4e658c20bf430544ceed86f2920e3a22ddebcaa4dcb8850dc4eb263c56daa000884b492b04f1c9daed1470655bd912130c8826cabb08bc610c79c0af957a6704797ac6e9b4579c74156cc37f98d0a82ed0c1a132f028d9be144c5482d94041d55bb67dbf0c786d2667bdf3eb0f2ac5046608db4f0f56da8cd3451aecb4e76e87ac6876077af68d2084eab291ffa1c0cce9c286cc2f14d0e00ae61f9ffe2ba4d28dbc4ce46e271e42e687c40b9ff02002ec8c93f829c87a2e80aeb13c17b2333b7ddb9cb1331b291760defcf94aa49edc9ff2490bc9ecf068c1fb10bac3ac989e30afd9b799c366d2cea9706ffbe282e024f5d3e9d71d5eb6b9d9dea4fa0b61b0f308992159e6cd544e1360ac3fd690bed1858f43495966e2c3311385f391285308f10fffa0df2335d9ebe2f508651abacaeee1b101712d43778ea7567473b2e1b9b91eedaa748a84306057b80d2416498f56104f1287f93cd1a3e03bbe069846f8fe48492819fb88bd4bdcaff733015996da7a3e0814c5c09aba0937b47f0a49dfb17f8217994c7a0c88befbe22038a00934f91df9b1aa930fcf7cd8f0025166f1648586808c5cf621419eafe1eec5ac1ca9ae0d805e3001e493fa8cdef5303c68c5f51f5d7a9e41503b2427007f222bd0af5775f77c24085cb27f858e007748b9f8356a1189b1975ef9b7fa430b707c59dac851dc68ec1eb5f5e8aadf73b16360d2016bbe7cee9f2049470c8e8cc804703f9e450fb26df38d6c615819d605889f0d684b803f9ad767b7da010d38aa687430ace6044a651b0e7e7d468462d8870587ccc68735dffd44d05003c03d2b0b9eca35f29dc27f633692febd06a0929205f748f2f2727536efcfdd23fe2cce9fb5b2b56175baba519b26e40a649e3bb5062894f1afcb7631c1bee5462909a9c0af433e978c0805c2862bf6971208388643918973314675555b2cf2a0b79e2cdb722bd5a99b7ca137af52f0f78822591eaffad96d2f08f176b970d3070d6c1c28660a92274cac83993dea895066c2bb6185b4eb5825a64e09cffd5b002d037b4ebe59774ba660a50459d0ffb13399546c562fa4b54f6f8c23a59937b930af83d8cd29b2d18dbd7d42c5c7b6149ec767072ce8e16d2b147572cb8a5e0b33efa84b940416a25e984ae1e6cae73d81efd59bc80c7a9652f2fbe1c7b9f236dceb736fe79531c8a3828dbdff13cc1fc01452a4d53f797f7978ee640ffb919fbbacc231ecd5c98ad602481a09aa9cb486a6a18d01a1bf40f69914721d2107ee1cf9d2a5c4e0936e375992ec354fba04cdd7fa2386d3dead855d29ae90e2773d736b1cbd3d1780e9d73e6ff3fe479b5bd862ee00fa89e263ba348b08d4f1937263b1ca579d5c94e4724d2ab23a2f1a3b02cd210474dd96e2df88440482e89f79b914e310548f1f84934dc848b5afd978b508b2dbb01bd1a8b6485f5f7dcdcc1e84346cfb6ad8f84cc0daa4a32bae41fa14fb661f54d5d78afb7d683a25eb9889dd98a4be7204c93890321447bcc1bc1eb45c9f87df10ec92c5b8121559bfdf18ceead18ecb26c5b4f07674d7e8307c44d13f8055a4ba143dc6804f8143f68e95fb98466ccc0ef689fd1e5a980d97b69f06881cfa6c36256c1abe18b3c8c1a721b6409ebebc9139d44b21737b18637d22a86dc4da9e51c5ecfca901e538fb197203b90e15d50d4088160c749789021404e85491f9739b92cf21d7d4104bb7dd7891d77bbbf8a0401a98c55bc9777957b424c19a0f7da93edd65c5300694d90d9fa12594e9b29c1dcc8aa720befcaca965fb83a8a8a59baeb91e0e3e8c7513e77b80874e4f23d2eb75d60f0263c0485c609067d272dec54b980309e33fd39184e2e07fe53f36ef259437c8eebf6844199407a4d3cd0376396a2cea1dc145c4aff369403cd38d27b93efc8bc6de19ece67a4636bc7f116072d04413139ba4d70a5065df01571074d7055b6ff57366987df88735d90fec45afb4100e1bbd82abdf3c5b9b891a88bd57e7d9277bd959c1cc5d46d7e9a3de348c7a11e651660c55d1d4a98eccd85771f0e1f9b8dd53cb6916c9001c13747eca48c133725d887eec5142b1d18fb2a795977bf55f2c47a08d4182f2beb951fb25d375c20c838061314ec2398e1269d2fa98287474ea0a2e35950ac7ba1352820cbaedca904beb7bf062afc36c473a95ba149022eb37ac2233355940c917ab17243159faf399e8f46c6a8421b4e564d9d8381315e0a2813c65478a4e8337913eb2cae2ab28321ed1d41ed9fa8cccf8859c96f82e65a488fa418266344d78ca041ee9bc45b3815d4de036b88a2ae814c0e603b2f02ceb61c39a81f0d274d14366b25f0b017b5cfc062f62fcfc2ec2ae0bcd703ce24ad119fddecd900e0e5531c91727d2934896df6fdf13801b90f8b6f08d8510eb2afa77f9c9aae26c915ed3c1d20bd934b90feee037582be90e6c647df08a018fa390524652ac525360b3686bba0c6376bc2106647b8a944077e5001edb23db51a1fefa94df2a0b0011fdbaade0fa0cc961742594e6fa05db85d88200e2a642746ecdea2c52cd1f14e24976bcada97b8c6114e8a728925057724110f9024f1c01138fb60077f455a6dd440e1682b09dad9026aa263ca238631cd2981a5a40db9c4a67c1e063aedaf4f16aaf7683c48b88f9b88fd9821380041f7ccce832cae80b074d392248fcdd0a484e73418d4b5a5036b976e8d5da8e917456033872dd10d20fb4b5a21a0103214931d2eeed5c27aeae410231d412d9216f1673b86176b931a043eeb82849e4c649e58583be9547d4322ac889bcdaad704b179f61eeb095cdb31a23c475645145f2bbea31063b9147193758d7c37cee7bff4f287d21eeb97144c9066b03abe78423b008bd4ad43212b84395cb45f2a96d44cb1e2cb40ff546750c18583230136392f8ecb7052df99c32d496d3feedcafcd446cbfd4dd5a867f4511cb3b57d4937a7ebaf9149b0fc5e4f090d51020b0b350a44adeb0f573419ece4b6a0b535efc3211698632c093733d318fc77eecf2e9c7564060d1f0bbfd85dd6244c1a0ced80ed433bbe2c4e92cc0f676c55902d98c18a44afb4d7855db836059a02865c42b4b68e573651a8c08908856b5082b877a8dd67e5d1bc2d6c8b69d82d6ca1adc102065cbc5a64f00d022935cc9e44f6b17c73abf7c35523489b61a8b9a015c019ea238bcfc1b7908e42dffbe4d5fe62b26f0035a0834a4c8cc6f803bc3750a97c3ec7953e2265a5effea0aaeda01100e3ce2b415bfa24fe9755860a89f1f1c1bd563ce7e18152bb3b4e76f44c3651768ef1ee8610e6dec7a9809a5b0bc4085e01bf8f177ac0f2a6ea66b35852febb7d6c68498403e789d97bed21c0823ce1823dcbfc234e84e328ac0d1d93d217e89efc68ec2c782072d56ec9ced64e83a4d074674ce3fcb0b50cd8654ffaaef12c2bfaeaef00f48620ad8d3f99fbdc67d6aa95abef6af4ea206d9721b920ffceae710df940e7e842e0f4cef1ddaa50ae1934e66f43dca3236fe2e116c01f17d3ad41f7c06c2567558d3c9b63e7f2531ce3ca853495592d105e15092076a06de8e29962c0c704371baf8fd2026760642459d7d8e49cdccf3b463153c52bc0fdb8601e728773575ad8501998411bd380258748f8d106d09deca6d8b5dca4365c0e308352e63f6159036d42a4d6731e38ea005b437e1adf314cbab326ee9d76799d18102f54b031db8369513cf73aa2b28a139d761d9563445a7bd6564d5f6bb592563ffbbcc0b1170337bf88a3c36a0c12ead9075e19871359b06b5e7c0eab43a334b4a07255850082304198dd301ac31f9656b39959d936657cf6cc9f0082e3549f679cc6c669ef6b8434b0075b0477e829d0bf34cf494925c5f0c91b8763a6098051ae1b0bc1a23665e848a1cd7a73384bca6654e5a753635d427e11de36f721f2f79c56d7b4fc4db1257e127c6f035bc983e115eda5616b95e85059828fc37cfc0e1849f9fd3ea6c7768aa7564389d4858a5648c265f3b874aad032465f30079548662f7914a072f2b195981f6473d7cb32300917d7f33df04f3e6c63467fb9c02818995f8c1b78234e2dcdf86517db96e98998fc7b364953c0f248cce38d344030ebb1fd7f51b6d9f8909448814891b409b58df7b804d1a9ea297a985e5a3dccfa9c97f97c0bb4d4c252c5e858c830da1f0259b8114c6c6c70b2a7e44c40742e9b9e2d312a1403528211747099d8ceedabdedf2dd1eb3601cd46f78db649b0237cef7b6fdea9c157f06b1028e92f64a99b94ca193a3b874d90ea30a34faf11b42d45db58b22e463e06b20982febd616f6fc84c9581db82c60e2dfd3fcd9f2a5e8a17ee435ef8c2dcbc12bc32989166364ef5ac3b698a7e4ff340fa08024eb412c2a02503cb73ef853a25852f6e080b2094a08c6f7a1b1c00b4cb42613170eba8b5a5d1e9b7d378b5218e88b119d6c0442196a981dc41ca90a315484a4a41a7f28fe6c12a50a7b2a3b89ceeae0dd412dfa55227c351182c73bbcb2bce88d4aaebe14082c1ef4e967b70b331eeea88679d9d01e54d43bd2e80640be120fd5f73a09652c533cf0d7454a0cc272bbaa67676749a741d31e30815430116bc9947c2b680d2e9cd5258503d6c1ead6ead820ba5946180761ab112fbcd58de4e0b68062e03b776a78f288d0972b5921ff1c26a37d02d9ef41952c80319312a337bfc81a63166cee040e501c84eb6966ac29df8b56ddf3d3a3e09cbae377b87d21b798eadaee6c32c36e915272e160b6ff3d8d77419fccc3e65cdc7bcfe86155c59e116517ce2ae93f5f0a32429936a03c9952ae68d16200ac779e04837e814ba59b97c41663fbf11f0916e03c50123d82b8a410f365d5f02abc0f5b5dca21a4dac25fbfa96b5a1574191dae80fe654b1a6da037c3b01098ba100c4c232c689479c0f9ea0622340912c9bb8d627bf600ad10d10b30e54401d1786d32a9de5fcec10168ca8ea64141086185f9ad0181f8feeb630c52c40c2bba5fa09c02fcbc0467239c05711311e18709017e69c9ebc630848e5a94b24261b9248475b140b950ac435f8be44a80de4f63be7e53f493eb8debf844e595d9530168436971c22f19a94210abcebcd17c4ae2d115150df61c4da1be26d39f2eae16bdcec59de4cef65f0722fd467a8452789e68e2fba421c05b4d9a00e7d416bdb0814753b873356d388ca7f4cb7f01d9699e5b4a6c2d9c8688805384c4be659740eee6613f67006054b6dfc019a142760e79fa1a73cea278281f67e1097b0a2bfbac7df6269237cbfb61055313bf0d78cca237eef26b30f14525c8642a3e07eeb8ca3f6e1686caa302b54f5d4af4b0aee084cd0561513dd73642e67cd2c61b209309e67c8b5b38d4d05327b0bcf06c33f094fcf8d79b2166e367692ef94d5a18da3a6dc9c185ec55336a0e1b70e7c30cd1de83ee92d488f1c9c5d0bd627c3242c2a8358f830b5943f82c395dfd4498bd9919f7092e97eb42672e8576abafe3d0e28004fc88e3a9be820d914fd31569ba5a4c7de820abee48faf25381bbe5df56960384b248748c1502e4caee6c9c5278990865d33176729560b283c2df36496af4714079d69e370d4e07b480ea8d1fd62f917ac2eaa45761dc5342470dc14a957345bee9cc20708ad4486be81446f0d69d72efcb3f726c563349251d75659f238e7572396a2a6185baf7aba55f3497d876a244961cc4794515f0e656396ee39ae03623ffd7289e4a3cef8d9404f41705966c81297f84f9e7c879af9b48caf665c04d918c1308d9ec014d20128fd363e22568834b1b61d3ed6638a3cf2179ec5fe43850dadf28cdc0a8897b72bd3522a79373a241e353453177e2eb4e203a18133c32393eed24c8abcb60703dbcff6af12a019bfd7c5440c30b9189e1f4aad6338f712fc05f31732aa60827a24696c7ab249c17fed3f66649c7e8973119242803af58d12a4f1b6030695829a3c244bcadef5776a95052bf3136f7d48b232f9c4d496915bdcb70633f39b6934501b64881a5c27b5f583699be0624cd38dc55b10a5750b7a4dc80befd3a30cb357eb1d342a5b9a798cebfb029f6b6da95d4a6cd0a6157e80dc733f4b942ad9e59c59b22d19f9b3d3900b1375d857cb3afb022690efb1df0d173637cfac7ac8187207a78d6b2014f8bede6062494a26fc3cbb470479ab20364b940fd701e7be6dde2d8285689adcb89147609e3dc404fded2313ce354dae199a0d8edb771e9d95ab6cbec3d320da7cb797b9886a6807e1909da0e86586aed5c1de7be0e89d60d419cbf9876de6074cf96305eb674e2017ac587a6ef0d6e3db850d097d6f0ad240845d7be1f50ef1cb4134eec1f6f98807e6ab7f6186ddd17ae7472befcb4be2a1800359c20c0f9ce9ed15c52e6a12e88c579ac9bf554ac6ca91c74c5f4a3036da0518cafc7920dc06059b7695d7287cbd814a1b318637d3b36818a755fd286e3e083316ede9d76cab0308c46d030b21932415e5bbccafab8649fd95aab3c98d2d4974514e5a99498d03e598f27f7b05d5110684e617c5776989df89521a5fc0e9e39cd0d519f34aaaa03c608f2eba8e3e3ace83cb1489ad22aaeed2e5157a863a3cd2c369b26f42b057f4abd914cd92ee7a7956f945edc8db7ec085865e6acb2e6b7a4ff73346a634508ccdebcd1c18e87cc18d406fa49081f002e17a335ffcf212d817a57f489c33413e4253063b9f439f8dfd69eaa1aa2c56a24940d7cb1897355a343476c777a6716a69bf7db93b8d4928821d4ac991545d50daf1294bb305d0fd34cd6502318e094f8ec4bd737e142dd4554c87d4bf5b818beecb0b2e8046bc5870b0f3f55426b9046e3c8c23dad4c11f519a4c93418fd858df045650b91235765f79b6f4b736ff1c5c308cd9dc530148eacad50150164c9ff743e9e47fd3d83ff8bf1c0cbc35328d674ddaa0cdd1c7c30cfe58bc7c238549d90f4a9c2e04e93e82dada2bba1ff8a61d7fa18bb1d1f002030d86511ea14b9b51ff1ec60c506ec6c728655ffabf3a1b063c99f3a878ccf1d05f6dcf775a083823a26cf780cad0dda46836ad0d6ca6d4b5cbdf43b3b036016a4f1482b32d603611bfd043a65de5213e0e088ea41e7ee6a5edb648aef94154236d1091676a5c16e0fd0436cb684edce4d824e837d75375c9b206dc59093ef2265b6ef75b5639afac7a5de080b716a35d1f5f40238e3800dd4e2a4775041829142fde376c0e782117a2e006e14c743e473f0e43633538550b77b3ffb30a64bd4ab1f1855f3b0a90a3dbc76defae00a4456d24a1923f52438026ced0060331c80d1bcf2cd0f6cb7414e6715c932d814d49020a90f00c61e8b16c0035388fdd66c2605de5fa5400968b5274bd568f23249ee279144238887b37e70a5453ddf7bc1359eed056e824b25b9b8315addec775fa1a98cbce35bac0557d9ab30425a7d60be1b3319fa19e0cd6435c90ec1695cb930ba4fc6f705d09d884671526f921b1f36ec97d46e2eef73e0d41c34d07e6ae01a0a7e70c6d1675dc3758effddaafc9de566b899b13ec164b478fed86a20e0b9f0bb7a2596160ba260ac7ac14ffe685f84af6c5233db0738e898f71ae28cff11954cb487dacee8d6abc2f32c41109542c3af13bee61f5ba9173d0acb22a665baa4823e7d5d7fd89d9d172319a37b9562bc64d6f754413036b2df982c69e479e5c5a198fa9f483b1c872653cf33975d7a2990da37d7bf2b89cb91115ed12a267ca53fbeb155098efdbaf546a63fce58993e2a779f4dfd42854e9bd1246f1428a1356e7d8edfaf2c2b755f3a1fb1cb6e124438508022062f338ac053e5775c20f5fb7366a07c7c8f23d5871ab1e9c481bf78b9822a6037cb557ca2b2637113f2922a0a9104dcf5dfd588394836b32a58500e46b3f8be861615521940491d6179b37464b925f75ee4af972c1c7b7fa9df1a70cfaa7278068e93216eedd59d5f9a65ba6b3ea4f15978ba29016ce44d113273361bf5e41c68ecf2c27e342fc73e8cc15b578d8d64a9fd3b0f34285019aec878114ec55b1f05f449ad1121f05195809f30ab9bf7280c871626a967d2ffd0ca3829e1c357710afc7acbab76396691655277e652c3fe09345263d2f2fd1d8d396faf5587fe7dffe6806b28bfae1b01f628ac122a974de12a8a56057d54a8e849cc8e26a62e6d781f4c7a0a98f08f6c4be2620e343cb56e2f895ae3cb2809e38c98788ed372d8e36c2c04a014a49350d15144ae47a29c9410dd767481fc891d6589992c9b561b42bcfc584a264c774b58d8019c0d0c3e27d9a0b7a06125a3cb200187db27b86de012880cd9f6222f269295a48fea712e3db33dd80ddf865f4e21cf9be25edff5e50183eae2a9e0279be1c3720f4ad5087cb187d0350bf8d6899fb54091b459473233c82c1e84e5d46c1add869e665669b4c03830a174d040165915b2c1bb8482e9629f0a9738f3d752ca4b0ae8eedd7aa75083db8bc66b7a4f40ad6aa5b74c77ffb1a8a02a93b17ad00b4761f4150d5343324ec4f8c91b2e66557c4363ef382e8bcbefe69ca113858ac7dfbbf1910486c40e9d83ffea0374ef47d5b6ddfc66029a400a569881b892ae72f030ca56332cf48d8c9a36f3d3a661c8ec64fb3a648e09cf46f4f348ed07ada78653f3a6a238d650a8765876950dc8a4166aef7513047ec73182a46b893296b8996d11b5146edcc4c422efea80e06e6c0b2a842385206b6e238bee21363ce9657f327fcb6fff7af68fa382e2af4a004bd4e154c12a3ba3104c7ff0d70665ecbfa17d9312de8d89a3919281308d309a407980851e713b5e25da6ddc3e55b5aee08fd0eeef85196b7c791b3a4f246847b042f555aa09bd08340f5357ef97bdfe85eb0e08fc6d6365ce5c4264694b98b76be0af36a12580237a271a710398a89876f6aac7a2346416179eb35509c6cd29939303767608ec6f541be6e741f291deaffab839b715fcfb9a60c035644502076adae9b2a06ddc2cf324992d4b74099f22d5d8f1ee818afe928c7477844b9b122694c04287afca10373adced8064975bdc83eec2a6296daf964f716913a697f9c1c3e5f36449fca8c3acae70a767caabe05c6892b0d7e21ab0362072e35dd6b24df13a089813c446442fd6f980f5fdfeb1f3e4824d6662a46fb89493423b88566233862c093848ac47d114e624e8470deaed0e20cddf2cceacc8f7ddc9ad14a254e7b4a547427d88a8651cc2c82ae20a9c5f706cfa4beeb5cfafc180cf2e94e7c77c37595ddd3a547dd7f8c006ef2db6f3c3c0ce98d2c3ac163289cccfae91e03b1e0a2647cd746731ba5e3dcfb3f0bc3e8fc1cf902d2774eca97ff4b9463efa21da3cd7ef0457f8e0f8bd3389e2db0f5efded200e1d2b008bf885ceda22feee1d44d92bd98a77ff28587dde6ab7f7c92f371c4c0cf6896d162e083af1fc3e340ffbb20acf70d90321770f0d20c60b920fad0b182059708ced77779f88c48e7072c7e36d613bee1683c2304992b1cbb3da2b8eac8bf355fdfd74eff78e217a7e683e651f0c79eed40ea7f7b1702f2effeb77b19ed1c9ef7aa7574d5717fda53cd7fe8f0d431bfa459d32d2ff36591562c4db6ad9bc7ebf0145b6e7fb8054faf39467b2ea060bdff461bfda2eb7767f4f89abd376cc57f0509b4a285c633f4f8dba031bca039eb5994427c7c0e8ea666d4fd7172b8b15efce2aeead6bb24835a746e6dff06de60849139582c1c797edff9f588ca79ceda10693322da9a7eccdd7fbe3fe77bbcc741b19776447f59808546bc86ed57f359d40f9f31e825542f5d10daa335b70bf1e4bbcedd8d033e355d0ff61673fe71fc0bc37b7ff1babb427f9dbfaea9e8d1cd804cbc86f82566b32d80e58c1f3153eef49bfd068e7c0bb61cfb6befabea2dcfc8e1dfc7a79c2fcef029f36367fc9ddbce6b6763dbeb6c543cb96a649fd896136352eb3bb1d77f4c474f69787ad8417cefd2e95e63c4193c8f712870fe50195b57729cb9b9df038e08d4d576380e608989ee0e87a92f9fafe9ce00679995a1edc6b7dc2e1a784b17df486bc616503ce075bd0cc0c3dd5c04183acc097676c80ba077cc08fe47456107b7eda81e773b28a301ea5dc80d6076bc870138514d9d0d1eb4cc01f03191198fca07fc0df1f81260a07a6f80d30c1cb140373d15e6d9c24416960666a89282ed3f4f3e247c8326d84fe32041254fb10114b9174ed1d01d48c4f4c6ca9f8c67dd2470cfdc418e66464fa8ff947afa4a28e3361a7fdfc4dd4fc3d4b4b393c89ef38faacdb383b84ff7a07645ab563e01edb49afe0e601bf3fdf0eda75f9d4cc7f9bc8ea74af146fd5b507d06983c737e2312a18e2f094c54cea7b7e8d0275a5de64b7d4a447c0e6ba6f124235b426bff98e7f851141fdfb3d7956a995184929e78f0d89a1e47ecbfd1e3f1522f299a4853408cb8d83db81171b506c54c70c8787ba256ca8c3ed4731ee183ffd73aae3ac24fa2165e47c9b3369a9b5e863fea7dd450f55fc838893b4e73cec6b051fac69614f02e843602c3649cd6d71f2f9789ff8ca52be64e0a8ef3298bb9b3b364bb4c2fdb89a70294eb9a1c859632385d94f505e5b137d5dbcd398b84ddbbbd314d7a2f22df811c306d06375b59f08711d1194edaa77291b103e372fa36c94261e23904dcd0fab01b5eeea0450975e019dba65dd2ce05c763e2de293ef10d94e438418ac7105b862f6b195ffd310338fdcc4b82e79adc107112153914ec0de3bda9e39da3644e5dd1c143a0d52809a1088ce2f0a7db760ed3ad176c6d559ac7eaedaa9e19dd5344c9c7892fce813a18f4b03177a6a6af5c9f41f62424f8f1810ba5be1912d721a4a945ee2e32531095940ed60154f4d61edcd6e299b4af8d112699d0c293797c442dd145dc64ed437d38ad87d0bc6e1a8b015064b09b3074dcede94002d35ac99474cf91203fc9a010608221b9e9b4b478dac3d3963aa76743f92f87705dc1a83d0a0ffbb2a5b1b282d6938e2b350564975f2c26958af8d78f4c916b692af38a8ae35045de1824c57a637240ac6a79ba879f792f15a281995a4476e1f50aa1b8a02bf118e87f3125cef75d52d9561632bd728c650381922519648ad2910e3d554d1ea6b79fb16989c5cbdcb61726321edbc613440fa2a559a93ec2266ecd20e10c9eb580d03e4091e373923d68c4ad37a995fb670e2b19d221dee5f048341c64d636a18d206e5b12094024e970533db5492e95712dd10d9670a6c85b7267f010417e1874106e0a590c415f593747deb9d4148959ba7dd7357370199ec5a2ca5b49ef80b5ca206b7431f1c8aba874902d372a4ef8017c6a02451c7ef717e1bd5b76027a840a547f155a1db6c426da1a1197b01f77831b14b9f286ef6b101c6d38b7829246d3b9f2897c9f45c6c06e68cdcff467918e7afe799d3de8feebaff7678d1975b74db4fbb55c065dd3a003fe2ad62c8eed6a1f2f4852656fc060b2149f1c4ac86644decc05cee1a22cc2dd8dbef4cdfc2b0ea55714e19e799d8b2fe192c8b99b5b30eba6e999262f2e3c09f21b4a87df4aba3babe7df73087fe27ba310ee17ee4c4aa1469e207a56bfba7ae6ee8c9706b9c0ccddce7e749da445112a46feae412b4b2b63a06835fb96b420bde827cfef221f23395cf9ac8403e5545e23d63233ea30ec311821af2816ffeba29ca0c1c02d1a021f30778b3300f7ca46a89963e3034a1e821e6cd82a40eee317f7041360979b9fefcf28163311683951080643848e93ef76fbe093bdea21bb8145a0ea0ad9fdbc299ba2a365f4330a3eac0c7fb7566edf01b073c67d2807852fa1f20700fe392598e6166710d56223f564d28c50ccf780acc9106889573734162624815e16debc24a2b7671220b6741eee74c276cbd938914c3b14e8617012724c00ee666741767c33e571e0d1754d50cfea33399977375b0a4ab95842561b19755919dc80f802a996e0c670844dc66dbf0d50dc806daf2da28411107864e636fd39014113c54245882c9b27bc3b705987fa64c2987a057b264b3663f9e3f9b00f135f0d21b651d88b141b0c89ac0c5dbfaad0d9f4649e69e3a6809ef5899ceb841fba0d62e2c4dd5a72c2b27d3f0f898d241edadc1c273efdf529302b961fcc24a66f411403081301ec7fa240980fd50f1431800a11d780b2aad635a03eba6b7c4b7445547f402eb8169db2e7f1380220468e0f18b253914459dff13066ea646405606c104152700e7bbda3bd8ae9006d6e2bcef7b911d2ed3638c396b6c6a7d4f1cdd317eddd727c22e6cb75a174418650c00b3cae44d933ce2b0ed862ae802d600940ea20dee03f8403330ee887fa595870d94a3da51a6b9a97b0e00671ccacf2c3d8c6f0a9a1ca97729fc88b9b497e24f06d2e342399fb8929fc009f68e4b0f4dfe1ae29e7cf3706fe5547203c9ff04587deb0d5b8c0a3a655c58ec4213f5b4e55df54ab74be0b7b1c2168213c6d9a2249f508e00fb0baa19c7186cb807fd45a102b375ace99023b2d69c692f3a7f8a5fcee93fe91e59dffdaa82a803d7c16fb010c1d6a0fd80e9fe431f95646824ae5b7da206bf04dbae101c5d2825aea36b20ebd00039eacff44811efe7ae1306b2c2160d3fdacc3fe38da91c8b5c1c2172d41b67186fa5789b42f21cbec462a8b4792fbd150f92ec7eff11188104170a2d276409460f8dc9b2727268cd7f40c28a982eeb83d3f0d3dccb15da096f577d9115b05db61bb6bf21b7ac1925b1bc8e9a5f5dda0c2ff5e6fe8d7a660a04a630cadc4fa38e6b25dcc60cc6338e9c41a6c6e17553e63b7849676cb30af4f3c701dbe919ced1fce84cf12112db393d256fd97c2ba697ef29681ef347bcdf527b55fb51b4b79585a0961f303e85cc86cd19909798dae3e98d54401cb1d77a240c66d6d4ebe5400acb271a5f97ecd258e247f7d0436af91803d097f6acddb1cc8d9c49f0958810cb40f1e9faffd9f5033aa8a894747cf494063040dff02c6f9cd96ad1e9755fb59d60f0dd9fb89d7a6e08898d8466c4e7c3202709216ef564f773a5bda2320d710a8de256db11d3d4af74ed55680731a9b074ff2e68f797480321db9795f68be48d5b62255f7d8abd8bf235b0a4b21833d1dbb23a7062075187e9bfa016ebc2ea36163f65b522af49655c65a16471bc85c16f0bf21948724631c9555d25e6449d1d08902f8dbf17a281ebba14292667e6d14f76f1218c6cdbc37603acf744750e2cbabc332a50f63ab630b02f1743050d2be0dfb52cbb4bc3175d27ba0b93e5003b3de5996e2baa2d8bc0b723421018008daecff5b9b5ee03a32e3b4db3e612bf9c10cf8a078bfbb0e4ab51fea8ee4c08653c79e7187f27525f6ffd1b2a1408140e834a876a709f3e37d34b805217e38c7e8e3c03300fbbde3923bfac1cbb475f95989fce9a06366763adfab1fa74b2f9392ce66df309c6ded4a7e995a3cc2b899c900de99e5086c402f657858b0af879f7896b7574ef0afc3267b7d0eda5fc330b30d862a9b138a0c8ea1f7d5ce2eb6f02b1d2e4cc7765f3c4cd12d94bafca388f8954e29939d104f08e5b04db34de0edad07c6be180fb517d74cdf94876ac76cc6a136c7ddabb29faa2d7e177ebe7d007751206f8accdea026b8612784f66221652f22dd8d51b548ad7569b3894ce2a8fe8c79eeee8bb3ff21d236ce0bf5867ae7ff6c7c88da6aa6fa1b16adecc666e92058d50759a13c7fc71cfe94c2a1740ded97fd10ad743de3b3ee07c33fc81dd8046fd6897c64cfa83653a56d9ae2fde0c4b7d77c37e536fcf8f8eb618fb30c4495f2f2cb2c8bd84ebcbecb7db8623c5bbaef835e61b1d56dbeb55f5f8e2ea385cdcf4d6eaf1f58e0ac864b166f6b4d6fb6e5bb6a31936f97b30b1769170700b9bd4278709ef19e64f4147f6881b87e5dbfadd7c75abeff58642d1bf9b6ae572294c9702e4a396e4b025888f39ff93e356ab87d1f3eed062ff37f6ac2c2899e6207142e80360f0fc2787ebd3dcaf6af84a198850214599bf51142b37046a43b48b023b9ac577fe8fa808e0169915c3d336f4c3b718f48b42c353b0916c79a90f836987696054fbd08345ddcdd77f19f6b5d323383d054df554c7c556fcadeb7d1df58e2e9a8f9318789f582a619b786687ba3f174df2c1d8437b4db23979a94ee1462761584f9cb4bbe78468c2b38fd0d18091f932371e1cf879cf89bba1a5b83c4effc6507ab8ca0e10146240915de6d55fdfaea794a556c9c160db8c1642bdfa78d177d1e404152de1631d18eff38fde80514339396d191b1083fc0f28a3867409115a0473d467d3fd91b855ed6714793dded1966b0de620f58ac917395dbe197f937ecb941a879aa5f2ade3ae8958f89ca31fe777eb5c1a138280ca669b08f5028903df33f169ba9d30ca6cffb765f73d0da4aa6bd73f8186b3c984caef09832cdce24f90e93c33c5d673169c304411a1374a388441c1bfa40c9d46f501c01e0b0de64d79fd6bbe2c33bcd88a0f6149b4f83810e363fc07b0d3705affb54b02753f60bf73612f7db983c0ceb20e717b94e4b466df48e42ba1eec6453968a32413d6004c3869b630e9701c19c6d8f4ba67a9bce6b3c1b47c28edf6d33df288d70fcd8d95cc26aaf7a5ebbd8708bc333b75fc3b3ec9aa688b57b568e7d1b041712b866f507bd8a59eff609be5057ece2ec9162432f8d8114bfa1bd42426325c959f929e2f79894bbe16ab133379252f51956d105d09907ffffda274adf1af809c23acbc7ec2e547758a03fa50e4d8eb64f404be8a3a369df9dd1507fc21a7b52c13deb7ec3ad52ab2986c7660f40c9bf73a8c42e6007ffdf8806c7f8151a4632c940f705cd5dc68c75774a65490b349a5e4563c53af52b34f3c9cb1335d78c38081fb667d9748839c50a58b5b30ee4f56f0bed9aeed6e012be160fd5750009ccaacfba58c78485e171c09d46f2d0218731019db0d6b9cf85bba83eb6ee0b8f536a2c70811d297d7622692b21d082e462fa8cb23bad8034d5ec151778cc991464c5555af2aa64087428b3da64bf94e96fa53480198746a337bdb057ef08d71d3e19803dce0d50b2b531e98b7feb5d8c3d20321bb104059e0c055010dc087dd9ee53c345f62357de12649f77feb2054cc82aae062734fe74ac20ce21c3ea24211690410994b022f9e8cc5177e87ff8d43489727f65e5aa04183e48b77d1cfe942bc4fea32996ba296496fa0fd3b547902df175ed81437d84020a28ac7d6c632145b0842ec1e9c1b1268a82ff79c90a1c26d322806f4ca4fb7b7e997dc2093cb34e73092bfd450ab6ecc136283a0b227b5b4f1bb93e3c51e5753bbb9480f9355e12ba2a8d06f5bb6d8edadf123ca1fa0b04c3e595c2ef086c687e43cfb9aa5f23f7da4a96ca0fc3ef086647c3891652f1a840643ce544b861d505b948220d8c127463fbcbfb8a4d617f91b737c69ac6efda71ab7c392d771efb0e49a928d0574fd3c499a143c4981110d8a00b32c491c942165dd04c66ebb9db51f0a435ca7544bacf93b30ca79137c93342acc6dd32ee0340993da5b43b6066def01f4261e63df6ae959c5ee483a1231348be80d3ca7aab849121023e43b485db30e5f849bbd2f683592711b3617367a7f77f8e2060772e3df683bfefbf1c4869d5ad4d88face17702404054cd37d4a59079aadbeebee659753fc849c7bc70b361fbea73353408df0798126f46b377fb10274d1970b37262ad4a5de70daf18a39af9be155509f5b6a3fc13f189af5eecff6a286a113cc355c757efa7f9c8ee27a7c4dbe73edb557a7682bda86524d48c06efbe41d909385e13450a37a584019edd54190eb9e813fe2f799ddaedd2fae11c73d6d563d5d8f3f083d4aa3b6b8cead5327bb3137425162e9943c9e873735f5ca8eaa94c9c21a08ca06800b00677c53558e1a9941ceac37c0e5ddb0b3da749f802b3f6abb7e9680dea5efb847630ee35edfef54cf7217618b18bb535a203593309fea86909f25e7fb52a657174fd887082d24c5891b9de5abc466a1867fd2576ba3c3345ff4c97341b743d2c170780f7b5609ac54c9842744feb487f296c73729a1b0e25aaf8698b1cdfb78f0d783cfc21fb9fde52a174d39963545d1529b6b2833f0ab0602e664b6cad6ddccdc5d9848fe84cee36e46650eadc2c7102a6b322332cc546d4beb770cbb3f854bad1ba8b1da8be1f1dd5c6a4ccc01edddf737055d2a4fc8a3fc6ecbec835fb59a6887917e0d731e59e441c63dff51af2c08bfa6f2e5d049ffd7875ac3ff4678fa7ca9fc603ad01721748dc786aca7da9267bd2de830dfe87fd81998c8bc6579b95f20bd37bc610a9a23d773ef26686d37eeedd778721a9e4a12f1cd3fd4e3bead359d44950691c8b8a87c730c26ce3b8d98e12dceb0634d7b126f022ff4fe7489805065575fd7bed6c07b6a1675a5c8bad8a23d580ca7625ddb826ec487a3e39ac0982c9f65b422f0c1e1f6045acb912f22573d6db9b16967549b7f8c90b50f485bcc463ad6d2fb6ad9a152f58f95ca9e8ab89999975e499a557b47c8e4202079cec9ab11ba71e8535710285e6f80448f05df304d3c44f6afd50f07c437dc32c4fdffb928aacbbc8255cab3a28887765698de058c0396bb23309cb1447bc576525cfaa1fd2025957fd09a2e67379f31952c75e4dd3b255ff878063de5665b54bd52304500450bee84412f8440aa0779b9d42d1e8439cff0bf7a6bcb790befa720c7562655194384bc505acc57e6dce379db50e4f7f1bac693eb415434c2f2bfff87bbec59d370ac0a20225afd3d8b1ec84013f7238d3780f94e0a68fa7a225b6aad2ce2b5cab79e0597aea9ad7028df8d2292ef333d0b1a5f996a43338e6a850d857048aaf9d25a10b65968713fa91602369fb627f6b9a7754e52aae3b73b523eb9d4bddc576aab02707f0016efa3bd098242db2e1e02a7bd45c985aaaa9a49f9939236fa98ee2f07f4fce901ee6ff44d35d3fdffde7eb2412bc5fd41c1ea8149967ee1a5f893ce7a11996fa767c948ddc258b5f0a2631e1051092bd053d5224655270899829b2b3d8257e94a53f259dcdd2cf3505f9a0a704ad5e020651cdff04e48d5a9b9188a533e8ef52f80d0002ae38c9a8dc8465eead50e512a1604b492eb05e63d04b6af08243e5670c12de673d1468b68e97949dfee7a5fd923d8a4241f7942ccca534ffa5cbb23fff44d107f6a2b02e27297be6fdd642a1ca4e9ac01c4e04fdba6c1e6238137fc037d8ae13e7661f5dde6c0435438720b5f2a672086104e7048ac21dcc41eb49bf7adde08501fb0b1fe8f545a11e97b24ec4742ce79eddb802c29a6eacd29a94dd775a90afe39f732fd146659e4a23eafcea5a8f34778650603723f1af12f6d64c998420164ad4d639aa8c03dc0196ad2b3b3a032ec7c18c5f78156fba0eee50105681f60237b200f40cffd015df7375dde7c9a94817b7d53d68ceab5f4e7da8b30ecdc569a42944633bbfca65ec3c754efe565729ca88996c81f6d938fa7ce2dfd799958e466f0dc6194c6bcda42b42e5083c192c9a97bb66a8f60b8d5259a4eecf71a19f8cb214ab105f6b80f9d23678a35a867e22bb4f96bed6cfef82fcf26db48e90b5700e42f78c16770a47d9abfeeeb793a87a05bf45c465539e3fe648b60d153a4d281117fd3d2bdb975a17d9af59b9c6b4ebfe82b99978528ce916838eef16b599101d6126e63ffc7d2016a43d967a129df2595b8e6b4a4ecef545526c97654777bb2821e20658e328d4ad4b506be0fcb5ca2113b086d3a5af666bb8923b2b4983cb37e91328b0fd447580303f6e72071a4cd89317700928872a1650a8195e6050a8fbd9f4e84be8bf6aa0dbd2d34c50af432b91ec41fb80369e15604f35b672d746e96d3fb010b02178c683ec0779829e83174832d4805b1defab92f9449aa963ce5fb9170f8feaeb1da45f4b11487f9adda5dc03d75e1e2f30ca817c0e07e9e0cdba9e14dd4afba7189ffcfd385e9242212823e0e4506acd1bc604a5a73eb15def23e15e96c688e008803fd4124fe499af3e1a3b918e9a135d5a8c62e62b7a3198b70293780d3be1f3d1cd8e791480fd67a79223445edc1f4f24e4aa254be0776dcf3679781140d14ce6a3b457a5af4c18a2db76c840ab3b096f401629843627a6964428f368672f06e216b0f9edca5a75696f8fe4f70961a63497815d2b9b1f56cd451fb401792e6a2f3add102e0be772328374b018e3823cd1285cf7f7e0f6a3c8c81220eca3297734a86880ac8d8a0a9e17f66c8c153eef8a595cdf738c8380e44008102c36e53856e35c20352bee4dfd8427fb1244d1f22d175809ed878ba35afadf45fc0334d306ae38c840df8352504fb32b30b87183b9519309eee053552a1914e77915885dd35823dc1ae26dfeeb6b43bbc7c52134fd8a588b56b5a5a45d576ad45848257fa70fa009442b38078142c378632a823b1f3d55829e8b6ea495cb5898441af7ee4f19979ed1c5dc1026e8b045a090cd1c8e2bc1fe15301481cdee0d5bc068f4c2298055ebab7f7fc9971ca4ad053c1482b2de87090a33da7293bfa1cdb8eb6fdc67954216333ab57db906e349439fcb8e4a36695feb09c92b2dd90e77fe8b6f9c5d1094cafb0a064061a53d474763d97a61e8f71b9494d6b8c34a213ba8edff9afe3885727c62ff2cadfce8f9263a6c31aadaea81e3551ed2601ba23cf609a7cc60dd18e7909246a90886b507ea7231cf983b58f47c4c17ae8c984987bb21991c352707f95c13c30b37e530deba5bb4c4c81ae0bf90adc015f7ed9d0b10fd9fe3cb63f9ec5bc1af233c78e23061b433e1865fde5f2b2d5b80035b3cace68d0c317ee64f308ffc88f337074db1dcec1322a84612f8024c1f68f293144721295a369dbd26ab91b574b7380f3c739ec232af3056b0c8de2fcf9e5177fa48a5a998b741e5ef599730e8ba3ed472534b518ee60f942a839f8bf173ffdd834bdf232c28f0fce7cc8e573bcae2bb93ed29e8ad4ef5bd0516c1b7878e4e47617a9efd44f9d7f89be391370b1646a28942d991c06d2ec4741be470a56fd8308e335616872eb9bc87ce68768b36136bc03472ffac34855123950bec0deb00ddbbec496ac009f5fce0f728276ff808f896576e46f8e9e81fc0e7243a2079331c6a7efd1ad77ae1e9cb4d73cf5fa8e11118850800e2402ffccd10d93a8f79c291e30f63de55ab6a99f9d23b2145df5c15e4ab1bb7b8d76d4843239ba1af706217e24ceea5a89d35265f0ec79718425b5ab97a95a132c8cdc6ea3a8e86df83eb6f9e314cefd5836b1f109d6848714b6d42da057aa451318202a2ec1e4c6645209b68a1a126b2b4ce5728bfc62365076bdf1f6463239dd5bef385e690ef5c66fa3e7d3a89041812c495ac10a2b5811001b838918104fb30c63cd36c66a1815ae4a97de64ac0eb41308cb4282fab0a773265c5b2f25f976a6780a11c37b3d6b90f10576277010357cdea65059d15e206d511be1842b626db23285d3a455f3f6a7c21fb309ebf07c0bbf4b9785807341ae3fb5e0997f71cd5eab81d6f3660f2b94c5505a0bb5ba362d0d94144ec514aeac49afe106e1b49cda109bd6a99ea242379be096176a9ce89ea02c2057b7fe82b4a78e22a67f41dafdee9c17d80944c6a1efd04f93c2499609b445cc2f71cc47c3948bc54bd81aba3f655931db7d113dc7b543dbcdd3a0d23dc5aecfa166e597dc17b8068404197d25847467ad86b1eeb5e7aa23b74a3130956e9379e9767c23f4ae24dfecb28a975f2fbe956b3134d5e48d2075297e7b996ae971c3c04e1b0041bd4d02448cf41df5ee10a955a26ec671b9c915321a3b5bee0a4244a61107638c893a8bc0d739f06291f5107b356f3d12a992a66d45811b18f79bf73e80f3a795d86b673a90de14d3ad01eec951d87d834dd63349f5e21c321a7cc091f349876356bd0cfa0e8edf7d9541944f545b6f5a93112fdf317d2efd7cd38df345d63e43546b0d4d53e0622a6f1d4b0b8eaf40816b8440734a01372863fd9ac9ea73fc5c7d09f5d219acd83105fcabf32ac68db1626e9abce608f35cc3d247e6e7dc7e55f8bc54727af9b498b7d9e769578d69797e897b4c5a1eedaf0c3f24568c3934420a8b31443ddacbf2783d36a75e0e17f760374f156178620917137cef7b0e70f9c668edc564db954f8519d6f0fd6e8b9c9ee0f23e97d6223e79424222e17cb7000a47af7267aca7b272aaeb88ab18c3a560dfc69a56f1dfb5aeb53bb798d317b88bbdf4a128754153c27a8d8ec07e33673a8734168f719a103b009f371ae27f987eb38de869e2fb3c5a2a777580ff896fd64e137e98c3e83fd49325734f5842a157e896c2a5e67c4f5407ee9efc56fe6956e7036dd2eafd1c374a682dd0a5a91e5600ca338f2fb40603e379da30bb80af11438bfb89c8ee7a3fa09ac7387596dfd51c378bee544288c8c444844b3085de144f02ad5ee5407e4a4c935cb7fb8788309845a42d6c10635a900f497bf76bae709e0f160add86771030373ee4f97062903b0ab6ea90e91b96fbdda3f4b2d1d8b8b147c6fb956538c21c2ed8382e92657754ec322b9504580e256825710fcdffe762770aa0183bade97a3c562a32166ec7d22e2df6873582ee275173871bafb40325455df7f2a264d61e01638a622e2d1572a59bccf8d2aad1bbb683ee89404d0095ec081ac96491b88bad80a1de34a5bd5ccc2e9c412d2f4e5046844bd3c0f710c65a416fe4f77a4ab95812699c5896743bf0345226c1a232978560f6f92a4d8cd04fea39bd22180f261a71ee4a1cfb9db72bc47eaadcc309814d2c213c484a8ae4a1a5ef008abe68113fcd453fb2bbc90aad9408c2cf928e408c3fb9ef2092a582e884a7635e7cda592e96bb7a86545800e176aa780324ed5cc73291610aa0764fc059f2a21f5cb91508aed58a26f08656566891291b3d97aaaddca8f91ae8417502778fd3139cf1480cea826ab43ebf2a71437b2a2307f85d8ec308c62cc19bed9806a5ae095afb5293b25c70b307d7a84c0d9a338ae3d4014f3b1d61f6e630a8237ae7bf4601d930d5f3a07a71194b9e326a2f7813b947a2c2384fa714455c4dd24c171f89d19c6fe1bb0f7d3c3214cc0ec71674c8673192971872df4400f71a1b9e1d6a792658c453c8d8cb5fef776c5181e665972f0bdbceea35dad503cc832d0d3cd451e3ed61359ee211af7e095dacfe723fa89808c0ca39bfa21adf87d0831102b04fe1d04f9af69b899e0032c4cb11209513aa565ce64a5e7f82c6cd641130feb830bd4674de33d8d43ad891a90afce85779916117ca548f6d0005b1ed834d11835f3ebe2f7e1835b17879cc07594e65ff75ce9a3494c88a83a7d4200d1b4c304c32358b8a66c6ab35f964a4f2246cfdf2cb88d70a2a318bcfc358ad7d402723e5d273a33cdef113bff309bed509fedb06ae8143b592bf43a0c982f007c9dcae326402d8ec8636fef138abaa78557b69520dd589a70b38c1bfedf06396adb1e435c688a2432981c19129cd9c882950f1c0beb5715ce1c74a5760e055f385ff6e334d67e945c85277d0818debb1f9ac4a6d2e9dc0869664391898cf792d9ac77408e645e9927fc0d69914f7bcab27abf75c5516cca66101ebe562d4e28f5838b373098e6f4411f6b850686664b8c16035b6529a4aaf130bba3af7b6af05da187c4ffcbc4690b634386124cf1c0b374ea4e6580a9cddcb5033d5203c4707f82379b582b51b4fb219a43d9929c09956eb058306bf23a98c4f53b3945ce114a0dba842e2c2053b544ae6525432c395c9dbe9962550eb18e191cc752090c26631b06ce69e5bc575ac38e76b470dbd36390cf6fec7193f7c7a28efa5fd706819a2d41d2589f2627beb4ae0ff22b2ad75349c62d93004b1b84c903bf0d00aff29ed9d6046e21220095613a70c1c4fcd9f5fcf20c2898d88a70126a872139755c38fa7e45a76019a250595378d1e779eb21aad174ee2ca642915b56657bc2b96ead4ef2d62b40b1a643a88fc4a9d42a826c686693cc6b4d064953f034a277d04f01fb430222ea6f1c49b482adf67916a8c94c4e198c0fb925868313c34403a9ec22f5faffc8e0df52ea01c9b343c1a8960ebc8359bf9dd823ae28541faf145976ded17b8d75f8d2b493632433260e72c74cbcc79cafc5b323b35499ae3d537c8b66ebc75a41e8ba598a3b98cf621547d5b331d25d0c7582c17778586bdbbd8557334620f0b01877b20e49f7e19e870a58295f2e8cb48e35efc9caf81e3ec0989b62f7cbbfef87d979ab39e9b8940153aab52695d578229f8e61cd7a03e3b81f313fd931229002320b44a75445ff960ed69f07570fb960e1cdb00c7f34189890f1da1410bf715fd2e4965fef5c3d2a183edee5deca37c97edbb6a213d99a8610d6c112d26338550a789c16069dd1831e23786cf66e2a4a0f1ebaae916001186bc658ba43a2693897f15e08989c0e67dbb2920a6c19be7991a09de668e24ead41a55c1d7ab02827c2b04c6f6c5b951a88b7093316737f21b6b52ec9a7abf0cb9f681486a4e4fe82cbbf18e61e68548a8a7a624b07cf9c51ead2ed072709d0801134b2f25a762ed1041bbfe65e92c19b70276d27bcaf3595844b54d924dc42267ec3d75d26a2c1e9feabb10a255c32cd80ea5644c88bea97ae82cc102c7e1592adfb96eda06597da65652e57da5d44a44a49764543585a322184fa743db2f6d564adf430f6e22599875128558623d00fc36047653e770b685ad52fa73c6b305865ddd7f77e89bc6ca6361a6aac50116e67689bc36fe0a73fbff52193da8bad338593602d31423a17f722b71dd446d806c4ce0926e9fa23ea609560d287244a5c62130c77145bcfafc5811eae476f2dc4bdf98dca7d8e68b72bf8287d602e40fd6230f4f9b683078705d7a59e6becd0943f0efbb9f15f1069b0e5b3dcab0ee23b6c605d45e1fcf78b8434f548afad8d017c97ab599739989c21fa6ce9dd8b882d8068df8af6297c36a5823abdade8889f2317764d9b78d735c2c2c3e0036fc225e9bcc490bd49c0e5cc29f5c2345e20bfd316a100445a377b921a9a2393a941b13ebbec2b729cad1ca67dd92bcf686ca40748c3392398e677102a614ab1d648ce98dfb00baa34bae02258bb8e1b2b47921819f211321b35a8d8258bb6b7fdf517ee37686c9d7a9a08604cf781a085481691e08835734ccd9505cb3186371d194a2dbcdf7b5901d04b16042503422b2545cc2b1f947074258f8923a8502b552e2ccdb7cc5073afd196a7b371f857734e1a6bb160d42871e2334eba8d30fa407a91d8d20b9148d8ed15da63951eab36a5e07f52881fc394bd79dd333e01c1e708a8552177def64f9cac1285439360e4fee7dfeb91685518be968cef8d1c1f045fcfcfe6922893d18eb42c910d642894ae2fd659f2db25373908af79f4976b6302c4c057d7da6fb3b8f7d31b2d8c84d1d1875c197bb4b48b358ec688384fb57483334c3dac7b017a0b93a4dcec967329393fd7158aeb5deb1e7373dcc48b29f5c5b4103914c4af662f45c4cc562b6ed079f13060164a2c9e61eb2ee8c5b6d74794510df1e74b0e10c14189a1524f233fadc336d160be3495e46570a41cb9fbc5637fe0ceb84b089220cade5416862fda4c5d324f1a8c1888722cfba155d218544a4b940b51f7b7bea6464d7df96f6e0fec147a6e714195cecbe7cd45b639f3307ac522615a9ba2bea1faaea58e73753d1fb2b8b9ebbc0a2ebe3d50503c8b12b0f4f90b5321b3d3d0c71a151175977310e53aef4defdbe26d7b7d67456a6d338f8116653ca1fada1f58e4b9348a4e042875096ebc062b4ad67259ba83d5c09c85e6dcf443acb2c52b159203af0a5b781aa6bb95504fb77948530230f7439ddd01d774a990e925c7f76de800baf6c685a219836ef8a1bc4abea0a218301af899a84172f601f49b61118677a5b07a4f79e58020374bf25fd571e6d71b1090544ffb1aac1bdc005766f7da02454d71888a28dc5a30a94d0afa5a16f6898b81d93ce01c88a2b2892886b823f16c4e8ca1de315696330d1eb7101a6bc527724e6ff2f6fc0df957a36739a1663b984f52092a150391c8b58e20a3164343830ab0f8e843f1861e337dc7f5040bbbe052d7dc67d6cd62d8a759275ef00616d3142ea3a90f6496d0702d82078396157b9a8b3e74aaf53fe13873c61d9f8dd7d73b371848134b41a6d6c50c0f0db485fd3d971ee99c5c9402041278433e3b03a094bed28575f92c1443590bd835052fcb1f56b2a49d4a662dc4516ac5f83177ece521dbee3b30c2c117ed00751d3a14c1a165667eb0841fe576b69497a9a1f2d7d40818f0ad1ef2401b82c16c874190f596a12c9b743df1523d4b62dc5f0334391fe03774371d7066bddfd4304c477d046a2ef05a4645fa4801fbb52016a2820a8edd4bbedbba36c7afa2be2e0a60ac8e79ca89f8b7550248e4e1133904bd050167740b12bd957e7c8fab15dea86bbb1e9c02d01a5533196f2581f50fc6b612aefed82593bde3e6c479b6865180fbf47622c9faf041976ca291c76ead7bbc34c8d9292d5f58b04ff14e64081314a3b68bfe16aa649d7d315c244ff604092d93647ce7517446e2b1879a2c83246e13b480c159b193f9a8b79f513935cfe7360948f1433adb6e9839d33f93702a7d9ff22add00ab41a0ff4f35827df22c3ed28ef98f45ccc189aac72c2fd45345ce817999e3f48defb81b267f769fe5e1e401b481f6ab21a65c1c80bcc8bf68bc9891c5bf86782762ea5d948ef007af3eec26df7ced5158722b7e1732c96f19cae559bc07d967868e1bc854920e76470e6945b99812b1944ed5173651b82ac2ea413a80e6f1d7899ce05a8e1f3137b200260c80a6a3c682b6495782c32f5546e1a24a7e6382aab8209ba9e60d3f3ddd835c5924bbe919ad5279537d4823ac5cc5c7e9e68364ff2a846450d53ad1fea9d8e37783a2b3791c4826c04f8160d1ebae25b043aa2807da63063cf26c8cebcbdfbe66dafbd56d9c7428381f3ed293060bfec782a62c49371217546b7233ef4827812f0156f150e0438bba0021ccd940e8236fdfee6d9421974499f03709937b7e97f5f93687d2965911f3f32d75a3600f230b69e2c62060b78b6da16c357139dd87fa7f804d26f9822ff9571ccb758c0edc6bb6600a92e405c822e5c61dfabfd9a825387240225a1bfb002e004462fc6ae686bc6e55136aeee7ac3987fb265bf7ccfbaf82ce2cce0decb7c1f03ef753bf4a407e52f60a15303797481013946b41a199c702cee4182c065ec1c7309856a031c462d02d2c21a2ab45b58fe711eaa024ff61b53f7bb6ee05141c03aa02a5f952e2f8b96c33149effbe0ab06070cb8dd959a03e7f0cf49b48e7f119ad2cc6e7ecd81ff2362c964d169c160c5a1d1b278456d3d299c451e9b8105b948a050240f6534a2e1689ead03d93dd50603716f1fcf292b733ed5334aeae27480813f05387edc9f51631b537ccaa3bd1432b3f2606593c80838d50c34cfb762c245d1cfea9c22af7976b3718f5bbbc2a0f966f69f8c28749492e676f38ad7e141e9611b025206ed908cbedad75d8f4e83211c1ea4016cf25df47a28e98afe8b75c2fd7dd65ead208fdd97e00bf9f367c3024d14988d4161302a499217d33c0b787fc31147879f8b276597b7e454d93f098f772048b05c1a257be12d964885e1e693781439b330cc68db6c994a6a3e53eba570ee3af10236ebeaf465e1f8d874fdaa1a0da49b12e5ac2abcaf0b2bda2546a9ed343662595f5c5a4630bab88368b2c03f680b8b6150fca38135bd2c08727295c1751fc3bce2f0eec8f43ccc000a111b652d3137245e8e4d4f1af5f86103e940b2b23616415af198987f8c5f37bd19075c5454282e4a2772e7082b84e87822c0866e5061c7a836ba8240652312a5d3e9971d1c01eb90edd10cfed13382486c96e738719bc604da0b944c3eb479b87e4fc4042b54b7a6719b9b89b0380e6014ff64efc0a74ea453484a53719ac90997cbb623ea6aa1171c19f92bfbce6b1abcacc071fd563277cfc8d4efc2c5706ef8f0bbff27a7776e647f1f8535b446ce46e7f8c316aa30c43d82e58c81bf0a12b1ff4683b149eb456f83cb7e68b7c71afdbc7e5d1e847aedfb1f431c8775ba99749b116623625c38ec05b97a0c0826ff362057b72c192caf4f60d8bae3085e676b74d4098267889864f0858722dcb8852b9117fc6f243a0fcd329a806d9084959c91c07871f29034b9e4ca91d5ab86450bd6091d9b46bcd5d827b077ccadc900215a0dfa1aea7051e0af341c046f10315c09359a9ef06f3c22a2f05db12c4cad1c987f52c6f01e282053dcb47899c309ea60a53bba1967268ffc519c2734a5d45a64f0cece29972bbc8eeb99c5e8d426104cc50d7e4bd4e445ba1a9d86165a0a2d6cb926989bc4fadfd36b1db3b90841cc0018e5f19706d9310c05cc38c96c6c5fe542b365062abe98c4cdba97a5caedae8c54890dc9c07d9a7858a6972699d89170c4058dcd0e285c80b81a3fb501ec0095132df80c347c87da3158cf581c813c721d74d8bb9cf9d00d2beebad590375b4eb237c25169e0fba5115aab3e051054e2e04f6f0e801a8e455704c330a571443747e706d41238f06f2df9a423dd72ee88d8bc00c9db68aac589ccbd94dfb18d05afe5860dbb753568446054ab8662996623e99153bd9594c820095a56726ea5ef524a267833862dcd29eec93195df3d9ad8506bb725b0f074731d5dd3633808f86a023917817681b56d4ec4b8422c5c41a997babde560b70415989ae9eda62f1e16db2f14a773bb4f794011e5703a7119904ad626ca7d0a00b2edb32f13ef5e841f18fc2bf12481bbdbdac4750383a7fd9e6f25d77e4124c980f16fa7a16d1a090e3c650052d093cc083d36ed6bdd422318bcfc800411cc49bffbd1fd31e370fbf2f8fe1ed5b4f04fdfe30c7a7739a488c2337166855cab9dbee0d60fa1dbd18a55a3d6e4f782c88735a5304b85b33063ca6a8a50365c676ed8e3f32e7d0db3fdd61bc8e521d7ba53822b8021a32305de1d191aceddb8ae35baa86cccfc9e975bb35e92d20c7ad12d93b71258644d88a8702ef46019663c1a1d8f45f134e448166a4920d7454ea81691e8a0ea963bb07d06a77edfb930ee3ab9e54ebe55718dcc9723fc2b656e98d0375b6e61a82920a185fd65d0ca3243b853a66a2436e765352a79043a07507cdf1ad5861cdc848190375f607748b6f49727cb6fe22c085fdc39d16c0df1cdabc2f648d6759b006f8812e44f54605636172bfe7c332f036bb94826e639bad09317a5f4950d3bffc28e05ce5630511e92ef15c550b25732286c339f5cd4b339251f94b4c7d6140e9ae9bac0b75b9c58e7686bc38459dc1b842f8f9adb46e52000cbe778695a8c763b764c093084ac41ff3a43507ceb6cc2752b0c10e158cc400b41cb1dabdeebf167bf6afbb8addb5154488c935c460a90aef7fe5e84d7d300e99bd3512cf193c41eef6756ee4f6ec0fd9de4b3924c20a655cfbabd5c5a3cab1f5875b8121edf3441eb184f231b7c5303c1d6f44dc1b0652d9ece5559127eeb896a6589cf37370ae1c66e28e7f494af3e7d2e3ecbff0df6a97e701ae9c381e28e009f342d76db3b18a5f653c176b3ece6800e65f5773da8649425279a3dab06a98557ce4835d3de4ac17ca4f0a2b02cbe387ef38ec86404030f83de4c0750c56c8a4402702431dee3a64871bbe7e4db4f581ad8abd7d436ad51ae49a473452b8c7c11b929ed3efbe6b1392159fe0833c3a641a2f7f0b373d4569893f3ade7612824f27320b8e323ad48f3cff46d982d552dde12bfe377554d99fe92f6b2aa6a810e2cc45337791a256b2ff5e7c1eefaa67ab72afd2209665b3e4ee849e818628796985e7194b7f8b66fb9e3ad23be316683768105c237f6d915534b7fdbb439844ed71740d85d4cf1e7c0fceb01fefd81e95059fb486f225966897b18d02cba011970a0a2e60032ede86be3657cceff49042211bccab7c9d53eef3cd7033ada5615cb9dd51f7b4ca8fcdcd63aed66f264ad6d489f0122c95eecc605125212429345cf758b2b2b442d6b340e94a703ffa80339d16df1c340c46c4ce6220389f2d9e15572e735b2276bc24d72c2efcb7269da624ccdac8446e737137f9bc8198cea3d5ffa413f0a41645786678a984cf3c0ec85cdfdee49d9764d602d3f0af780c27a4a7a9486939961083b181f442677f01d134daf431f9bc8286d74abe9d63f702cc5a6e820edcfb9515b5655c067607b0a366ad53a8264324a4ece5e45d00ce2f8a81ea3d55f490f1c411184e72ccd81bc8763450726e159714ad205a2c3bc8db08f7f5d9504951f29696855c8a1ba35cb38a807247215a4fe97113aef7b4db73941703a76070704da5d53346d41e26c57fe2f1de1ba7a55a3ceb1e65b06a171acc524f6b133d619c58b6d805a10054371b52ee8da5d33dda6e1ec850da70b95c29d2d3313fb5598f9c5e159c0dc5fa5e923e918ed452e992742b7ab310473e5e2ccfe3dc35a673ec3dd7891a78871e9ed2d80073c4000b44c14baf4e29a8ae21d33d9db969bba8228cb0dc985aefe76d7bf81aa10fb6aeb61042720105fc71bfb7f241eb4c8a63744aa9f4d52d1860120f42f38b4537b1e372cd36c728e7cee9f7f9184acbd74e69a9c3d3fc4b1f0dfeb50efbccd9572c25a0a040a36d744f860922c8b8b1032355bb71b0f741d307a497260b94c95eb655383f803842a44106b95677b8963671230c2876879c1b6287e0786ebd9f49ee1b86e4e8abde24658836271c35ddbbefe11ba3969afb890789b2dd146f91cc5ec1c3c6fdd31efd341be639fe8da13ca81fb766292fc9533555b1d835fbb051c5813d236863cbaf1ce8cf5df1cda3ed63b2840f41ce4c04342ddc5120e3b2440e949102d2ad227816b0a50d3e0badb39159ece46177b1be034de05385b803f690dad27351635857599e1a7e771bdd2b9b006b9b8e130b21946aa183bd3871486976010c3108e76dbd36e9528d884daf923d31ccb3b953a8625331cd7759676d6d25e8e7d8c3987b65fc50ce20ce5a6a74de8f48e2eb65579c9e87eeac58f8d4c7a065c1ac87e055cf75a140775cc1455f9e6d539d8a84d37f77591912c7a3dd88c61a923aa021b2543fc7fa4b876a935f3d8675123ad3b6eea7c0a85bb55dfc8182fdbbd28125d22481e3323a769ddb8e398feca7f4f6d8062eeec6edf047756eff6573ba7f4a6e0783e952640bcc129419665e345dc27b9d4b93de978855055220da55bf6a8431e10f852e2ee41daac2709965a83c589c33feaf2bbefe071ac7ea368370b89c1881898593a89c09c7454d9b415f608dbeeb20bb4553b88bac0a7a2e41c93f61150813c4bbdcb397e2def90020a08daa1c5e696146831afeaa7287fa15d75a90049d2090d478da473013e907d11d0b77e436625efc4284c69fec0e7a41f858a0fd850014ee1aa06ca4b5ad2405ae680cb7e6fe1f3327a5c82cc8b9c498f9ad052655224bccfae591ec615f48029102d1ab8a18278d761d6f27a00af85f932ba758a9919b664c757b0dad18f15cc66bc3977271b92a04252819c691578fe0186bb1f5d090baf4f21e013918f4835399616293feeb41760faf7c4b2e5d5a89cddd48d49ad941796ea0e0d796992fc88781f31fdcd89aafe402888cabcb30b9987d44829d41f29751af233b6ae54a0d5719d1c31041fbfaea9387546d4908a5899efb1dd0ad4a8a81a41fec7293a3a0814a9abda11bf00bea1a237f3ec4181c21591a49e9b0d80b4e9e08c5486d1be9d3f23aa0a72488b13ea3a612f7d073f5132aec63a66a0aafeac29b6591df76bb3a68eb0cc3492d683b3e709d8c18d042043131bd58a8fef78db26a37d158b371bbacaa9d78bb2e035a6a70d74df2b0de1864a391e3a383cf7d23ae88ece3e9bb0c607a80f5f98f1f053fb920ab74a719340d10521ae0e1a1085ccb53ad379e3c77aeeed7539c6d4f4d6a3a58767ca82fb9b9b709c8ad8baa98fec7e67c906734b13d6f15483e1c482e8cc610ddf16d3b7970e65d4ddbd6fdc6aaa7ff5715529c77a85c2d85beb77aad34c67959d9b4f77fe6d23bed55cb34920f91b9f4ce3c98a1b6c069941240bb7ba2b1351eeb3ab7f1ed3f7e5100363d9d0b3d6e8be3b94761674227f02394794158db8ff2583299662128c9c63fe31efb66204909a327a5796df2d7abd412572b33ef7f690cffb949e1bc3b3f3b00ed7fe0eaebb8a977ededf5c9b16bb9f8f325d458b8f4c6fff3fe5bfbc60c3d50c3b61813553c23941acc0d477ed45e2d51d1043f8ba77800ff1ff6879d7f8a88d41fc7008640e2fb15b5f22b88b9c791e26f1d09fb6ad544edf29c707f981ca9bf61be342ede5e41b41cdff3360f38a1934373c904e0a9ffd95f01d9c83e9c278fbd897431d2d378111c182856d0b4f21c64e9b4550e36516ad792070c08cbbc48f0566e83d878fd759b5c0309d653d6894729449f301a1f89930383e3188b48786cd4b609bc94164684f95086531a01d3c6a7f72c1bd27e66a4604ae178103bf860b1473e5e2315fc30fe22e8fcc6a9759ed6ff2e8a83b3a75a8c0c9eb482db5e3f43e06fc57239538a6c187cfa1e93bae8e3a0cf9e039655f446ad628bded924c06cfdb16b20ec7cda6478ce4cb38e7f90518cf2f68f03c9b59cc6ae7f884d3cde11e3f32b47c570101766cdd755041ed40f3b2a44c79334df150f353c842d2ef8025b3e51a424b411c5fa405313325c6e595c792c847f76cf99a98d3cc590af660bb94aac34b595b7e0d8588d135b79969238fba37aced9a036bbdbd65a30835411e1069201356798f5df80e8f8f0117f38df58d8bdbfe8685c0e0248c0bc9a84377168c85010e280bfead31fb48a2b44338efae345f45e36f3710b0c794ac88192c7c1fdeaa232fab590d2e33516f2e2cf1720715c352807a2649898bfe044a70731c5d9bb0f0a16f73aa66f2b106492b3aa41ac63cc7c4ea737961d3dd934c6e8a401bfff4a27d11e8cb5e53fa008ebf1dcb1b6806856bbc3dfec53c60dd15864f020a88a29bab4ec70b9a6116b01bbc8592bfacb9e6e5e43179bc7be919eab4b9276e35e85fe90802a08a8e4b882968cd02a0ba6876088c16c3e9ffe12521cc10a1265bba9a2f7322e04587ee8f76db172aea587116bef2b436a6e02e0d7524cd30bbe2b457f92b0cbdff64d81adb95e19d5b3a645eea078696abd2025b2283ebb442ba4d8c6007cdcaf4fc9e7e3cd460316c6c939cd17976ec3a18a0c7d2547966e3453631ff5e9aacdba5c67490253c14525594d7b0c7ca8f62fe830a65235fc2a832159c734a396a206c49a8a8872633744795e83625a2d28245369fb6772550e97aec0639b8440c87a46c1f8dac73729a22c85bbe65d28115102b675898e4324e660388bbbcab3dba53764871178822512685147f13a466ae1e1cedb318257b061c918c910747f04c2a7a6fa95a8edf2253c4a086526208aff5ad54956cf6c4310e4da6694b75ccf17ae162c73090548f42417db6892d269b810078e0ed0bea93e1195971c1272ad6416b811c67e94e2733bd2cc36e4597b60a7bc504719ccbcc2c588b10a893e76249c22d593ed33bc498252ea1c3b274f929e62e289fb57bd1c48c5bbd49584c0b983f71f08b886da860c089962c3e7093eae3ffe88980b03888ae4d72ffb3106900e08732b4a08e9bf032c84c48d198a638c9a8ca26202023e8754d689f55b6064a216692763eb44044ed6e0b6e4e6a391852210fc1ca61436f70c8b422a6a54a8d6feee581edde7ca25eb90b01382ccda60de743e158fc7dbc0e22c4d15477848465b735f29973d2e0217541665caea8f7d629fa7921fbea0135a4fb6f098f1ac09051563550a2aa71aff2c3c27d1bb5835bd686a577a70536a944919f860efc800f63a66336998c5d566ba36ad8a0352b32eea3da24d6e882ce318a0f2e4848cfc69a7d68aecde5737472d11e04c69daa8f6aebee7b16d060fe2bedce4ee4386568d025dfaf17364e9468e25a86a2378a770170778f6a856ba280870696c41a6287cd64aa89729d3b600c92012e493e28a770e19e49864e5b6d4dec31758659f0535d0562b78548065f9f9a9d780ed4c410cd887552c88f1607820a0722bcad1eab3d0fdfb167a511a7c784b01521a145a69c1e781a0749bed3acb68d09e5e85299dab4d107fabb1494a8f9962c339e49d1e8c4a45a78358d119d46197db9624093ae69e6c220122ae0997d88a15b262425b17eb9e2e3414c33adc4fed9b100356cd87b5862917d2f30d1abe4cea433658c405bf29d659e881e331f8d3137d9a90e084503f861927118d48d34a0926952e4abaa0a691c0c32c906ca3ff2480eb5878ffa9c6868a5c8b88c355afe78e9c93bb7603ca30731f6187a1637c93988eb621bac108c59b643115e82650c58e807e422cad6dc4004a92c45750b7786c02383086b3d22873be98548475ca7fc53b4758f410227db203a789483100dfa1cc284dd9725448d6ffae7828d2690cba24f01279c0528601ce9cd54d98e1e69c558210aca8f26fba3d348ccf62a4b4032ce682f47cf4703d24d69bf01ce15db6562264ff56a2f833517c0725b9ae67ffea9698f655c5947ea7f289ce6131778227a6ffb60c2724eeb043025f13b9a70154ccc45e38d196e8c2df581a25cd5266d4584822c3a962cc563ee08395ab86743f0a7030a31a31a42c0cc97aa421c3dac7ce33c13ae74e29ffbdcc8874b82e359576005e0b8c0417f4e3664662abc801b51d0601ad22e4407e05816d9a04f2555980ef2d5fcade4f08bcbd1147e9b3a7450ae5fdc430660c88bc8d9c533e63e485ad4c325c55fd538caac059f079f192c173a2dbbd939256e12ce09990d8a6824da0c4e13fceb5d539a51042cd15c0029f090ad957b2eca0bb4bfe4cb20e1987b5e65ec4dadd52608dc98ef2d0273bc07dcd3875c6f6c217dd8b6c3a68f4d62924a91c2116f8a8001810d64c67d3457e033d018eac1967d391833d67e5e1797582f01b7cd6e7ef694036971b92f4dcda8e21a0b5b38e0d6143522ac8873d7d024cfe98acb5eee4930fdd563f6ec95af4a4def440f22d3aa869620eba1e5d1c8ea3a4d29200055427dc33af8cf46274f888ae625136aa1b3e5c8aa083ccb1333b9ed35566736377a6659caeb57f1a0076a39e7d9b043d02499836e2508b50f9977508a15204ac18d70cf6b96d8bae7a2f7ff143652843120a83120305d4381b86e012ffcc8e60e829cf88d40625ccc51b5f5ce02e0aea3867c4485861855e5261a7420c6aadd7854faae91c7d3ce5b68cdd13f70fb79e0da63bdeaa64bd01e0e3243a1e68d84fb7cbd4144be5913e49177f11a334a785a16734897609d44ecae847854cfd06617d10f17053540aa7e80d816accb5026c8ff9bbddcd81e22d7d8ff5f3211439ce175123e7791653e6ef3f7a1ff4ef413c405cbc3d6c4d9d0224358243ea850f782f0c372b073e476be90fc2dbe505f567be63337fdb77059ee8bd032781af355ab65bd0f66a36bf5eaf65942424ae07cfddd0f009e856c4fc457c1aae74bdeb2d342338bd1b1ecbbc7b3e20b291fa1880e14db5f41961f50871322ff7f2ee8eee7b39f913378cbcfdc29dd51c5f46967b34455d3d31d2d79e646ecded59079a0a63a8aa6f72f5c7f3dab2b80669635b0e314504e5070d620ef889ad5eb1e3a5bcd51158db0d95d01cfc2b76e8a1b3545158c83a89cea4c373e3adf1d9cbb6de0e72077c08a9d6903fa397b5be308a5d3d4098b9d5b871b0ad6cc0b43565c190666efe4536395f4d755b3284dbd985363a78cb3833c9022a6d34d504cd0d44e183e1e4c42efe39d515cbb2bf9a6211ae324a3f9549bab10a01a0200b21540feadb22ebd34285c45777d81f57e765e8769496e3016b0957fd05d4f44bbee33b466a437b0e8136154bbde0fee0be375b6a180d3eb77744ac0fc9d514229edccc81394ca8323103293810687ae38a693963ead147274007275c6d08c336d2836b5990a46c6676c1a1b8cea4da09f7f2856a1385cabde6345e588024958ff997d161bd7b64aea4f887344a58a2e7770a6556cbe8fe9c3effa6b5b390b688cf0ddd969d9e551f30d350728b5a4b6f1ba3d6cd5ff1a708ba2cb8e85630b7be0f042742a31e4bbc117010777ca19deb146630817a7e6ec0c4172c98b11eb3919bfbb4b50a4cee01a26e3e04990dbf17c8010285f7e26c06fda8a9221f9bdbce7f343c32706e1df84e58649f9c3114818f41bc3874cbb9c6bd9a3b2a282a8fe56b9f3a536a94b3260b6cd10d67743856724beb7fbb6bdc0ee563b9feab52df2aff86a4be1cb281f607106d807a33cd5f920b409d102e58377314ceaa6cb54cc5da9017a7eaa76e62601ad8f275bc391a411e60ba06e1dc19b5f09de974fbf10ea5b42ae00b0434d37ff03f189685bb3a4f9bc24f706ac348da61806747c7d1ad9da77c58763859cca894b940a85d47f4817f940a324a0c21fd6880bc5ec75032c85a0ed06463e408d22901a7ecd4995bcb46bc2e94e87b098ebfe9eeb9e89123d64eece05ede4f51e9f9bfcc4d63ed9e4f1544922161460491c01a0bee3d627d04be76de34610eb9182bd628e2f0f9b742f675103ba4be214ff4a9810df1816d181423742999491f065afe206dcbed2a9710dc8c16240c674261d7187927610ae7a4ded9ca05e1cf973fac9d4b5af73002aa901932b9867b8c806cacd59f671decce839f8c717476644d754e0208d1cbce0d18bfa86c5b23158160ca784649e6c5783960b0ab8eb8e4110f505976da1960cc077841eb6cbde6c3944af516cf723a3c2c7176497d8f5d55b3290a78f9dcc4f45e253eafd511ea5e78ca347d9bde943bffef40d6a86d56674c8fb054b2d0ccf43169b86906c971a8f88751df8fd4de874bd295122db9561a24563bae7cd8edb9ff352b7122e528106aecd31d07a47083b4794b16814dba5bd217b0acc67153fd00a1c696c590490889a014d03e5b4788d7c5eb1dd9ffa6bd2282e2344b02a4a4e60321e77ccec1acba320bb6d1811bd8d6945a14baf2cfad70a5cee62816c5d3a2450b94103446373b07d746c78be8011a85884832dda356635cc63a888c80c0b693a99d6d35468d13d6fa6d0fe10540231978732c11ac87dac58b19a3cc7ba3527f8a61e6eb3479743d2a83152dcfb31fe916154a5f9182bf2bb21df69d85766fcbd0c8623b26824a5aba002406dd8e327019f64e65799dfdeb3ad219bcf036ff62de1b38d8a2ddbe7a4b40b0ff50b25714d67f6e671ec6c84b27ba8b42a2e725fb43fd9803b6591c67f7fdbf2eaf02ef33c61cb6b11ed877ad6c373fbf10ca3f7b92d4619acbea2e06ed304bf02a0f08344969ff77ef311f970fc9fb2f77a5c5c5b2d3dd89d2848c243e3c34b25f23ed68cc69df4dd41d47687a1b225c7f5fb7ffd7e3a86e678e8973e66e2f0d7583d5fd72eb896ecf479bf3462dd59689fb5a7f414c282f6829d6eb599e1b570391394d638d4bc603ad3201ca25272d640c62e04d701288295607ffd0d8c7bb8658cd53f5979138570473d6153858fa90ddf9315cd4844fed0cdd05895bf34f475b2691a6eeab89e019f101e6fa6fba9b7342d347f53e4f8d11759af5ec6d5b60a412f1d49f16ad8f69fef43ca6d0d9c84f6102a7565a6d5e90c97ced33f20360f71f7d525203ce4b79a3e6db4a3c727ea582c7d5b91cc9c236651a32017cbcda15f623c0d87f7614e6b888a7c0c78384af9432becc473b58d8ca8f8de87b8c69a57493c0a4c08319efb4313e7b91bd9d4bd12a38190d33f9534973a08d0301dd06b53e8fe54ddf03d596c3746d923a333ba58bf586f0ed731be218ea1b1689b77fa2f6973c5b101f152fa8f302cf037451d0808486393c3d06b98d62193a1766f4d9ec4e3843ae6f94c40da2c9ec0d6908a6db9bd9ebd3bcf9dff560e7795fe0ab2ab24ce3579847be7a8a8fdbf0cd96d41ddbb6fde64a76de6c9d3a73d59c3e6cf3330d561b2db720042592b29c17b781dc3f5b12918293b2133bd7d1f09d93e63f3f29f57d5661ae245c19047d834586b186eed742f3e1ea037b56d0145cbddbd630c6679c2cc40ae8b8e140c6706b9a77788fdb56f44b6b002211c7356119afd52cda04c6b92c5a613dccd349724dd0551cf45e29ca5e0262098fc4553ee142a26e93a0ed1ef94dd73eae94ad1006c84cdc98f23b01fa03c3e32d5413bc9c7628b0d8cd6f0803410dd0af627c2885b74624338c4e8f1fff9dd753c9c91a1825ac531057a78d0a5756bb2249333fda5f4f64ee0feaadfe1046f35b547f83f7946de6ad4c8d80f4bec8cc6776f8936b53cc11d45c5b954060987e43700afcb16f7337e6b29831302e2df4563c9e688b9c25cbdda5861a0fa021d6ebd7a81c92f4969d3428dba10e732b97e3f11e549bfdc25e4c4af925a0b4386c1512f53b7f4fae1787a7a0d5482952049c61a344a78dce1540158d1e2537cbc21abfa8b68efd47539e67c55c0890e784fed602c8155750134a3037e82d713a9f90407b4bac3828a0ce874ef50b5a1674ca36fc54bf630cb97b823fb8d24faf919e6db2061fe12df5db18d581bf4cd9b51e2175d523838408370f3eb93f5421346ec87ed9d529765502d02028625c6abf44cf9b795dfbf78cb74733ccb465c0274e90ccd6a1d7dd05a22954a3489ea721254bff7c4797a251a06b0217a3e29fe1d2944f447e0368511d125f4865f96c11699abcb7dc6ec5b9437c370ff0597981036292094df30bb54eb97d3faf3e5fe449d0259656342173c97e489448cf84136a86cd914cae24202a1c8f2da0b8161d46cbb9ef179bb38ff5ffc82e9951dac6482b09ee5d072c51bd56a38d0d51c827829b899053ecb7af42de639fd53dcb8a6020cfb6ae169fccccc5186782d1927a8b1d9c305481e52ea3a0df90166970e49f134b7ee85df19c164ad8a562e6cc0f6d304b6f02e6575f187094bf364511178491e08a419803cc199968203db4519f8aa5e0e8ef591a30599a2d4b233201724b9a5efd5840af0f40a446431b0013b9e8f62698d4851c81684747785aa944a8b02672715000f56c915782feec3cd7ea9f0e761cd75ffb8d85d8506ee77583d4614f86a3965837787e1fcc02ea96b4db7716cfe40b72d3cf2cd75eb3438cc4b8f9158c1dd141bf532104d0a7daf768a17225cdff66495f0b3a6494af3d20de7a2a8db44dc81bccff8189454900666461d648518470b69e1ebf747eddf8de0c95f3a352bcef325e04123be242dd237e08525c271885907ddeb61b00653ab536e10fdbcbd49aa94864ca835d7ea8e184b50ba9d7189ade50645b6373dc3d7eef8a375012b64e6e331be37d6e82f264b9e7ebe509d398ac71686d276c29a3464f95f048a0e8f04de6ccea8ff08824fa9c73b00d2a5048aba441689678f7e4e9c4c91762a82076f7500e13b7d9759f2107b893c23c021de13e17220906282dfa9fee71f64cb5238486a56d94efa51fbfff11452a21646c31dfcee3336d0a84649b0ae6d78d076d0c3c8b7db836288a9e02d1181cb576e6f632c4d161d8c06afd6562371c16d67225197a5fa5a71a05d3a91835c080eb5ad80d7c237a1eb1e48f6ffd414ebeb2e8b930f11fc1f321d9813252d565a950fbe0365a6c714ea2d2b901d5c36840bc1c890031cd39c47979df2e38d20ac592350b00c5f98330556ce0148462f975e669dd490d01383a0c7001ba7882658f21363c1961d655e60f42a3646cbbf32f5223e0bffa8664b335c0f241222c564711163d016432a925547401245b77257f625928c009a84b1597926fe49465544f53fe6cc851e3e3b96eb673855657b0b2f3194456044e97d323ee4fa3ff8f875e1b2ec3650164b5c399ccb9be118f2a08157e1d41207f929e7f284ff9f83bb8e1ed3b903119da92ad97d880c4fc394c7a96bffb32b2d3e39c63790851eb0efba3300deafbb4c185c04431321af6a813267a6756d4fe23145daeb4922748934d21a569a323613f1503980094b0f88f4eec736d7bab9a941dd4fb7c8add8db8a905e2ca6894f15ffa650a80c12210a3976a552b4d581f437eaea5dbce05e7eb81fcd5f1388c475b22fcfd45a78f7f240eeb3bab2b9739b88cb5dcd313d047941041cd02d2f8881351a6d5d028149cb9165f00f9ba95dec1b90d4f8f4214c833a189591c849e870cd774da39d80c359c475c31948dd24021f3d887dc5ccf3dfa32dc220bc56257723f0e50b8612d534c2d9d566a9e34d3904f80f4dea5a78fa19ed049ddd68e6dc1d804d7573c9e06aff4567e8153cff1fb06d4c794343f17e7d26fba6532abd7991d1766701c78bd8b4563690b4e43120487036b4c0ccfe37c00a82262ef2f15b6bd77fa88a7113394137602e96d80a7cbadf61eaf5923346240ec56331ad9a68f0388aaaa0e2b0c0b06092e47d02d50f6a4b994aeed9123ab811fe275d9c8110ca63527b5b7b4168dd212d693ebafaedd9f60b872fd84782ef21ab6eb9c19da54dd5f66df8f932b97c3d336f365f36d91f6a7312c768809761889432b2c2db6326f5655336b8b60c3c7b01dfd67ce12c67af36540202d180da833d20eb64ea977607f830e551f1c90260810b631ea692319eeef427aa0b60c12109f2167913e3e3750b936c4b93cbb218f9f452cffcfa6643043bd96bf582fc93f6675cf82ced4458833a92e0ef6b2d49a98898c1ecca1c19ede40008680ff11bdc513fb144aca19ec61a86eabf343e2f94e843cf9f3ea2bc0ea73d126c6db0e20ac41b94bb7258f832d25067110c4dc452f0d25dd8015d3c94121eb4ffe38f510d44c1642cf16ff94771e430656f87f92412203d2bd12bd0bcd6866294822c3beacc60a845be1729fb68e6ae7b4c5b46c0c5e3bc422c22ca574414ee18090743ac04725041afa4cdb86b851081f80732d4f53f1addc8d2be7af5477a355df7bfd05bf2032bcb200aef2f65dda5fe0da1f7db2348485f4232ef6f1b630e7a724a0fe635de18783858b5539fd07cf51fc5221237b04a721c391f5d6e36ecc3d344abebe428ebf0082dbf12987b2ff04688a86610c5a96012e2d4006f3ba981d72bf12ab6895a57061e5655a228a69e45e039a3cf9e7d270fc235f4ea883d1a535ec7d9a76cde0d5dee933c64dbff0e82b1a9c3cb3fa55689daa0f4aa50578351f3db60b08eb8a206163ffd238637a48cbc35cbcbf5deca62656f85194010be02b7e4502bcc38d819b307d3cfaef8ca4165c299c3b888a72d35d2cc801d4019d90ea5321d40ac3a02ba978de3613ca2572d2f9fe33cfa4dcbfcfd22e0d76a650d42c2eaf91afd67ddb4f7e40ecea1fdf54ef51011257eb8e64f0391185bd98554968bdc2bdf2f009c38f1c41bc342f9d2cc3466fbca0a68faaf47f644c30fd9c44115108ea3c3c33a37c7247fcc229e32705f3ac7c94c50884ebcc8efb6723c14cf4cbe0f150f55db46682c779d486c2c0439234a4cd89f1e51d819ae1e46d857daadba88f390a9210204675f949186c2f6623c1e02d845e4ae43ab3388f0cf2dba84f0e92b0c97627eec00ace0eb865c7adb53d730ad066c9bd495660265ebc237d80249786c07f640cb7e831a32f62e1f6b70c9adde3d9de53bca81737b19f8fb8e123fbd0c74aa3bb9687554d553a661155fa72ec8e3fce8a54f9fb0a430b12e5e9a0c364025b202f27a3332522f6aad16a164a07b6e207f24730ef6c574fd0e7b606f8749366208d810c3a8bd15a71e678f9d26a663a2fde6945dd065967d07542d83d0add882b365f146cd753cbd184675ccb1c2b0c1b9c3d1f11eed33d887b35f051faafb8c9e5cfcdd09894436195f0f07540f96e9f8148c18fc0a1f582bb58aacaaaa814156ec6b7ea7fc18e2f7e01904b2b8e20330eccaf797be19e7fcc4b8fdbf7ccd8868d3827a6420fce223e50dd7516bae81781d88a14eb8ad775827366139ea68afe2fa8dbcfda3ef06c9913ae092a4227e11c7be92bbc4862d3914a72f3cc2346c381e375aa0377e55146ac5f1c032d86b5cd5744f601c0b1a395dcfbe84e6cf95f345e2ec399ab63dc7f9feb516e81e2e2c5c54e29984fe44480e32ca2defe8b7502d73020ccec52c1c7b6419af3ab78a3ba3aba5dc6388500f30dcdf8ad1981143cbba40bfdcac5875de18fafc16ce39a7c9a786b5aba7e87da3c4f9c7001c60cc2a329a8a281cb082c3c4690dbea73d588a1a4b35c1a98c5c64a9b647893bf602e7ff2e12e445d2c30a53c61331e25ea2eb24db623c171dfae6ac790e9d027a244c2feb88e69729bc401c4fe85d15e674fc4db948c25a84fc10d78c3abf00e92e3f3d4e628149295116c7b55222fa5a02317d045f97b0e8d916f7183a2641b5b99641c920915a249a0f97a120374c80cbf5cb4a5e1d281ae0d34271fe0cd3d1606f9015b463c6166699e31cab47f693d50376dc6269df87c788a74f1087a7b1a1b12ed97ac476c48544758fab1c314a6b6ac70e899b0ea2173139061a60fa7f0a3d1a0cc01c6b6f8ad6aea95c458ef75fabf29d1e2882ec28a83c47e85688f02942af46b260042115dc734a0b267182a0248ffcd85aa8eac95731f0868f4cd62584afc762f009d56fb1dda6c38e8bdccd8d48aa9b0e8649ba3dcdb82f0a3130c23fddd6b84668a0c25ad415c3057bd9129fcdae4481343350e6b97a7a913cfc0ff238a8aed3d0f0702c50703862fd687e4c2cafb24cef5904caa72c39a083edb97d17e81ed718e3a9ca8a7d5481673081db70aeebe17163917d631743604ab7603dbbd9f28354d3eb09ad887b41b054e7a2badf524ab13978e3979253135b8d83499f97d26e32f97c48768396fc10ffd5b41deb7b2b6e3bd83daab60aefad3f04d6a31ab8ceeb982ae6d754b2c70a1e20aca403eedd06f0112e10c89aa403ffffffffffff0f72a1df9a5bbfa36c43324949d28a155957d4fd4e534a29a59464d2fe41ccdb7ffc36951357119311ebb042f48efc28566bc96141a89dbbf74fa3b590c66151bd8fab7b565d1a6ab3449c0187656f51659a5548b13acf1ec40d12269cf18605a5943c2974e816525b1243419ce18645cdfe6466526850fb31c2196d609368d2cd36bb99e343b45727e1e9a5da95350fce60c392875a19deb3fa7ceae341dc2039c2196b58b6919de9ef56ea0e9b1a16a590dae64a4537669d4aa552691d8638230dc97a6d7a57fb944d22ee3b2ab4e792fd16f1f9e00c342cc676679649b39468fa8c332c665cfd90419fc60d42332c09a1f3cb4de6ac31fd679461d9fdbdb4d61fc36558c9b0186e3cc618d24bc9f68c61515d297b9142c64ef2248625bb97255b88b89abc61d0ee344c4f4fa38a698ba6eb7d6cfdee2055e36058509d1d4a266d37d5793de30b8be1c9947febb7f713173141397f8326314551e3c2195e5850f1169f457bb7feff8c2e2cafbcce19b3ff8a70b507677061392b29cc5cfa9f8aabb6b0fc295a4853edd102b2f16d7334d76d70b773d50e79f6f57965fa6670461616b75f44f8ea8fc73f61a14f4dd1d8cedd0fcd90ed4f33e2aeeea5fe8c2b2c7b16cd68baeeddbc9482226448ae17ceb0c2a29a3fa566b5eb9c5959a9a4a008194282f2463aa30a0ba23cac6e9d35ad92af33a86012d38ca7a16f5ee2eebeb76a75cdba14abd933a6b02619d70ce75deff26a5a08d55a5d874e9f43d26c9c218505a5740c552dff594d7d86035f3c230acb2374fcac5c4edbe6fd378afc9021a724290cce80c2f2471da34cc76aa54a6c5c70c613969526af17775a1a673861514c83ec983ceafaacd3863163db8c656daf7957a8199551c68cc592939f756efb18ba36424212c40d928a8e652cb8cc78277b4a66f2a4214b95b13032bf2bc4f5c45b55c2f686c9104cc6b266c896265fbe0cbb29617bc3640d6470e762b6a6adc935c55bf3a8d39b495b7cd6bff186c91a464c82542a6f980c513cc6d288bdfbb756bb2ba4256cc6ac2d25c249e989b041dc2001a2c3184bdb6922c4a84ba95fa618cbdbf2753ae5b29409173716741063695499dc8e2523bfe74739256c30890d635997d039ec33cabd1106a9542ac25836294b4d8bafcf4a864ad89607aa23188b2f6ae5687d1e6dc7048c65bd15772737298f17572a71d0f18b65dd0dd94c713a676bd9178b9b71c57c99e838cad38b6515bae3460fad5208bd8451da043a78b1ac74d239264e7b3b07d777b1f0eb41755c998cd964aa8be5efa0396c768c7750371874e462e95499e9e668a17e75e5206e9094e8c0c5c2e655de3a49bd2bc7d324b75814fd4966e84f1d5aeab658d02cffdd5422fda3a884adcff66a8197c6d04df3a2bbb19b2d1e6f667f528ad3512a15944ac5f524c5880e5a2cfb27fd9d3368974cd32c96fd73a68f2275ae0621592c7a4b57319b6349573516ec6dda0f978d69dc53346e97baf70da5f5730e8b459ec85fa993f248ca49a9542ac6482ca1082192bbaf58d425c75dad876873a95283c4150b422b213ebb6dcecf722b16c4f76767d23928bdae352b96fba490fbe27fdbecb48ac5a8a3a9678c3287898f2a96e5e384e9d5d8606f2a167784d2b331cbd68da10b80a001a405424e091a6baea003150bf6fdb1f7d4c7c8f42a614b1152440809c9aed10217d4600d186cca11d64eb19cc25595693c29decf0e850e532ccf47cf31eb09f9dd5f76424729964efc9f997e77cccedf408d4aa552c913212466b8091da4586e75e7ca9372a1cb8546b1b0ad994abffe24853cf518748862517b3ee8f8eaf8712d9433040d27838e502cabf72ce2b945c7c6204a1c878d4aa55269126b1d5bd0018a45a54bdbca7b7cf89895b09d9890a4fc1b69fd093edb4334683271f9f4948faa4568b375a0c3138bae835c5da545cb6afc2709a40d18542a954ea4e91a7673edcbb9cce6ec93b237debc4cef4ad8887c8a1092378420b49423468aa87166c66841dc2039d1c189a5ed8c31c99dd0d63259453c08425626e8d8c492686ed3613fa7755d7235da35b1a8dbcc3547351f674f995894ed54233d7bd0e246985810916175d38aaa1661099b1a97588c1d85d6bad654daa312363f394923bd111c305a6241975aa93129a9a42aa54a2cc86eeff05a7b72c2d3031d945872d1a91ecbb3a951fa492cdddba8e96a7ec6fd24616e13d3fbd418a31d4d7bf489ad0f4ad31189e512134a66ecf5fd55572a1dc40d12387440624964ea8e771dba745e8f580e61a21e4aa8f676c8118b49f7ee06ed496afdb2114bf2fd4c99b47d77b5296153434c56a69c3480a001440d206dac01c4c8a141a562e4103153d0c188e551d9aaf5f1d5db774c528aa8a1feac0b3a16b1a459ea18f46678fd61f446e268c349ec0b3a14b198eba1e5bd7ccdcff4891889a38d34e848c4e29d0e2d456388b9da8c640f3a10b1dc75a2eaa3e6294c46f3061d8758124a456fcc0ecd26fe1236236843d049393161a3523150d06188a591f9f842fd474d1e4bd341211653b62b11195e843acd1a1f74106259954eaec56698c7ec6a338865b93775db202f9ed615c4a28ccabc266390eae7c771925264c141a5920647a5624662697060ea4147209644b45c9d85f4395fcf24a5881a958a494a1121293a00b1284d09a56b378aa7fdfc6149a9ccadb7a5dafa9a04d2060c801831397941a562c4e444191d7e58148d6ad7d39c6bf58d9118cac241471f963e7d4ee74a4f26775907954aa512474aa5a2c6482ca508c7917226ab8810c387e577a55a0857baeef6770fe206890e2ae8d8c3b26dc7d9cefa45f20ea40d1800592a95f46afd0d2a1512a3051d7a58d42165aac9d49ee37a7958ec551dd3de9dd68c080f4ba376fdb5be380f75562a241dc40d9222e8b8c3a2b7765a132decd5b5346e00044f528ca01d164b69b7f7cf314ea8084f929214481b30005284e5890869038d4ae587a4a02328954a077183e4888e3a98339b34663b75b3bc88766690ed42f70619df55c2766202c719b248de103264c8efc5a1830ecb619548716e9b7747ec5c43c71c96b437df3e86174daa55c2f6cadc000d379394943f49411172e26de890c3923a1b75aaa2cb4d3d435e489087c3e4848d4a65d95086bc32214272b60c1d7158d8edd979d396fa2b75946a411121266c2c950a8a3a3946504f071d70585a9dbbd5a7bcd8b0a220e878c3a2c7aceaede2567fe712b6214788e0902334a8541a143adcb0a03388cad65a666e4a4bd88870463031a26d580efa4a5b678d5e916d099b10929322a05ca5c25cd0c18685572e36556790d5cf98a4b4694a9127f2431635e485a4913ed0b1862517ab750eb62f9bdb29613376dab9477099073ad4b0a4a156cb26a5e263cd256c4448c7486aa552a9e41ea9542a1555a45249d6f1041d69589cff72e1f1374546a8840d077b72869c84b244f821473d1c2747d25a10374854a0030d8b2d73bb34b7722964ec0ccbbe398fdcfcbca7319b1bc40d1220e8308326349adc8ce765f296c1d52193de5ef71e51ea20c362cc594fb3faf27c1ba5630ccba67585b896f921d6ccd52186853955baa575c6bd6e991e1d6148f7552bb96aa3be2f61cb65234484a01a3ac0b0185ea710bddd6ea3e316e2821fe8f8c2b296fef0d4dee985c91f0942507250a9542a448e14d9206e90201d5e5856bbb23c3588d7d26e4d1d5d58ec936fe2476cbc55a2ebe0c2a28ebf9fe99e6469d92a6133c217c193c7c1490a0eccf82405055dc71616feb647e40ad1f84d112129f243d8e2306292d4941e01d20610a44780f8bd0e2d2ce96f9f10e9da2e324ad1d09185c5d659dae75d5542f4752cd0818505e54a8f4eada37123661d5758d628f3e596ac574a733e5da0c30a8b3b9f3e958f8a68524e481c063aaab0fc5ac91439224a84068bfc1092d317e8a0c2a2ed678fa9a76696b153a2630a8bf342bcfa0a29a4765309dbda490a8a0e292c7d56f3f0f639aba654c2242842ce5c471416c6549692c26c7d672c615b45f244bec4c8397943ed8c65a0030a8b27c3d7f5cafee4327c9282a2e309cbe2853c33fdf61013139441dc2089810e272c8de6091dd97313f16a131685094d233e3b958c5b3261f953dbe7888c1e2b84a8cb121675d8cff4d3b9f5be7dba2861b9f3cfbb6fa55ca9264a1296bcbd377fbe9794d953c28624246f1c41aff690b0a0bae1951442dc6b1d2b6153c70856e25850fadc5fc4d75b7eb084ed8eb09ca2295eabfa31b45ec28662a2d2f42405658db0209a54c6276d2163747200c7b29a5f0f35face35ec66074716eb65061fedfe59a32e65e193687ecae6cc514394e57a2d6ca56c7d2646d314220b4f963ea7de189ad9634d53c236e49cbca18e91b3934f49c3bb08eb97a16944dcc364256c7d76842485c8ea348c78ae75ee30d3607e6f099b494a112124b947481ee5e41849a381c9b64994fd8834292b61d3932f52448d368a2cce8667fda8755017a221b2aca3af544acb77253255c2a63830eb14220b5130643929d7a8376ed49d3d2c6123493b2949449ec8aa136131a70dfde44a6d3c75b6401d362a159493ef0de206c91b4358f8b0ffdcb151cc2625dafe89493a214bdf695ca9c626dbd1710de206090f4a329d5f1a32761e99599741dc2011c21b0b4ac9a84a5d0f27dff113ac5747711bfad6677e0ef2cb1d4f4386f098cbb62c61436913a8e52dd14aebea7b8a7c4ad8e23843d0342d7dc8faf1e8f9735e47801653555b3ddcb57ac89c95aa511d4bdef5db48088bf16568eda7f76a9f1b21220411c97276dd3bcab3edc6bf83b024a5ea6ad9a07498c9a41a4058f43e559d844ed24bcb26ab881a494891c346a5c241dc20593f58fad672ea3fa6892fa5378a0821821b1f2c8d1077ea599686ca34da9e1bcbf1266eee9a293636b83d5856a9e4ea10ae358c58311bc40d921ef060318f6b46cd3227f4e64bd89410923712470a0369030640e228d2824a258e22a78dc5206e902c3b581aa956d785b8c6483ba083059967344acff6f8383a343fc8c19266199494f267a2af3b8252e38d22e706954aa58262c484c438881b24aa8d74cc6c68ad5fed6c4ad84e3e450d342a26a95241a9a471032067ec24c5880e38e0002f8d591bdb50d798367accbe7da354aba784ed531113ff54a40645d2d846316282831b2c78ce42aad8df283a8d256c1bb0819963d432ce44f7cc696ab091d09ca632ea1873ca53c28636831aa86370fd35a9694ec8673208727c2d1ed4d53be8eb6e031a3c9ee5f5ecc7d8495fc2a64c70b0061edefaad9567f19052cae40633306ee92993598e50cddd40068b31c49fd0e3ab45bc56c2d6c66ae84a69d111e1dabc75d7460c96df5b6936fda17bda2d41615b18589fe9e95343b41afb89989c18b1c3411a0b5e2b6367ad215e3f54c266c4e444995916bd95d4ee21834bfda512364941bb172cc9f98809513aa72f623224912421292705e5ccd058545a467dcf49bacc9a54c246c4e46465a5921e0e206dac018488c9491a954a8a0b2a150fe20609125cb0f4d1848a963a359af8256ced821620abe23708fb2c7a2be14aa552413d4931628405bec655d111799bcd5e6685081c43865416468a523722f444c93c02690306404e4cd61b47d0bf60881122424e891c29822944961156b01cd25b4bbb7aa8e6f755b078365a88949df48d27ad1a2958304f2564a77b5d1fd3123612345b4b0f470d50b09cb54cf1f29ce4d367256c679b4264e19e60b1b37cc9535ac5e6884bd8524e9a052658d61cbfef84188da2df218bdb975ce7f4d1e5a8c932a512b636eec520dde55e6697c1a5b084cd0826276fd8f19289d69c2bde5ce8974bd84e52507641b778a5c30be13a895ec2964264790e74e14374ff4d8bd3a9ec1236384ede8e7b908b3a9fc60fe399fe9456095b09ca11232427268b645b00040d206d0069630d20e975c01ac40d923570693f6e0eeaabef5ee94ad84e4e4922319f22e46407954a0a915541393b0062ee0890942344488c0671836407b76c2b6557f545bb74991236206dc000481f0192c60d800069630d2024458490183139517c272946f04f4c520671832405468272e48484440286e40887c8054a60242643484c8644a08201128060041a10019010a07c00041ff8802a7248d20321e880a16c800324abf7424e524200c4031b888006cec4e444021930921060400217b080052250010a50e00313380d48600211300818910718c99f2c9213939443010768a0012be548051840e448910518c99790902414214428a080dc0a24e008264648b10002560f0307e83312445262000c1400850023188091fc1b1e10000909087ea49c9c140f04e0360080226f0841c9800f4389e38940f2811e1ce071ca08119344426244583d23e9517807c907ced8800e232171533df524242947780ee4d029d81d94a1e3899c223c0c0f80c831801ca198852f5ac0231616a800052620810840e0010e68000316a080042080000310c08f000020478e1c3972e4d0b123878e3372e868468e1cccc89123478e1c3972e4c8518c1c3972e418468e1cbfc891c3173972f422470e5ee4c89123472e7220e0023c7220e0024674872306902314b0f0430b7230c0178dc0852d72e4c8a1230bbd7045912f214139a9a0f0b8c5197ec89183872d18d08b36dec08b48f0a8050f5ae42099852c72e4d8851872e400c2805dec60c02db4c0805bf08001b650430e3450941a97bd76884c86b8a016877098c0043c5c418b39f068052b04b10a55e4c0411b3c52c1e314a6c8918301b218461c455a7044120f18f129428a0859a3182264074386b4407d1f72e4e0210af5351832a4053972f008054a8e1c3c4051e4c8ca9183c7279410351af1297f928a98a091925a9023070f4f143109a2069183871fb213399a136b3972f0e0c32672e4388ce39c9cb40025470e1e98b8448e33724eda4051a3155ac8713a20928ea0d4d001ea800f95f8038f465022478e1c391832e4481a3f049d1493206a88490c1e0942709023078f49a49c98184129724eda30392992834fc324c8c32104c5c4a48d1c3978ec213c96b1a0f137c7cc9cf250c6d2d79d8ed5b4cdc27547782463d1f449d1a79fee83582d32963d86ec12fac7bc76ac54d8c0e318cb2b6d4cd374d54565ae889037849c986c21781863b1fd9352e92b166351b9dca831d748a53ba5081ec4587aa5655b87d722642c552a276690e0318c45257464d2ac26dfaba384e0210c4c630c76abc14333ae062d353a9c966e116283c158b80d9fff734c1b3bbc36a8544edc103c80b1a026dac5c4670f32837eb1a4e5398dfacd649e4a34ecc0c3178b617f6f65a8d9462d3642e4a42405c1a3178bff1e94d0a2bdc9737eb2b4a0c283174b4a6b97aa444db3a78c312d018f5d2c9be6579d75cf5d7e74b11cbeafc453eeeabc672e96e5c77022d3d746f91f2e96368350a39496d99d5e5682c72d96a44ea51d646bce4ae94d82872d96e5868cb265544a77743eb92678d462599d52df203a276d29eb4bf0a0c5c209d54a6b7f3d4a87cb089123954a1b21720467b19c7a2ba38cca3ccfc64f5a70b258565393dddcad35942f168be69dfbc6e55e7dd21932e4481a950a0a0f582cb7fc4f2fcd73f48eec9c9c9323f078c562b9d431e9737f6a73ae580e5959a9a2fff3fcd58a458d52833efdb66194162b1647ab2f4def52d2d6b58ae59c59671f2d3dc7552e552c7c6fda983d1f83e93f154b32c3ccd47c9a95dfa362715c4e5d264fafff54a758d61d5d35aef40cba27532c279375f9cae637955029164de5b5e3a7ddfe8c48b1a4bff7615f4ce7d8a1512c88559b5f09255d57eca258ec3a13cd29c308ed6c28963d26b9a6e467907a5d502c6e88c8f7feeb2064fa89e53c99abf2bb43d7293db1bcba6dd36621ef52cc4e2c8dc8ddfa9c6bd2a40c2796c46567d4259e6c85a9073c36b124523beba071569e6e6962492b950de284f4243528130bf274cef04a692d6c574c2c6b89b6caed29219abbc4b29c13a5e24dfcf76496b84b6edeec33c79c8ac6d81b34cac955af71255252ca20bcb4c528031e9458923f2f329d97d25a994ad84e841c232428879d24c564088a09c263120bff2f237e4f89c9ab925854c2fe7b74f2509b94256c79ea8f20248d3533121e91586cd50d277a5ce3fb4e3578406249add2389fe17396d3f278c4620c4aa5d4b5f24eec2b881b244478386239e87bce7c99c9657c2b1512c346f068c4f2299dd35796476f192571338c5834b15af7aeadeaff97c7226e8db39936ea687b768e6dd63f4ae68e41b74b79286299e636e4aade6f193384938837a6db11377b51d22b228408221242e8da7c29cf95b01941e9c3e310c6e6d69a631032b3ac842d8e1423445e83c0c31078fc6d19aa935208366db2d3e4b26d5be3a1ad9526b5df724e6aac846dc839ab011a406800a48d3580bc006f37881b248b072196336731e2a5bf45b6f5071e8358d4626f5b9e6c64ec773ef010c4a287fb24434be53a6db397071e8158dc8c19e7366fccaad3d9071e805814dffe9559c6305597112307ffb01833eebfd0da284cca78f86179473d664c9defc3a2abb99ed5ea2ac5343e2c89ceccd094516aeab41778ec613963c5f37e83cef0530f8b2ab44a71daf407d5681e9646346ece32297fb14934f0c0c3a278d6e82ce25d8492b71878dc6159e7385a66a1633d5cb6c3e2c638ba212b4646cde900081a406600a48d3580b81b78d46159f65febaa47656b4a3a2ce99acdaf9a2bb73c27e571e06ae03187e57dcfaabc83f094b29a030f392cadd45178d0555ea2fc22426610071e7158562a7dfbee368c961c0e0bdff69fb446e3b7d2a6a781c71b96540619bf84e97dd4628f81871b964bc9a46293148d6e771b16e38fa7bbdb8c3234cb0c3cd8b09c947aef7db51d75371b061e6b588e4ae43d758bd5ce130c3cd4b01c470b2d1fb2450ba1b56be0918645572b476ab9abfb3faa848d446d6fc0030dcb51766c0aad593faae47186c53699a3f227ba630c7998616935bf4b597d9deaea1978946149e814ea56081dbae384830c0b4a5efeec08ed3d61a301048d8d83c71816535b434ae5aa4bfdb8071e62588cc2574c5d73f0f4561896ec369a879455b31b836141de8f92e1336355a75f584cb71b32fb67993e8b5e5836d91ea174d06f9e735d58369da36c6c1642cf4bb9b0a0a4169ddd93b0d9a8f1d8c27278cdb1b55d8d788bfc101293222496060f2d2c09ddb9c76590e29185a486187b4f2be5e48185c5db91a3b3bf313347f1b8826e1e1be54b4d7743d745a7d049ce6ed36bb50c3cacb0202f563db77e94d2e51a7c1232040729425cc03ae4c0a30a8b23367afca8a34ea3691e545812532e94962829b3143122784c617936ee43fb88774735917372d8a8542a9584a35259226a09c7f921784861e1831817a5538657322b0a4be6de1e4e9e7d2ea187c272dc6c8f92316e2ed71396764da7ec51dbf192871316766673924f6a4d37aa19cb4a481f21775b87d44acc580e29ccb42aed2d4745cb58927ba35327376d1b3dca5812269e7fd5c57d6c9d8cc5a0a1f38376dfb69e91b1a0a5db4f83d0f4f43196c4764cb47fa7de781a63597e36f129fe7453098bb1a45a2feb1a2f9e1f23c672991a93f9fcfa0e1ac6d2f928dd3194ba4f23250c57c44e4743b39bd9c7e6e076721ea74a8d2718cbb9416cbde977293db473d8018c25e952d9ab2a11ba941c0d3b7eb1f0b9995ed436357cb1b4ee3943dd64838c380f3b7ab1e041e5a56b6911db311d76f062f14f08d7ff39da96da186b37ecd8c5e29754cf6a5386cc13aa547c872e1635a6bdbb8e5af2743817cb31aaeef9559aa575838be530199717951a55866fb1a4b4682d7e5464797e6cd16a9d87d8b69dec899c4a152f454588bf4f6bb1d85194122d4b98b88f2a413931a854f268b1244b09df1452e6a6ba67b1e89a61c7b409e9a263b258eee05de62eb56488170b53ecf355b3b6c9a986768e32a95326dc657783859e2b9eb1df62af311bb623ec78c5e299e639792363a57c5db134a66bd6c36ed4bcab562ce713f39dce3b562c6cea8d8b12b24593b963150b5abc8ebb1fad533a77627272f23b54b158deafaf2a5e6d274f2a301579ad8da71946b54b3f3b4b5d68d2d5f151b1a8ddd32af5bcd26eeb3bc5c229f9d939536bad3f534e0e1b4bd8618a6559d562ba74bf739e24093b4ab130da5bb6c7501fe463282888e2488a45cfea636a73d50f1fa2bd61c72816d7a58cf5f2f720efb543148b4969a952ebbf30a9b73ed32dec08c592b86cd4a85b5ecd4941b1a4437a4ea1312b79a3edf8c47249977965e9dcc688ef0b3b3cb124d47970a163de85cb113bb1a87ee49994bfab737c716249af6e255b5f6a130b26de9bd24e0b7f5dd2c4620c6bfee93a7f36a59958f856f74a6dc38f7b1ec40d12931d9858b6d72e633cc890fbd02516758e6f27d5d9ce98bec312b7bc6746d1cc2c6f4d1a3b6729ea4aff698c6109db8e4a2c9f7eff9819659ef5204a2ca6f454a1a7677db3b563128bde27eb5db35dd7a676486259c94fa6b4abb8c992ad60472496e3e6ac0a8fa259bf5ac28606831d905898cd9ee95b8ad5e29e4aa5523938fae03822e908e8851d8f585097514ff52fa5122bb32316346d345ca598be994f0c3b1ab1a0437e8e1d3f44241dc11823168592aaa34dbee857af2aec58c4f2071d234f9e291d97a01c31e2abed821d8a5892b1eda31acfd1636d89587c553d3a4d06710d3a221664ffe7d55a873be1c9432c6a4e5274659c3e5342432ca7342fd9a5c3ef8eae108be94d9c92327cedfd86107db606d518d6dd4dee5e579594e17163de6b10cbf9f4d48acc53ab418ac404b128d3445ce8d357b283815044a379c779666cbee694d284701fadaaa33001624188adc9fcf9566a7ba9e41576fc61c157d43b9bd4fdb0dca64f668ee52274b5fab01cc7d4ebc384f8b0a0616fa3fd646ed1dac3f2be8e1febaa66436f3d2ce96a9569c4461fdf380f8b1d4496cc696436f31d0fcb2d374be529f11d0ef58cb7ef6233f219a2f935a5eacbb41d9645774c3df2747f4c6ba5b25787054d5af6e269f479cce8b0ca9dd8ba06518faf8d6d9317b3a9b5bf65b4e6b02c6b430ba9f15a094d0e0bfaa5d3c8383a8378b5093be2b0acf32617197578bc762a951d70584c1ec568333d23e3868d0a3bdeb024d478f68edf3c63329529ec70c362083d0dc294942363572a958a939861471b96cfe6fdc36492e2e4aa844dd1de2822a4971d6c588ea7b44e2b53b65def1a16dd53dfdf6cfff586d4b0d85ada3a662df63b7f695892f9f37542ae16b7523bd0b0ac4ef3a59491b14da8cfb098a5a91572f74b2b3dee30831f9f257f3aa8cab0a85634676d5faf3e3c325ceba619c76bbc6d3644db69799fcd946e19cb60c71896476bea18a1994f8878871896fb73f468aa67f793768421cfcf587397d16e315115af56b6846ace656a302c68509af39aeb9439a44c6a61c717164e4d8750e13f7faaf5c262d2192d34e444544759efe8c2a2ab099971b4900976706141eb3d5da9b426f3dc99b0630b8b5acb76ec0d8d69eab4430b8b614caa6aef9cbea49c85e57b4d3f9b3a84cc1516fc34db530dd99def0ecfa0941add7f3a66a52b2c96167e5a7714e3faa33bacb0249f4e4b99f15beeef75d8518585cfa6f455ed4e86b8c3b0830a8babfb95689335dba754217112c30be206898e33ec98c2722ed9495c2a1342b770871418d393394db5cd262ff3a7a3dc505ab776446149c5d3a8ad2eef4e9db13fec80c2b2abf2cf25e30bcd399c840c4147d8a8542a954ac549cc8c9fb024a37ae149b5582df3b7c3090b6e227568ac08f9fa366341cf2ae131a7cdd0b967865d9f269bb1271a64c35de8f039bbc67a4ce72e635967d13198fad9c97d2963b9f3c72c364f7fd666256361e47f0a1de2830b17321685dc15a6e4bd8d38fd3196a3ac0c4268f4af15bf3196748ead5ab7af1ad1be188ba35e54890e21b46629622c798a0e2ef6d27fb486b118c7f7334af99afd0b6331a97bfcd09da7842a188b9921ca63f011fa4b09184b7a664e84eea7d650fac5827ece60da42c6a8c2e48bc591a3730a3991a54ba8170bb6a1dd937bac3c1df16241472bcdad7fbb6a3abb589ad79e32ce4629d3c675b1a0be356d57bc8c2b948b2539df59678f8bcda771b19ca349b5c1c47d8be5d42722e5aef0d05adb62394aa973f62aad5bd65c8b45d171b559635a2cdeb996327bbf6ff32c16a38f9907d7fefa49592ceb142e9a3a9d0935c662e9fb37d6e998a3c9a7b0580ea5a23da6dc2afdc9572c46295db7bbffaf085db124d37487beab8790c1562ca667f8d237ad5f988c158bffa65ef6c94ae1fd2a16f3ca38ee4954cb9ecc19aa58d6599732cd623a09256a319c918a85cd31eba0c5a3db9f4b0d79218c8ac56c23b46754f2d95d5c72c629164667791d4af5bdcee30b82b8417272862916c4fd892d176af50b4de18c522c9bcc5accfc6c4398162916d47f7a2de5b75af973148b659fbf4b4b6dd0fd8962c1ecf7c534eaa8a55a43b1f459c9d3bae14d885041e1b966e6dcdba676ad6928d9294343538de6d02716dc75e82d791a3d8dcb130b3a8954e1a65787145727964cbf3ed531c6def71627963cdf7fc90f3791d36f62d936e58ede285c8ee935b1e09b3ac3bf1622efa432b1bca52e4255be3af1284c2cae6ca6ceee76bab6bac4c2cb88e74d2247b9505962b94f87cca8eb396abf124bae45c9f4bf3a95525162c14696ce9cca4d9ed224167554697676a5fb6592c4d2abbfcc42796a59628ac482cb1c5246ebbce64b9058fe8daeca94a84c9da5472c6a7552d9780c559b428e5816528f6a2da3e1463e8d5890a346758b721dc277462c6ae64eb7bddaa24abd88259d45ca5329e4acf056118ba2be1ee3cbcde71d4dc4924eb71f9d44cb935311b1987baedfec64d8faf4108b2ecb95ce78eda1f4698865ad3e289959284fee6321da8d39cf273d2116a36cd450235f6ea66e10cba37dc67534d9a6844e104b9b5f6f387dfb4ae31788a55dd5cdf8292096b564dea049bbfa8a7f58585d233b47b4956dce0fcb5b424a2935ba6e93f761493dbebddcead73c1f96d388955ad727f91ddec3f287bdf5bc6d39df981e16f4864c9ad456e7ea0e1b3760630d207be48c3c2c7f5ab11b55fd95cc5ea9542a79061e163cc5e895b2d33d6c2638e30ecb1f76aa79648e359bd90967d861615b860cd2f736be9aeab0a8ea5dcae0a756e6937430ee67a9baecbcdbeb896b162f4e65fa90499ec3124b2255eb243666d6a1514e2c7796d058766253bd36b1a86549d3b97a93b85e138b39d6f52bbdc95de933b19c1fa3d09e3b798e8f89e552f17a53c88df9a74b2cba96aa56271fa9aab6c462d817e28348fd61e7acc4826c692fcd14df34272596c6ded36929a54ac83989e59f0f9759e36916da94c4d249fb4e0d5d456231e68d1bc446ffaa2e21b120d3c7f0c1644ea5b97cc4b2ea866e1fcd20bba523964cc91b59ad972ffe462c6a4c7b22d474883d3162e93bbb96276a27565fc472d47753e6522ad9392a62416bb91aa4db9888e5d5ba6432a974ff8b11118bf25447d49e2955150fb11c464aa1b65aa477898658f6af39991f7fb285588845331de6579ff6fc3924c472eb9ff97ded1be6e1209664069deb3107e5ed12c4624eafdd75e80462d14406cfefa893523d20969314523eaac888affeb074267a42449ac620757e58dcaedd9c5169afabebc3e28b93a94c77ee8f51c78705753f57cf2983e9e8f6b09c5d7546cfef223ee7f4b0644a475f74e68eb65f1e16cc755e7daccfbaeac3c38230d9d349b43e7b7d7758509b456ad1f4d96139a9f196313cdffb7c75585e11326f997219577c7458d23ab558fbd3e52edf1c165ccad8f1fe837cfd9e1c16e4e327b9af6344292f0e8bf359f5079d2d1ec583c3c28794f652b90a9532f786e59cd1a67eba5278ccb961416356a3f4698e67af362cdabdc61ff1d3dd2d362c7990c9f47834f7cc5ac39269e97ca9f7852e931a96f4c9e650ee725b8ad2b0bcd137a857a9530b1d342c862cdd39e674567dec0c8b5ae85acd2f21266233c372caa8b59faf5686651dde1a357f5778d4c8b01c93c637f3b81b94d2c6b02864920da9344b88d1c4b02c959c9c98370f0d5a1816ec94ac8bfba0a47b0686250f2a4ce9cbf8b4cdbeb0986d6336776e467b796131e6f471c5e6fb91595d3007d1bbf3252e2c994969a659c83df7b7b0589a3b497fd1aa99d7c27212ba3bb3f6fde3290b8bf946568dd04934762c2c8f1a1125751663f715963575b3c7c67bd36d8545ad934c6265ba0a0b5a636e84faacbfa642f719ab94763b85057b9d54654e4a61b94ccb890e657ab68bc2b27c8ce7a29594b63206005058d04a7bd5c9971ab40e3f61695b556d663ba9d46900e08445bd2963c98e4e33965e2b295688eeed879419cb4296cb579bf7e5f4b60c3ed4444c3698ce9dac6988d0eeba84e68d4e198b9f7fb3d9270d2545c9473216b4de926feaea44664862c85814ed5ede7994569ea5f618d7c7e8bd988ea6ec7e69ca76cef2a95da5ae84cd8db1ecf3ef724deecdfc5c8c0599dbdb3e99d8c64f89b1b8d9c3be799632744e7d0c6379b3462d8d396e3bea7d08c3b9d9e45e5b6aa2b69a55a498d27aecbb77c33e82b1309aa3d2ff3164d0f80c7c006371e5891bb5c95fd6bcbf58d0f5ce764aeb7759225f2c69cd5321f4c5b68b34881b2446f0d18b652f752bde7e32ebbd0f5e2c99dccfdb30fda93b7453f0b18b65139def7b2a3ebb2b5d2c7816a69ff737a80e251fb95834cd332746bc54ffb684cd71b12c4c8eda16fa5b6f7d256c7d8b0519354ee8f4a0fdb5aa84cd6cb174abb50c15a3d163524bd8d0063e6ab12ce3eb4f5e2223a4fc28b458d0ea95de0825575fda3c8bc515b13bf7a685a7777cc86251760affcf60327b2c16467a6971eeea4c63bc840d16cb4a369f365579995453c28627e424255fb1a461d44bcb5d6aeb57c2c6e68ac51f195c099dd63e750c4a2b16659032e7e82fdc5ecbc48415cb2594c828a5f44bd8ccad62c974879c7e5d26a2a354b11cc5b52b1d1f71ae33a958f290fb6c7ad387c7d5e203158b2a5c456a102dfe758a058dc935460a13d2a3bc84ad4db1d852da7ad29a73568a051993dda9fd478a659da19bb30e31a6e4a8846d47f1c7d42ccfdd3d2877b90f5174269e494b4b63345bd5d598e6323ec26c937c846239ea8f4d3eda048a65d58ef91eb3b5964affc4627a293c761ead63c7d380b40183214688a811870b50500e1bca048d4aa5623e3cb1a76bb097cd6ff1205a4911a73e3fe88ae0a3130b4ad4c49abad0dee255c2462264ad3435d2488308ca3142a57242cae083138b711fcda52c25a39b2d6123b29b58581115235f063bf1ff64a56b62f9367574d29da3d4e0d01af8c8c48292ae63fce871bb8489251d7e5a7ae9d5cf881c297289052d764273ecec937b0c7c5862f9b3b810dbc1bca4e8c7e0a3128bfdedba528fd7df8e3e28b174aed36bb54a739cc8580d1f9358cea069f7a9af32e75ac2d69258ac53216afb7184af4c09dbea4562799530cf63326e6ec64a1812444c4c48de38212109808f472c9f988d0b2dba3275658219f0e108554488118a1c49808f4698e195980d1dfdfd95b03dca902739312131728898a91b3e1881ecd6edc8a69df3b8557fe97a37e86c6ab44639eb32f0b188c558b222eed49b7e7ac8102133a85414b1244f2879f5e9d63ebfd268e023110babdd56f87d2b4f234241c4b20b21e44709a951ee432cea16ae2bf5454cbc1c0db178f2e2b4a72afb4fed74e0a3108be3a22f65e7fc4bbe12b66ec3072116d5ebf22ce95ae36774100b9a649aeeea5c9fa53784a05c101f82583a5d9771971e4aec5ec2168805dd5a5f5747b52e27402c9c681de7f4dcdcabb6842d51f9f8c392129eddb5ca5c7fca55c2e67e587ad1da57e7b6d5bba612b664c3471f16fc9496792a3cad945a09db1a0a490a8a4909c99e8910122328ca87e52c4ea7d1eddbba73b58745efd2171d4f7cd4bd7a585cad3e6e275a3dff9f87a5ef24a5a6fd3452d546f1818705253f54e9c94deaa7e3e30ecb69b28332a951cf48d961414f07e15aca6785dc7530dcc98ec876dce98dd641feead99c0e8b31b5b50e9baf6afe3587c5385a6a4f2bbf478f2b87452d74daa0b3361ba447921e0e1f71583a93d15a5ebbc82c49d85ee0030e4b1a4a6c0c7652bbd07ac3924ba14a46a9a342ee46213182b23edcb02874ce3aeac61895baa684adfb48dec0471b163ce6bce9bfbbb3f565c382ae3e7b95779149843ed6b0a8a3663ad551ae37cb1bc7393939aa06e4564bf436666e86bbd996d9542ea73f65d8581a96847833d935a1e33521263a20e2030d4b22b5d660eaef3b3c7ec4c71996b665786808996d820f332c48d9d193fee12f754d62aa041f6558f6dfd6e5779edbe322c392d051937adb0a99343686a5dfa47ca3de58a594db62583e0fb759bd3eed7099302c9bafef67668ebeeef900c3824e793a0b339da53efd1e7c7c61e9ed75e7183caaa708796131ed7c96d431cb5cd7fae8c2e2781673efd8acff1f157c70615906d5f273ac18d7fad9c2a2cce5ef9a59ea6db1b5b0f47df721c32b9d369f5958dc8eb34a6eabdf5a21161644087de76bf2ea5ee3e30acbaf315a97cedffcbdf8093eacb01c42a38bf6d1f96683360a3eaab0a434947b47511f34735458be4deafd637972e9691f5358f04fdd7256c675f9ef430ae64cd16c8dbb71ea35fb4a3f5dff6e55545bc34714166567d4e6d33b25f5018565297d3ef3e7914297aeb9e0e3098bf97f62ffcd43b6e9954aff890f272c99c66cd261dfe23ac94f7a346359c9acb4f0d5f1c6bd45e9c18ce57e71954289cee4590b25c564084a4ba1c73216749b9efbf97eb96dca58f80f539ec9a31e25c4642c09a54ab30c13f74006b679a79add2a9b346b549598cc31fdcb2d8fb160ca5cce699dde540831197a186349948e21f69de76476a5b91ec558b64f4297d0e9b23efc22463021314969206dc00088491b950a3196f467591e1b3a2a947c18cb4a26cd9db1b4857c2f8c651d6fe4743346a551d92318aec86cd29875465537d3ea6a52fd2d602c9eccb56de2f4fd07f3178bf995ccc1420f5f2cadeba8a92ab4379657c2443844d44ed0a3178bbf615ee95542f657ca79b168ab217a4246cdb32533f4d8c592add242537cbccad12d0e3d74b1e0762be3aec6836b66e6d023178b42c6f28ccd41fc948f833af4c0c5b20b2953674f6fdfa1552a282928ff8610c56de8718b45cfbbf9b436ad73e6962d16b3a9870e6ae3970bbd16cb5a6526296e665a2c7face9f5d6e9d4e6b3c72c9647f7492d21576c6a942cd236d7ac9965671a4a75ce3c27af77e62ef488c582d8707a5294cf06b5562a439e08272726454a124aa5b24ee050420f582c481d1eccf3948796517b74106fa1c72b166ba45cf3ff0c1371ba6251b938a932fd5f66946ac5829a67ce302dc30ab3dd6babe6d6b03937a64d527bbff9c89c5fc592f299d1d8faa2939a1aef7aa862795c3cbe4cb34e375b2a966596cd195c66a8ea302a96c48fe9c7a8158fbe9e824d5b6b1b57cf34cba65db9b579c5ae69a542648a05dfa4f557be947ae42e61334224e149eab321e706954a299675da7aa9e3744cd083144b4ae75e8a9b8ebad695b019117292823264538a08c9c11b425cc03c8ac5945949971a8556e266512c6886d0a9f756eb98bd502c6a27539fc6d4c713428210373d40b1a41e548590edd227f51c542a67e8f189e5933a0bd91f7e1e5c26a588902345e8e189e51ed75267daf39ef53ab19c844c3a7869d2b06e0f4e2c6f0e35e56ba71ca4187a6c6231aee798ccbe3fadf6647a6862e964fee9eb9fe80d0e210a5fe07de69989a5173254b85aeda8a41c13cbfa31631237dba6f7bac4621032d7349c29b5224b2c6effebe83f2d43a3a712ab9cdec6186563565bc54ed5dc866a9cdbfb9458dee8a22773deeaf87812cb3207294bcc7f8534690f492ce9d0bbca56e66ae624342a951e9158f6794f522b2db337dc9058ce31eac8484dca84d679c4b206f1e8a5aee7bd85d2a8542a151447f43273295da92292384e52fa0a3d1ab1982d57c6dc7f2ee3cf8845d5253a3ccb983e374bd8d0e238e97e418f452c7b9cdd5b994b5fa7b087221683965aff66bd6486532296560bb1ef2e5577577f420f442cac969b54fd70ed269a2cf438c4722b2da366a735dd6a54c28622a40d65889094238658566d172de4cad810ad37484e4c488a9cbc1a6c27df417a1462514b8e52bb1f1fe39ceb5ff046e2688383b84102470f422c77f816baa4d070b93a8358d0324367542de6f3b63d04b18eb8c8bdccc7b87dc7eaa05dbc7ca53e79842fc2c6102338384941f9428f402cf6071b1d55bf9651fc8058d67cdffe59da7f580ead8f1dcd46e5b558c2a6454c86a051a9700f3f2cbfda98c4ee8577aa911e7af461e9bf64e89c5c8c9b7ef161d93edbb9942e33bf50f7b02453acd2f0c2446e9f7a58d6f93fca0e5b652e5b8f3c2c6694f12232d568bd71206cac01c48df4c0c3829666f7eedaea1e8577580ce662648cf5597554edb0b85a3c544917afe386755890db99c4fecbcffa3b4b22f4a0c3d2ab5399540bf951cc8343e8318745a1b30e6abfb2b5c88c0ee9218705d72e5fea09b5a1a4f0c8903f31c2dd230e0bfbb13cde689a13d196a41c36b224e518c938522e7bc0410dd574f79ebafaad31f77854711b73ee162a612b4949d1931423263ddeb02c84941ae58448e96a2659cbc1a3380e0dcca5082922a4c8119247711c2267449e480f372c46b53226a9e46a79d4e9d186c50c4abe909dd5adea4c0f362c8ff96825b6a783de6f10ca1a954a147aac6171e3b484961ee21d536a58782d4e4bd159fd6a152ae19422271d1df419394490a4471a9653477850ebf2a6d58f86e5fce1a7b35dafac103ac3b2dedcd27d9e49967cccb01c45cb3f3467abd57a191663cc9eed67fe547325c372dbe911bfa2d5547763584e5b9bc58e96502f5c312c7c8e2b56bcb3e8dc6918163eb3d8acb569f92016426230e801066b6f65c3688cece8baf78eecf260aec22f2c86f49c44d3ea4c4a985e58561b333548b197d1378a0841393992fe84c4fe0db59c5f030d3480a41ca141a572f2ca2448e58ddd2b9322a96667a8a1471796464b472f99a37d90b9071796cb3d96964a1b1752ddc272a72d2db2e5eb7ee5f5d0c2b29c0e1bef31757978e1f81b542a70fc89137a646169ed4b963635f6c0c2f28fb6d6fd981e57406c3cd3b9ce6aadec96485b91aadfd58e2e991e565870a157ca0f9a6476ef4c624ce85185452946752ed9f11e54588cb1a23b22b73d8b70f56250a9748f292c871b19b3cce76e49d552d0430a8b41cadca5ee4eb57679099b322142028710c5ab978324d11e515854afb24f9c560d6ded081f83933782c91a3da0b05842cec3ac145a8d9063207a3c6131c8556d9759838ccf19440f272c49e92b3cc73bcaeb4f8ea41c6ec672d21f5b0dad49b53695b0a53c0a89391cb4bdc18319cbfd193cdea4eddac9542a46881c09a058ccbc1a1f75d66c353a9f587ce525640e328afecff1c472d8fa3ab1f3e9c4a26a7ecff2aa3168f97062613e2ae5725229f5966f6261769596e539ea8f51ae894597311df376cc6e936762b9937dccc23f475cc831b1a4f3b61a59cdb52de4975858a9849baa972973905b62e1a45c7bf9bbbeeaf14a2cc8ea6a3299438965f5314dc91cd4beba9358f216aa4f9d5c29ae4a62d94de9d196e323baf4482c661e51552ecfe3c93824163e78d41a95beb6f21fb198364a31527dd399ef8825b532b3721d9531a2462ce8e7e4f2994b463d8c584cf61b1e4d45b9d78b5832198f52a7861e9315b1dc31b8dcf8ccaf344ec48247ad4be7e8cdcd65236249776c539ab13ec492d07ddde967e7c967432cca18b3497d2b2ec46297ebf1d14a99e9141362f15e5f73a37cdb68e2412c7c77d631b2eab4eeb02096cfdf5f55848ebe610ec492ba884b21f24fb79401b118428aed90faf747ca7f58f619617293c8d796ec8725e99f836bcaf8a744eec3628c52a9da9451ae76cc87e5e049c6942b8318d7da3d2ca94d7b5532662dd5b57a58ff795657dada3c2c060d7f32099155e62f1e96a5904168970d23326aefb03c5a85ce8e172f4667edb0a8fb59bdfa6e9929df3a2cea8dfcec4286c6782f1d16fe930afbf4d974b2770ecbbaf327d235556879e5b0a4856a6af5bda542ba7158102e3b64126d59f2a3705814fd4929253b856c5adfb02cfff2c4b77d4e35ed86453d739a45c86d58143a830e6ba3abde65c36299efefea57aa7c5dc3f2a98cb9f4c7dc8753d5b0a0ea75cecbe36fbfd2b0207f427daffed74fa161514fe7d29a36af29d31996c757870a3ffd5a6586e5127de3ab3d8f6ba80c8be2e4bade0efac977322cae0c9929a227e7d53986e5243b68572a743ca72e86e56c676718964c9e1a19942ac1b0a434989677ea7ca6c92f2cb9cc1cf37e68d9e9e5852577d9da3d5ebdfa3c7661a1262e84decf8505d33d5a7b9eb4bcb48505992e464a99fd29a58545216f3eede356cb0f6661e1f3ee861f511b5f266161594c888e9b5cbcc292aa514fb917adb0d826739cac7a6122f52a2cabd5f64995d2302d2a2ca7fe8a931e7593a65358fc4ebbeaa371f7258545257a5264b767a1c5282c7d14b2cd6c95d8ea100acb2ae6aa3c741262f44f587e9d3d5da72e00272c67a1e5680e79d9d16dc69249d75bdb5a48612a33166556356bfd5221c2652c6992aa654cb6fa674e19cb26575de4edefc958d620738a1975994b858c4593593386d3ba4d4ec7585a2d3c99fe3bcf51c7184bfaff9ebdd35fcb783196b430913dab9f6326c6e2be692144cfb8187b188b4a5bccd37c979055184bf2b4d2b06b528a3383b1a46d6bf3adecac4466c0584ca15ea22ee59ebf58fa2e39af47c667b87cb1304a7355f57795ccf762593b6cff5385eb282f165dacfcd135b7ba3cbb58f6a8756309adf3b975b1a8eea14727114f6ee66251d8b98f6ef97d225c2cbbf86426f42b99b3313f6eb1a4956890adbd4147a94bd8e2782290388a11131283821fb658b0f711af3d3e976e45ce10365e9904e9572645127fd462e954a69652dc5decc94b8e16cba765aed4ef93bdcc514e5a70968ac481923a8b36dbaadabe6894d958cd32f4d4ee7eacc9ec0f592c0a5365ae3a37c8dbc7241a6cac01248d1b005103c58f5830ae2a2eb73963eb22a3a11eb56444c610eae13c493182b058d8289514d2b59891a255c216c7498a195b07718384053f5eb1b4c957b4fadf6cb22502a40d1800a9545aa003c41fae58ce9b3a88d82e4bd848549133e41cb662d195f220b73609f71c562cb9f0dc8c7bfb61d45bc592ddc9cccc6196b0191bc20f552ccb49214d4b66d2d175667a383c154baf22746f7dbcd05178e975d0c61a408ab098e9c0c30f542ca891257f62f273922b94532c88d11bfe520bf1639b31f9618aa55159b9a1aacb479cf0e44729964d3665d6ac5d099b5aca0f522c89fed4a7c5bdafae55091b8949904aa55249b31f829090228b148f6239476919b5bc96c9750413444284748620ffc00f512c6a213f85bcaf1afd3a144be2b5124af67678f6068a25995d6dd8f0394eebffc462a86bb966af6ffad713ae464da2595edf3544e75c5a6ece1ebb13cb1f5ff73b3e6ec5e88ce107279636a594c9d59c1bc40d12921f9b583a1d3fcf394fa13754a91c1c248e6204122328260b3f34b1e06af4939642e45f7f463041a3a3f02313cb9aae5f9d89ff7c9eaa6062e985efdc86de97583a9d53e7e4395a5df73f2cb1182a33eb0f79d7d8ed8f4a2c2b59a63cc89895fea0ffa0c4d2eb98a6be37223baf7e4c62c9dd9312e7ae57ab4afd90844193ddc87cdd9e99d6c8519d4f52660effcf8f482c9f566da9e4bf350a1948acfa6db719c7c63679ad4bed3228ad4f6c50063f1eb1f81ff48ed4933fd76e137e386259a696d13909f5a8b4da88850f52e9a9b715cd362316a5bed05166ca70a1d92216c48b8f8c21fb3767fc4311cb61c5fae8d2e2d57e9d88b434d3b8becec6dad3d97a61ab5c95d6c1b71f88580ec27f74897d98da1f0b3f0eb19875fc7ed8129fb55e97c9f0c3100bee9ec3b44cb3aa4c7ba4086f43ce912298422cd8cbcd76a6418777152116432951ae3609b1b2fc12b6bd810f6241e429a1372bc7576c2a951f82585ed5b9b47efdf00c9d93a55239275fa92421427e0462596a9549bcbaf41ca40c22e107209695e63b3fcd7af5ca6d093ffeb0e8a5a5fa0ea14f8fb87e5896531f7757558e5277c8f9d187859739f7d73c1f16558d6bcfab41f88cea33093ff6b038a6bd46940899f0430feeabd35176cef9a9f7c18f3c783a082d2f6e3385876db3b573545ec63c6dd4d744cffbf3e30ecb99359db497babdf3f8c30ecbbf7baaa693bf4829d76161d3cb482947aa7ac9d061518b4eea7a63efe1c71c96ed7d5669a53ee2da92c3b286c9088f2fbaeab43fe2b01cde492b8fb254faad7ec06171544b7fdd1ebdde4ca9c68f372cb7d0352bf5d7a6bbdf0dcbe6ae4f47cd2acb4da90d4bb6312293ea8bd332870d8b2ea3874d99a1e3b6bc06bee33ee633abad66075dd1913965e6fb871a16def4d3e383b91632f5230d8be184145f3d3a28f9d50f342c6b4d97426b6816aeb612b63f2b6222648dc45c0f7e9c61f1c48ee63ef5d1a5d4fe871996d48c7417f16dd1a3ff28c3e26e0b8fab31f259c6dff8418636a8e6cd6cc7eeb606977a5abb68195d5346436e8ff0630ccbd994fc95197b84081d0ede2822e42405a55239f2430c87968b1d5d1896621a326a21ffef223fc070542f27eb4bbece7a233fbe60ebdcc66f53ddcf9871e3a68ca1e5e8c6ff75f0c30bcbdb397b38fff0d516fad185a5919fc306a1e1af7b2e2c6e1ea9dc7fb43e4e5b58ce1ab40621a558d7911696c7565c6dcb6c884f1696852a25437e7b8c2d2cb0ba7d9f3ef72b2c88c9959991da39e85658ec9e4ffa465c85c5a04a9a4b5f25aec3545896ed425caa328f513a85c5da20c4eb98dbd7cf258545f5519ee84ef7afc61585c5a4aabf4a6b416149ce7dce9d95d499f43f9eb06c67ee992f579de77f38613154df484dc273bfcd58d2322a351fdf5f9dcc58fa1c99f97412224eb58c05f32edb1c6bdc334a198b9da9225fce5c9a52321666a52aadc3f43b9e90b1183cebeecbbaf6173ac6a2b77c36dde41942c8180b9e457b899dd1cf9e622cba965c69b255c6df89b1ac6544cc88573d1b1fc6e2acd966d67f42ea5918cbab838bb56d9de16f3016455d7c3495982a293016c39672d1d020645c7fb19cfccc940615e253e98bc511df3264e64b91662f96b588aaececda2e3f5e2cbbe9dfcecdad51cb76b19c231f661e665f64af8b0525befa36b637a9cde762419ce7d8b6932e3b0b170b22559ffdd698c7ac5b2c6ed0fba8566fdc8e2d96348f6caf916db67e2d16bbbe5fcb68a6697b5a2c778c61756b2835be9ac5b2b99062be5ec9f9942c16b4874f8db3f1b24ab1588c31dfc9ee91bfdd81c572f864e27d7514eff62b9693b67ca9a276c5c2bad0efeca832acdb8a25e5a75488334f0058b1fc62466f92a1fb39a602b08a5eebd7e4339b52c592da461dae6a747c52b1f4be225f4793eba1624967cea75f576a999e626964beba8e37dfa12916657c7e78ad5476f7a5585629b39be84dba7c522c7876ddfd2b65a6ac46b1b0f94b98bb8e49bc92289694c8244ea967ea0f85623979e8601bdfe547790a0028963eb33e2b2df654f314804f2c88ad932796eb4f88b8ee18b3efd489e5ec3166ceba99dcb58913cb651e7f940c35b2b46913cb716b749992e1f3479326f22c4b8d9272cdc4c2d8c8ef184d881715138ba7a536d1b0bdbacc7489658fd93cadad50328ac912cbeb2ebef9efb5085daac4828ac7fa4ea234bf9628b120550a1553b9e23c6b124b9a5fc744b8d4215e92582ccfdf4c2a676448456241ccaa2add78b2ad82c492d41cc59a14754a8b1eb118eb34b80e21e25b77c4a2f6997a92d13c576fc492b7fa24b30cee326c462cdf48fdb8f3d332c668118ba9f1e36ad5c6b42a52c4f2ed9bb8172b322aa2442c7af693a74d5f86fa102296d4350b152f93f95e87586cf1b9aec3b80ef50db198ee336bd05d9bd32fc4b2bdd2ece144f67c4588c5a4b33307bbd1f2518358f690d2855e5d1f834a100bd25785befc729d9302b19c25dfbee9ed3f8500b12c2eb35bcad149e8d01f16fb3b5cc8fd26371d3f2ca9562b32dc2bb942de87e5123a3da51825a5ce7c58d4f664a3396b9dce7b583c215ee99c6eabf3ea6139a30995afe5e485340fcba929e3eab51e0fcbf533524b31e5b9c77387459132b986d4328ad48e1d16b7ed5ba833e1a39a5387c5cf9a42dfbee8b0e4ca67837934f9e09ac3823425b370dd742f5f0ecbf32a334da9dcb8290e8be67bea36546cbe101c1647a649194ce959fd79c362544fb54ae6fb1ce56e581835cfd4c1b3d6726fc3c2bcd459bc523b1b96948ffaa8b3f8e47afb1a96a53a99e3c97796d976352c692d368ffc34335b7b1a16a50e1be1da63f0133b1a96d73e4a0ff9f144643fc392d868abd110a7f775332c6915132b46c6cbb0dc59c346eb92f9d3c6c9b0e06965cebd53d5e7c7b09ce483cca132e7245562583e61ffb2b52236288561d1d577fea0c49b3813181633a6cd28b1939a85beb0585a654e7d511e55c70b4babde47da27fbbcbd0b8ba69da21d663784ce85a57fdd5cba1e4e6c7c0bcb3156de768990216a2d2cf7e8aad2163ae5db5958d4b92bcc7eee4dd35858cc2d6eeb542835275f61d983de8aca18b6456885e5d13ad878381dcd835d8525253c6330e97d26c7a6c2622bd11aa74b53a44c4f6171b5147a93d62b946a4961316751adbfb2b3843e0a4b1f3ad5989e32f33e005058122956eff8aff89902f084e5d54a6ba5ba54ca300a80139684e8583a8d1236fa6ec6b28a6688ae8d594a3533165c7cde28a3cbcb58b8f5ccffcddff8282b6331ca937d52eccce4cac958ce2ea1a555898c85d3a419b3a5fedb3cc6c2bc14f23c5b8c7f8c8db12cb5b3d8e6139d8d713196958cd97371253e6d9818cbb1c376c89ee99159c358f0ac4b37eba03247bd3016756a394db23b8bad82b19cf24908539a2b3c058c25d32152ead58f5246fd6251eb9c57fb2a8d1a4abe586c21f74a0a155e5aeac56254f27532b151e58f78b1a419bb4c9f679d5f67178b5198e9a8a514aef7a38b051da4de75ffe869e3b958925b72f4499d377e8c8b65e1274f9ada66e5ef2d169550b2499b966c7d6db1fca1a5bd9ce89c325a8b05a163bbd4502efc95b45894ef1a153a3b9b94b3587eed78a695f89759288b45315a9fd4f020c775b15816fe526650d1a5b46c61b12853baa6d4303207d9be6269c783d0a9b5d6f531eb8ae5d23be739c768ad5bad58cef95ed50b99f455cf8a05ed2e424621b4e6f4ab581223956cf9952a16b3ce79dd4d3a5728150b2a85f89c52e6d39a848ac5d6cdebb9447a8ac5e449cc8a959b624928a121f38a198d7b2996c367eda8b35a316f9362413e87794c3267bcada358b2fd24ea77f4899055140bafa4fe93afc435c61a8a855979eaf37c496daa8262b974d4b2525e536ba17e62598b1d1952d46f99a89e581452d89e6c2164d6463b1190e8a864ed2604b248180a85028130181006a4e977150013140030181426114603128120c89a771480044a503c583426201e18128e04647138180e85c1a050200c0683c180502810840bc1d622362220a81f29614b3a9e12b2c71e101e028790104c8447168cc4bde5d3673996e5cd5799203f93c5e5c5c25aa02e440bae85af1866ac40d400b5b9583bc5b7e80b02ef83f2d030a48404e235168c959110a484042196f8f6f3314f2808f540c7f83fba40de10160e89d1181ec2460c66befd04326273accbabaf3e99fe5f3696173aab83ba11156c043f1918f943144c160194872789a7980283d5640a8a25c299a0151890fb1b9dcbc023113899050136812b2ea13972115564177bd1b014a80d51c19660b78cc4c8360860ded6a1ed57db387b361ab5d56d0ab6024d4157da946e454d7db1a24fd956af29ec2a36155b49538999ad8293470cc5031a83c076044c358b9367fca5f32aa065343599e7f41437607369801dd282d3674ee13571e3876a3c6b97e527392835a8adcb21f3155638f8fa002fd7761d713299a88e7a337b7db92e1fc819aed0c691fc3bf6e4be903558671124d6229caee642f96be60711d2c0e0a8b4a33114264195b39d3a89b0bbdaca7eb4155097fc8361081b8b892733f33850328a811ab42593cee100af0dd40aca0404024be9f446d0ae66f1030d68d3cf827f2fbc398d4af5322e386c83d5ede923716dd7a73d31184b3508752e0b65ac40874e7796f00d4e4575d9493678d0a55bb1e6ab9c427a69313faa8839dae2ed610c8c93248e394023dac83d4f74c5f3fe0db1cfc4d06f171f8fb3d24466d309923895de810b6ffd848a3f42db8c1884b5122954907e572b358dbb93f7622371e61f693d5a69dfa83090ef2c4d79ccb385a4f2b3d03be7a40196786be55347d790cc04a77beab6e8fbbdd7f812f3b4aa1bafffaf8c4bf159b93ad5165b846bbd3e49893e42308838afddcfc521c6f4d9126231cf63ee2dc456c8981a0f2ccab3bb382bfc3cdca1f582232907b1f011aaf8296998d0005a4f33e8c6c661cbc28bd2bd403ca5c5dc149191be4226d3944e424b2b680cc1b6c096fe7e6484810e4608ff8539d24ebdf13b5c672525f8d458eadaeafb0be14015b7a575af82330631c54adfd8bb795c0e607eeee1becf3bd7157788b9657d0a4e81be3f91caa38a984df2eedbff4e264377d439c9735ae0213f5ed17c7a03a1b23e5f2a4e2a39f4a5d71592e25a2906a1e088b89268893a5a12a8002f6c5138481b9cabb39f5a4ca268109492223c1b01e58313dd3cddf4477b77dde9a99aa2bc0cda6d83dc40260c0ba60ea88d815e19b69298468bb8109b4980068261fb0c5d699a23e2683d6ab342cd18b7dee6dc93a3acab5a5b238d56ff45df1f9c489091f78349982b99ffa479afc57bc81660c0349b288abaca603764f8c1cc48d2dc1f3e29ccca8d444b02df4c59387e55dd6064fe548b1305fa845520f96137d48e2374c29c8f7479ca9ae9ac748c1d9407feec012b09d12c25014b17e571768f69fd15c0b37d7577a28b1a9ecf4578f66cad8a8005bd646c7ff0ac444aa011806af659f65597ca150e649b22631ddca2b9c134834e54b3ddeb5a7389f955b3ccdfb200b7e61f67a1b22bab020fbc43d979af0aa36c8670745e9afdde34976c9750611cdb5db8649f64d37a9a62da6c6bbdd0e4dd188de667b669f8a8daa5ae30a8f8cb5e99a98f4855c68d0c5ffb51b060085aca29a4e1ca59f67071aeafe42a9519535b009c3ed34d7eeed6cc873eb192712933d30a409f3ad1374fb92a20596ae5585da9bf43840bab467f736e8206b1a05a315013a7e33cec7c6f5402b329602a58a6fde857646a896cc095e96e2c76a39b2e1aab1b3d13514c19f5c4ee94bc265baaae6884f9a548ca88998872dfb3a3502228f68f090f66c18eb110b507aa495424db0d1a0d14fe329866cc574368806b7a4959239a9b273cc65f2aaed6733f1da7627ecc69b64ed1f49ab9593005b36146d3758a265326b023e226810c54d684cea1993cd9137e324cbd26db343f36ae1200d1d152ba3d73c5b936853f508cd999017115a9782ae3d13de802f4dba8e4bdb3c2269b06700cb088001f0003f87d43d128db28a80834000474213cb8077e0c8e8cc118a031892820c00730c3ff608c8f418dd7411a5f83371e06613c0c6abc0ed2f0b9ba0377fa3498e30e1a59d68dc9f276634fbb758996f3488ffaa170aed6293af806056c4003f20109fc0c41e1e0bd210bfecbacf13708fb6111d66b50c7d3208fefc11f8f83313e828337a087afc11b0fc08117208d5f928e60fe48c101b891a48ce05f38024d83d4900b8552694957d34a2dbf25580c2bb05a0bba885678712ffc1a5c198bb1406b525144a0cfc45f8f2b86d3f5bb044e8b2cac271cf0bdfe750440106d20e8b6da8f9b513fc4c01bd0e00d08f0009cf03008e335a8e36990e20318f001329275bb97973c588419ea26e8225aa12a87b02e180d8119f4c00c5591407d944bf2924485946aac1cae25892f18400728c003425003a801413751092f580776b0000f50c0136ca01ba4c3698447f5d08f868800ce04050a7908a1911bc9e00d06d000c2e0384872f55edb34b4a9f3f9b6f5d3422c0a2ed5a34e0ab1bc881210f4dd9f12ab3e52f45a111b7e0276dd097b9d751f792cabee3ab2fbb3750ad94d04cad0a39a4ac1079b255c0c3da0c86a33f139c9d7516786168b4566ae32f8f55ff0483a24674000720a84b215cca01dd2bbefff6f2924c27e291aab0029d111f494eafc1ad41060d2b272c5f5241f87a378dd0c7e1e2531d626dc986009b0f9aa6e2ec927238ce339e155c065b23128fd4cb4637e1e44e870b499800117eac94a3be3b64e6d956aadebbecfeba88dc72f7468e5752c2a24c7f14f5e62139c3458a10aa9d53e38137bd63f95ac3e88070fa3c3cf536e80b49ceb26a7f080ed9183f3540cfeec0c1f2e9a62619cde6dddb724b85d4cc969ab6587f99af7ff7ae4ec79f80644b5b7dd9998bb529cfeab069c1645e34629bfd6000ff5cfa846e8f07ae161a4a642a0ea73380aa4d06e860fbdb6c953914821e6671e672cdb4bccee6283dd93ea038b0e3f229238a82859860969f848943fa8427a058a0f3c1964a4e244e769001f33e61427f7ab1f3bc16e3627c6a7dd5d2948b4ee1f822730264fe45814e8ee75ed6b5531fe7ddbc8aeaee506cdbbb9f5c25067865250412807799fb3934886550cae31376b94f0ca20e0160de806023e702e3e6190a8f0c54d0bc99fbcac84a1ae2118e3efff7d3d424dc6bc025d0686f9de248c34c7afe0b41561a6533a96187c88470588288f0d7e8323375bfc7420529ca8bf0d82dae0ed845e3ec18e2cb9d2d73a58b8bcfe4e076a6e4b61c6d7e9d8130928263baf0d105e05d6d9e2ad814a2440b92c40af5e98b062995c3ff3345df0cabdd53883e39936431ef552237a80614f3049a351e11029a0963c6d2a89252860aff0e0a105046a899ff4c29b09a322a4520a49af528902118940a63b9d101fd667d7514a757923400f86951731b7737f9e53b1830cd307d3a5a8ed60869467e4ad55893010830325d3dd87dbee9e8eccccabb9cf201c758ccd64c69f0ffd186329d3c8f8bad150d6bb0d3b4c097f07747063ccd3d4fb8ac12fc5126fef6fdf78006d70aeeab7d245ec423bd088352a453b6bcb1d5cc933b3f76cd9a47111498b74ab721e292e5bf448ed51171a6cb19ae24da0b70152da3dddc45a394287f1627621a8cadae89435b5bbab67a3c5bced2d0b352a2f5670d42ee2bb22bb2d6b31597e911196074eccf3f36832a317b5f32a3061248f05320b5e20e48f06ff67c31a27f80d1c1380dea44d474be82f17104baee423de45583fc0e8d704be9099f5460438648cc512b1123d9aa6e2679200988b3e8c102a8f0c9197b2ff60bd3da492d7e8d884a0929f53ebcb7ec9873702bef61e2845942f2526a536669b05d9368c1a92400174192291fdd30c65487d965aa31d5d360df68c05fd0b7a6b70e5c2544abd48b459b7f494ec08d3e2cb320fe7b5ff00600384e6c2d71ea571259830977a82985bc23561c7c2316863cf0d4f0ff6f6fb25da8f46e100aec09c629c8c11591eb9c741f1db54821eea9090d4da42b16936bfec840ae239b82701e2d94487d442b2b7936a7ade560ee985e2a977304b87bdb48835432098f9cbe4dcebd91ce8518f8cf8eb8e54b2106626ec22ac516104e07809deff975dce4e413f4a4ea5c02ef75300d517e322ecc5b1faac85704219de1317e5c7e0a576d86e5d21bc7349cd52e5f5e2366b92a7f47a9e89b08a72eef1effe470081b9dc4a997febefb259315ff3dc1a2bc16d922def83174d6e3a1643a1a2c3d9ad9eb4f3731c1f2cdcc8a5f6e95d188e75ea3a2ec717a3f7f56e2a960a151f74ce570f6a466ecfab39536a2ff3a975b1deda4fe344764c30a34cab3707075ea770b4ffdfcbdfc91cb9d4708850299b8d82c6b081c5de77ac4ab3b725f5d531af107914bceac5946a537cc23b8e527f24dc9ba9b3f8234a6a18196fdfa413784f17a3931be29ceff74e3107273502ecf2469eee45fec180263b19f8e659bd68d36e6482c01ef5167ffeda0edc2f9e6fd54a4df6f9d0e7c1d445a1b108cc0e79cfc915d01e21aaadc97cae919ca203b6abb2286d3df3f39906e86c108c34ca364e9e6deaf24ef06f7a563ea2cbd91c330210b7f73c7ccabd8b00c3b0d8cfbfd91e3a5222c1b8d2f7ba6c2deebf48393966be136c1a3035efa9334c4f516fa273259b1e10bba7d42e30060bc6cbc0ddd28790f800e2ce23e80b408cf2e817e77074e09a442e3911f7af231ccf04b1c4f7470dd269c74d06ac880c2ff69ac298c901100606c59db520d882e581af69862b2a9746bbd2737789eaafa4f83ba46eabb5103dda3a4c34906c126b4ab09f697268ae57a7b00a7bd14f7572cdc9b561bcc289e4bdddb51ea3022c8c91ba6fc1c644650cc1ccb61f6b8c8d656b00fa64f5fde624a0f0abb974951e4e3f086b0de499a723dea87090e27b68b1d6bc3c7200338a8cad853e0a7d5eeacb63067bdf6d329888e423d28b1ae4cea2810ca010de581d7b9f6fc63afd491a3d4f5bb1dd7a60cf0da4a0f873f7a18c91f36da6c5098914e2c901d8507ececfdab7c889b79bbf0ace350339fdbbe3412725058a65e723971b3a1bb5106eb7221755a9b7c9d9b5970ee6d105a8fe36f8ee80d594cbb877287467b8d622f8eab126ad795a2b33f8ad1018381cf5b9675c02e444a39c2ac66b3859d654d3ebde4d28b3f82f6dee1860db0ed968196c3c0cbdc61ad8dbe915c9e979aef36f75a001dffaa3d1c1af17740d35cbe022d0e56a1958cf2c832a4e32de5943eec218d8a5a785563075a3053c219377d7a762dcf4eb14cd4e9602b1136dca39f56610e8aacc2a4f76edaf031165c533162890084e358fc98691934249023b16a4c7e92a093d09ade2792d07abf20c9cba490ee805aeefcda6272b258818da5e59270bb8fae6280796bbfbaab71df81f1f11832651acad8dd3ebaa3b62aa86a13a0800b0a9a08c5838393c2f40535752953ade819ca4ca2d9ac956c444b74493b7da039400be8d8303a0ed7508b60e0434a8535afc3184fc7fea8cee0ea790621b3648c8092a6b517ba0a138d6230ecd7be370737a4ee88149fba6fefddb19e3941c143865e01b6ca041041395c277c517716e266e7469143058dc0117f441a131c17ed1b7b83a6614c6ef4b5ee022eab5f50d1a848149214bd4f922e51dc33646d3289963ad70cc3953059b8758d0573966cc2fd2dabac419473094bc5ac0feb3e6ca910c8252ed70c571ad2bd28195ef60ecb46e722695b7922fe5c92cfa4c5e4d40dac0600760da1a716abaf43edf3b14a52a07a95d6a0dedb204b46906dafd24e360ef3870c870a8731f7ce5b0e34e92d0ac12ab117240dcc0880b18295038441fe8a277c4c99fb041b03092978cc0afd16fef4194dc65607e624173a4e99560bad9b0faed18e9e1ff49e372140629a9b0839e969fe1add64f8298bb2746831d5d024e5d22986364e3cd6f7ebdd0ff68afb398636d1855f6062d63f9c9507dc31f46da736d70d18b33fc663fcee08b466c193cec7787ac05106d701fb07a8b8a20fb806e9af960c96fd81e359837be71bb12d39a835e660f09bc942e40a530adefe3ab2a9ebb549b53dad7e3ce5d7efbe354aa09267cadf87e58036239e7314a98213a77063a70dd091da7d5f2b2254221a6a1099c6e6d00b9b798763d490be66ab7abd46df5b85b60e7f9d287d0f189aad7596d3adb01ae36c36ef4adddfd16615e98b25111b4fd187cb6b79dd092d9d87dd3f716958cdc5084da9ce5a7434f135ecbba93267c0ebb41c8b7d6e5d153f866b39e635643a023fd82ffe1a4605aecc62f7bf52d2aaa0767a87e0715c2e11328d7376ad9123078503c3ce42e746fd22b73c8267e7afa673e0db45c93c97c218621d01d52d3a66417563bda792f0b51c360874ff28d627e10b426c9a887985d98afbfcb41ba2e0f0e6148af029df7af9d519f1439dd8bb895a8b94516b513f4c4a4a4410e47cd051a3377df02fc6fc0617540ba33ddaf999ff8d3c0dd6e6dd32fe2999154c8a830d56ddebbe488c4dbdcbb6ec465e4cccd57eb1bce45abeb481b64fa964add0da3bbf88d6a9cd4347e4aa25239b5c9cc843179daea40cb5bc51bbb3d229d05c135d24959d5e2fa9afddec5dd426f56ad90951b2adbf711634b3a2e485a9e7d793e2a67a7e8ed1e53da7d24b8ba464c84d3fa06fe6c984d4a530461f0afb9219859a05a28aabeb54b6d50d0af16bd0cc2d981990e85d96acfac9ea3498b2e0d15c79515d2a822e6e1a35e369594ab784924ed1765bbdbc6865a76448c297044d2cee221502736333690d1be924012e9a045db30fac04d30e732cb56c1154e135551b631dcd4cadd108079800b47a22b19873e3e3adc7840e7b27bea2f2ca1bf1fc98f269a754c50c88b6e6a1f4124eec373890d8b2e106f7e13baaca032b51e830836021df841022a40011d90011710800972601f77104801099003cc0264b7437af36a3405c28d281a494b21d014894a64f7d39d249131e32a604c969667d2db20cb786a41e329341f83732bdb23e0769784136c0bb445049fb0c37a8d60bc44c20c1c5cd8d617690640f9a23a3292ebaa83494b369de7c407600a11929915103e27cd2f5294b022afca8f39a57fb999b31649975ff37663c114ea596915ba4301b9349e51853c9f4d492b93ea38ed94198ac7c5477ec50818fc088a94c62570ae4b668a62f00d2f01314e00f6db4abe3bd3800cb3d483b0606c10813a500b3449f35da929ee0a3e912d3f4b4ca1a11f4c3229988f8454375156b1c7479a5d97c005b9ecacebbb1d4e0b8e7d9940832ff29765a989ead901c24f888b017fb28e7987c442d7b82324bbfe195ffd944a96c85418f121d0518da685ccf14de99597062a02229ddae0b215b10b493d577b93470d1314ed2bde90e210965bd8a869b7791c913a32b4a119c86bfdb51f572cca450ae010dc3654647f36c5a7a8b91ba64eaf2ff66f1f52047762ea9da6979fc8beea1000683879c32e3d25c2d63da2628dd55932b921b3b8ff42c29747b1f034e1cc6315f9384f4f186cec32bcd9474c498f53d2ed423562eb8845ad264622943b7330527146dd51f3ca21e56a0d9bea97781b0a68949660f93592bb7481d9eacc7c59cea71539ee0ab30153a2bdf93f6c7c4c0ecad7254f9cebfc116e6347cb9945574fe00f5555074d159c9ef9025675a9bdd3ba1b8854bf99548a527c6c64051d36712099c648ab1c842c35ceb4fcbd2d7d8a22323157f44b4677c08e8b2a48272e248be12b5f92df9822e22907c4b2f9ee7d2aa4749aabc7ea9a549eb582d5e0256fca1140e740e97b37d005ab439edabe1d1a90ce12772be6e3b65e8a055ead476cc03239e8beb42c8cd740531fae9e3e7b13eae6bb7ae21d2a74899b3ef2cee055e085ce839b875fde0126eac2ea5b72c0a2db540967a1fe41344bbb551b048956dea0399c989c6e3254e6d707acfc338db6699acf48bc620464b3f2fe24049322eab80d0704ffd4d566762a8e08051284ffdbe57730dec63a21b55de1fc1576b15d602818dd6690c504a63645ea9ccbf011f9d5e8194503c917186ef8bade995225fa5a008afd8eb521e49f75831122dc441891410c38014c1e01a0149e8708bd5251f64d2678f80b01a7d2f06ac0091e62610fd58afa3aca2c389cc0b2de5a33fab00d75fa7023d17e11c2f3230297fff2824c44d2790f53aae892c2a50e52457533fb422e6c8b3ddb2ace21339c30687a841a17b0a4674c9d3d86365ec47839c79866667158117ba1dbda37264faa71efc8343954ffe8dcf88d11ddc4a2d5f7f357e73f9cbbf2420aaf7449aa17d79887bc894bb6194cb7ef785be76681e5b0d3ddaf5209b5ccefe22d3a0c170887c4aba70bdf592a6cbda704e48a2e87eb55fae5b38d469406bbd3ba18cb93deb2a247b90b23ee8f0b4cd3059a7e0bf82f25ac6178598f525b9891ab1d6ebbbe11154b375ff456d3b92d4ef60f72d573c6c69a9400095d15b9ea8b77d83f84ea3802f80ae75994e25096aedfddda1e98a24bca8700598a97c81f660773010ed25de38c05fc92ca7ed3e598962010613e165aef2ef041f0097b701ea59f53850172d415cd02cb029461316d0708d8d572c37d15d41906c06851e1d0d6b37a795646a9c5bef13fc5517eade8250b804f7d925c757d9eb941fc3b6e43e77b8f0b69ab020fbb380cd5f4b3dd894d8721003b06d8245362ae0a220a989e0ec55d22ef76ec46de6e790a9c358ca9a200f580c0258bc1ef7688e7e872e8be06c49554621614806347ff566ecc60ffe5f9a3efb44aeb9f5632c45fadd92a79cb277546ece392e0f5a8d81e34bad1fbd999bb04831d36f69b30eb2b008ccb0da6f10d0b54285909d48c6aa5bf14b7fa9b989c04293c63fe5285e40cce531c8a8d2353f59de22c86cd111e843535e1ce26f38f584323cc7b9ba26ea2bfbf0a1632989680708bbb0ccf31e19cc5182818e2045d0f7afe751fca704afc1a92f43acc8cbae680334571979143be08147908ca5986da23076f44064b0ad4608c0a609e3b445650051b07c14b9b08f5f700be7d7a96694f1cd2e5f51cc247383a1671916ab871dbd8d7f8980374583bf8f28f54ff04703cf2edffa845b7a02b0944a913c31480d92a3f1866344809fd6bacc8405d82248dd58d980fbb424423408370136844e7f67e425c47e5525c28f970edfe9ccc13b343fe10611d7ae27115b237415643e446740c0d080ccbef0f9b154569082de1e0b716397af17900902550a30c192eb624bcb0f0e90de2fe4c6f72c8d59524ac0333bac8f86779c964c9a0c5ab0d06cb15b7a5fb5c6b3f0e00e0e7b9e1d0294baf9a3b502df9963881fc1b4fc1ba43fe10ea605df6c57d16b0d55ba379f66b4a24d185faa8e651f03b5982405d61f08b74f92fcd3bbb8bd0006d2fd1e91e75587d8bee909e904657bc03b7b8d74c637705a9eb3da0148a132baf428f5ec8ee58b918a2fd4d8d80cdaede0c9a25b186368f76434310fb98f726fce938b13593d1c141b863feb9eecd1b1f66898450856aa9afe10e41049fdea089699321fea67d34db17f495c78b332e72830ef4a81e8e579d2b676a6e39df7e794c9e17c8a1eca66e8c898e7171d2feccf8bec321b7e2af84b5f81bae1a69833748b3abfc1774a52fd74ea7a76caf03aeac830a87a940d55088c3a87a9aa5580f6968877103741c31e5834a6537f3560cc2e232f3477b1ad30e433486b3ecccb78a6da1d7e084f189318d6187211ac31923c38c720dd11933342605fdc6ce8a71c1b28610f56a855fa6a56db136dc35b26197c942a1c515e333e3691a81ac2ff63ac285038db766b250d9e84a1f23991a2eee3cbca47d783cd5ecbeac27cee215632b4a7283bb821ce3b4052ea7bb7088e9d1ed97aa9e2895216793b3a35cd83ef6beffe99482210edb116df791b77b1f0253239216aa5d1267022e50443fb4a206e9c88f62a97c09564345c8893462a3187510844828421c84a1294a507cee0d32bdc98a0f1c517b224ee5ed22d20f508dc2b3501766017002d2390a3fc4e419b0b46879a3c100144e150b837016685c5aa4a2a408112a55c261f3219618040def3c0f141bc2281a33aea2febe190d8f3ca91b58490b39be36401a501cb069c32cad4c6143078935dde526b7331e2eec38266716051b6f2cf8d5ca411ca5875b7dae5837edab0185744e27c4741cb1ca02c7e4331815af6e9560b1d714cc3a96d76b970f60df429b74a59bef34fa5b5b3d96e3a009d7f11bbab0880ae6e1eb0a00c5f6ee6655d4e13e1848d859411fa48738ae8df70ba2914202f8aa7b81079eddc84af1eb88f6f20aa18e7addc6813594be364f4f761b1b9ca5e348be0f499d467057bb342b4b8ca41d180a7e3d5438f9bc6b843e2a8c552864265804ccd3bb74416a158c66176e32a8f752afb3cf0e8f30b7f7bc8a9b2e97deb98edeb4ca4852266a258548a0c1b139341b8c0d8783eaa4e5e36754f0c011fc32e3a945deb416b0a8d7e0415854b2a88502c5ab2864c4439e54cb8d23737b09512eb63c6a4a13e30bf158dfab9ce28b6c468008761484323bfe77682ecddc63a8de5b40253db99fd23f68f296662f37031253d7346368498cf29ea12d9b685bbd158d6309921ae0002ea0467eecf837615888f64778941c2c4ab3ba609e023827b07c06a2e69bba392864b3554782a0420b8152e2dd6c13300b36e91182e4dbeebd0b3d88f9d9fce2e9d29da1fc1ca29c1692ee3ff7542eab20d2b0239b4c1709b2c2c63477f07ff7cfc84551f4c7e56a4718109a00d64a36605817d0397314f09f1dacfb32c144242cacc5c551c9fea2d025bee5e69d979ead6a8acaf2cd1df57f573b66bfce31786ab9056910c529436dc845a202bdea26b2e038c044e3f6b9e98ef42ef13a3630383004fe8854b33102e800492400083e7ecce9fa7f774861885f48457a6c1175f10023bdafa0f003de49e779ae890e1b8bf29eb71cf5b10413191b134110a7908409a97b01a09385b472506caad8ee691ba997da42e6f1a5911fd64986282ecc7ce221727aed5c996612c242c0fd396fd623494288992cfe9058a255d71c06d91771b4cc89d51c2abb12d6a1d14f48d880b8e05db7931cd52868c704a40e24a06f034876bfa635239b4489e35fc3cf4f311f93bd97327f6a8d6f6badcf51961909f6cbbc31303097d711495bca08d681314b95c4f417075018a3813e313e738e4014b402aec28715c864fde8862bd8298ebb1653d71ce835f79b2c23ad72cb8b8826e88a84184aadb8cdaa71526ec56b566191be5dff1cce35bb4db1ef5b96a4cf4c9577fb191ffbc0b437721b481d1ac70bad49dbecaf570e272b4e3aebaadf2254d6a492cb592d869b41bc950154f78e1fa74398be78479bcb9063722039f67cdbc7414e5031c99f3bfeefdfaa2c4cbe721a273f89b020f193437b70331940852af11f56c6b959fc79fbee784d3b34ca36ceeb712bca58470d3d6db0e792245de08d0a4f40fdf016189cbb3efcc7ed05477dac35bdcccdcb0b2a94ae1158f044bf731d541f0793d63ce67251a0d920126c796a016f5567beff370fcfbebf5808b46ef2bbf7d36799eaf223b6573b5bd64cc358303d38cc6a382b745944bab4c1c471c96b76f5626e0042e9ba8122c058ddd1069ba7f4156f88796b647aaa8b46a79c40f6957f33dbc4006c6793e24edf14464aa53625a5d49a0d0b3608d2f634ceaa2adb3d693a83a1ee5f6a8b93e8c0080c0d9adb286cb2ee542f9e4c29f2cdeb22d1ccd1e916f3196b58db72405f7b7a5dfdee43df09b41ec9c72489a9dc29e79fb4f8563600d44a58131252ab36f1fcf269188e60831097f19915cebc2447061d540ee87c9b94b1a31f2debb803ac8140b51d826d0d39d50fe4816cd0ab034a4f96b4b72edabe4f7ae1f01e73ecc8ee7e87b0740442a1f87543238e235fc357048520491e1c22c71678adb017997be72a45184f3be27cbeac2d9b9325b401f668f28d87153f549e9ea20ec42160b0b82427ec8bd81e783e9b2dfea2b45cea886dab0f6bdbb7e1a80412af54bac1e447102cb6db24c85223fbb71894e6f09a0eb679dd5af6b1333b8d12b5b2010a87ae87d41dca6a04f0d7a02db81c53a993864f4fa1fa0499f16de38cb5d826a15dd9fa9edd009f747a01abe2628bb030d5bcb0ce5c0ce4d8ce871c6f06000d65897a4d860c621c728e2a6066499cc723d71650798d48295089a79a3263718ad0146a3d089ad6b5e1809b6cb4285fc0cd06d9fe66d2a52056bb0730bef3e3d79f281922ccbd46861b37301f1b09e4a11d7f9b9398fbc62d85a7089c0c551bf4141abc36da1859e61f4d875d65e81c5ff4a3e55672810677af69aaea86acad3f016d9aff60d025fd4b8882f428b99b7bf3fe7e0954fefa0548b5ca12c68b9abdded58209483373a083cc058805513767ce0c956b701f0184a3ae8fa3db2c0e39110b826494c8f19263bfe1a16a29d6cc3fc4b2bed15e325f051f3df80e814658eed6ff9e55a60473807066062d80ce1a7cdbd597ca8470d88f6a31304fb4fdc2838b832f4e81840b485817faa8d3837099263a00a0735ea17ec91e0d8c6a9e7899941a66d0dae8c6766f6fa3a6ae9a3a8604cff33c29923b79fde21b5079f16fc0c8749b2f02f2f2543d82cbd283ad2a08aec23769aea46fef42981ab2464134540c6e0778adfb5c128b03b88be472021a6c80210470b371171c49d91d668fc0821411b70b5abb68c2974ac5e521b9a304c6d505963c0cdb4e804ae12c5033693a4722765e713111cdcc6741c6aa1560cb5cecfda7d96a927e4f57b98e4cf0f4043fb4050fba059138e5adda5be1f56cfb8c6f135678a2411badf0e35a1d162edcd1a4974a3347a7177cc5df7cf11e2a362d5f385166f6408df509ee35802d6aeb0e6f0e718c7bbe534d4a998354c9c8c89899a8c1208d15e49b415023d8696eb183877a31de98b3636c813881082c0bd799be2de90f275e9cb3d9ac24cf84b05a72e7a6889a14650232b30c57eb943813928d1e1d1e70c40dea3cd771e1858eb6fac5a21e6751e0ecbcfa11e93f13a10f287be1c99a6f5a327588761153f8727d0e32268482857721a68bcd35e1042a4d49484d797d02a72b007ecb4c52d4a451fb41201a31780aee9518539e923859953bd06f08fe5964b4c3a1433c16953f273ba2dde305063590ed13fe44d79714575c5281e08ac8f3566901f0cce75525880258c0bd39cd26cf21da274fc088c93264dbae6835f3c7b9282416f4b0f5b9c5460f927bf4d6166bf90c7e871a95e0d9494843d26d52c871e46f2d60c94567070394153bc99100c1122841d6c1257cdce73e2176fe0fab525eea9367296e6033beacd6994a0bd2cbfaceb0c1f83977062e57906a9e6abc2472e4123bc84b56b4d1d7456013437d2f252fad8e443f98272cf344ea7955c1fc040b58500ae46c9393d378e3238f9ff87ee3e1254fc161c46229e96f981d30d9428f359630a95aa06f7107297df57e106c402c15278de9cf23bcf3ee9c3f3a45c5bbfad173bbb5f6caee230cc6111d0f9aaaa781d1496563fe694edd8438df738febefb90454fe6a790c0db03b338527f0d3942f175c19c0f5c2609fa62acf15ee14aa5b56a097c78bcd945d01b139c2806fde3bd92b81e87cd48251dc260e0f93ba795ff3d0e2faf4dc106956b8b5c10c3764837bac46e02b52e6a4a556fd39a382ec5e8498b7dc0e8a71189081dd430eb262f934d5b6747e032cd69d1e0c1491acb5e3d43a46a6518c1cceee7b5a8f49f25c86c07564e8cc14d857638431112067f0b9f5fe846e4f6c675eb958528f1081305a436d00f78f168c6be91139a0c67eaa63083722d3951cf0afe7a47ce76167ad704f727e47899c8cc61a7a7fd363d36eedf585823fda0b4179bc846810d0d932be3d56979ad0fbd2367b0e5c6650336ad8adf7b5caea927a3260ed53e632b2cc9d18d3fdb304fa9749710846302a44bb60ab8404e72a1a338dd359712b388e787eecd5a9191d67fad7ded4b2c6805f2b1f5e876ec63b895e4a1b0c2b8e3996429347f685a160ff018d37602526c6e3160391839efa17dec1447a7558d39d65595d87e889329e88328e6e023f01f97107d001d0c6163f389b501e94f013f454fe10f14c138a998d52ace1965113f38fcf2f71e64c719412f9eba54d8188c90a84a9c5ef09871bd49c4e602bc3821019d637e9b9eaf9f8eb081986e811ac3a7cc067c92858340e33a90f09d430c41b685af94037e619a96bdc5446d7acc6fdbcbd7a356e45b0ca1417f37f21038ae392cf4d689d393a43ea7acf21035acc4c382f15f603be67a3b8ba80384f75597d217b2ab18321394ad745d4ddce186e98726654ce269f9e4a16c0e1ef3623c353be2689fda4f3fce391c26bc410217987581b7db5aa7931e5bce58db22950d551c0663d027c8c87d467726a0f54e7b493d0c64b029e190e2d8ada52b659b5a3370000e7223d3fc8eb47ce7b7161408908888405925bc3932fe93515c62845c8a57c49065a124c20ae14fc6a7be0ad7ad3d90440232098d82a8f8fc0215fb7652cf779ed6529fc7fc4e9b53b23374f9661901fcd54e2dd55a8cffa600790f232ea513812e66382daf7e2af5038d615bb68e83051c5fe4420dfd8cec1521432a311095d2bc60204ad26387e300e014c422fb89387539b96f264701507101d9758de3d9a81e04849ea270562a8a2491c0893efc1b914fa491097510aee4619e5d50232b1c59a32fc4d1ae7cd098a33ee001103ba34e9400a126abcac9e6524cecb246897a216f2f6e27b5f1977c490ce5a3432a1a6eb25ca699e4c986eb2d713254efa72c2e4923d9a3869ca879376d1c00c068b1390b639c5d77010aebbf74f13649730f28732184069bbc6738392683e39466824e98008124133b2313210b54ab9a0321ab1171afc4a684cb828351ab84878710c901e943c5cbd3e66b71f57c1b2e5b49b2d65b8ffafbf988156a722f18ef8037506040f1a3a7a8d5f1e92f94d09d6182552bce4de9268b42a1a5049f079e965c8a29876a04bb54c18f8ff25def0d165e13467603b7afb2e1f2ebf738bdca4fcd182fc541418263dc362ad731d3c417c76f2499f7f67b7b4607d09ea6caaa31e18b59bced1341c1cc766f464d08e021c57e6cbd2079526ad23914c2823028709560f84372ebfca87cdef06c2896f99d88d3238bb94800f0491921dc03b41d01cf4225ba495b642d504927cfbaa996b508311caa3f32ac63ab2f820eba909469c36338e5cfc63e334352efc9cd518c58a250ce4d733655fd8b238d4dab53a8a1d65a94a3d95a532b2b122aa9dc538c50b4a38281012106b668f697bf746fe0ec8322e0306b8deb5eb1a1ecfe1dd6ce71801d636d83f6ebabef82217ef8cf1dfd2ed398511e596b8751ce3afee815bc1e2fc189fe39e848e7468e58918880e03632d6f2b9e1979e9df44c695e1f159a67d0ae4f711fa5914ba1b0c7d425a742ad6e8b372c3d2262b14b662c11495fa1ba5b9e35ab9b1dc84b883b3eca6e9d339f9f47da498e8795298b98ae07662895ab726ae81122de11e9b2978ef6c67e25398a8e4ba5f923c32fce782ef02514816c3868317d76acb83697b68ae9f43c46de1307fe8c28cb042922ec6ec87e20e70770c224da9380fe9793141ec490a6f4a8533eb2c2ba068643872245ed19f8f413a2a492a52b44d44919748550f7e622f13d486964075e2806f9580a70922c421a59f5ba0eb1f93b0a461ec62dcd4c00867b516c43231cc3a2977cb33290db7587832525875ccd416963aeb58b13e5eae83ed5a875f93fab067f700d81be3f08f57c91089347f79ae61aea21f9249a80c71ac4fa5dbe75ebd36c04239d6300610516c2c5064d63131c0279b1bd3008553384a7df4314e9bac7e8de32f3f0059a785cb8cad352e68e2cd5c8b6956cc305a1a700576eba866cb18ed54311b95f745ae5113503382938d301b179ab1f3adb85e5c246b37c56848011f435ba8372acace4c68dd109c39026e6244fe0a277078e70ffc0ffc0ffc0f0cf36f9a7253f41a70969bc07393ac8854f024073dc9911c492febc040e40a160b370b661d49d9647ecd61c7118610558bb7d1eefe9e4698a23dee96a49cead8c308b3876ce220e5971cd545987365143e67f97d7cb622cc9ebd8d539474228c9d3a634f21a334a93222ccd02c7bc78a7c085384ec3997df64bbba86303d9eedf8ca17c2605a97b2e9b0b4d28430a66789473e0fc2144288c94e5b49951584793b2cfb51b018be6a204c21480c71106c4018f5bb7c25aee50fe61ccd89ebb8d8f14f3f181de50951a1cbade5ec83e155228aa7c639594af2c11022724a3d71d2ec8c7b303b58958d8cbf5672a807d3c5aec9b70af360883a9fa2526c2c4d8207e345d2cfef29fad1237730ed9a68569cf5f7d80ee6e8aaea9fc8d5c10c424f18cb0f72ac8ca383214a4e4e6ed9e6608e69114246b927e5c5e4a054ea49df5589831955edbea48f1d720e0e664dc71945cafff715bdc1f440fdcf3f94e488dd60b230b1a5d223cabb6d305f3eb14e122e495dd860d4b56871b234ec8cacc1fc7ae29375d2996d6a30dc4c32d14d2feb290da6980a6b1d7e5a2363ac208908004128001a4cc1c457fc42667bf80c86ce312b799c3f6e143398f42bef3b2c1f3beccb6016d752fffd7cd635190ca3df8d62c231decbc7600c0be5d520470ce638f76d1fcd1306935b9ca507e60183313a3baac13678ffce170c756e5a5b298549d2f18279445fd2fc7cbce99d4b2573ed10a9b96096fdcd0db15dcb7f0b46096175fbc1ac05e356dcbfea9c6ceac259307da4e4f0a7df73238c05f346789c75627d458faf60d29d7ad78fad158cdeddf20f237aee8bab600639f5198edf436da582412f42d60a977f44d52998545c7f6246cfd4a85230a5f171145cdf51eb250ac6afb43d2be90205c36ee7309d81b8bf5c9e60ae693d8baf473b392798553d23b6f7cb5fd70453789cb3a452ae9c5b32818af4472ec19c2fa5989fea2f750c9560dccc7d2cc127493068aa7e8e8548308bb8b785c7c142fa8e60eafef865b9b2563288114cb993846efcafeb5e04a38497aa0f1193cb12c11441233b0eed7d1b6f0806b7b9eb8b7c9fb9108cf69bb9e127a1831f0473a7c9d925f5e488a70040309a7e0a6f59554e56fb8541eb524bcc9fc48c7d61fe49e720e46fffa1d50bb3443893f4695e183a6dde85d9272f1dc47ce059d1851975920f09b9917c47b93074793d984871ddca172c0c6e0704c085d1abff1e5efc6f61f6748d1da5a8976b7e5b9872f28c480e91ac77af05492ea6ae45da69619471c7368d33de8ff82ccca5222ea11d4e1fe7c8c238f12ff28f3cc85939b130cc64e81844c98185c1c31a85ae38194b9d571843acddcdfa3f87bc2b0cab9197e3e41879f66e853956e6a343553892342b4c32fe293f63bdf1be0a635e7665e491d4c9556190b1e8581b87536148fd9cb1b1934b928c8a5ca2566d720a73579830fbf9e64a5318f633c6fdd1eedef196c2e85052aeb0eb125d521876eeb43be318c9ca5198c6e2fc416d4c38504561080d2fd487e87c4e28cc96a912112dcb970714a6baca2d9978589efe84e953dccea7638fea8a27cc79e219b754affbdd0993a514353ae4f1ac4b72c20cfa63bcf549aba33761daf9c61d5b3ce5859a306d668b7fc8312d6f98098396dc9ba5799cf7c184217574f7240f6adabd8419a6ff4aadf9f75b2c618c9a14ae3a924a181ac589f9b841286198e065b9365ba2e593309875ccf55b12865c927148952f12c6b3898daba555c2789030e420a9fea2e41e61061f7c3e58b0988ee20853d85699e49f1a49b34698dbe5d4662a54cfcf08530e529d9f23fa4faf4598231d955d63eb93b8228cf5f218ea3412618aecbe0e268b8676b482240684a0023c2000220c8e53d68fa9f51d81008730469e79b12c29bc7e5c8bc04000431cbf46a2a56c54276d20085008f34ba83c59a2b27d5647a9820084783c4462c4c43c7ef42088bae3a1c26a6ff54510f69c46ca87eecfc70a9252506a0f040884b9a35fc683fc6b231e4018a75b32fac7b50fc2e50fe62c96ba3a42fbdf4156903488007e30973f18c97d91f3394320401f4cad8ef16bbdca23fd9b40003e185d43eca1cf6be5990d01093080066e83188ca0010d8081bf80c90d76408310900d98200502c00301f660c6dee87e2eca4e62bb82a4149c1608a00753e6a4fa88afffe85108900763f5475f1d5d2f8f1e0fa60db58c2558145b99dcc110af6fa7755f3b984384fccff5d82b7cb80e268b8cea7262495785e8608e9ed1597b3cc67d3f0753459c9b207ea923a3e46069300a03049083f172f89c1ebb1fa758881c408038181c36bc74ef90aa0910000e26ad10527564984f0e4941051ad00002bcc11cdf29673f8c8c8ce31524d5c059402a6840031af00302b8c17469a24904ad48cff509046883a9a15d92b476d960fadaf724f143cc9d2328045883215423577dedec91b71a0c2ae218ce65e9dd504080349841721b4fa10e6210810634e0b80e2ed1e05ecee670cfbf2cbf82241710e00ca65fcbbd1d24228019b0ea14a7c3227e8ac1b181bbc005db81063460073a88c1a7e09c6d400358b03408c169054014042883966f262c5ed6abc90ab2010d78410c68f00d68800460c00215b080061c204f10800ca6141bb53f9edc296a40038a0830067cb2262a578747bb1870f0285d4ec3803b30b1cbfbff9e0403af923db979bee4be808e8709496e4208034ede80012f60c10a6ad0007701af0d70c000ce1b30605c0d08e08557c33b496ef08262831b68000614f00001ba50ce3013ebca29c9325211d5304b312e25bd5db07d30a04011800b668c937e8bd8630549640b04d88221bcfe2bb936beff4f0cba06a4063190c1068a6c032648010808a005a383b88aad974188e7c982293bbb62781e3b099f14088005e387be8e5fad9d974208700553706c6715adae20e99415ccba697a711a03c7c05c09100254c1e839c5bead6c895c710250c1fc29b62633a2bd27dd04049882513e486587624ac11cb93c69d8fce4c9d20d08100543348b7d67f1ebf17b37c01210000ae6fa9c2dff5d4d9eec4f3097cbdbc734b73c6e911410c009478662c1c243323323aad66c1d665f037743500119d48092004d303cf858f13544cbbbc304435ac465281fd33ded7646327d2e4d22400025981fcd5af48ec5ee8cf1000224c1dcf6ed8d63a7ece86824183a77e8b4dcf93fe51ec114d24c6ca5b7b6fc680453083a295d575ad68b45503c7d3a22987ce6373d8c4e7e670886fdb4962096de7ca40a104008c65dbbd41644a31a864130669735862f0f5f3b45002098fcda3a23a5b4d5bf30d7d99e7fed4ca5b82f8c5f16af9dfe6b2cdd0b636e38be14f6365fe585a962c8fb8a97bd85bb309c45c7133ec7db8fe8c210e3b18eadb930557ccfbef21e17669473b8a485fe16665429a5f3f030563bb6307b90f49effb1bf836b61ca18a75c07939d724a0b43b4bd91bc0fb1149c85494ac5bdec3e3325b2305638aa90133d1e5f2cccde511a86440e0b633c6875e0f05718ce1d58766d8b49c35d619e8c90957676a13ddf0ac325bd9c996a8f31feb0c2944d52eeef0f12f39e55984c2457ee8410721c8f2a8cda8eeb1d44a66c785261d4911091bf424e750715e6c7f9ede748b3f1744e61fc08958f7ea643d8db14a6065211afb397c26ca76395d1d4689e4d0ac3679cf3ea7ee558668fc29ce63c5285e4da48b6284c3712c2e46a7cf9c80e85e1a3ba1f4fd5ed4f1a14a6ca8cbb99496182a43f619edcd032b6762c1b694f986f2754cf55d599df0943ce719751a7efde0f73c2ecc8efb37dd6c17a7813868bfa7e36296da5963561088dd16464933361c858c26560bb396932260cf1730e9d22126b227c09c35834aaf4907c428a2d610a17f6b42e663f565c09b3bdfba4a824318f9812e69fb3fd5991b868f024cc515e95b195b1b7be1e49fc10e6f3d56ff9faac1f3d8630967f64700da5c3a985a02c43f78627f3394210be4caf23e5c9741066d3eab309fdca218230543c780caa715c6a74208cf71f725df21910944fa354ae1e76237f304b0821dee998e5e57e30cdcceb8d87e0e0d307a356dc47b62ea39b321f8c6db11c7e3657cf760fe6897428da13a3ddad1e4cd32b2b71745f2fac7930c555e5da477b469be2e1c6a042bab4e1a8b27107431eb324f31adf73297630ade78fa349e13abf1a6238401d5095ac0d655ba24d0763dbe3f449f9df56953918552cc5f7ab143250530e4c5c928e162b9e38182b4feed686e56865c2a146491f8a45db49ae208946180ef006938c571c6de91ceba21bcc1887fb5d4d8f9f2c69c3ef9ead753c4f54d860f6bb64b1df3cfb37c81a8cfdd959afca27633e35f01627397ebae86ba461147325dee3d0a1c1947fd2735547eef2cf6054d70a3231713318bb415bc9c4d55d489581d5e9db47ddfd1baf200969600319f0e00064307b4adf5c46765eee188c33d3a76e41bc5f138349a52e7f4e2e699141612898a77851d2bf78e3c1904855c3896421fd8251ac24a47ee9054348921c557434b95f72802e1862be6195ad4c8c6a70c1142f5aaac670291c600bb6e3ec4a61f3bd168adc25251d26bd16c9c2f071b8496942e2818566e2872df94bfd197705a35f6fa38c72c7bde85ac16cff187cd66547322955b0ad2cd742e7fa97a8a088379824e9b35f53d872bbd42363dc7dba140cf12578dcf95bb03638281c200aa4aa8c4d82ff5e83a1a0c5afada89b4a97f2278c523f5db7f969e4ad86fe9dd7fab2be4d783ea4ad745339e41c8009a6e0288ada6479f0f05fc2f1ef2045e790d47628e1e4e9cdb3d844679360ee9db4615ea51e4b47028eff62eea6a8a49e47b0363e8aa78e44ce08af6789973571a10314c12067561321ba5ba5fb0044289cbf25b1eac6c8fa21d439d78710cf412442184faf798ec67bbc1784da71f898f8bfeb197f0020943da5fc1eea2af78b2b9bbe6f5975aaee7c6148c92da2db799bc4db8b3e4b4ee5387ed7c68b62d756ca33d740ebdd45e7571d923fbe8f82ba481e34869dcb8595d21c95a5608ec7055641622ac5254b836f716c758cb2e655a8b660f734e651c6f239856aa124178facbfd738b4304de742545f3b3e6316797e5d5fcadc364f165dcea8815b9474dd1a0bae42168f56919ef2b078ede26a1fc4ec5e31cadb713c67e407ba62e9ee9ee0eb51713dad2059639897b4d33b4d58f1f7e4776c5f76045761ce919eee8f43483a892a88d10f8970a1329053917968cf20ac45560fa2c2a91f954e6db51eca5318f284109b91d2b4774ca1348aa6b38e32ab52181c39ca39dd871439c828fd3e7c0e3ed151585ef2a1cad2279d4461cea132d668c6819b5b8128182014c6c919954b1ea2a70e0a834ae98e7c4ccf9e439fd8c124070f2a82ccb99e38bea70b8d45b4f64e18a45fdd752c27e43b9e60004eb497ae425073ad47eb26ca1fefb7cbe6acd304a2a7d5e7c8842185facf5956ec4e764c186592edc4079af70ebf8451c42eb767d496304c4acaabc378fd70254c161ffd747c90912053c29c1cf6e978b08e8aee240cdaa981c56c5937874ac2b0924e3e4f4689049e356374bc41d6b12161c829788f4a368bf32a05e905033ce21c7ef13be4b73fe488457bbced2dd7db082394e5c3c7a0d1ca88e2a3c66e5ad27b2ec2a052951cc8564a5751447f2b591dfea5d4de27c2f4ebb7597e1e440cc2b8ac84f4be790e6176fd97eebce15da621cc8ea41a4978a71066f95453a1f2c7cfa943083a2b2f48ca6dbc0da28e9c0ed5a1e5b2ba20fcd3cc7b903ebb1508e3c788141a7c0c08634e8e9c54dbebbbef1f9493ca22df9e2cc50f76b8df4c59484615efc33dbafb71563d72ca873bf3a25d5c72bdec1e4c1b7165d7fef13a8aeac19194e00d1f8eecb97940a5c5dcd4ef623d878741766d8716c92f4dce1d0a4982fb6e353c0b713b18c3735c99dc2aabbe0ea62cfed93e5ec6f5391db27c19e438fbd28ede39e4a937773737ff9783b173f0bc61596d72ca3898711eb3bffacf1febc1c16a2413ff401d7fde50486be319bd6e30a8850f2d19f12ca9b5e1c7da216ed9e59b0d26a99c2cd954ad93576b30e518d33e11cb991e3550fb7b227feab9224f8379a71aa3b2600034982786e78b1d34e5527f06737a0c3dca7a96cf8f37c3d054c6310ec9df3298c187d8af2269fb2b4e06d3458be9b00fe1ffee3118238bcf58c68be1cbe89bf084c11c7f735af2e41f59c0f0a4f353f5f20b65bdb3dd19d1a8bd60ba183953742423a4b60ba60e6fabf095e3c2285acaf6e12d181a6455e44b650e2ea505d2af5eb677d4b964c16c5b6ad31fa34b44c282a1d5442ae3b993d7320506b8825992a42393d0db50b78262eadf393e6891dbab600e26a9636d6db27ec60a9252b0031ad8800af785b43d139a82b1f55dcc75f42f348e14465a0e631bc3fa8b76144cdeef56eb6d211d83a1700e19e9c53dd2b49f6094f91ce751ce39e18a90e76c2e477ffd4d307985643aeadf1d0f638297a63b5cb2822418b0e0532083a51a24837d81d120063658d58101966012e98fd0198baaa4d0201dd840062f3030e0000324c0000930c06fe0821d010324c0801d3806565083b5000324c000068420030c90c15291c1bec08801946092af757496f62f65d38c0d900453aef4d1b7a53ccfcd152419005661002418643d48f63dc93140020c90000324c0001a688001126000034290810634a0010d6085018e608e7df8d937792f59358011cc78723807df5f5e25af200f0246570628021f1d54a52bafd669600310d0c05db06d002298a3db6b441a3137ef94160c300473925e499d9fbe87fb160c2004e39c554af9dbd2874483608894968cac35ebe1c0820180605e0f2139a6566ad0458202fcc214ca73a3f0b8e85fc90a9290c8060ae00b73ce203356bf62bb1dd80b53a7cba0cc66f45c6a2714801786959812a6c361b50b6305f11cd153c838a63328802e0c5142863b66154b7eb61614201766f3f3bd3c8df49fd32b485a41c74006be8202e0c2105cdac14b5a465ee70a926490028b42016e61aa9cfad2877c27f3fa0a9286190a80130a600b8347713c293c3994edc0200647030b380c38d08006902c0a508ba2a39021555661c941e48a9d759797e6133f2b481ab430e4e9180dbd10034250810303d38006008014059885e9ca1ae407e5cac2281fee725245ff1f5c44140b7383f058bb301e62a70d64c0026fff8004740c0580851987a34e212532ea9c6101068420030c1043015e61f29cf2fc08731b512d802b4c77e3df5be1b01a550ad00aa3a7b9787f7275b39102b0c20cae52bcbb5c56905483183c049c9750805598e2bcf5a48e7dfff0b32e50005518c3d61a05bb759cf35d4112394f142015e66029e460fb2b17695b4192d50858200308f03a2f0f3f14001586a924d5972b3d7f031ae00d8318c0c0060d68400d1ad000093040023b7818a88005201881061ad0000930a0ec06388208c4e053e082f643031f034844014e614a7b9f9f1ae3ea0905308551b2f3995aceb5725f290c11d9f6fe73496cbca430a4e31033b9d3280c9ed3b6ef3afe5d7888c22833735f11a19dd7344201426148491a49ce11bfc76e28140014c6778c36eaee234c265328c027cc59c7d27f44ce1f245f4152f6a0009e304f96e04025fcb25f7605494e0319940e0ad009330ee1c349c706d971423841febcd9936c58f70a92720405d8842147d8444c6ba4200555301041031a50004d1824ec27c75521be2c54804c187ccf723d90d2c619b482a4183c0c58a082241b78054a110a8009935545e7f078bfd2f397307ed01ad7a990cfd4b784e9d6a3c465985dac7c254c0fda7214efe8f68fa784d1772c6ae8445e89fa244c296d35f248ae23a64bc270296d5d42cb62871e096343d5cbfe97cec973481c92ff5aa9781e112a802424800fa413b00773946832312f5ec40af560f628165dfdea2c03290179309bf64843877139b58c07f3bc456964bf0d3f7c77308eef8d59fdd967745121017630accf5f6fba5af413af90803a981bb5564bf2d0c170717dedea2dfff736076366cd45dacb3edeb91c0c3da29b922d458ff08983e1372c1d46aa3cd91238984f625b5a7f74a8f45148c01bcc1865c718e3fd8a8e221b780d6a30680b097083c91e47ab9c43420d24a00d667bc9efca11b9c4ad1524917902d860facff1e372a3cbf1dc3598835cb66790e17755c80a926cc08217b0c005b909508331e2fc43242d57a6bc831aac551a4c691aa1722ccfc61eb7e3c07c900034987de5513c587d073dde064c90020e24e00c265d2bc995380cd6062c6084800d5810810634c0062ce00d9820051d4880198c1d0eb561e7cb6e562b48721eb0c0062c80c1d6a00ca6f151cf895b165beb0a9252b00317c840065be84a90003298e2a4ab9ef6b76c213830d21324600c86b89ca8725c5ffd6831985bbce1e448179b74e321016130cfcd84bf7790bdd31d020283d9f53d83947aa1813e3e015f3097bec3bf92d50a61424102bc6068107d2cfa6fdac1ba82a461840474e19c4a7699fb78ae5b41520e96062970ff149021015c3079d6daf0e0a22ba5c0ca0209d88239c9a590ae6a1bda578783024302b460b628c16b5692e8a9e9850464c15cff212a5dc638c4bc3b406a0f09c08269fa25f34aca2b983cafea56c3a3bebe3924c00ae6928eb69b0a6a123b5530e94a048d0b96a482517a6f3ac752e475255330ebac3e8e5e69f52f1d1c12200573f48a480f6ef352582f0151305a75c8d0f4318a52900028982be579cbd1e14f30b59a4c6984ade038ed843f2c775b37301b0b69600319f420014d307d84b9a87cb9723a478943029860ec8f5efd8f0db3d1684b309f954a774e58daf2dd9000259851fc90b0549e336126c110d1dfd27a0cebaec90726608135240009e671d41949638c3424e0082689aab10d3bee5888450d09308221d64ec655e1423424a0088608b992fbb5554a1d13c1dc219ae433717970e12118f55ec527f2830e9b9e19122004d3c57674d1dd967b520e8c21014130639cfb3de3ad6d7c27b1210140304defa38f9ca3c3a58c2f20e017e68e5d8e333a3d091f5790d48006b4e320041960c00e681002a3761c10a92f0c77622a8fa203ffcff7c21c433b4549d9f3c2942d57faa9cf29a8f4bb3065a5bbd020ab57f8e8c2e4f9ba758e920b73ee5648907aad0ce2b830e4d63e73fcddc2a4d928f3d1bcd7576e0b837607b3d3add3caa985212adab6e24d0bb38446ff8d1ba5ac55c91910300be3bdc553bdf49185e937a342a2fee3994f2c0c391bc4978ca4d3ed8185f9ab3b05edca7985d9e5a23eb0e8b74e5d616af0797774247fec5a618888fa603aa39bd45861b617558d1cb2c15cab3084e49692c36ed0dd685598c54d24e8e43d943f158614362e675cfd60755498c48245ec0cc7b167a7303fdc9caf643615265314213bed5cc8e05218e493e5ecf493c2d4c0a35e847f4761c65221b8bae4c8a05e5118462a392e472a16f386c2d87fd9a2cab3fd5f1614c6cb93fc84396517ddc70ee473e20973a66d8460e39522a413064b5ae559328828dd72c234118ee28f397c94b19b30834bed592a57ac64551386898cce0fa962d267c228a99e54e4d3aa6c4c18ede62704e9cdbcf0250c9e82de390a8991a296305d87e4f7dfaf7c965209934e4a8f73a30b25ccc0cb77f5d2586b9e933063cba9d69e5212c6cfb6f0b12ab7f73212a6c893b7a2786c18718484a9d7d2a274e8ccfae023cc57de395c7f0e963ae80833aefcda92e5d0638a8d3085f0174f613ba5136484218f7f449dd391f17011c6ce71ddf1ce3fc7a1220ca7fdd1a9e367f90d1361588b101a574c29cb134418cca2ce3c66e7c99e1cc2f83956e70adb10e64f6321c2e43cad7321cc5ddb595daa5159268430845422194467ef996410e6be498f099d5d5b24823048a36d9cd23bd281241086d46c997b8feffc08204cf963a5c5d0889c0ef20773e5e8f9a711bbfbfbc13c59dc7374148d71be0fc6fbcba858a936463f3e18acd24bd72da5f8fcf660b8fccd4923973fa44f0f86e9797d6c7a9164be3c98a3ee41a74e9d4dc2c38339575a474dcff2cadd1dccd1f05383c919e556cd0ea6c828957a86a7944eab83e9342292fa7779b68c0e86ff776dd47f9a91d71c0c153caee7cf64945a723845b4c7d21312075394fc5b47fa695e8383d12dc457caf13baa566f30adb57894746aa38c7283e92a8c8479bdc4f5db60b238898d68d9cc3e3698c3a79ebad7680d866f8bac16cf1e32911a8c1e3a0daa34182f6c866499d6c61634985348ab9fbb7b1f79673045c48b8bd56217323398b72d4a7cae71e80a0494c110b2c75dcac8d3f2545320800c861819578c9b47c44a29103006c34b3ada6d954be921314080188cb532dfd2285520200c8614641fc897dae7ddc160ced99c98b895bbba7ec1a86e21bf2cd9bee5d10ba69334e1547feb41b420a00bc6fdd0f750f52d4b084100170cffa1ab3d54454d3a37c00009d800044e83184800015b30648f3adbb1aa97e868c124413f654f777b336a16cc495566dd63e3f89760c168e9310a92ca43c0154cfd75eaa8c7bb53dccf3a0d944000015630cb686535f9602149af82b9e3f566b278a58a53c19053bae4e29e77000c089882713d7a4aa7b5121fbf523047fc95d4be12ec721a05935ffa489a34718e52a060c838d4c42f74c593c7f8000324c080e101067420030c70810c5ec08214b80b6470031ba400c90b087882c93f560676592a55bcba800027982bb79fc78a907168e0c7200634e840031a408317bc200532a84122a009a60c87765e951afed91524612310c004a3eb84f6efea7093ee14d4c04f0c605037e0c00a6ab02bc880b3800621c8403b0d2a47818025500310a004d39fa47d05492980410c88d011084882f1d5b5ce4a2e4f5cb482a46440082a70011870a0010da8410b62108315181602014840c0114cdda37f72edad918e4a03028c604aa65a6ef1f416d0200639288259525013b3991c8d010144303f48fdfdf921040cc1fb067525777b0b62108315a4c00587055584408010cc2727eab971f56410322020086633fff94b639e11342c0302806094f31cfbcfb15eee06030e4860053660010920c000093080011260000324c000064880012060400832c0801b447e61c8db2167ffd10ed2a52f0c92b916d3b0b61e5507e885a13d845c93ede2f4745e18c2477bd949ef96e72e4c939af91dba1577b12e8c2d252712f3d9729ee4c254eb392eccff21bd85ae6415e73bc02d8c63f9d304eb3789ebae2049ab70005b28ba93bb2fe58adfb5307a12b7d78be3b87b9bc2016861ee8be968a7b37b247f168648bb901fd22e0b2ad6d2681b0bd37712f9fcc8d724d5b0305a187764f9234a96fb0a53c87d8c1d489ca5495d619c4a0e5afe3edadc5698d24895ee5e921566cbedad22d9adc2106e62c45ab479f1c70a5215869810263df74bd8cfa7c278f11979cc93c7e71f54186b92a73da88ebfd43985b973a6f765e74d4a1d5398f365742fc75d0aa37b0c7520a91dbbd9a430696c3a2a0f2e9d7e1d85297efdf927159368a9288c1e972b48834a28ccfddddbb8e1ded60714c6bda8dbecd05179929f3078527f189d2557f89e18ae5de8e420219d583672be2c51e4843be9bf22dd8421ad2bb5758e922be534611075cf403adca794970973d7573b06d1aef3c530614ee1a1adef3336bbeb12a6b0f34022ad650993e388a974de21afac4a9841565edd8a73b1aca284b127ca51c74fd6185493303b0ad239c44927d7491286defb495524cc95637f83f01c1a0c09d396c76f71afb028e911864eaada79792c65768439f5efa4fbc8b8f3d408b3a5d889faa713428311a6fadeecaddcf0ef7f115ca7b71c37df2269400390080e075004729d5d62bf62123b8930f73bd4d69e499f538f08435d843febdcd6059730e4640ff9b2254c8d326abc1271f6522a619a127fec5adff1154a1872747fa6caa4aa3209b344fcd94b3179a948c21c2e27efb8d1f82b190953aa8c72fe0aa171242161c6af16a562ee8c898f30c5dbd8ea5e879c42471882ab3f8a32698439b6743b86aa7de131c2f0e9a139d2bae8d02dc2e47f95d1f5cd5ead08633ac89e114d84e13cc4c96a25efa521c2ec12546b5d3b8439079551b392eba90ce17cb80cc25f0833f0ba10e6a867744298c4aa246689db58681086efcbe14e4241985ae6d3b4e718611f1808438e8cbf550702c218179722b687faf6ff6086298d4fc28412dbfd6070ec51ab410a29b7de07933a5249099d0f6690f7d431063f613c7b306dadbee77390726f3d98322e6c664887ac771e4c73d96aeb49cb77c683513ff7350e3d5651be83213fc65253723215dbc118f972238f708e515c07b3d8942309b16e194c0743eaded6c9d3ce78e76094aad0a235194865e560b4c9d1b137360e061f8df686161d54858349552a7c46f99125f50de69736ffffd16b98bac1a00d426d6b461aa66d30bfff4b598c07e21736987327358f9b90d89735985cd3c2a5f0dcb152d4609c8c499d3d2d42b0a4c1a41971737f52c44fa2c1d466276e2138c6769ec1d4d93ea78cbe7c759ac15039b5d393f5c52fcb5074644b295f4906f3c50e0f17a1a1f73806e3dc65b750a933ec89188c1549c28e657db891309872d7a6e7778454c1604e5267e19b3a9e7ec1249f62794e7954b6f382d145ecbdee8251577e3b7ab24a765c308639d6b2ff88796dc1148f42c628a405a3594e59af615930ebabb687d90916cca0d723aa55c415895cc114d53e9b85f14615112b98572a875bce7e2519a40a062ff59b0915cc713cf75218cd6c0c3205e37fd07fe85e96752918ffe1875bd1cb157d14cc9db32ac9f145c50e05c3afc965eb1025749e60d4926958b5134c22b9dc2d7c9a6094c995ad7d33c1d49b79532125bbcb124c162988ece5ae7cac047368d6abbe051d0727c1a413222496e7cc8f0493e34871943f827127d24ddf76878e110c1feec245ca16c1e8297f12f128118c0dbb1bc565014330dfbca97d6789e0960508c1701f34373fdf985a16100453f0546625faf1c2b900201846c7a30395cdb8e42f4c793d39f09026560efac2e45172aaa0622fcca13f5dd97734db9017e6cc6d155b51db89ecc2949523f161f338447461e8ae1017cc8529784d050fd1f51e5c1822b5a578f4ca856f6130b11eb320afbe9e2d4c6b169d9bd7c2e8e3a0de3f460b63a84cf9d8360b43c79e779c43a4e4992ccc511ac70829e3e38985f9dd9264576cfa0d0be35e5fc658e2a8ddf40a439a947ca942ae303fd4f7a46321de48ad3088674d945769c9b2c2f47eda96ea2acc934c2f245263bc8d538519adc8787e7ca930540a9151ebc64ee751617c7cd136b25398c7f6472f66a2546f0a9334fc90f17cea4a97c230ff1d331da4304fbcdcc7d04761e82025bed11185197e84328792b34c1e0a934c6bbd5b5b8e7883c2188efb71e6fd0963f74c4de5e839a3b4270cba0d5cb6ec2b3bba13c6ecbc15fa27ce3bcc09b35caacbbfcd7ebe9b30839a64961d74e5753561ba18e2e9153ff7b2993044abec66197698473161860d1aa4cae5a55f2e61f6f0f03ad7c51226bbaf9c6f2476cd5209b3a34eed78d9331da484d16f22e29493307b32bb47d171f2a1248cfd12d342484f9e23611cf9b1f5742b5242c2e4482fc44a1e618cf5e821c3d26e8d98021c61ce13d2b89d8bd8444c01658fb5095e21620a60c49f41ec7010ba4598752c5c485b11e69bbc6967c9c65d4b8429e7753017ded129438429be8448163292903f84c93f44b6ffeae8784318c244b45e9ab6f10a61ee9caebfb532429843be88d721fa8746d920cc392d4d444710a68f5607e9fceddf4018bc42a38c62bd68840061cab063a4e4102cd27f3063db37c933dd670df38339e47ca7e396da751fcc38279d5c89388f9e0f8674313f9defc19095723d18d2a3dcffed20789e0753e8e9758d1c3c9845628ea7eddcc17815ccfd439cc61f3b18efc7a452bcc87dd7c11cab2a72a84acada743069a4ac06b1c347da7330c8e4a852a932b249cbc11c1fdc8b6e778fd47130d884f49165c2f2633898e274dea7b30a91f70d663191d01a6929a4d50d6690652655febc16d63698f751ead8e0db3ea96c307ece778e4fd760ca6b1de63188f97f5183393f3ef2ef68469f067350b390fb2dd172120d46472edb95d16730b67547f0b6b3f6d10ce6bc171da98b65307a6cc7abc617134132982552fc4848b89043c6605cb34ac15266058d88c19c7a921be747913b1706939e854a29447a990f0c860e8e7d32f0862ede17cc6a5ebfdf25393dce0ba69447252d6ed50573ecd19bdad9d7dc8a0ba64fe7a525e72885565b306d58063b2e133967a50593e5106c2c9b390e5965c1d8b223f3293ff44815168cd1355b39ee2b985e2d7b8e5fea37a9ac6054079572889ce2034755c1fc1e2fa37af76419a3a8606efc30d351c67b979f82a125db7b10998cdca5604629a2faebffe7f32898b45278a40e52c27aa0609a0821d3249a95759e60724b4fe943630fd2718251dc1c4a3f4cabb89b606a282622eb19e6a599608a20692b6b99494a2fc154daa11b445909a60acf21d597a50f71128ce136973b9dddbb8b04439609be1ec1101e66523db4faac463045cec152e8b7ba9c16c1343afa1a3d11cc915274fe7031a196211825d885ed76b5d44230c5d4914e5f3339190463a472dc1b21fa47140004839f383899ca59bcfc8529bf4ef4b7a02f0cd2359f7bc45e98371f79a989fc4ac80b536af50349a99a38d985792ed8e6a5e3ae90e8c2d4fb686d7fde76be5c18cf51e5f8b91d6fe570616c64db0f4294ca92bb85a923fe4fe5982d0c49fb6cdee2c3eaab16a668d8b967bca1d4450b53636ccdc2ec0fe3645c259a2f0ba3fa5a701cd5f29d5818928458687d20f9c2c2389626921d4409788569afbe533a69899c1f097085e93fcf8590f8931d3f12d00aa3f75eb213af2cff19096005b962deab304d6e75b47d6b445b15a6c965ed91bc92073b15869056263af6176f1915869071bf7bc8a730e5a892162cd64a8a4d61eeb0f0ef713e470697c2fca1b6515e5897baa430b68597ee4721bfb2a33035f6468e234f3faba23064774ac6b962594e4361967f60b99a9d525a40610a91a1cfebf909439ebda9740ee2bcf484d12b4908a5e3f9b2d80973a8f5e9f83cec2872c268a1536d7cb896876cc2ec3ed697454d183a5abe8c1f4c86db65c29011f24df98a09535e744b572e6186958199a5bc36cf1286341d75c23ac8cb2a6108693e1a78a789a82861ac4fd9bbfb6f9d69128694e63d5db5f9684918a5c36765d70e138d8459a662eb3a1ebe080943909249ff38ab446324e011c692afb20cce778431f294e5f851234a5e8d30553cc6ff25d9d3c703070960c4feb8c16d768ed3007511a690423acb78bdaa244f80220c26dbc81a5df8e94f4984d12dd794a4f55b9e8c12901424001186addbf9f23891d3d84318d2d4ea785ccf9d8636b041d9010d4240820418c21ccdd5752bf9e5aee80a924450086a000c12408816246010647c9dc28390410881a0010de840068a68600319e82101823079e898103957ee309f4098b5e2acc13d3ef10fbea0d8e0061a70810c5ec00292840400c28caeebdb5b24ab4a16fcc198e97ff7a7e60dfc60980cc72242b0bf08199790803e98ef622603cfad3b69f5c3021cf835a00112300d6840031a2001067480011260c00718208106342089eaa8b4ebe62262b148201087c3c150481486eb5701531508201834208f469218854365de01140003542c22362828101a160c0e1410081c0a0a03c280d0401c0682016120180804855c21226808b42700287841f6c3b01d0bcc63764bcdc8fe11e6ddc7250a8c79786bcab1db7b1e4c57ed62b3fab18b467c476e3acb4d080967724a37f50f9fefd9c7901577e50962849fa8d5e620264a442a7e2b0cb8d0b95739d809af7f81656c87c5f1a5a3db3007b7d3800c3cdf2a975db20da73bed259fabc6fa3bd9de6a533ba21f81fa0ed2611afef27c27569e00c2ee378323d0a29b8e24f2eee4f772d731042f5c3b71a2598bfbe31d24b77e022a6002e49a327666052ca21066f54653dcbe10d9aae9d884940f7b172d0b38426f5bc07e04222424c082884647ee01110b8ff5460a8021009de53cdda3d323696c6a4d4b10ba079f37f3e22a869a1001a6db76d07305284335991a464303555fc6502942b1daa5742d6bb51bb2859a617b2bec8a8488e6e85749e81e0a7ba50bc21d1d29ae540681d58a28ea2c6065b5b316bb7474f2d3c6c358110c452e5628ce16b5a649ad64bd652d9b9b385d50c67adec96e62efae17652154552b4e0624003337ad7d24907737eeadf88884b03e62f057cdacb85a55fd5fafa797044d3083dadb55980d1e083aae47e8bd5cf1ac2ce1080f6b1b4e0138b9018763aaef5ae7510d402ba07a1704a615eebbea7d0bd9bd8f379232bb9acbce79f1e318c0186bd80bda1d2dbf34ddca44d87fbbbeb700247194d53a6a33257b22832d6bd7126d36775271ba14f2988a43646281356a5c7044f8bc7cf41860008773da456f7c1d0b4c630e4bc1d2a520887ab305aba074fdb92d49ec0e6ba246a3f8f262dba52ae69f1eaa6601641aff91f2e44a2df72cd6e16e37cb395d78869f8e9f8c1535ce637ae81e054f6cb75913ade2e8c54708d4e8f2133798d163f41861aa134794ee4592ce1063d803da7a18a2570d33bfc130b658c50ee6804623ac2878c50526b6d74273fd89f434faaf98175051fa6301b55eb33661297589eefb2644400c9241a0fc9dd6c0a81aaf2542d28ef01b0f759324a59267a7c973fc9dc2e50763373ca60df23b22b0845654ac899a92782d30a0cf506228dcc615378eba1f73068d23ef22eab8352e83bca252fdaf9163d4a50ba257ade72d1bcd81636700d515b91be9c4ee6e461b5820b0fa824d362bdade87a12def168add8e643945019b61ad5d3eaaa3fd3d0738a4c52882eaf3724299d305789b87ddbc1ae6dc7c5d00328473ecccab033e2844f0c9e708d60a8e76c6c6d1f8a9941ed0b4406c8dc190459a016016296bb4652345550ee2ae28b346162a4302d8b34605e2711090c61dab0b6ecc121504c65eba63d4833cb43bc4f661acae9f2c0ea0cfbf84a42da283423b7208fb1fdb7111f03efac1e256a1026db353e5e5c32a5d4e064ecf238e93f1b0a9a54bf10b66811a4c7181a57d587fc99bcda26919e949ee4d14e74309e1303aa549184b701974e0f421a74bbe74754e80fe37884531a48170e077b15e93db17ca3756410049bc896bd4087729a5555023aec7410977930c0704a90076cc99351702d506526931b16ad053834da840c8c6afdd030078360850528186a397b8261dbd40a8ca9903fbd512d5c7422c642e44c45969c82d9f39731501c0a428084025c54d062224cfbb5b8d038b42c77ba206a997e54d94547ed8b08fcfc424b5acae54e142eb5c5d61234e1ba8197ff46afa0d72752a23c52225a02088ca27533a2ea06d100a4c984fc79a4fbcee002a066b256b096af74fe41e4dae758f7f3373d3368ea73711fde215ad83b447beed43a62977d18437f09443fff8e5becb68182962a57aef700a0e84720e2cbf29602d3bb8c2911574eb2c0f96754a702eeac47a56efdeca3d251f153e21e9773f1849fe13ed4f53a01e4b382c7abc7b32315b456a333c41d7278625ab83d958b4a24ffdd55dc8532bc403e9e1fa125edf934c4765cd33fcced471b783b7ec513939099be1f001007a3d82692d13f6056d17caeb510ab4db0ec9460357334114ac96c17d0d1269c27384a362726542b413f8c632cea233661d86557853239c7d032bf14a205dd902675f10a418c3ddb36fde84d60f85b9ab67234a6767c5767e562a42f9560fe068f069a654ef06f0fcd73f4d8212cc0d07a598cd0ca6e530bc57d648340a8145e5ec0721f5a7374d124b089cbc57c9e6ea1d08a31178d4b4cbfea1872f7554eae8aa07f9fe39dce8aeea727d2d97afdd4961be4074ccf9b87bd3c9b67ae3332a41da82001c8de85cc2864680ad531df6161e9e699a6028cd346e769b0165447e81f46f81206dafdd42635ca2c00182352ead06d28d4259e3244ac371088c0070d3369382b8e7d88965c662d07f0c029bd2885780cc941a840dc8971b74bdaf81c270667c60a28727dea1327b10257924a92d92f290e01207bf439c6502406b0ab95b1b144dc0a426b8066f37bd882494b90677e8b8681e459690259b2402aeaad41fec18eb6fc6f25e74234612bbbf2277257879d8371b15109338593cd0e0f7407da2af98c421948868403fde2890b565ea040b6c35005688cbe4b0d5f3297845a716a4d6e9fa2bf7c016e99de310c2624dfc864846c83e5ba14f158d5d79de49bcd530d072eeb836fa117582db540ce9d3a00eee805691b19299c10c5cf6ea3b57323cedc5ea54d769e3820532db77c496543e9e2af9c74fe21153568aa5296b226efa2ad5d3e1d6b5068b292965e725ca1bb45becbdf8ad7e8dbeb33e23ec795c6c47a57481b3c3d02cf26f8d7923e061e8123423e73e61db17821f305210d01b67c61e5ea56eb6102e62a2eaaedf2516ef3920511f05c24bf777b3411ef6e798d7d7eed0e5ad4b0062a87b780987ed79eed96dc7d8623e2393641ea5da692acf2705e654b46e74ddea5ff72e55bd695fdaade985e905e98dc3cbb94bd75bd55bea8d8ab72e8fc626f0f36efdd622ab69b177f90c60eb94ebbed6cdd1cdf2bb69728f60edba6639defbd9fb94774bf7421767b7ba37be37a05706af7ded9e5b0950c54797a0d9ddda2ad5b0907409bad8ba847535bac1757774bb74ef747d74d5e866b3ddee811429155d7b73780b75317509ea2aba8175b7773b7b6f79fd75d5e8b674cde9267579ea32d465eab6d46dea0665f7b503243dcdea9dccebd055b3abdd9bf978cfc5ce000ca1eeccdc5d9e34921a2598b237b6b767e2ad538315679499a046d3bc6b42ad9d98bce807710b3f01271842255314c461359397ccd3c6c32e005433aec874fe686238b151636bea4731b2474b32230ac296da19c1b326459a19977a256930e40568d10790053d88f529381a5d6c50738b0abd51f7f922d7b9bd44327b776744132967d951c8e1108a494724ca29f726ba43a2e0c36104fdd1f85a9e93a0c0ba9be235831517d3ae49e453f154b1dfc6205c3d7624ab8b908cc38fa1d19b91f7e23f1b7b3f28717ab606938ed9f6869c2cfd7591c81c8bc78724c802d2007c0cb3860f2b2302d388d190a4614ea94a70036a0c5de861c52e4178b27393982e3ecd639c49a523237280a0233f444347c0434303441366943899632aaa99335f86beaf9c9d99e347585092fa6b126231bcc70ca676e95201bf36416da94920baee4fb92fd34e443562a18505ed480abb3efe42bffb0a9ce9422d2e4696e449d7374d784e71bcc930a9e6e28bb14ef6807d80749026d7d3b2a17f9023c8103412a60d075181d06b298a56b2037dd71100ee36c6446439b2d84474ab5f4b3d3ac3f396f45e05aac51429e801aa30db8c4c5eea54a94d7b18a89a23ba6081f25328a9087ab8ae0609fc2a5078131ad6ed45ccedabbd6574b0f9b68bef3df2362f20453817c294f7711a7aa5ac0f847051e2b422fcee337536fe8c1663df9f54be3c75f6cd904e78ae3a9564e45eb1d9719d5ff95ab0e88e16312d9fa2d27c66f121537fcfa1950a1cd383781bac21da21006427f030181348ea64b7f7f4ce3a29daa70e70ebdf314da84a8d9573f4b902a7ca536b627f827d5caec45705c00bd801140e00f3159d5a337ff78b91008610c5877e6033b46873d44c3c49ed0641790e84c90969891555dba181ccd29cf97c16b0c628f59675640f2d4b35e85605940a2806fa0b820cb394b42135814c9606d62acec0191ccfd0fb7ca160c5026e815581ea1bd8824af15b1b02cf023304f4d1c08be4d358ff06064590f97640758ce0f8fd4d5d47dfa69029e6737f3d608d5dd20c268f92327e327ae8d1929a238024a027f04204e285ecfb05a3c09c0098003c01f81488e7fa9ccd0b01d4603d00b20046193fc62fae4c8575b0d404bc30a63427cd2cb2ee6b6298aa227a1c5a2b23652ac323bd8bddb40d95fb489231ed696b0702f611787934b0bea004ac587e2cb4b60f1f0acb4d8db91a486c031e319089f5eac8a106aca9ed71bb9fcc4d80371836f00c82cc7d0240d409580788175dff4f206cdc795e78354fb0e0a15687702a333720c75a632d8d5ab8e19f4b39a05695081c0a24b57b96347ba1cb7155803a27f234d8496ab6f598162fa97aae82d518af42ee663596163d8f4c06e05d14184d20aa5714b736e9b46a82009c804f2759baa27b0e0d171a78340b5e6827545100a50156e75fafb109d24c12c9472fd745303d1612836d437b02e7fa28e0294106aac0a3220551fbfc31cdbe569b5c2b34544d3401118cf72a2dbfacc4484f6e38936456b0ee7c889a9d862d4ff0582190236018188faf4094570d6c16714a128f98882b92e25fdafc23c88cf74afbe0cf1ba3d1fe18d6c896784e15273e77160450e3ffba6cf9fb84045f4412bb4d1c2a251ba2640408613f02f30ccf68ce69fad23a0bcf75d493f107a02361ed536c41b80c42fbc80621f364e771d5fbe763ac7516e91093a16e3368b065c766b01f9b6374adb6c7b7b9b649cf9355154c39d33ab9d2eda8d79bfafb46c5a264292501de00330370886d43510dfb1d99472ded6c3834eb7980d19e776bef8492fafdff3ebc1f36dcb03e864ecc44ac011deec3c090cf5178d982a2c870630a5af7e19873caf9035546e3b6df35d5d68c93bcdaa0f911d708c85de8ea88b2c5e1eee043641b8fdbd7e41791e7c8ef5eac38369a10bf798013274628953710b4352727e2b302103c341eeb5bb4db3306c052daff95e1c4fdbcd2111c7dfc2703155774aed5f9937b5d340d65a7aec9b62e3304324a5fba4a3b765b83e96a2cfaadd085e50d6c8e018d6e344a0c82867077aacb63230daaaf413c4d938b7aa19b6c76d4432dcea8d48fd19f99e64d472d80948d6dd1abe8efb8c4f72c8ede70ea744c6b88e39be4ecccbd34af49801ce567c06910d4d921404f02846d0dde9279d345945ee3c36060dfb433c21742212011a94c6503be0b23a55dad4ab7a37b393796aac78460442ba0fd96a33a1b6cd43ef39eb471eb35019257c292029bdebe9f1a0f8aef0b8d7b2404a8f1235c8d2fb24bcddbef1d63dd6b99dd30bbde78408c165e99277859e3f21a019aa1c4860a1b5d47d93447f335a1404bdc4533b7c1a7ea58cf4d0cb41f0cf9e256ef51cee665b9cae051ea0cb4b109b1df7b183b03539d97df0b25a2da2d1f2869b0efabeda0416d424875fdc288d7971cb2a4d1af41d9f3ef0eca361c9591a6abd4c60e1b5adcea0c3bc34bd2827a8a9ada428c000a2b0b71fa72c78ccf39a983c02e3a4641802e5cfee22de65157bbb46186c4dadd40187bfae38617e62df31f11f48bf5c7eb18371c8395965643c6eaa0eba40e39d94341e3f4c0987ce3d11a4c1cda08c9d61a9dda85993c4ab5a44849a19adc3522ea9f66a88d79db9aa4dea089712e03e641c85779f00b72b1b4794443243a496cc764b01c06938a7b2ec9a7904649896e3b06416c606f17ce2fcf0ab26679ebf9c502c5bcdf976fc78a45ce9c08669818cebe07f314be3ed78145b410f7542173465789327994d06798e7758d59fa38a2bb208e1dd321d98bda6b7111257a90e645ff50d26629e465c621aa8da2a8289ccaee0aa4a4097c47151e545102189f200aad1f269a5143da2cc12b71520fe2ae32d05889831560be84c603632be47f049f288e4c0f54daf3b72d57e49576b3f82d9e912ce1b7b5f824edf962fe8270855383df2b6f8c58b8e01cb3a385183219aac3c7e771ba3fc11174950f1dd09c70726e9086e2cc404fc43af88264b16e6b0a0d8b9d1ac037915abe80fc3673edf873227916fc02ebaa621c956e1d3a59f1abad86101566099c9134682223d537aa812b8eeb6dcb61deca71e3cd3697920f739a576b1b8b78888826fab4fa1ed48f995e91589bb153ce20f862ee3c2fce36271d2a614672e2ccae9739efb6ad8bcfd9f76c4576c2a6886ced4602d067abc9038b0e4295c30191e7027725536029295f7a2bace2ec287e1d3fa5b0ee082aa7a68be185cdbf24a04e421144772b777d5f37c18a6393016cad56c51d10780933fb8b73c5e13b5b8de768f0d96deba3f8d3deec81241192a786c7159477c9a80768d789f5d671b4e6f3f27ac2e6419448df8cb41e491365b621f35761f5a6123ba417b1f7cdf906187c2048ff75ae33fdd92009bf4c11e990603f2b4db30f51cec65faeb416729f00e190d46faa93085cedda2faed2e173a76cba0d7899d94f5ad1aa096c29d0ca02701cc68d426c6a8c40be28d902aa40c637ac859ca0d01c2fe426ff9b622ee9245592cd45fe2bb0389436b3675055062c79dd213bb71d06e2b57b303f5442866342989349b8c95bb26a08144ec628aee0db8c8aaaaf6b4c9dc3991c45fc67889675c1d3c8d0e7d50c77f14505d5360fb61f062a38e90d3aab5ad0f0a593e9dbaa7ada40ba8331ee8da8b7fcbd5a8c1f5e9cb51e96a7f3daa92b2cccac84ab4f4732020bc573ead62021fa73f86219f9227475711bab08c9b0c5967e10cb87d180eef90010d949a87589eb6aa7313220bd278137403338f8745c61da42aec64de35196e7f9fc796c5e93169f969f2e3d42d80ba01f38388dd357292b4e796ce073c4dfbe17f77b9dc2d78b774527a0faf5a0695e095d9892eaa5a8a8af9aab16354218d4e54a07509f809b5aabff96fc97ce1ca9f359b11077f5bf59d9f79b57614d9c3a09287066582d8ae95db69a04410b1344442e20a24f193f91efe28cb4f1b260e8fc947928e62ba5b7abac2127c2800358856c7dc91a2831c03ce542cc35864b3ded9a9fa12b6ad10c89c9b9cf286edf90891397a966612226e81cd19ca3895264d95cd921bf825bc4851308148974a20c2701d4d98213ce8cfbdf043a996b62c6beecabb85710f5e5bdd0a5ee3e491625f8a0e0dfa1ad416390a44df223c575448634195d68cb568eb7dd86822b1af542132e7ba763372e7d75889426bf69f9f0e2e25e793054113a11fa2092830c59d6ac276bf988a1bb9a5e9704cff65a042ce8f66a712a453cec007ed792cccf4bd3676ae881d027a216130eca1326eb368b33b56b539a05f9a445f5709d916092296869a80decd12bfcbb6c5b53e2dbc9607816516d78266b321ac979844008500d3805ee81c4c03b307430e4dcbf730c188fe27e707478297273d8500cb8c620e02c810d6cb4afe2014c0c870d620f8118638b1c2509fa930c6e2414993f8860679444efc190b032511a0227c1852012fc085612f002ce4c958c8227207148c963c36e1bbde1057d3c6c304f40eb373070212ced11099835168b12e1eb587a3c1f25460f6326181121b9939832320de7736406413fb650867d7e2f9d892643a1dfe1ec70af336f95a1a6bdce4a07e9d00e72ecdfb0fc78cb685e18742a2074d20e67e7d371d605f43f529d30679d8373be3995f3bc4136f4da3427ec591121a0bb9d3d85429fed618cadec0cc19d1fa8aa4823f57ca12008aac1a80102f6388b52905d6734b18ae81ee6a720f41c3d7fe72879608bee7e29c2d0eb70b46bc40e55fbfcf5b0b54d8051ebe9e3fb3309140de901dd6bf6c63623f40291816c58e416e87e02b6418ea29444171a8dda4cdb31f5c13d4e160c9d356a060d342d7eb3f2a94b6848352341422d5590e940928e6ca5e1c8e5b49530444427eca9fb4a00c64b9d3822a8ef04fa72fc28092f0142e675637ba6bee4e47fc821a91dc54b621db87e11bd141695907251d89e003204c72967020897437d2c487f94191d895816663207892f50f5c3c53069ff5583b82cc21fc877fc853142b002897777718c2c9a205b028d223fe2cd25b741321ec3312a688a651cd96bda10869846e372ee316602656142b4a27a2efa832c83748fe2c4acb634a371c1393146f56118773454f86261a3de881a1a029fbd213700921b347bfa0b2c3b27493844a2147b4428519cfde78a153870698bc9393581ea5c6c1cd234dcdfcf0712dabb8d784d700dd86439101778b4527f680586c35d502a7de44cfdb575a51dcdf4707720066aca2fb17f65f3082f69bf46c907b851498398c4aca980a205ab91fd023986a130ac00e0a975ae6c27506ae0aac428a954aacec2a00204069f5401fb28c7972783d69c658cd4ed003ee83edfcf1d74bd9c1001fea02380f4bb018a72ad4f094efe05d1b78209bd8c8eea88cd9f83a60d17ff41def2b8f8990a51f0a14756dc7a8a1f83ff4ec3624b2aa19604dcb0868cd765db24e644894812dd59294d1e42846d0fd0996ba8c4dfa24acd69997258866fdc108e7801d0bd06cf97af9237c185949fdfb347095f1e1fc5f7e13cfd60e3306568f5e034be7c988a627bb7b5a03a2a518b76ba0c1a93efaf28ffa18e197be41fcb8b515340bf952e03a15babc2e71f6081add9699a2180ab9a785285f12df2fe1e2f830e4f5f71d0b5d466cffbe5118bd9df1b99c8452b50942da03765d91b08de96b537ecc6b2d24861af7232bb696da9b8e0162d06a03edc743e8dea2089d329c4806b3f9143b3628fb887a07ce72847029a7940f683b59572284363afc1825aeefa546c7796f1ac6eab9e8a0d228e8cbc329381da2d233620aff87013ddd3f5fa1308e8501cdb6cb688702aa97276b8bffc7cb6b61326dc256b635835cdaad5f3a8408258229cbe53c07ed5c986e1c9824a444e891c635d8a402065517491648d7904332da6131a0136f8b5d423cfd9c7d777b9bbd548546b6e758d559b5ec5357528e2c10b5393e90d8f1e3ff142f4e4ad746e64a1fcea294816d42adaa5060d86101b479b66357c6408b5bca45dade32c506e9429dd0bc08c80bbb39d300c38f9f812fd4e3763f0f36914562be493185823ab9b0ad625b5091ea57411d5afcd13f783e130db6bf60486e6473ebf0e63654fd3083c54dafa1571f828e82513669f2ec7256cb41f956392e32ddc50e03dcddfce9ea13ce29285533a6f9b96a2530c2121da4036d785fdc56f868ca8bc78a520ce8f90664666edfdc0ce04288acb83ecf87347483a62f280fb67bb9237d609d61bb1fa9de7c2ea35b79aeaad7a47876c5dc2c6e9ab13db2b15765886d84d585103c3c954def2a77899d243c87a8211e439b36c36821a26110878f19bb03b043e56f00649bd6d451b26b862d751feaa89be512fe7f5718f7180b8da6d366eccc8838a028aec9051894469aa3e5447b4c956c24dba949865ceaff2c6bb8819bcff44960a0a58efdbe79dbc7655aff57bc5ae5856813228d1d5625064b72145407025dec3c7f0313c860ce3616c88c7d18d1fdf6af93d648c9ee133e40c1f63c7f8889ea747fa003dc63a18bbe19728de5a580957402ab81256429410a5fd0407037cab022e0945d30c2e30f44c07c0300cc3300cda01482723ea275a262953bd917049fbe029a594649229aaf3856eb3b7b8781306c0ec0d030cef0c990c6bccc0a48e3f1e3c83bed294819310b42575eaab774b64e0b425dddd1abd17b43406c6ac7582defc295e2c89810bbea321b7745222248581d3eda1b282aba8094960e02745d024727d8b9ebec00995938ac81b5276c90b4cfea8a671439267495de09365483c2b7181778fe973a6243fe6fc1698982c724e217b8ef1d7029727fda79c449e907d165813f1d34cd5c5ba1e0b9c274b5e810d292491df9ede222b704987a6ceceec243f55e0daba73104bae993915f80e66426e2ae96bf514d031e4a5c0df0449599d8d026b22eb96ac160aac6f7c513944ec20ee1338bf51294b595576d4098cc6a4ba82d7046eef47ab6b52a222ca043eb3ae77aaa4325f02972bd2bccb74106e2a810f19946a0b39bcb449e094dec58cfb691e3290c09b89a09e3976a4143c06ab7df7adda71ab640c7e84ab6f0473cdf96270e6f927e4fe536f1383fd53f2f482f2ccd80d8317ebd1d14b04af9230b82e0db223b7c6206382c1a690c94ac8d4a94cc0e04d4b4dd2a43653fc177c998ee8a8eee611f305af9aad74647ac1c9dcaf3fabf082f1be4b757d179c6735355d962ed85c9a3309a956dfd5e782f5bc96e28237299262a6102ce4c8df82d38d41e4d415296ac8db8253a1272515f9d335f2b5e04534a54fa243544db4d0453bb36024e99bd8953a485216ec05cdbba539787a1f0b26063f931137b38960c188084f4adf2ea7f2bc82d399fcd4c44da9992b185dfbb1f094a023de0a4668767eab9d7c7956b012e2e73821c65cdf2a38d77c6d916a5594425e4d1e940a465f8558c51715ec7607539fdc53b039a6d469d3712bafa6607b7563454c0b9aa3a5e0b54abd9668b67e9514ec5ede10743b0adef55424bd59535da260729db0a4269a864443c1a7491972c9fda849a0604f6672d141d8a59c4fb01dd3baf5588ca07b821fbdcea349fbe59d9de03fc8309d5382b61539c1a595143d65c9f4eb6d827d2ddb94538a0a71a309ee92e4cab66a67f94cb0f1236fceb969b3334cf063322b678fb14de8126c868918aa2b8778154bb0b1eb92dfa75025f8dfcf1b47a41025b82c3a94a687d41443d02418b3d19833ae95984912bc5a3271bb14963429126c7a7928ef8a41d40f0946a9f014235da8d0fd23b8983caafb49d09bbd23b8bceddcbe135522df08c6c642fc8ea0f47fc40836a96707b10a39e5a045b0a74549bbed76ef902238efcbcaa7b35a5f28117c90f62ad2434d731c119cda9299ea2be27e7e083e57bd07171982abf41ebe9f7d93ae0bc196e73fffe8e32989108c07ad5e291efaf941b095dba3d65e0ca11404e3ff9a4b734e4d6f03c18bf2e02f2242d40408c627572695eb1f3875d1204ff503377a59aac634fbed0327e27a8bf4732d11f9c08b1695175c62ce31b80776eba4077624fe991c15cf511e58d32a39b978e0925a69b97777403533c60edc48aaf209eeaa2953073e644e971444f0fbe9c0c94d1e938b6af091cd81bf8d3957abaf9d6b72e02dad23a7cbe97bb438b065a2c181add4f5a53a5ed0aebc8189eb254b6b2425a63a6ee072355489324badd769036b227a760b62036331a70b5a2dc8ec92b306b6b3a84f963fbf29c951031731c6dca7abe4a7a434f0266388219eff65484203a73e9f921efa33709ed3c3abfcd2a7df0c8c488a1062eecbc0e613a5b53c3d6a069181f3af1b9d2a2a92058d81c99fb2e717fb10e111031b3cf577163d0c6cb01211aa93fa2f0e065623c61c62a6da4bfb05d6bb42b254773bc9f5027b9dd72ef04125ddfd5791d5572eb079bbd1c34f5be03f9ace0949827237b5c0a6dd9861a636be995960c543e40aeb4875251638a1fb2ebe05afc0c62bd9d12afeab67053e9b55889ed4b847ae0aacd99806770d153cd95ba7f29e02fb976731433785ac9102d79f3541e5fcaaa489026b57212b9e5e51218302bb296fa6c79ec0e7d89afe39bdbfa8b813b8f4aaeb6b294de03c96aa18b4a5a47b26b0df7ed1f73693a8e54b605395573e253fa95f096cbe85a77852401238912176f03c22d7961480045e735547da1fd1fd8fc1e6afba96161983c9aa7942278fe9e58bc1060f33cb8c264a44c460cd3ff865ef87c1e56c7929054d31a52061704945758c5d9dd99f607021887bcac9fcd27980c1dbc7b4aa7f6dbd9c5ff0f9ebcc7f33462eb92fb8112abd66ab4f5ef15e70da75f725e478c1771cf11a99a7b2d9bbe0ce644af9e2eac9af75c1f9282d79335e69a473c1d5a58a3caa6d927071c167105da545d9e5b6b7603fe97a336dda828ffd262c45e224356bc14aac93a2b682789c169c69266517eef5599c0527e26dd0d4a641c650169cde50d5d593bce2c5824b23430c792c491fc182efa0469675cc2bd84fa362901b4ca5a87105232f5de50d31dd0ab67c4fd684245459ac59c1c490360795bea4254baf82b30a2106192fe69c52ad0a6ed3eba78b68db24d2a9e0f4e3be6fa729a5218d0aee56947ed091371ae953f0125325517e320557a12b7adf4e474b2a05f723443bc716f52a9182d315abf43ccbac4aa3604b445688c12dfef9a2604d08e9a6f25a48c20f0593bf6d7276fe0eb90705a35a2bc7f491bf3efa04a794ee90214fb031788879c334e8d53bc16fe6b6dc3a32a4d539c109adb5316766accc37c1870e9a36794a6bead6045bb75766522be798ce04bf571d6697f3fbb398e0839d59d273f74bee25385d9d94f45e8dea5b824fb50ec9eaed52502538b9c127e91825e587127c5a7ad795d06d4f27c1664a6c9b14950497a1d62a84c50e291a095635e44dab97eaf484042754aa8f353e8253eb3b29a62b933982bbd839a458d9da62b2115cbb47c95bf9a7828c60b25ede360d17c1987774eb8f22d8a034dcd293ddee26116cb06cda93aaa0e46b10c18dce88a94e08891d3b049f3244724dcc10ac057d5aa2975e8da5107c7674112dd512a216bdcfa3fdda41b0263bc8979833c95cad20f8c9494dbbfb3ce56a03c15bd29b629937640f5940b0db4954c8dd183f4ef60fdc5e66e7f8b771a364fdc0db56ce932f047df9d407be2c9be81c3dd976121fd889a369d54cb2c9d31ef89520c2275dce514f7a6093caecca8fa09fa63cf01b29c894f9c703f79f76f4fdff2fe6efc0a74a954f56b251526407ae724e2a996d291583eac09e9d342d2934a40bd18155b3a03249d38e1134076e37ec3a88305da72307feb472e4d3afaca41307de73a974a33588fc3a1cf8b4d1425f820eadf91b389d2d94474ea615d36e603f66affd966bf8db06367b07730d394e122d1bb8bf88e943661731770dac0799d93a79303fa51ad8e03fc27f4dcffe4c039f134789dce02979120d8c9550a6d267e07376483a5494bf976660b54d721a5367da6519d8f6ccf5ca37f164920c8cb75587b797c8791c032f297a5041796891a318d81042fe1129775fbb30b067afd12595cae93760e052142d49b896b4fe0bfc69e8b7c6161ddd0b5ca624b25a4c5d60d3eb2787fe8ef91a17d8a0420a522db345d3b6c0757f7aeb8eb4c065b6cc9e274b3734cd0217e298523a6a4c9d53140b9ca766bdf368138445afc07d6a9a4e31e6d1a3522b70655182bf264f0d12ad02ef2b2ae9db1c3a1e512a30c2437f69d34f81d7a8a93599764fbe14589139855665d38e4781094a089dea9d3c68a1c08ae88c5127895e143d81d1c13bd47c4fc5e4042ec8102a4bce2b3adb04aef25772ee18325487098ceadb324d3a4e3097c0eae9edf7e80950022331782c551d32783a014960edb36ae9ccc9b5b30940021f546eaea04a95eaf6185ca924628690bd63516370124ffdad697cf16431f8c827c1748d12b64962b0312cab4ecaa376c961f0373978e57f26e1a330b84939948e1083af2718ac45dab2d171b73bc0e035fd4a4c8929237bbfe0e48b29f9ff49a6ccf9823ba126c77cb97964d70b36a6959716cf0ad5f082338b31448e0eba3bb60b26fe48522ab787654d174c1211c1f6b55cf0ea9ec5ce74f89f70c1a5e062e941b847f3168c90394753aba42d1893346a5c2dc74c95ac055b2a5be8cf49d28253b5dba347335696e42cb8a0a2764487caa49705577a53f4f190fe3bb1e027e74e7bcaa4086d820523926d25849457f05f95c6a4a5a9cf2557f0b691c23b4d63ea5bc19dbee5fc2bb182af3e8da0761d94c8af82115b31692152052f71437e0c69fc64502a38694906114d4d9d205430ea2e6ebcbce942e99c82cd1837546ace27a2c7146c2c79f9ba544c13724ac1568dba8da89382dff05d8d172e3ae2a3e056549d55d9eaed2d0a2e67dd9eb421294f772898e02d16226332dd1a14bcfb958f127a54dd8b4698ba09d2dd2d6a895c0e3ea4d3df670896e9233b234bdb70838d2be4d843c9a107d63e59d2c1f3ea251dcb1c79c0b49db8ef85d2cfc822c10b34071e380df93958c9cbe4b8035f2fa9c67eff1c76e052db06cd0d3a877cf91c75e0f3b7a913225e7a5242746083c48d2021f5b5d83d07c6e49969ae38ba64a939e4c066298b962ce418724673c481ddf40edaa39aa49895030ebc896c6f956d3a39a6e47803dba66aba9a9b1b1613e4700397f9aae63922a8944f6764f9e004a1ca07a7064fea0339dac0b7da85bd5994a03d31482d3863c10d7c08600939d8c06e985f4e9e5f21f86f06a936dc60430a39d6c0c4503299f69494d098c9a1062e5f0e5df9374a47093923ebdd5803083e7ef419ba468e34986f1f32c7539adb33b27ebc1a6bf82055a573a08175cb8be4224bf3c11b6dacf1c60fd00739ce6006377fd24b95d44644ffe1c61a69a4d1df0629c5f0214719f814aa4ea787d29519e420039fd35dce18dac2418e31f0b9a7fe3d45ede0a532258718f81c74de56c8d4ba1e623891230c7c0aed9c7c6d3ad37f4891030c7c6fc4902a629e81c8f10536051bb3dca2eebe792fb0b1eca35b577bce910b458e2e7096f559c3f4735fe6cec8b2f2460e2e309641c81c838fcee944c5448e2d304a984e6b25416b798e5e22871698782b7a255528b12b0d1f6ae4c80293eb34fd456db5b43158e063f55a26bd4ebb529121c71538614af4ab7bf2bb9eacc0da47cd69397455e0ab7436e14174c897c91c54e0b743da78987e0aace8acdfb7bae831e7a5c09d67d985e93e0a8c34fd8ef71662e7eaa1c0995039869043f3f6839ec0c61c9537b132e8582327f0a631761226d52bc94f13381973905c26e4e5c80f13b89129e48d77dbd7b62f81cdb14a62a45191eb5702fb39afe74b7be531d33992c0e8f518e48aaa1c91750e24b05be72a4295f6188c6ef5a8f1f4c6e054a444896161133c5b0cce563572c62d75ff2b31b8d166f9e9df47897c2294d24f6d61be30f88bda31a83011330f06a3d094f5844c2160f0934fe76aa6f70bdea3c9cd79359d47f37cc109378ba7136b3508af175e1ea5e993a98617dce77429877e8d21b4b60b267d90db7551f7c4325db0a251b4a511a973cc4b2e18b59eb137c471c15b052b9119928eff965bb0417e56c8ce9c1b715b3039e25d486aa337cf5a70a9f7b53eba9b6c5320c0410bf66fb38bdca07365f9b3e05bc74688decbf31c248beb4efac45c009e802316ac464dde3d1252091cb0e0d63e620eba27e6e6fb0c385ec17f2595bc4ca718f2775770213da36d57bc15fc0711a38e055119496505ff5d6a4d2611d4242757c177088d5c317b0c1d93aae024673291e36e59d61a472ab83555329679f55a0a51c1fe5bb0ebe4962776e614fce78570d3da925b1953702f1a9e1ed2941c5529051bdd53c89734450aae62cc371a7257d71e0537d62316592d578a105170d263e85dd0f96ae2474b8123149c7db9d6e84e5b891a5266bb800314bc8ec689e2d92757868001c727f8119e2a2a07bd1b707882fbfa1eeb2455947bde093ef78d891cbae10427644cfdb46a2262fe2638a12257768d29f69aa3092689143d6f3fa57c9d6682cb31a8f68e12d318494c70233a6ed01ff53fe5ff12dcb88a4a59eb5b82915fd2d4739b3ea16b2538b1324dbb9d39e61e08fd8312ac464bfadd2448fc0d4d8217e16e6a39e7d4299592602547cd9bff46c41cb148f0233a77094dea676b59230dd3230625e8d182203c2afbb10624b8bcbaaff7af3d820d3a3b67499db1462b1c8ee0fb3679bcb2246358d8021c8d38739021652edd703082bd4f2aa8bfd469cd2b05c722d858b1dc3fd2a664d32b82cb6b95c34af5cef39a08f6c42a28bd31a7931ac4020e44b01d826a9041241d55ea21b80d8dd143f7890a3186e03afd8a0a5a45c78d771c701482cb9321aea5f897fb070e3808c1b5c4fea0a1c34ac77b10bc9650ad1739e23db136e010c439479b8e95750c04fb92bf9479d0b13257c28003108cbde86cdfba9f29df7f60ec73cc9f5c37e7a6710d871fca142d68129d31fd81d478630df7e1c61b6ca881868ff369bc95fc81a30fec470b223ef0f6bb1a4b4fb34e74f6c06d4eed98a457f4c0694c0b1559724c9a3979e092ea5ec490f2261d723cb05f4a26153ceb77e0cb4786473ecd22bfb403e31ba2e8a471720af25207ae3ae6edfe943747a50c071d0ef24c86e690691f9c1a20351e0666f42069f0a7c1c61b31e8e10215f4581c73684d04d59e3b38e4c0e43bcdbc23e346b63a17471c385d9245858ce8c15f8503f77a1be39d9ef8624938dec028e5956e1d93af664f0738dcc09be86e9b763a6ded3a8e36c000071bd8d2a642baaaa4ac556d0d5cc5135da39b995496fc0fc338d4c087a0eee717b4d8c6ff8cac32caa8c5910636568550b2be4fafca33b2b48c4f0d8332caf0e183130483030da6bd1c3d6b77720d367e7c1b69021c6760fc933cb7bce9e149e4c60f355ca0821e6efc0f337afc70e3cb28e3c70f37be87193dccb836dc60630061c061862f7268ac0c709481513925fe869c4565d3c9c089b9e4fe204fab4b63e0525fdcdc1ea915fae16ffc30c50538c4c08b891011f4ba6abcb000471818dd9362d9dee778908f35dc000367a7fd157307959d6f83940f4e0daea800c717b88b7be937521e871758cb482146dd495b4988a30b7c3699ab4ed856ae45896102071798b89f73d54d9b05c71618cda9a693444fd2eb734696e2d042e111433a9d459f8c5e8d1da00183f2061c596082c827bc36443469212cf092264e3a318921aa73053ebe5fa6241642468e15f8ee94a5d512342bdd2a709ff9224c945aa8880a9c5645ecd41f44852aa7c06e9ed893464adc37498189bb291ea26b8a8ac128b076d22c7a484a37f70705468be7d5cad021dbfb04c6bc6cf2c930a55248700b389cc09f36b9ba9ea993a6df04266bf09c646deafd424c00028e25b092264ffec871a3faa704b6b4b76385d812646db4f13ed8681f64158e24f0257b93ba5d15081c4860d2ad9f86a4e516553c06a75392a23b2ba5fff491e207328cd1e7b3fd9c2de535ad8719a709328ac176101237e8ea85d0103288c19ace9b7ae49f0ca219710419c3e0bf4442dcd28d38a6148230184b71572b88a44b2579910419c1e0b3377d8a1d2466061d185e6b6a8cf96cff718320e317aca9a4f1b2d65414ad8719a67c94410519be60a4fe7e75685a46196a05327ac168b9047d398bc640062f388b7875f94fb34d62659451ca479d25c8d805d7b5fd3da269da13a40bbe4a5269f85d0c19c8c805e3934aaefec8b738e91f3dcc28a3948f3a3c90810b3e68bd4b2ad364abde7a98d163076aa0d1c38c1e583e0a4741c62df8d8973f48d552113351810c5bf0513d644eac4e5af2050c64d482cd227314a134e46e510d2df88a6d51af2e52a6e467c16e6ad0fad3df651a9205a7217ed25d623285f0c4824bba1e7bcfb64fe50e2cd80f953f9ad0ff92d2fe0a4e65ef919d9bba72ee0a46d3a68a1ecbebb56e05ab1d4c6bc692413dd8ace024e52cb9d99e47c67a157c16eb903f2a37544955c15aa9d76ff732159cbe8fd6ba955c534ca2820ba946988e123c055f99f6d635e7d2b49c291819d3ffe2674592201a6bb0f13e4023066594e105324ac1e579909c62e8da242978f3a41f44e6890c9e1905a39ae2efd97753c88b824bdb1d5248c1fab3b550b0aee9bd23ed4f2f4840c156758e243b25fb88ef2758ef8f29ca4c98c88e7b82d35be953d74fa49b38199d604d4d0e9226e59c23d1139451861bc8e004a374c6a44cfbde049f6aecd2fa4a7b454b13559ba5c4cae0d94c70164a66c5f5d8256aec61468f32ca50031998e0afb7828841477efdd825d858ff13a4c9bd04820c4b7097714b978c71c592a7470c54d0630b116454823b95ca76f3e428c17ed0aba9be920ab19d04a3d299a5acd53d1b8f24f8fedad21b53ce0e4a6d063222c1feae8e25351ae69983049742cef311267d043f71fc949af4273c6d47b067a2ec844cf972096523b8d6b13079415a5b14d5e02b90c1085e7c47f2afe9641e25642c8233694975e78abac0076bb441022b3a90a1087e44d475d7a02b2a3f3f84f0292023119ccc3475a22506119c088f54b293e9108c8c755bba762b537b48a0061addc608d010fce9bedcf0e4bddd27295f20a3107c882749a52b47084e93a7fd51df4d25a91eace123065a2063109c10318724b6179a562c4d2043105cd09e4ec8ca91b5731e08fe547bdb86ef89849433b2a240062008ee1252a8b9998b866c1622e30fbc69168f5d79fa640c9e91755820c30f8c70178d9bede49b521ff89084a8b39492fc58e203a725afe7babea855da03af76428fa8ef17fdf5c00611da952e520a269407c653aece65aa4479101e181534e516fd1df85013347b9262be213b7057ab3776ea533cd581b77c59cbb64785acd3818b97deb7f3a4c898039f839936a58248d9ec44861c18bbec8ca3d27160b3ae4b9a680cc95c3870aa31445159a3fab86fe02dbbba5a27bba7d60d6ccea2c5f4bfa856b50d4c5a8f413bcf06beee834c59e93796b90626d5ede98ff7967935709721a67d926960a3460dd14797d0c05a884197929b3370294632ab7beb32e5e9901305e3596428a546e91c53283825d307b7fd78f97a030a3e8b4e72f7ec2973c77c820da1fb2f44fff3b18d2778df10112328d5d0a5e9045b3afa7324fdc1448ce1045f5dfe49d44b5d0e319be0bdeca38aca9b26386d422925bb35d73c99c8ba7d635025c704a3c6d6ff638e2be9be047b1b4904752247cf194b301a393dc653418e6e5689b5f7b3bda894e06b74dccc79a22e9c041b634e22e56ddbc44c125c4553a5af47876c2a8b041bd40813957bccf604094e861ea1377d1ec1e5dbceeea33a828b16fb63c468eea92b8de054f618d57a46304a66d122f8bfdb497a7245302a449294a73b87cc5622f82cd2b4eadb3b6c2c4470664164a4b4eb10ece98ab4963d4c6b5886605c47a874ab27357655086e4dc468764289ec2142b0d6ae294faa589e4283e0dffa3b8fbc05c197ae18dc6272f73b10ac78a552d91744574070394dc7f8a222e470ffc0c4a8f71916294810fa81dd184ab7e9d19fe286f481bd9035fdb87e7bbc950fecd968d22afa9fff92122c600f8cf2b47c9f22f3a64aaf61017ae0921297e81f2aac529507c63bbb0791738f9aa4c6039bd63ac7245a3705d1de81f38e95cbfc3fdf946a0726efc694573159072ea80cfad143493ffde8c0c8a8218920e29ec4dce6c0dda80aa64268d21c39b0779e9f374ad4b08038f01ab2c81aa99b9ae3c381516e96d79644ce90f537f093ce2b9508d11d217b46a661016ee04c62c42cc949c83c69342ca00d8c0896475f4ceffb33b1814bfbebfa2b663939bf06767412b3d148fa4474d4c0e49077fb94b7b7779506364531bd25ef4403eb212d37f37c64c89b33b013eb2ae7d04cda259a810fa6a92142779610b1332ca00cbc49eda43e6f0c19383f5dd264b94e4a311b03b7b14b6f8724d7449218383f1192822c57d38a8781d51d1952c4d366a8b2d13ef081bee2b0003030d6e95466254bf26aff02135d47c895d8bf5d322f709a72e70ed94f4e0efa2e70fa5da66f6495a5a0c2054684ab5f1276dac296649688efbd5ae022a41bbd49080d9adb2cf01d1eab62b0ec76495860d287d0499ab7ba48f50a9c8ee962caa6d1bb64d20a5cb0ce9aba61153cbd9a2d444fb73b1510f9efd2e5208350ad4d810b76b916e4b8e47e8d0f0b900296821215921610053e5ebbbb96360962211458f3effc0cb5ea61014f7044248ffb9af6960538c1f0d0eed87d63a326701fd1a359e6601fb38909aec5530ba2ce4c6eb78025f07a42fc5d439012f8d22d7d5224060dd5594012f8709341284f2a0b400231a668fc94478fc81c83f5e0a37e951eaf4a6b8c7e92f60a7631b88eab12b33f4b8ce368b41029e5ccf2866170d57c1274b583c23082d039c41f25841b0c2ecbd6bf9398b21c638051e5f5a4ea764231d0f10bc62ea70ba54cc51756ccc943baca0d29bde05ca3468fa35379d0c10b6c2f95e93c5d2165d32e76df8b4164754b9173bae0ce34d59d89d6d86f6764fdd044e8c805974247da889f3f8485e082cb2354d34d75b0fa112274dc82cba0833ee5b14f4fdfb6e0b43b87fc192af604752dd8b4bda0cd532aebec68c185143773525a6bb4c6cc82b1bc1edc42fefda98a2cd888e97eec94af98ba6251cca764a6281b16bcee9a9f473cbf4bd52bd8e47ed9d15ac52dc8158cc58f7cb1528e97a3c408a1a315a8124965c9aebb531356309a557692a388dc745ac5a6a9323c644a115505e39a3f9bc7c8b944d0a88146b7815281bd4557bfa8da222a4abfae2f4d9f4a7b8a3e6a47b22f0f915953acab59523ff5c74a6929bccd4b2dbfeb94d7523a48c176ca6af1c5474bcc33081da3787392c80cea3de5d2e810059bef675e91c2a2086529213c9212848e50e021a667ca11d7aa8d77038daa1e3128418f129414f430a347a5e0d3f06ee3dd38011bed03523a40c19e781072fd6b1d9fa8320c053a3cc19727a144d24a9e8e4ee496797bda54678f137cf6493555b936dc6043c726d88ef9e97ebbd194b2e8d004a36bb925d267649971061d99b0926e8e9e3ad665ab7d58010012746062f18a242b08e167ada087193430c3aa4b709a4f44db686faa35b564d06109468f4a9653ab0b838e4a7022a92ca9644a398f327550821d9de9b3a97b8a219626613a24c128bf68e25722547dae23127c4aaa7c93a20409466a0e7a820cd623189da7c73594d69455a2c3119ca66c2a42e64f216f4e4723d8fcbed947c9aecfdad7406ffcb0820e46306935448d1e3b6b124a3a16c1e660a2d2c93cf6ed271d8ae0db35547617991df329117ca76bfd9f7c26624204bb155fb2e48f9ba36b8760b426d5fda0d1c4842c0d3a0cc1a73f7d4bb2c4f385c8051d8560f2a52a0d0d32d907f30a3a08c125114d875892932ce828021d8360d3be6e8cd1af3d84f48c2c1f56b6063a04c18bac24cd26de66c531108cde5cddf3542aa80d083686483aa54d11b5ed1fb820736d67d1fe5e59e4073e980ae93507bf8997f781539d3e846421256a7ef9c058929efd2a46f7c0c49042cc2427e64df7f4809e6a4b2a962e79e062ad862599c52afb06f1c024ad3d7ae944eec067eb343a45cf5f59b303a7e4de7ee954d781312ff54a2fba1f74d081fbb698f775b1937aaa7ffcf8151039e898033f6a17dad64f6f1a4d8f18a8a0871374c881495faa529da8a44fdf38f09b2f97074b9a42ed6594618a1e74c081714d13f2043715e2e674bc81f588f7a934c5e86e313adcc096aa8b218eaedac0568eae623152ded0c1067ef2aeb75590b132660df4c60f32051d6b602bf333254b7c9149ed820e35b09a37e27d4c7fdb77a7812dcf753968487540071a3477b1ce182da5f654cd5a9b941fbee2f79f19749c8135994da9e9e4e425c121e8300313278f4c4ba23d45ccc90a0237117494813f3b13727f2d3e7c70a5470c4a10833fe820c3679741b4a9ae05668c41c718f87eaf5832350479ca2406de4d624ed2d29ddcf330f031a8ec8281915bb2e37566bfc078b77be652d96e42f5029ba964aadfbd0be91c4a763e991f2ef0319ebd8b16dd1618adc1da2a4f694d11d7029bb9429a8a9644a7a0cd021f3b44d5dc1025242bb1c08585db974e1bb6a7bb02a3448deeedf3244f6e0526483e4d6de94cb3b52a701da3692e13e9473b440715d8e8e997934eaa6fad2441c714d830db18f1e35a071ba50e29683d6697924e9efc828e28e49a2984360b21e9b7a1f0798e59b5e4c40d7fe3870f83838e2770229daa6e8fa9c3096ca96822be7787c8e80a414713584b1f4b42dfd6c1043e64fe9074b4bb94a16fa30dbe36742c811fbbda94cb4ff8279312f86cd93fc6932244484e1238d531e5deccdf2593ae03097c9a0c6229ff3c06a72fa8fdf86756abd318fcae88975de724fe6231d84a0d295695480c4eff93b6e02582ca21330c4eb8e80ea1d2688eba1106a34c475bb4bb44099560b0bf56baa2a409cd6d010663b253546ac97fc16b5604257aa164fabee0839806df9d98363fd20bd6da4b4373da286a7d5e3039554410f5d8b9416817bcbbe667c918316d8574c1dd250fd7169d5cf096459992db1d3fc7830bbebd7454b38ba342e5dc82bbcad6fa313bb56bb705376ab2c33dedd6d35a0b7efc74365d3bc293a7b4e035f965299114e27ece824d93f4ee95530c2a93b2e0723635d9926f2cdd632cb8cd418504ed65754284059b310955d16465ec0cbe8213a9c7bcd38daee07e77eb3b7f5e09a15670371aabbd32a5bc946305ff2793642dcf1383e456c1e5ef31a51b9e273b46158c6951b3d3d61c726252c1f825fd1854d0dcec182a18bdfaaa74a64207f529b8789353caff4f63b629f8133f117b7f4f575b0a36a79351639fa4e093e57831e858954ca546c1a47a11dbfacd508fe410055bd74159c6f554af9f8d1ca160dc745b289be03992aa6ae31fada146d5a3d1861b9b03147c75c8addc93ff04af66f235794c97cd523cc17ad67fe4a8e51bba57030d5c418e4edc19379d4551af914602e0093938c17a50f74f55a333b2d858c30d34dc071b3fce083936c18b6b26d1ffc819593fda4023f960e307f11f6998f36cfc3045841c9ae07ab456074ba24cf07ddb954b9f12138ce5182d8252d7f21d2fc1fd86074f7fa352d3b504272b5f8a3cf9df3c69a4d1597ac4a0043ece70838d4741c851094ed9fdbe7bee4ffa254a7021c88ce49db43526951c93783d4acecb3b046ffce047707e065596073924c1284befb14eff049df748b02dca930ea612fbaa83046b19262c7dcefcb6ed115c6c93a53b47529afb76045f974a869ce2d9d8441bc1f79a4e4af3ed7a7918c15a4d4c59c274aad0390c722c82cd9bdafdf36a3f3a0d9343118cfea5b4a782d54db2e4204722384fca549b57be94ed470497f2e9f3d26a77ebfb2198dcb95363d5e854b13604976d1f34b7430e21bf85e03d5eee7b06330b4984e0ff3f8aae5e0dd2f28360d35a34119d7d4f7229083694505716cb43c66c20386d37cdb01073008253f172d2a47a8399a7e4f843499d0a76fa69f20357aa92e74922c95415ef036777e04452ad693b444dd4b81db85bdbbc27ca4209b3ebc047f05ccd2f5a3a70514fbf8d0ca18359760eecbb6b121a72c7ebc9ca8117cd11237fc80ffd6b1c8a233b706035fe6da70a3965eafa06de63bafc39fb3e249d1bb818454adab798d224d9066e93ca9dd9ec4799101b58f7dd4eb9f135b039c64db943836ae026474a1e2c8269603d491042c7aaddf8a381b1bdd310a4e67e1edd19f878fa19beee41fbe8ccc08f5031dd86ec19d12f033faa729514afd4eac8c07b7dcebdebd4749b1903e7c1b62b3b481eb18c18b8e0292c977af7f4a030b01d51837e5f090cec87c79f186a17f12a5f606b633093a7e4053e6b2fe71449c9beb70b7c04cd79628af4e7738153df4c162496b6c09ff012a9ae6981d52ca9bc6388ffe9226764f928a5470c4aa007066481eb3c0b49ee792915f4a26000165039430aef13ff0a7cbc18feefda7e820156e03a28cb49c43d195005ee3e75529d92252dc1002ab07eab39e5b3f813bb5209064c814f77c1544c4f5630400a7cd6c689d1deb792c49831200a7cc50c42a71ced1c2a04052ec51c5b3f9ddbdd9aa8c08027f03b7a3f5e2396938c33c0097cd61e3d41e8cfdc15a9c981014d60f5753cc44aa78c3218c0047e828a56ddcf10f34c4ba83218a0040624813d4b66d5a9e4850148e06ad72ce40b25449e760c366e2c19cdc7524a217d041ec640838d10c080013c8a418c1e3c86f1a30d34d808c18f34d658c30512e0218c606c800730088f5fa491469be18b5e6080072f2cc063173ed06023042e80000f5dfc51630d358e1a6bb0a0013c72f1001eb8f82184ef410478dc020d364210011eb6381eb5e8c1831608e0310b59f0e0110b1f68b01182f71601193c6061011eaf501eae58008f56fc7834d06023042570000f56fcf8f169bc1bedc60f1f0de0b18a04f050452a527f1a64f040851b6bacd1868f367ce0031facc0003c4ee1c38710be0714e0610ae4510a37fe870f520821fdbb0f34d8088110d2bf0870f018050e1ea238e3479f21011ea160c3c78f04043626c0031417e0f1091f6aacc1001e9ee8041b3e68e30c4e6c42136ba41d3c32f1c30d351e088f091f1ae0718906f0b0044a038d334e252ac0831214e031090bf090c40f08f088c40f36de58a38d1df870000f48fc68630d377eac61021d3c1ee1030d3642d0c6f7a08d356810011e8ef0a1c61a68c420033c1af1831110e0b1081e3c14e183013c12f163070f44b40f06f038848f03f03004193c0a81001e8428008f41f0317848a2ea93f2bebf020f41302a5a925497f7e5110836a98bbbd72504083ef2d58bc6ac1653f6f0f8032b761f4f8e6f5af9ee073ea59259734506c941b6861b6c08a18d833cfac07b2e7929b4d7eec8d5073cf8c0a8673af32e399ed6da0327967ddc6e5c846ba9072eaa7ef5a44ddad355fec60f7482cc039f526d25adada9a44ecd030fecffa81a9944c4e30e6cc6ecd151f275ec537180871df8102986d0f9b779f1af03fb2bee7ea9273d955883f3c1830e5c46c92109d1146b47c7630eec776fb20cca3e7bc669a3ff870f21b4d16aa0e1762ee021077f2ff69b529b1df0884395a1071e70302575d32f3966edc60f34540083476bb0a08d357efcf873def851c6efa07eeca04560460f646ff4e841ca1b3dbcbca18610900f373a0d129451861aef861b9d46aa2a027481c71bb8aa728fa7cf2da52bb981750feab446dcdbc0aae520bc2fe488954e36b06a3a8a4d50bb962cb306cedbee5275686bd44929010f3530ba3282e93125ac94d6682391fab11fe091063e99f76bee789a37da2df04003ffe31f5f824ca292483a03ab9a4727d8c574a33d33f0272be7d02054f49c2c1e65e047887c91d4a5f4f49c0c9c0c49e8d13e216a5f19039b5d1fa6deb3ebdaa515fbcbe6c1bd0a031f3c27a52f42464bd38a117880810b993a4b5f4a216668bec005c92bba54752ff023e297975e5d60340615e5393c5ce043945e9a1cb49f8d108f2d30da7e2bdb8b540bdcf6480f5d62423b8d2a0b9cb2f3ac9326a75352c2051e586082125d59748e4c41b55760540c29a9a02d6b055662069dffeb23664a5681937ed29366fb5259192ab01723be04dbf414f88c13530e115f44a9500a8c0c65b6e952b288138c0217440e79aa153f236f063ca0c055fc3c425f88f1b34a86c713f84c4b2afd8d7bce9f5911783881b1d4a7da64c510bf727c706a7050e0d104b6f30565b24d2b72481713f06002fb9af2b386e686c063099c679668bf53151e4a60e2faa9144d49891b396764a5d13e88c5814712d89ce2668ba6926764211ffa061e48e024784e89db9736687a46d631d8f70c91dc744cb602a14fba61873176148309f227478b787d779333b288c1e4eb7cebd1a64753f7d1c630b8eb0ca6a6631421728c1106272bf5c7b89f538d57cec8f2d14629c1602dbe57fe3ae11959c0e0624ab94b622ea17caf33b2aaa061c72f88ba79b4bab74b690d1f3d3868f4c00f3b7cc1ff7dbccad3fe8cac1dbd60548ee9acdfa4c8b1dc4702021b3edc38adc62b2f78b3131de28de7269d9c9195be0d1fb90b4e73f0eb47b361c2b043175cd60912bb5bf4fdab33b272c105b9a7d6394ac4c70f340c0c3b70c1aaa6bce4f974a9f9688d53f2167c9946b0d73bdbb6a048ecb005572131d9abe6207ca3b56093484c29ff9f3a19738ed8410b3697683e19764ac48e5930b14ce8994ca32176c8824be5a35a7a92f67d0eed88052783ceadbaeece9fb12dc40e5870a63eda8698e5ada7a484d8f10a3ea53755b1cb3e234b0d34fe0710d070e3b41b75831daee05a2b6b05a513e2c5c7f7a07d402c0d34dc78c3d860472b38c941624e573982a70b1f76b082cbdb28923fe8dfb17f156c96e6cad194af0ade4406b9a9562b7c47a9e075553f7f3508155c5cbb8b59a28d8bbfa7602d24ddda27f263d66b0a762b9f6f6f921d2497a5e0df840475a12505a73d4946cb29a7ed1047c1c9647d2288720d726444c14e0adea3b932f5b3150aee5744505dba25da8382ab94ef8256d23d71f40946898955ce1e62bcda13ac0795b427e6c13a5876748211511ad3290b31abc909562c9a98daa0dd3a256d820da9a24648d2ccca9234c1e778521d236711294399e0a386a4d55b3998e07f376687ebe813e55ec2743aa9d051444bb029cb644611424b245a092ec72033c83a213d8d45097e2d73d0ee793a9ea48c324a31764c82b31c9e694c9fe52404c10e49ec8804974d07bdf6c88104d72543ae5d87a830f923b8f7b46229a6db11dc464d37823725df9390b7c1b455463039f2a824ad4d49cb17c1058f6c29ed8ac55b4b115ce7d115f9d184d43849041796fee347f5c939428860f3f2f23bab7a0836c85c49994fcc1a7618829394cdbdff4db7e9a8106cef9985dece494a758460837a50ea72ddfa488b9a61c7203821f2421251da7e4a1dc30e417025aff24070a9723e750e3b00c16736cdd5d94e56fd49e1b0e30f5cc6985cf74f33db327ee0d647ff484ee3a554826fd8d107de72c454596342725a75b0830fac29534ae6f66469b0630fece9f6915b67631976e8811555a7f62e6af795bb64d891073e57085da2dd967376d0ae3d6250821d786043d606d591edb8039fe3d9ea496d52dff0366280d490811a2d30a3470f337afc93608d18f488810a7a202576d8812bad7e6da9f4e66477471d187d6ae26731addfe574e073e706e1fd93938c493607d64dfa859894ccd4b0430eac6889b6412b7b901363c38e38f03fd23f3764111cd88e29bdbc267e69d8f1065ea4a6e5865c1eb4e705073bdcc08a4af2c694a7520bfd0b76b4a16c12534e1792f4851d6c60948e560dc9ad59b2f4c28e35f025936da50e16b2fa038ded118312a8b1430df95de49bd88e34ec3726ef3744a8dab0030d6c8c24ad6db3e876297960c719981882f214b45c33f0b984469012246fc5b70c9c96681f4f7c32f06a63d9d9ad6234618d8155d1edc95b317061223fa70ecb1b6c8481915d1736b1b31d60e042e68ceafa495fe05f92054f9b17f5dae4057e7cd2fe7b8e1564307581fb1259b3554ce2029b524ca5b2b583992c6d81892554cd357a957eb4c058854aca23bf879093055e5448428fb6bdcd712c709da36a5e89d9cdec2bb0fe6e41dd48adc0d908cf1aefba92955f0576b4646a85f0a9c0ef28172172041593ca4f810d95c9bc944c13c924053e9b66ff8a6e14b8cc39f89f9857c5288202132c06cb6d2572080f3d81493adb43de9f08962127b01152529325869ac0e851bb2b3f51954a4ce035a4dfad1eff1a7309fcb54beecd56e93125b0b7d136bb82674712f8dc2f296af65eefecec4002e3416fe9fa6cefeffb3178ebcdd71625e48fb71b833fbd7f3a4ae2c5e0a39f486e26d9724e3731d8f292277c9247116d0f834f7af29916353956dac2e0b6821c91747bccae7530f8d050b1369d8d483a0b0c36a79eee5bc839e4777fc1ee2525434aca085a5d5f30b23e7e86e8509ad2ed05e71d9a9457baac132e2f381996dfd22fbb0bae2467ca9dfd21a56a75c1c74bc29224ef949c930b563f8552e2dd1a348d0bae744704b35119317d0b46e5c97a97ee62d66a0bbe4b959212453dccd45a70f929a83b2f559f374a0b3ed6aec7539da2c9340b76cda3760eb92efe4816fcf875946859f2a5642cb85c2a6f093d163e4958302a8229992c423a1d7b05934be77b8919aaf395aee02e28dd6d3f229e0ac95670a1ad2f462db1823d35d12969d4697856c1fee9f4d21e4755f041d4681665632a98a0ef4ae70ded54a643057741a5a0a308e91693ee145c109e64e63cd24787ce149c758a9336678cc1f72b055fa1d994b6d14c953e52706b95c193d5370a46b9fe5db6308c3fa844cd6402e3b150240e89c30141200c6eea3d00b3140830282c240ec501a12c4c24b51e148003482a1e3a2e2c161a1a160e16100e120e0c8542815030100683c180302010060331a53112a3fb4421b4e90d6d43561a106c0879174a10c20842915f23cfe3901193e826aa7d081122ee21a64894ab9873c7d62bca81cb6e7cc2209466b86036f4378412d1a310e4a1508aac130913c23604934ef4364f091c17fa1e874a68c83c8e550487c81d3cca75da617bdc50ce1131d4bee745b9bf9297d075f14187bc460b0375a33fc64e3fbcbb29b0700df6bf9269890428d6276f83cec94d2c3a18bd04563f54e6b0af676b266ad4c6b5d2e18685c8f77b7278b885b5cd9d4c6ca504db47f542d2c5f1c2946eb11a90002b806454d890e2176089bcef6139dc3d0840c0d9a3cbc4eba78aae45bbfaad6a7a39aa8014c7a9a2da54509de4d8e36bb6b435506b95a576e1db1b006e77f7990178acc7d4755dde8c32193d417ac886cda1da3d888040ab1f9920061826fd450740097ce6f6a5abb1e7a55497822e1310e12d60b8810b5d8303bec1847a43abcd00c1c43cfc087eb01ec9ebafe0265d4b1fed2b38210e1b04b1cc820aa769846a05412a238dcab082d00a15e691cdcb727e956d84df47c0e68cc61e8d1dfb3d47b78ba8095fab768f66fa9eeb195034bd7bceed5dd4ab9f59d8bd84afda56cdcd42bf88b3517c08898ccdb43a2ade3dea629e33bc594ed3d7d54e9ebba77b6ffb781eb55c53c26fe70a2a1b06157f039eb43824ff8768a47c523dde04c3140c5f82e7d395e85de83c2d10f0bcb238291917777652a314cef02ef9960e6e6ff8c9d1dbb950d5cff5b87078947fc70e3ab968dd3abea85ae8b8b7f914c137dcb058fbe3ac512cf80e489591b4382a8ab36144f6725162a230ad7206d5de4374f186a71a567f2a17aea33f8a9a058494fb21e67f2a7689302e561b0e2ae40acd0d36ce57b415463deccf3c0c7b14760d0b39367261ab62e02ed84940d737b10f922aa2899d3cd9cac4a1649c02c561a81144abdd7567197280a02ec54d37d70d0167e366f873f37ec7017671a5cb7d8a1acc2c37bf07884c1111246fc351bcf1ff469aa56c36392a760f69be62e56810e6471bcf1d4ebaad8f9bf61ccd94ec916c2e3a689103c7e69c40ad457b9a8e06cc895a040b295ff1a4a4a9e9dc8b32d269b6a15ab2a7d5499382872680d21644fc1826cb99b9fd9a829914f6ce604dc19bb2c446940248219446ad1a857f2099eca978ba4245d07665f94aac9ec16f3ee356d0eaf1dcb8829e73a8a7c5df50a64a5d2fa5079216236daf0a2a1bd06625f89444d57805b4ec4d4cb0c0f634b506adeccf438ed37e9f01cba1901896aa8bc826d1ccbcd354b539b0810777b40655e74ff86a5b1e02148ac0f1478f029ce92e7807577c45074bb4dd951ba23197111e3b6d4b3be664525b39d5e1a98b4da7fa31a8ced02524a513e635680d4e60fc3d7cfcde9112b89e58efdde46249217a37bd42cb293890f01ecf838a5095b9bc4de79d8c393c694915f0c2cf509e9ea2e35d7eac38babfe780b92e07b4c6db16e075ee1e9b3b99757d008cee2194cf53f4dd7ecedea0e152a38e708bd076aaa836521f6f6e4619e1fc2e69b92b0023cd7f4df48f4b14cad5c374d7ea3c6274d6346b0f4c4a3c6a766f8e3f6d90e8d09ba95073af919849aa5fa960c0451ba9f96bed6971913b8a56624792e07d5aa2e1dfc87c4dec2b5ed2fb731a3cc9c3693337167c2b960f7270c5821c320681c0e780e0f10382bff024f6d74038e80f4f6ae125aa31cd043f977016c1c93a60af0fd70bec969339b39ba3a8e8936a8c0c1812fa14b1b574915882ec933a69953d70ea454a3de5aa3fd6ad43768d0b172fdb9bd4e95302a27569df98ccd24be7cfb4092bfd36a6d76e3bb61a1e3e2df30eb5a6ba14b5c1d7022dc445a9c5421c6f5ec886db90206a527298f4ac923a407bcda27627f6c1c4d3fba5fa170a25891aa9366ca46fbd40d734db4246a504e9405d6b6d46b88245996a182c91f6a7544f4dc6f9b66e9911d4d889c0e89fe4b02fceb6416d27ead9472d1051bf5b3b8487006e7aec80f59a00d67cbaa0e70809482c162942eba72776f5f194c1c6f906cfdef8a567206cde8068da443b8534379ee4e7ffff2ed5b3009d300f80412f40e5275e7a12ad989d16a81100a05303dd73b1bd39b18fed41644c2327ede8f8ffdab2c7084b9ef82f657ae2ad8292084d37dae30bded57f89c5c1ed9c742dd1675779680f52722d2c914904d6636f6587c21dd6a57425e7f5314f97c6009037b85c480b3250ef57d117e032d3db99f1e26f814114f57238f3a3e3bb68665c43ea80f48fd0a25542265611bd9fae64a8950df2d865b775bc20bd5a296d0db82e77716108736eaba557f2225f237be19c107710a8d054919ef830c72500d840daba77e6b22af810c797762fa241958e403b52c961929df05721ea8d9a823968fca77771ceed9972727455930e3ec7fc40ed20f0ce65fc805329effe43523a7b46a06a0aecd087dd2b03aa72a9620c12b85a497e19182c687aa980bb9b741d2c37d24a2fbb95303832f04b9fac643a03fdeb0f4938c745e10a99d52f14bcbfd3b4b11354bda71f83b367ce09753f8e93d256ae6a38fca01e2d0a6e27bc654427cf0a35146a6b41cdf83e6213274ebbf1dac8985f33c925facbc5576be691cc93bfe390d2d1d1a8b120e8b129d231d7afef0d1b79e4a6444387238b6ec61bf775ccc6baae54f0f09b9b6c91655a8ecae40a27eca275ee8805f3e06912df5616d1094e281e4ec0084aad0d0db1db845870d7dc811fd0e74ba0c13b86b456d3a00574da76315b35bfcc2058d78405c4ef659b5ccc8df2d906261964236b1cd0ef84d1f69127e228a561b1f1a1e6a72c02c9094d9729046287a9368e63ffc4f494dd8740dffaaabfc4b049c8fad55f55ffb3fea2473ff047ab7bb3c28f419f397db77d9afacbea374b7f859d580ff9cd4313576d1de32f31f4858792a0be74e6515e42abfcb26a078d7d1f1b0a845d603d61930fb0511fdba0c60a78c040db157b13b30984025413523449fbedfaa30a6b644096a026a63ef86407131120ae89391f2c5dd5eb2a73d6c15881ba35ee9df15d2d47dffeb6b220dbc8348979e3bb2bc1fd3c26bf4eeff253257775c69fb4f949a527832b516da46f931a2751a961e8d595bab753a5806a944e51d51ae0d528ddaa7ce1610a5046105de695bf009fa4bf3d134660506b14706dba1efd28408c5839ee191ee99f253138d453128cd1439714aeccb3fd82c879c53b0c5500db6da45e44eba8fc2daa33d6b0028b0bafa44dc38dc4166511dedadb4a5ec9e2a7f0031b1526f662fd39125e7fb110c40e9fd0adc4c60fd727d9a4360237f7918ebdacc9e5a4ed120bea8fe03619d64883c15edeaa9f2fdf21f653b29a274f245eb1132d9a546a1dbcc901596352e86b566944cf800c9c7bb2892ea0f7ae965f811a03022115622df13c733addcd3e9147a776b63d7652eaf5cda6697a59ce469f260781234dd0c0e6ee1804d5fe93ab58a2480e5f7ad51a84216d9480cf58a383b4f816c8713156d79ceec6d1b3b6a108a06dbc38dacd8944b30f75c60a4992d752744a312bca0f01864dd77bcc47ea0ecabdd80b61d9e6aa51ebbdc480495c5697c5ab958bd1d46f36d287026d30c9010541418cb8d3920f9a34cdd747dbc6a4579945cd5094b5b854a9285b7867c402fb03462ec63e13e656c7769cdd7dee0be2cbb59c5aadd3123b0e03d4f00fef51534061572383805ddcc32a8845548b518ab4c1ee539970c56c213c4b06200f60f57a83f2374ff063f3eeb7a6d928addfdc06619c543b1e6a26509f1b376d0f2db640a1223f4720df1d7f082432db639c0464b156d15c5a53f4ad6a35513fc439dac0c24dca0ed25a0e3b1827adffa09043cf07ee21717c0dc9c56b5e79c6d9d254b2ddc191e804bdb473145d4705137fa007ce2070f4d4a30b08eb50d5bd11b98f56e85f6c5ed417d0da1fdd5a5214536a67e67af009b5116132c882d6686f74db45e08ba49170e4932544f1c42a8dac34d563bc61eb71fdb24b551e5b659c50f81217af0e6b22adfb0b06ee0ae07eb04168a1af2c25bbd334acf35c8ade049253c8a118f46a970eaa6c3be7262c01c5134a69be3b89f169d90cca0d54812ccbd59c382d2715cde26f9afb0f99678c06f8c18b1a53fee6a97bed68bb823d914db07d18d43bd20f5f3ecaed8b3daf5aa52a132defa91c6dec5ca4e0f5d892a69a39b0514353cc83c567aa6018fc2d82ad546d6022fe0f9c8162c359ea29601bb955e8a18327430810c920922712d914c734d93424c223c28a083c6f9272e2e867de0e8e2557e80649ea202845eb7694092a18072b0f8455c12b50d071645d2225936d55515a1c1b45c6fb0e763d2964670f8139cd1464e674682fe3ad60601a8b125e86fcb79cfece40db53f0e3c652ddf0b26b23cd5ca11aaf19689868b40b02c7838ed5b020484b40168fb0ce90a5537019c2c2f64795900a369afda35dcc1a220c2834ea2ee293a892730583e258a08131038b58e5ef179c7232a095b41659cc829aeed44f8ecc6cdbb5983a957fbe6ced3c6e8988b059a248dc14203d1594c437006b82c76d9e0b85624038a3d44fb3b89013d83887c4a7fa21e2080025c7878bd16985ffc99d18498a373c801f08f4bb908c9ea5edb3fd276459a048f71074b658304c411bfa2a50762764263eaf0f53ff87b37f1815345e0c32ea3704471217ea4aaeb66ef3c00762f86a276c7eb013c1fdf4a05658dcc15c7bab7b7546b11d321dc8b2c5d79ba3693cccaa561dada05941b5a2689583574362ee2c6cf158a9571a8f35d49ceb935aa8111dc6430b61663012dda0f7c4ce22f48195fd5d813797232c1db9ef79339037c8a35170c028516f81e7958ce9929941d1e0536196db7f211c02313e80191d0422b1b3a195540a71f965baebc60d714c342f928769027ea24287c6b8da0d55d0b211ff636c2ceef03fdf04ff0331e3a3e73f2f5fc903a965623400742eff6f7722928ffb4000da3bba24e3ec30b3bb7a387e6af9333564e34f3e204f53a0bc7897aa3ecb31a6af0642524ffadafdd414011963761bd936329904b4b65e2ffdef1a9d59cdf41891a8dedde485e7bd8916ba38ae76a5590323927df5ac010b5836e94422c22bf57e56054fb5e3910b80fe2a2a648a5ea0ddc5cb8ac5ece999a32fb54d6594bda0ca65ee83e29861c4f546192dd9f6ee84059295e440ab1b3e142d0c332908aa78face80c877eacfa77793a7314b91795660025ea6f0cff98b7dd6ab37a0ce81ed50865ce868bf28fddb0b71a99bca4fb41af540966f4619de037d9a777356f2e15c29484fe72340e2af1b5cadd5875fefd8670ff876f88849e58d94e3bd2d1fcdd840fe807e16481d1e03fee75b721e2ea39a9ee0babeda53b69587413dc7654872022b7c94a89064bd1683ab25b0653dba6a7d0ed10182cbaf1aa337962d263a3b9a80b9e6f3515ca289813595bb6e4049b96ad0582f8e348c6ee508c4a6933a572ebd830ca505bb2ac8c7d64a64f51809be905c018538549a0fb291ba50f5349e4f38e08818bd57fed5ba74d9fa00d8a062b0a6225e3778409cd969eaed7e076f1344e8bb4d95f1c045bb84758020c4b806f51391af68318ba88b0762d9e33f10d7840d5f2d6d964003dfab8772a98415cbe1d312d27e179c561d169c298c64ab50215d4efc0aa06c02a13f1d641826c51e320883162a0b34f92984e82f53e8691a238a919e1873f52c976f630c82b1d72e29010c1a775fe287c1c5f74cdf5b11c4b42f9bf3da182a1c79408f1d1920f77ad6167025a9f561c59383e73618978d5c7ecf3d11a242b24fcc9c19b714938e7310fe02845cb68d28ebebddc783bd261c6b1dca7de002b0e83b46a1e79f1b2b75c4dd26f38867a0098f18db72354c5670ea9923e222dd335332fafffa3220a8a34e3b2ff84de076076b1694876bec50413e9d4abef21612aaa0a31fb291509ed43ccbbb6bc2a5713db695809660d6fd0a9fc1fc02dd6c58a85daed986045b6380096a1f9a2393682383b560bf00ee9f9757e329ae74bbd6057972ef6752ee0dd6355531f64d010090ffe1669400403356168d63dd53f276f64880444c76ac38a26977510f633818c459d6d45fc43c6ccb8129358c1aa606e5f8f5e035cada82a08a36b568ebae11d0f15421656a245c962af1ee3c59c397bc55dc6ecb9015ffc8cfe903befb5421e21573bbf6e497ad38a3af7c7c66c96cd4333290714bbb306147825f1922401969a740ac20e3800d67474c0329484540c42db40e5c9a86db44bca76a91703834ad20e8646399a637804e408aa01d9828e454d4c2b308711011633fc82b59cd1bfdacf1fb80088829aeb4c2f4006a899dcf44cca4d5030851583f36a0382c1ea4e00b8f4446b59b9169400b31d5e96af4e355fab1dfe45b7f1993eb7683ffea13918492b7ca7556a5742d78a673aa6ec1b7fe5a1a5b349e89931dc25614ba11c5caf9e1ec7ef66800293c18394914c59ff8f8e1efc8de432acfcb85ed099b273a6b8a0ce7f41d8fc970a319049908a8f8f52be850a623b408ac7ef01e5692da04628ef80009187021824e91ac11aa0802497d2775252086fcc9482a7c7940bee4829020a40a4245137ea420af2029fc265012240a740251814b819ea02aa44b874f46c83c3c0f66088e0556a1a2c02a283d705230145cb478777abdc873041809f94341d161bd1ed9e4498354d2a0512c721c866a8e18d5de379326b5c4bc4ce57775cefc9af6781ba758a41c4b9121b3e633205e65b440a2e595d41e66220973af23048bc625cd0871f5d6327ff54ba79261e1419cccd27617d032eab6a44152ea92097840bb45736dad1fbc04c917f0676113b7883af10f463474097a0a013bb22e2743bd773f667cde8ee1d7138be55660ec88b263abc1141f8f05e6b17d7921f69e4e248266ad42ef1ba9e8525e1cc4a78a31e5ed4cbca39bb05e3a858dc467f8df2f3ccce9af869ddbd934d1611c42d8561fea52135752a457be1a2ed46d7c80581ca59e13256c8f5dae7d01f0b988914148d4fb651813085dd0db1a28bc8475694f0f7503b5d2348936a09d873c03b6e9c34b945e8142f56c46e7a10b9651fa6e256cfa75930e5133a0ffedd51d7fc87ad1299bb8b5f0668694e1f67a14292a12b2e304ca4f1bac870bea86a53d09c9459d86304b40f1e1a256979b3b9a12a96a196ae0623c24ad6a6743c348d36217f42987fc488b768f38d0946534a551a53733afaf856714ac68134d2e4f6a748ce82bc428ef7792391a31decc44a8a61c35036bb5e5be212625587f4ed487921df0989864313576d06a1131820ef1d4a2e98626613f21499f7071905f65d83c0d1402cbc5d84eb0a783f284c95488c0064411274e1560e05739b96c3b047e741ba08eecb300a6d882df07167489a5acf32790a1a8ece46b218adbbad546195753531f4201ceddb22f5263f0b672dd42bb5d061ad1ad892758dbfd97710dbca31b9e58ef458cb058fd9c53c6258ad34f040e08534521aacdd1f31293c51698b039e0a827061dbaf69ebed0bed15a912a6a62627e1a304a3552e0cc32bcd4c18f5a53780255f533e54e91be95b0f166b4ac99bb262290b03297e1bf0fee77bc5bc1407ca98a523fd0eca75ad0cd94c467493a8cb04143ecc2928dcecba3227a61dc0cd354703dfbb898aec4a0ac597c591aa7a3eaac451ae0e60438640f2438b910c8092f145ff82f2c13171a45ac4255f965205d25d9417e7c3cde6abde548148d85ced59a63be31f3f12df0dedb742e3f30bc749e58cdc6255ada5b2f52faa8722d191f5d345655cd78c37478ba472e1ee19ceb3a7b56b0d7e60d35d518705b16a3fc06fc08a3dbac1593a0220f5396177aa39c3b8cf0993123a8acef5c3d9ca066aad2ffd6f461d47215a1f02778854c453cfd10bea05c7318bc41448d5b0be11e9d3050f942207cca4884eb2f38772bb8c42490df9ec9c717adebaddc13d7da822fc3311983902b0825beb71008e42b7f4b7bb376f34b160fe089f215047a6d96e532036b56288cb59ec2439a3fba457780e3e83e5aefb7513be87c99ea2b3c74178b22404a628de7a258ccfabd65b91d46242e255de97a4ed714b055f9b9a97063777d4f7defaec51b07d5e45d3591808442bb2268db138ff5064d5de5ab92727c186a8f5db3a02f68a2c05e9273d598b054911bfe419f74265e93f07a57f4b88f176444bf44870270bf1be51ffbad7be5d26ce4dab2bd82972f54b94b25e696cd3b8e2d8edb7117e613deb944a50689f10b1c5b31500aa1aaa2d3d9532c2475eae504b390fb478cc8d4517b7857f8e3612b09202f93361cfb55d566e99dbb9ea6164ba356203d121e079009aae2380d5c772005a104a3407c8531430bd8c6f16f85c33f11d13318771ac2bfa961b5cb1803bafe5b3789a8accbfc2560b67cf83011c75b0d2c3e5dc04e36a5c3511349c75b9dd21c4520f98c68b3ade8b02b743f3bcaa7c9bd837f353e75fccb554a0f03f21f25f6679795f1c63678b867e413105105061a982d18fc80f8c95ab7e5060c35aeba01ceb65b1cecb9d3f6ebb4041e6bc4d6c3af0b9cae154ddf6b6490d18aa8683589110dc1cd1985653e86a4ba95ee47c8591e275c72389fb2f10496b4a4d8088bcdd3fe66766dbf81815b8a66aac6e3f9818c2f20d149f0504d5857cc7441509f5fcd81e7a03077b4b97577a87a0344f182328b4cd4624437ff5008e6d9329f140ea84da2e96682b0f9e36b36cc367ede9b31bdd61f60dc86fc3633baf06673fe11723e338098e94defe159de1a319a88f3081f10f7d164f17c81201e49c002f1b857a56f06e4c2a3ec0a430f32e87227c02fcd521ab9df4509916b2d1195b1744fe36379f0f52d8b6fbb766f814b8c7b2e0598d160c4cd5fd4091a2db0a84545f0ca5fec66e6829403e06972147c109a4be63357edd473f509b69a8cc2bd5300f52e1f4305baac2c12be639276965be6f9f61f092583228c925721ce08996423f29bce2d50a8670000fd34fb99d35e7dd8d8d25d52b4794b753ea815c1c5820f29b681bc072ecac42a2a1b8a57b309201b20f694eef5aa325140f7c9c53ac5c638285a57a62beeb6a940864d875178e81454a4c39febf17bd742d59597c46b201a874ce3357907529dd7be6d4110b29e90b3a3aa778671cb499d418dea68c8c6a01bb5cbb0688b27049d909ae73e83bf98a6d1ba9caaa691458ebb801de100a26e1de1db95235f2f5220b6b19c64ecd043c48820309ed752399c6af4889a7ba8cde1b20b3910de738f0940dcbf72a5c11c7f556ca5d13999c7b3ab3fba4cd273218d87de11e8b7d89dfef642e89bfb322ed89dd59e6edfcc9dfe919fb9a4a6586a3270bdd7749e60db66008418f2c891f427d04d0f6f19e81384e94a90fdb15aafa7bbd4bbe3d0b97f33034d452546dc190db665a3171437351d0c657639527b5bc133fa1ce1ce3a7f192c479654309f020a63321198c70d46152400b55e3df5bd6f42ae3de1e121ba127c16403720ae00a7810505bd0c20088c5f002e03cd4dd6e4402423bf0da6c49a4d15598cae65c1f1c5b48f8e88aa3a1c728b566831b2b43388f97942e52f77e72cd6201c57ac4d6de9360f067e4b14cbe8099f4b85b254a9d7d8d534c3820d54cb80fd05f2e1910fe4066843bbdd136e8572650dbacc896185c255dd8ce8dbffbe4b7c0c5d55e73c997863c357c9932aa9931e028af97c5422bbd6c94dcca73e5ab0ff62f0cd7f5132ce2195afbb3959f229b2be1f9ba8fac3ba9bc04018639ccde0ee91614f95946c89b3d9e414c9411535b0362484cebacfb4e096e03a2ef62117ad52d821b185b9954c3f55517dc292e307cc995c862bf42318b922225216d39601f06456c16c3268fef828e76dd7b29c2bc65aa847daaba69519d0f89495e2746eaf9e2fa525314f79ece0045d6b32ee6ee7e90428df9b0e05f05213a14e95ab525b0abbf1bd818b58170c8f3d99dec43db92051ffa3dbf5ce44659ee4beb1419af24128014845ab737b52ae56d8416a291763b666484d3561c7a5743a0a0f59a8fd2fe3486f52cd36b179bac2c15925834f5fc939a3f1903463984eec490af812ad5e55467e49eabfe959b45ce6a87e8817f7e88bf7ebc7da59f266a070447542b9c325458f7b7a50a1e747becd0898e4172f9125bae641c8f0dbfedde824afa3f374f738e087e09374104b973a840b00b6973f6b7b467787c92deea2ddd2f0a95bcf289339c9cc1605799d508ab36476e4f304d71f1d436f4c9098bcbf89cbc773fb47bd2864f41c45818290e2baeaa8f150a4a8004d2b68a39a2e39b49dd10a2e7371c01a6dc434189a35c9bf7f3ca481164443c5f73e2025f386376f3dcf6a4ca9a9b9451926a0203c9b05be6d58b1d4fa7903948c7ad05b52ae7d8bed5539360a2c64fc14542fda9b088441845de5348849fea00c2642918588d813756472276a5c974bc280aad5b88e654ef5922a79ec1550adf89242cd1840fef0c5341fa1a01f7d47752b6cb53a8391ce25596e69365b13d2e93663e1d790498c9836e81fc575f686f2d3f46597060e4a013ec79b9f7c853ecce5993e98df105c1fc51e1c2e83eadac9cd05d9a84501717adcb714b927f61c8fa551d2d1dd2c92f29dcbec1710952fcdf203dc5a22e61725e0864b67becc800f79eff2166712ae665c6ee01b90c781dfdd2a879afa73f2de212f132c7dd830b3a324d8f950c014ddab5f513ef1b5c467e7eb02f50bf890f0eab380297e5503dd7da6657be7a05f660e0aac28306186a7f69a1d5d105b84ffa61ad165a0ea59ec403c9656273930c4af4abf65543fd0edb2bbc75eccb79cd419e038adeb218264faca500d90c7c20532285d397667eaa9235917880b606072ec34c0f7cf750a1a51a8bf434112f20c45ab8e3298aecc9f8b0b15801996d5e920b5bb8abb7faeca189bf2895c8d903199b5d8b4c51822733e6c6dc3e4d7d9bb93d5954357680158ca077bba248aa1455f58f28003d8eb7029735a65f8a1c7ac7547c78ab34ec9ac7024004a8aa8ffdec87f83da14f296dd0f3c2b950ead5da6264bcb8f2ad03704876a01c797f698aa216ade644a8173bb89dec882013ab0432d62443b1a753b86ca4e624f5b5a9ad14083ed0ae12a91d2d7ba9c994500664718444519a99580981e6481e0a4234cf807f855df2c8b4b8b9cf6d6d999f2a4923029cc968836a1972594124a265c8551c0abccb5aa9440d20339d6d00056888c41b7ecd1bcf8872bdb199a6c3a57965443371810c020fc090554341846d6fa5adc694815358b5416d1c9b6020069ad838400a9f10bdf1606b8a158e73f75ffd1db2cdd0966bcec51d6c03db7f5915e2751e820e06243597fbab2142fe8b26a01c0b34ddc2eb2ba661b1b3ed0584631e1d14d197e6d67993fe0418db573f5096276dfe2c5960af90d96a11680e26c92b5de7de3c6bd3a08aa6044bebebc6f4ec4478cc79608d888f4f7d41660ba65e96714c71acccd554d5e57b48d3a0a3469dcbc7b41e946197050308b0b0fb242936fab5b9574a8821e64afb76f6eb9e533d83d71ba38b15c6e9741deeeef261e493bc58c9695f51f43b2529c16560fb97787dd3b9a0f9f99e5423a8b6138e005da611bb0f40883bcee0ec54f20b9f0642fabea7fb3d9db2e8110f704c927182bc333209100aae2cb064416533addb35be76f5fe73057257d4876f22dda6b971106318a1b0b3ba25d7f4f439a333e3c57e5aeedc24fa4edbeef1a8ae0b8bdc78cec72cf84e51aa8cb158f9060881a740102efb31e7ca2e24d23ef3692d9c733d0e070df407b92d7a0722d1bccfa933b647451138aba66693ab5a197002c6fbbdc1d4b57f171bdb943c02f00d2f953b9bdfb5a41404774b4ecc17e3d3ca7715237bd02e0f085877311e5c1cd81b4a001111c98508c7fb0830c7d6c2f086e2e7ec7ba89e5371ad12ad31878bcc18c3fda65869ff4b026ddca14e8797672bb5fcd88260c7b41f3084fdc56375d7336b7802d7fae569a3e5e410e0414002000232c28f4701b25164379a66b4b182145177b28806b87f60708c88037a6b14659638c6523a5f8d12e67fec9c3d6d20d3ff73ba60a17b13d82cf68e76091d08412dad205d6087150de4e57ad7377370001bf3d142110d786b0b8f7650a7c5679df20831fbf1667c63b6c5e833bcaf863af162d32b21a2456023e15447c0a3960b024850330333333333333333333a3bf52bf8dffff636c516eb2ef3620640612209599999939e517c205fcf6635bd399894f67263e1d7409a20a480a0f0a6ee78929446b848f6367087ea78b5dc48f87d6e2118267f9d6728f479a24c8088293c7e766d5f10308be07d79c3972df03193f70634a9aebc6e5ccc7df95c9f0811f621d1b19d647c8e8816b7393ee038dccdba391113278e026d10c314df64d9b4645c8d8812373f5e993a64bd963f0042143075e46da8f2bb45848390608193970d2a6ab2398e4a011627690818314c8b881a3a216dd93bc8a830c1b7861829cc7642ef21fd940a741460ddc1ef8206bd7f4464ab73268e0e6987dc37fec6d697f170b0083d8c0808c1978a92eedeab591210327e639b1ff49460c9c0ef729667d985c9001032f0fc587363769564b72f05ee174e5d1a87a1ef8a654395ce147b21e85889df46204619cc08b1a374e184fd25ae1e78890c73ef848791459133858e1bd4628190bf7d1a3b60a27e56041ad625b44cbd8d852859bdec736e695c7a749852f217fe72f95ae4817e70b2f6a34133850516edbf461f26c6c31c729bc54e12996744b0831c2e430859fb9657e9046628c3162636b4be14f46d230f3691b5b67040e52b8b94cbaafc27d3ba78b13dcf8826a081ca370720eff23f59ce4dde31285b7398f3bddc7594a33571d3842e1c84a89bf774baf4b77e000851f2e4acac3f6984f60a12d669994493a7078c2efcca8511aa25530b7135e857091826434ed9c87e1e084572d6335639b834886d7c0b109577cb03977e5520d1c9a702666b35e8071e3040ce84c782d3e417b2c79606fdf7260823c1e747f9ffb28fc20360c3157c786210ca0fa828b131cc7253ceb4dad7f2ffb92c97258c249f2b17c2ae70ecb6922382ae1441f240ba721d8050725bcd03eaa1ecf8519101c93f046fb44642494849b3da8a7f81ec81109bfe7526bca1f3de14adb8203127e8ff7c5f1083fc9a61f7c5f49b86fbd1881026c182fbef0d3c505b8f0e286039cc0e1087f24d56b75e3c398336d84b39e3ea43f4f3ff2f459fb030723fcd8e43f3dbe1f5d6c1f8bf0c735659d613c42844611fe47ea911f8944d9bf89f02ee688e9246f0eb5220722fcf179ca10f2721cc2f174e6e3d6b4ebb18673c06108a73624958bf263935829849fc5e76c345a0e42f89b255aacb05aed23eb3806518270a22fd27c70a505bae04b7004c295ce83bff1b9319b1f5e8401082f04ebe81ad27f70ce63f7776ced07af5f7b90915d8dc72901a9b1b71fe0e883e7294597f470f0c13fd3cedc834b759939f6e08f3475c9657661e0d08373dd7d1624f23005a9e4c119f9b3183c630e3cb8315da650f37f954fd93bb839e2fc424e559b6348f1c061076f6343fb24fb20988497c051076f425a0e3d68b5d61c2470d0c1fb414879dcb93e82a90f8e39f895ba327ef345f10ccbc1e9c15606b909e3e087b09db279f44846ab1a6068a16ae080836f7942b3533a25e6ce1b7c8f90b3a71ca6b5730f37b83efae0d182ab86a70dbe8fbbf2dd0fc2fb829505071e9016003470b0c1eb711e661e5a1e6bbb8860a8805400f3c0b1065f354d5d84ceac0d1c6a78fc3d8ef01f4896230d4e1e664d99e12d1a98249a9369d09833303eb08bbe31461e5938cce0c677c7ab8f63488f7519bc9447217b1c1123e5b78d830c5e9e8d59c3c7233b8df018d008f5f5a907036e90e2108317176a837c5ab5a86c1738c2707c49df60f0c3b5e7f06969f39d66a5058e2ff861b9cc3d69f644eaadc0e10537e7ce3153a70fea1976c169f53c5723f9fba3e60b1c5c7042ade74d3e95c7d7291c5b705d7238edf2aa5cdf6bc11fd8df87c6a4b1297064c1574fa97cb482646ad7a0c081052ffb20fc9002c7151c2f916816da59426bace06b64b5ca8a1fa53c9a3c81a30a8efa48a366f51c0838a8e0f78fb7d56436ce079a0c704cc1574f1d9baaf375871f2d05673284d8f6bfd23e9089c0110567637765c859127040c1891ef9678b9a1e5c7abc170f4601c713dc8ee9bf7b2d6c4d8c392a38e90587137cffa1869a29dfc6568dc7839065c18107d0a04183c60a0c590078c0d104370f7d3cec089130c1b7ac21e7144e51702cc1a96c0f3e48a79ae6b70664c180a24a058712fcac937c5935b38516364c36802309fcd88731b1add563090712bc3cf0358d50977ce8e12d701cc1ebf4242169d57ef4af11504faf96cf302b492da9388ae0a8f8a6c550411b5b6b1be063303888e0f9f4403d5aeeecc16d6c6c710c41f9d598027008c193cb79e439698a6121dbd8521a6b068e20b8612c6ae40cc966d2a0d15ce000827767397a1ce1caaa35c6f10337591ea5aa5ff651f9581b5b5f70d1010674a1c506bee0e20460e0f08117a2b9e6b160d3a0a1048e1eb8e2e30ef36c612acce781db923a85109e1df8a9e436e72813967ea303a77cc54aeccf81933a84d9ec74c1819bc6f2860a5ae9432a37f0bee27f368f2636f07ad0a38a32c9fd34a5066ecae639dcde57a3a934f0a3a79c471ad2879d6370064e5c46899210b6328f93815b21657a33d23f248b811355b307d263b1c8546d4150e0051747035d5c200b06148103065ee561241f85be0fb182478b1b1c786ca1850d060821c62b8ac623862bce297d14669e2b37b42018a856b839524f76448a1f664b0c56f81721117c26de6359a87c1063154e88a5268979fca92c07093154e1ffe0529aecc195cdc9a9f03b468ccb3e4a7f31a6a8f03d798538cf1872cc6711629cc29b3cb4caeda9c3df078f2186293c0b93bfa42ce4f1307c4ae1e74c21ef9eedf615b6b1f55eac0d300804706390c21fd697c71c62875f1e1863146e4d96f071beec093144e19be698e9935078eb833ea930a13ae7b1a0f072d5e577c96bd7aff984a3f11b346795c852315e50208627fc3cb0dcdd9fff52e5f71181189d70aaa2fa7883d53138e1888795b8c82ed7a6a141430b0cace04c6013ce660d898d31347a9aad6b1043136e18911f6b8e983d48f03c608b1899f0d24a74b3083d88299f8dfd189870436cce937531e754951897f0376cb667e754ef6a896189f3e87325ab347725fc51f644f164ae6673c6a0c4b59d9132ae2d53d5d5af2995ec7b45a23a2f624cc2f791dc8d46388b8b1649f8aae933f4a74a24fcac31fa7388f41f230d0947b2bee6b753528bf908376f082934e4ec957fa023fcd4633b7b8fd6565989d108a7ee4355cd765d8f633118e1e40dc1734f5d8fec47c5588427777945f8e3fe8148fa4ff4716f12e146a648497945849b7b9083f838a887b0188670b53f65c6a679fa486b0e6214c2ed3489ec32de21a686106e1ef4209c8ae5b0b941b8f92de7ce13b425b54c338821882e6d0cc1626b38107eb220399b950f08cf5be5ffd268d6543ffee04d141953d7c89ecef683132bf75862979c440bfbe0af0499c80ae7c9e4e318c4e0839ba3c77bb81ef7e0786b8a394df841859cd783df115eb2d4da0fd35ff2e045c66facf7306ae9463cb8e152688ef97d1ce1e371072ffb072edd954db33d3b7817b5b3adfa78d0e38dce0531eae0a43cb692b246ef0531e8e00f636a788da231738ee6e0884f34b7f4919c5f1e72702c5cfa78a75849138b835766353eb67049248f8683a3f94f0383186f70e4cc37b95dca82186ef03c2acd6986d3d0d5a30d8ec498611a42a37bb2a420061bfcf1ca560861977f58a68dadb303c45883f7ade111cd7c52ac8e1962a8c1510bda11f30fd2c7fe34f8752b9a278fba6da28f062f6b5676985e4d21f967b036a65c3f0ee59bc15b1f4aa827eb94c43f6570ee42fce822d705ff1c3278d162920937daa539670ccea9cf6db8bab6d9470cfe45d0f4c17fa6bbec230c6e1ee4d82ed2ebe38f1e60f036d2a7c8101f4f92cb8d3270328f448435915b8bb0888113438f3efd52f63a9b6001033fa7b50d3df9c7dda31faff03c936695abd015fe8a6a8fb2438c98529a6020805638bda15e63975ff08904c00a535a4beb34acce4db37e74db121a32db390f2d6cd4382cd0028c06e37471811b278c08086015ce5f58c952cb51856fed438959ac7e62e5a4c2d9f6f0f158da0779fd820a3f85ad8b58296415af9cc2b799bb1fd3f023c921a6702ba4187da8757e9ed3a5f0eedf63ec339914de6c5d964d5e3956b4a370b4b37556736f715f14fe49f2e879a8d150b81d39061f595a41e10f6e738f731e8934577ec2111f75121f8f2d86f8e109e77a5b32870976c2b94b3f8ccbd82ec1454eb8197a98247767a42b71137ec4b8ea51901fcd216ac2b34a6a95aebf4c3892a3a4d58f0f34bec3849763ecfad28f44d573092787c817965c43e58e25fc61c46829e7a41934a612bebbff4545c775968c12c56944360937450c79fc9d25322c25093f8cc4aef47d24bc983179e5411e7c6407127e92c9eebb6d37993cc29388417da8227eb6239c9cc1fe47f73ed0781be1add824fb71d88f6e6584172d327ace70116ec8e13187ecd10704a00837fa507aa816325c784f229cb48fb12c5410e1d5d90fab7a3687707a23e7eb8dc810be4d8a957c985de522a4106e55f2ece38308219c29eba1cb85a4bf24198493e6830e8b1e86f18e20fc0e2b39c57c04c2d7f0d321cdcaa7cb00c2eb2ca129331bfb92fd07b75342f8a7b3fde0d468e51f8b364926dc873f250fb6c456e6833f98ccd46112eb0729efc18d4bcb3fa137e4b2560fbeca66da853063f3c983132115ed7b90e987321ebcd0e67d16be6617dfc1e998d17bd03d883539b58393529692854cebe0a50fbea47ad6c7792c1dfcc8f441f8a8ca39b82e3596dc559483bf1eac2cf6944dfa5c1cfc488bb1ba520e0ebe868a72519fc27c907b831ff2978f524cb307ebb9c1db58956c6abbbf52d7063f68ca615ea22a6b3a36389a8739848e9bf08ab1353892de7d3c0e57d192626af0627acaed69b434f8e38b593ea4081b3d6668f02a95e4cc57ae31fd67f0a67dbe724a48f9376670d3754930c9de25923278339a244cb21e454a17199ccc6ceae30bbd726b8dc11f45c6a09e43478e6189c18fd8a33673efdc7109833f5315d721f64a4883c18ba9b3f3fce02fb825e3834e75717dad171c4b96f17717624c77c1bbe8e9b242ca05bff27c7a892509600b6e4e3953f34948b19d09400bd56dbd9c57a847324f5799bccc4d2b02c8823fa18752c9c7c3141dc358f0c36b30956895248571022eb8888071000621802bb81ac32d6e3a6abbf3b0821333f3c0dcb30f3573a60ade740f93c45b4d05bfbe07f9d3b73a05efa2fa5f3c4596cba7143c0fe703ed61555170d4eb2452c84849d91a55e78b1570600100288400a0e087a514bdffa6436de5094e88a924dda7fffa459ce048cc6069520ab339a69be07ff060d9471d62ac0d33c14b1783fd78c74b70d25bfa6ca7d049e35582d3e3cabf8d2d14948075110248829787b1071e2a6b4ead3e90e06b9f99c64efea391f808be4dd43cf89ed708ae9a471e48fa615df8b408fe306d2d48c5985131480437a34ff27c1e5585b00dc18b357f89151af3a847d9d84241096a3c395813480b401f04200427a57f72c8e358c9e307c1c9833cfa98d2493b8f870182b33e13c6ff26e355f303e72f48688b9bbc21293ef0eb342bcf8fef81b7f5e1da57a23158080fbcf1f1f1f578d856a1911d38a1c652a7b15807cee590e347129b29979b032fa5cd9b33638a034ff3fbfae44a16c907dec04b1f76f4896c1fbaff459b420036f02247caee3e8aef5c750d5c49a925bd7da1c7bdd2c04f29c9e7d1e5f29852390327ffc55ce69195c7bfc9c00b9e3ed7f43831f0630c539962c347a58de0716a3cb6d0c286492f0400833d47f8285c1e5a5ee1e4f1e47513b5719bda15fecf49f9f7385274865be1654de96d36b1494392154efaf1484e32a5cf53de2afc9cabc73f4c935785efa1828f2fd548b43f154ef6d8f60d793c96743e50e19a87fb18ebff711639856f7d2979d77f9030b229fc41884b0fc36350d9be14de69b5786c7ac91f6248e14dd894b47e22212e19851bf2859c9456886f5114aed9fc98c5ce42e16d488996db6653f80f0ab76b4cec22dd2e3aff0937e6de1cef616924329e702425471ec73cb25349e984e7b73e2226d9197d104e78171b3252ca924df817b2d2fc4cd27c6a6bc2489dbf33c42533e15ab498fa4b0be21962c2999fbf10cd1e49085fc28faf14c57b94dd23f32ce176956ff6309570dd3dbd67e898c7121e257c93109fee3cd6df9f842b977e10bcbe8724fc7ca63e1ea9252312d9fc4584d9e4cf16594706241c4d2f6ed3f661e7173546d08516253023b871b8287b84df79e42d69eb4759e5be51630b72c200a34609b63841d11a41175af4cb7084a361d2e3458dc709c39c7fa2022dbe8edcb8800ab4f8c3818c46b8d6a13eea2ec3dc256d6c25256430c239cb219be7f06163cb2e123216e18f452a794899c743cbb38dadb32c38f0801a8a08198a70632799aab1ce9d63b5d189f02dc20f7d20a3492c5612908108a7d3c4d083143fd2ccbf8dad0d64c100ee43f8293dcc83cfa3fb1fe6db104e590a69b1e683cb63554621bc6895349845dac9679b10fe784542889cda769563021983f022f2674f361f2d6e700009204310454303e1e7c8f031b74a559e2c20bc4e59ce4731ff7ff0a6b5a77efe6386f77e7082664edb963b4590be0faefd054919b30f3eb8e19249beebbb4f17b30737a7654b85cc5d51357af0d29aab6466f9bb0ec98393925b7d184265fb94e0c19f97b8e853ef1dbcc8a32413da7bf031cd76f02267f5d6e7b40e9e7a67fa54a9a27970d2c149f9f3fdd053cae832e7e059a5f21c234239f81dd38f3a44aadf6717072f325d089e1a1c9cb58df9473efafca3cadee0b57c8f23edcd47997783b7927c181562a57a4f1b9c1c6d7c3cf00c3965910daef4b0a2074f392eb306ff926bca3771d143576af033ff65e989982ddd0739368a64a4c1c9f21f7fd63ece16520164a0c1c93d214506b9c8dae39cc149a98e791cb6524ae6318317cac7e390f3b95bb81f6570536e9ff99cd1f25b850cde66cdf0d3e162882a8fc14be6e3f17c9c6c7e4bc5e084c8a3ba6c32f28336c3e08498ecbc7ed083891bc1e09bc488325bde173c19914db16925fc6a5e7035a60f2c0fa4479343aa0b5e68856a9b903dae262e78d9a3398f738ea69932356e788145c6169ccae33c793cb4e88dd96bc14b992b260b2125d6a85416021959f0b35df609e9099d3f050432b0e0ab6cd096e4e12d3694710522bbe6946247f8898d2daa13a00d30880d84810c2ba08c2a3cca979344ef6c218b32a8e087ba0f355d4ef96205617471821a678b2fb8d0028c1a5bdc3828f0a20b2fba401953f047e2ef1b2d861e0d3e300232a4e0b77f2e51f7e077799c283841625a88f68182bf926ac3fdf82c744a798213cd07a16743e441e7e1047fd8c33c4c162ca73ccca18c2638dae3184952c41e5b763298e09cf6a035a63f1b5b4c43c612fc41c43c1ea787b86c8478073294e0ddf738ea627617601cc84882a6beb9e3a20519487052b74a96689a2378329acb635af9b8b86d6cc930823316f2c03b4c2c8213e5ea3be5a898208308016b309b8fd2a306be4f674852e663af99065ec9c576fff561867706defff8785c1365b37246065e1e0f5e7e183613035f2447f518c42ef766060cfcab1ebaaf70b6a7674254fb817c882b1c4f4125bb3c748f7b90563897473f4c39cb6c9a346185db79d8ab1524e63dd8ab705a635544a3075655abc2cbe0a366a92977ffa9704ef24056c3f8c7ee282adcd618bd534c1ea7f0259da53cd8209b37aba6f032ff7a1e4bc6aa8b6a29fcacc13a260dfd9b9992c21f479e9cfa7e1c21131c859f7d1c5af5e289c2abf6cc5e2f67bd7528dc8a8bb1c7bcc7a15603852f52d616b426dcf89ff062c56c518f9dc71fe2097ff3206cf4c822bbc75627fc2dd7fca3d59a07c089d6a4b24b6acf6fc2cd0c96da62fbd0841f2c5ec3c4f9f5e02d99f092ff202d8698f623b531e1a7da9b85b2774b8f2fe126cf29f8e087ad25bc98563bba27f74044ad84d777b691dffd3b5639004ab8e9e3b3c8ea3e08b1fe24fcd165fc5f4cddfe9a2309a772660cd1ebc72f93130937bce572befc03127ec83f88aaca9df3fa783cc2c91033e5f1a05b423ac611fea86b7dca337d8f479b4610fbbbc7a3ed9c6184b77962e69cc35aac345984a319ec3626250fd2268af0ffe3334c089244382ae97be4c3ac929683082fb3bf8714fb1fc9e7106efaa9ce3cfa1ea70bb621bcf3a4c92d7f47fbaa0be19d8cafc6f483cce39b09e18a858a792b5bfc0479107e44fbceaadd83ce694138271272e81093845007c2efa18fc2667df0d99301849b72c8101fd6ea6fd33f78c1a422a6fa20a507a71fbc103284bf7c4e31ceec839f075152247e201f7cf9614a452b3f4b37eec195d37c11838a7a702cc66497589e265398076742f2eca13b25edfff0e0e6c18fc223e53c52c9dc1d7cab904b635ede47edd9c18b6f1fa4fb61570737c2c769b97bb53e6274f0224afa60a28fbf6be6e0645a49e2a3eac8615272702a7fdd6fb771a82bcbbac427e170f4a872f2d78a06f086a26106e006bf72cc69260d15f3b40368837799926565a84bb4a82a18001b9c9e9041b23c5becbaacc189298fae320f3578e7e38a91c4a3c796471a7cededa154f09ae888a0c14b9595efbe538ec9de33f83dce10e5073dcc36213583ab311d3c69f88b96d000cae06af430644a6ff52164f06fc2c2d94fc6e06b3013b77c1d31b8290fe24b33e5cfe3f17884c18d6411c6c35fd4250f30389a72b2bb13959aeff1056fa687d1e1152ff811e5fe724ed754d574c14d163287cdf7e3ee70c14b9e23e5f1e61593ac5e172db8e263d236165d530cc9826b21a5cc63cbce232ec182f7e3107b24295c84f2d15770647bb4d1a11ab92f6d054736a465bb90ae823f0ccb2e992261f20fa6822be12e9b2b67aa973c053fb3656b0bfe43ed1c4be1db6c9fcbde63a3e067ad9210ddc9a2c540c149ab3aadfaf1c02cf80447cb56c2f80f23b8a64ef093ff207cce93aa43cb26783f3f50dfcae34f1961829fe97d2aead2a5ea12fc8dab1c238450095ea73c8ee6fe5d129c1f26cbc9273cb9e70e094e0cd15254538ee0cdadd6f83058b4481ac1df1c34560ac94e7bb222f8d353d987dd9ed3f92082d35d7d9d3547bfe00ec12d8f9dc716eec3f228044f25c5929f942b0f421504ff5444c2c9e714daa10100c1db7016ea3db96d1e4d5c18c00f9ceaf61fc67c317d3ec707beca667ff99052c7d9037f826769f549e156191e38bda6e2a37435e13fca0efcd7c81aca5ef2e07e101d789b1d39fba4f90c5f0e4c4bb93b67f4cf0d1c38d34136fd5f0815f1dfc0f3893d96ab9279919401d8c0e998ccc7b9938f1a7851eefa7f3ad6c796d0c0f798cd73f6e8f0bf9981b7f65d3f8ea9fcdf6c19f8a308c90062e0754a35f3f1c801c0c08d169e7cfc7914452df60a825458b5455f1ec0c2157eb6f790ba10bb7b4f2b3c33cfd9e39826ac70fb247dfb4f74159e56b8d274792e7c94aaf0c7f9d2d48a8fc322157ecd6cf4e50f3ee84b63810a55528945da6acabb38c0e21446834d9157b8448839fb44ad144e487d2e4dbe6a1e73b624c082144963bbc06214ae4c758f3632788859ba065888c24b1f29e5af74e6c394eb0d45912a192267ccd9d8aad5020b50f416587c62045878c2eda8289f70b5c8a3c9c63ad927b0e88489010b4e103965bfc46c6ca109b0d844185868a268d4c0221346e30421c002137e4dc766feb14ccae3125e8819621e2709675b371696f0c52b8f2b867545890a8b4ab8f279bc4135ab4b7c8205255ccfa62194c7e8410e9b04603109a7442ea98f64a347174bc2cd56ae592e620e7d2b169170d4ecb5cecd0724ca2199f70827d5d847844feaa39c61e1087fd0036be94c37c2cf308de623738f950c233c8d51e7513ef9abb98bf0267f748e756e23732e010b45f8a97fe89ed9f3ddb675018b44382115c2c4da0f1b5b551c30e78b0dd0a0f1e8e284a182f3052c10e1c96af8c1446bb294f6218ce1c9a2cda754808521bceb939096d63e5f6f21d2b2bfd498935a90302a8ca381c72928604108e726bccd864ee5213d42c062105e48e6b4e8d79b073d04e1e718491a7afc2ca604c2adc95929487ad78401e1fb0f3dbaa8f87ff0ce34fca0c7792cd37b3f78167ad05124aa4abbef83f7661262657b3ef896928758d94943f2f7e0899c54f058777eb61e3ccbf56e41734e1eb37970dbee356aaa6c554523c4f530a4f20ebe670c3db04c4172ce8e1d9c8aec19d2c7a98367f37fa92b74f0071192c875faf1e07eccc19f0df5e951f09490918323e9c783fe41b4345112077ff340a33eadfc5c0b076f2be70e957f9c57a28f3738dff711ea738c1b1ccbdc23311fc4b4c1b9b9f4d698cb917db0c19b9851eb2c8f47216fca1a1c1fa7d7983c839ac6450dcee51482c76ef1ce68498373ed9dc137fa487e2c68f0e325aa6c87ca19bc947012d2fe354d6f06af373c552e4f474d19dc3c8ada941072b8ee4106b752a898073fb4ada8198313da6452a79288c18dc89ed83912063f72b6452cf3516f8e80c189f083ab0ee1a3b4395ff0c2d945c8f8682f7852217bbe50d11643ba0bae5636690b413b0fd3cd052779a5f5586d6ed1ed2d3821e64d2e7e939ab6d68213dc6662d63e0b6eacf3501f7605f560c11ff964ab1ee641f2cab9829f65f38dc4369990ade05790891f8dd1b9625b05b7c355a464fa66f2a082231ba6636a264c3d0527a5987d2811535896a5e0d506ff415ca5cf1846c1ad19afb49ae3ea3f283876311bce37fbe47a8227290f2353d8c7bda44ef03588864d704246c904efed8357b2b1cec3602ec157e96e8db6397279a904b747561633e74ebf7d123c11979cc77122c11f8acf058d29634e161ec14dd1de525923385266533ef615c1cbe34cfef6dd11c10fd5fcd172ccb9c7a91b822396c731b312a2c86f42707ef4d17de4df9fce6241f0a3fde71a0d01821bad42bee81e5d7dd87ee0468d7868b128392a3e70d2a764ad193cfde21e38fd962ed335d279ca0392a805d79ee00e7c917591f7b174e06de474755e29f5872a077e4fda9c1d1f1c78999ad2c46c4f212537f0f3c69c5ff34bc8dddac0cb90145a34b78fecac819f428f3226695591200d9cb7b454ae5155f398819b9255b21cbc4a0fb5b145e6808e4ef89ea1dc6c72ac0ef73e6e78618e06ba205f6ca00a1d9cf033434a299aad7505061d9bf0cab77bec430d99993e5d902fc0a8d106461711401b41175ab0269cacec9b49f5dd9ecc8e4cf87f93c4be42c4e6dce32a011d98c073e893bfdf8b076fa0e3126e0c4bb36111d558c1165f7c410a011d96703552e51c6bdeb96264636b0537ce094c5009bfcc335f46f717e8a0c424fcf8c1f8a8bb7204f1f05b6971830323e882cc031d9220bbc2aa5fc798f3f56040160e98c0021a50011a34caa3c60a6e78c1c5e18223e1f9c46a3e19b73a1fa703127e284b6123743cc2b7137f79ff91fafddae108274c3ac5ce8acb7037c277970ce132683a18e154f8f82073943b16e1e68d69f5691f1d8a70a2d38f345df645b64f4722dc8c0951794b298f3aa50311be640f235ddf86f7d6790867338f242c8f47fdf3cea0c3107e66ac569bf437512c347414c2e90b9a21997f0c4d96a183109e5bb66baa8f6cadcb163a06e16579f264b944aa2f3a04e15469478dfa20d2bbc5c6569e1b5e84b181403831850a9624caec2ce77c8118b802a327d0010847424eeeda117a94796363ab86178fb21674fcc1b3b4ac29c7f4ced0e1072f5fa52cee6b51c98e3e385222b669baeb42071f9c921cb62115434c986d6c65c18107908e3d5c871ebc8f1e7c8ae84d0790068d3c78f29b520efeb10e3c78ddb26973cea91e1b3aeee0fa559dfa3846860e3b782159cb6485afcf9bb28e3a38162aab8f6492cf18b4b155e30b2ecc71c180156cd1c505ce08ce165aa0408b1b18a042071d7cd1cee51e41d231075fa3ad271f67700a1d72f05d3a5b45c44467ad90d0110727daab6863eb9cd00107c7873ea8de2cbec1510d5e176bf287dce30ca1c30dbe6f46f113fb3c61aa6c839f37c59e6cc1c20647b5677bd285d7e0e54bf1b1e3c7e1b45a3578e929ca0f62cf59bc9a063f0f34bf86e951ee71f6d0e0456b69c8e3fb18724b7906bf071f911a42aedc1b43207498c17fe9719e187e351a422a081d65703dcc36bc6ca71f772b840e3278663f0e5172cc3d4c510dc38ed03106cf66427d948bf6d14b1d62f024a22f5d24a4230c5e1e46bee8715afe199f071d6070b2b25448c6d495f3f82fc0c841c717c2d0e105b7a3ad63fafc47035914e8e882131682bf7fead6e8210a985507177c4b39ff67882629def0e23c6a8071b8d0228c1a37bec60d2fbe06e10d72bad08202070c2d5270ccd1020359f9e8d882ff69dd96c75f6bc197cd9dba376678c969169cd4357962b2346f12dad802e3d105b13a13410716fcb19567a42f9b902aaf200c2de4d07105cf7fbaaca387ff8f731ca041430d1d5670ae6a32575c92530db1434715bcca9432871f7c56f87850c1cfbd7943feacd9bba273e898829766ece65565e23d34248c30c85637a0430a8e6fd21ce6437becab151fa0230a6eca4a781ff738e7915b1472b6d8827051e36fa080eb6fa0e00cd001052ff37978bb4a2936453a9ee067b57f3bb9920df79519a2c309be4b4c412589848607812c1890c5033c0b0664d1802c18d0838e2678f5df9eaf2fbfe6f499e0b6975f4c51930ff2384bf0364358c7bca9a7c84af023848f1dfbb349f03c86f65c3d0e127ccfc3ecadf7ff181f47f04ca6325da8444d598de0c4ea1452140f69219f45f083a40b61c43df90491086ee50b9542bfb4cb6a43208fba6baa7c1021789fa145bebef2288a1404a763ee51c66c9dc74307084e321f64f3717794b6941f389add7f25f92be78ff9e090c713ad72aa66470f7c9bbfe429557cf48cf2c0dba8340966e30e9c0fa1722b830e1df8e314dfdede289aa5d2a061d5868e1c783d981cb2ed2b11a26fd58103efa5235aa53cea0a1d37f0de723ad1d075d8c08b9ecfb136d858694a470d9c90aad38f24c5b4102e1a785759dd225d5c2daa5930200b1a341005346828a2c0ba0e1d33f05b2698e6f70ab73efd698075c8c09b174b39c4349c74ae23066ee6c8137169d527783a60e08f63ea185afb7e85db294d76862b9ccd6fa132450f177d9dd10a7f3c0a96439d65440f7a4d2e66b0c275ab2c49eb3d36b6acbe20373cb00adf3f584d0cb5b1d273ac8419aaf025b3f99846aefcf51d61462abccd8354339729f58a3f03157e8e1ee5fcb3f94a5d730a27fdfcf8d3e7978c98628aa2518aa23183147eb758adfa7f925069c6289c3caea499f2f8643cda6d8419a2f0c4d3fcb007a9c43b653f98110a3f7bcb3d6a5bcc3a060abfc42a67c8c3f868326161c6279c091a628eae1fd8269d30c3135e12efacdc155e923f1598d109a7b2f5a8c948d983199c68346d44a948c583199b70abefb3ce2d6a0ad5787cc1c5e36668c2e951cab1a256fef1208f343332e1a88fa4d67c17240386a26006265c4b397adc16e52e4e8061ccb8843f4a69be7d46a3e45650c3ecc60c4bf8ed030f953dc8aaecaf8d2d2f1e5c097fe8d2296bce779a156650c2f31fc534953a6b63eb0b2fc200036f5815664cc2a9981252e5a07e1b6c2eb4205b189384af99c2d7f25869c59c2f5470380b3322416640e2689c04663ca26874608623fcf185cb83f01f676f473d30a3113318e19dd78c670e4b29aaddc28c45b8608622dcf08390ad6e1f8f3eccc6561717a041a30b2f12e1bc87d78acae3d2f6cbc616185f7871a301529881082f76a7149253d6c6623a9871085f24c51ef98fa7198670c644d37dfe1e0bc8820314c8a2060d6614c217cb9c3ed45656f2cd0c42b8bd31d9db3dce682fcf18841346bcebbaef5b43d0c656175e30116608a2720927ea51614620a834af98ee7651895a97d8fe26b1439e3003107e5dfd5fd2b0f161ff7ff07ea266cb1add835c793ff811fb72cae3f3f17cf77df053cfcaa64bd2e341fef0c10b9e329d56ccd9839799c7e361b4ec673df8e8c1b1ca434bc959b47ce8c983e369e224548f2a42fe81077fd2798718d51d7cb3cf21392cda46da0ece0f33fc53f378e81ed6c14f93d635876a8c907ad0c1efecb2217a62fa28f49883a715717ef946233a460eaebba4f4583fa1cc3671f0c7e93fa58d1e0ebe270d2113c5737fe70d7e90cca621a7c50dfe78642d04b510dc72a50dae241f57c874d8e07576da90b25a84c7acc1d7d4223fc851eda3a206ff2cf727f5b169f0d394b7cd0473d588a0c16f1f67b710cd3ec3233379741f7db0199c9c25aa215e633be532f8f39124f5584a06ff2c56fed174b4eb713b0637c47caa7dd1a28fb31583133cc5b8a4d986c10b2949d5f2ffd9f66070b4c2fb62c65667f80b5eeaba907f3b7d99e9055ffae6524586bb4975c10d3e2adbf61e48f69072c11189b616de913ae6b105d7828f7c70deb16ea2057f5652cc83c9ede3d764c1f91e674ee9535f112c78211d7a1cb36fd0b65cc14fcb9b7e2bb7b644ade0866069eedeadc15305b7bc454d62f2618fa482677990ec876e1e6a654ec1b38e9d225e2a54f448c171db1c6a62c3fbca1805c7abd5e2dbe4830f0205bff230254f36d1298f7c827fd992e4a10f3c2794cb2e694df0369a87185b66e1aa31611f7687a908932dc1bf9c2a9fdd1999234af05462878b3ef693182d5d3e538947b090e0675665cc8f23389631ca5d309fd7a88ce0585cf4a1bd4d564a11bcb5510d1a7d22f8f31ee23b1c82b36e97d7c71eba2d2304bf262df2a5faa07f942038214797bb20a799672038ee2999e71eab851e4b337ee0af4d889c3464c8cad10c1f38f7e38b19e1223d7072a8d75c8fc2e4d4290f1c55b5cc03bf16fb3b7bc60e3c1fab88a7dd26cbb70e3c9f8cd942983a0b0e3c80055a58000361ccc88173ef79a5366a78f5781cf8a53dff0dbcbabfb68f9d53b0946303bf8b2ea824efc402b260280e87c40181300882869c0be3130800182c240e0622a1682c4f767d07148003592820362c2c162426161418188505a250200c088502814020180a074281504014160e4bb5e603032aa4c10de1c855b8b926580d5be3ed2a3f4ef429b151ca449976672408728391edc5211e40d83d8a2d3a8e4e9d90a29c90f1921c1843e3dfe23b38a14ed27a94960d9b62a766be6d28e8aca873406b304db64ff6d95d6c6035541605cce0cab57a1f7f627fc225f5ab415b167fed0efde13dc362c54e12a9e09455542c52ff82000fbf5551e43f7b48100e8a29f94443eb5933c8bcd502df3450fe00c6da5b1f2107394fe3c2f7f7f4a83a63ec5992522c28ad0050f1eef0ff6933287b4778203d3a162d4bfd65504d0dace403757aea8890ffe8dcb358b57ead935a6f09a81245ba588289d9809408b913ad662865cdae37152466f08cfe6d2f7c982ad178c2ceddbb754e24087f90fab72e57667928a2f64a4beadf22c7267385da110f18b6809bda8c904c54939abd4e4eb2d5bf8dad71debf39487229f7a9d85b1f7f5be8445d26e6be4c695fa0fc6641f460988b88ab746c47d9480e2b0e1d0cd3c295102a0aeb7ea15f8cf13e0fdd78fd6548f2015ae643e87c6af238873dcb5f17a2012e00e2a329c43687970f3b181531decbc4ab29c6d169c757a31f0d10e6069f119cc5f688f77ddd3afe101962a26bfba9d6808e67186fbcacb89d6670e5a1ef68ef7bcb9cd0643f3aa173107e064d8eaa3630e6fd452cd62b2f000a22c748acac756a21cfbc5d6c0b3607e794810fb970507e7da5dcbf703084c69c72b3f0511dc3982c6aa589d871f26f1b2343d35f5bcfe0b08f1f837b985276eecacef7277f4a1800410f76b84e90271b5eca421e4ea0744eb052dc8ab14f165eafa5c611836a9c769a86110c6751ec114434bfac699c4eec811bc28272c963b8098b28730311bc725585234c202e8023d217223aaf364b19a8ee930ea4cb8d19ab3c355afff072a21c21395b76e460d312057561acabdf761c9ac29aafefb4a720b74b7f167ed0604a79926409ce90b23819e0b33f452a3223f0c3a7494018c1dda9e8c5d5dd0b691de1a6ab7208c99cf59dbd42f4e1a4dce76125a03a6741ebb010646b360cc171471f4e8603f62f3ed625573ec3c4e5602c358039ec0acf430ba1711e6e46205dc40969244c328e7b65ee4c1120bb86995c7adbfa46a7d8b1ab6a9bb04fa449a32335691022962ed6688b7ff1b912da6da93ee9eaef5bb1c26968c49c9f8d2d72fb2634c5813d03d21d4eac9c671186e490ebf057455b784e74d6efe9812d40a4fc8d79025c045c10909240a6a1b69c0dc8035a0243842f09795d6d234bc47edc0eba902be4901076669a73fe3595618142f2c085aaa21a54288ab2bd241a95b8127a549749f86146b624b5436891f2b89c3071673627a18d9217620d104c618ec03271a3f0c2fdff23fa661fd445059be985cb550882a2b0fd269f40e8a1984a23266f50ba3229f31a58e1145d466dcf5fc251fab8270b890f3994aa310dcaf4afb81586486796c8e37481dae0be6ce9b8e2f0f5c38ca767d7e2aa15ffb551de31dbcb89d85d74113c4d116dda0e5b73f8e91e19fbd71d439f3f548f34dc5ccabcb93089db9c3c22e05238dcfcbc8a973305f3a43f52629201b2dfe4b524ea952e17d30b8fd065e7239212760449d5b17207048a451f56c88275a8ec93500d034a63c01a0a6f7d3777d88921327764ca5f24adbeab322dfee9d35ab83010aa06834032a109e953274e0c243b3a0f00bad58794f96e8493a18ecc28f19609005e81b12ef7fa05d24d17f24644490e9c675111bb36064203122ea9a2b84053258d0f569638934ca51dd3b408d6abf24895317fe31f4abbe5bd5b46c152a5811afb88b4b50bdc33286d829c3e7c49fc620c2b5e58068d491fef9313352632b3c4f2a2e249220fe7105b3222e54411515614b39868c15195c1c844c18b9d25b09a304296601ee8e894edb721ccb6d2993b2a5d24879585d295d3fbc5c817c6eea74e392bd850590361cce19d00f2d01e26dbe628c33e3e3d82bb8f098ca26cef804465c9ffc6ef1f75b2c6cc7e3d1462c88baa454234b23fae589bcc6b6ef086760bff8b51970a5206552f20e20110ca629e0eaa63d8fb28167b33525a56b80b1dbae97c36c5f9f81e673bb47673d9c5802cadfc2b3da05ca6c915752c1e30f01e5be3de1828c29877488a673411c58073a381028b4eef14c958546fc2c20135109425db636817f12e3bd2d3c26946982534cacc5130787e6da52b7f7f4276c7d906d187d84168e98fc59986b3d466ef3fc1cf92429a18524ba55f130c2ad4cf69257a6d8c59979f8d384262a9b4d0093907e2bac8a1a8059a49e2b6f2ddc7094a77c4269fdb605138dbba51a29d20a561a6f8e9c5af6b46223e3cc691266e0dfc9ae6712a3bfbca36a0177b100ce009a74507869026927462f23829fc8194d466e770ad0177f9f0de087b538eea02fc6d3bfedf523430d5126f865a0580d59f2e5610cf2da5f19f2fe874f6f97b146b39d840489d304b3476fbfda43da24d428404b7c4634931a46a6aaa5c86062988a101cd00c1462108ac6a18575d3496956e521de2a31a83938b2985b92003344cf8ae26187f8713d7d12d890b787605074e3e021a04ebb5811ab0b59172324b815f2399231eae69b63d5d917241fdf600ce1b0a380653c795981fe75d810e6fc3a1f2324357a263acf063d82e3d049047bb95d16278a97de275b861243480339f1892de4ec6f938a4e9f80ee13bb30b89f1dc037a19c273e2f51f5e9143d7362501c9259d9af76fd0527b8fedecd82a36252af2412f628dacc81276581306639dea68f4152865b29938c0e8c86b24cc5aa7134266c88a7dab9f484bced5d2c9af41bddc44e8841d8935f9323c993048793c13a6c7d1fe17e22c0254ee916e60ee79101d591ee3ef30ccf62d753703b2ed78b8808e9b8fe7e552c5300bff2d332e25682d8d2131b7249d400fd018c8f7097d236ff8859158d24b557beb24a58612f7017f0a398abe7e0b8ef6a3d8e087015c818834432954c27edd6901b15a417d56ea3175c7b7922593aebbea9445d0b3c39ce147ce910058cd8cc7fcbda71a82241e7d06c24d5ca76151661ec7efc2170ae107c12bde1889ad211881521027c1452d6361c78021f93b5bb38a02ac990070eaa7225b21f88d19be289c3c92c759dc5243b913eb1678612fe358462b2a4135fb6b1b532ef330a88c3f0b1b18c0bd6fc892d540c4fc250ed73d066245397f94e8a22f30b3aa83cccfaaca5019d4a44ab6312d081ab85f8361c38e2c4ac893ce34d7fe1e5e941b1a131d6ef404d454df304585a162a1e08d0a777d497bde1ae83a43dd4157a177e83e432bfe2703bcbb7a20b522b227a18b882a55ea79a7aa677abdbabb4244b5c2686dd793aa3e0a65047d41f91cb4a20cee4062a3a259eae98c8b0a859aa1e0ded44503465b08556adc7ae73ae43a5bdd91ae5cbddafeaf3ebcbdba1eaf4ece0e62df4d37d441d3d7d639b5322c5413d0f7b4c2c13a19cc5cdb1613ad23f5444ff6d4369dc25c455569aae64612f58c951df786b1a644978b65dcd611c681ba9e88491467e395b5abde55212ae3e9e44d53164c0bd3b573633dc33a269f47f53035e03aa0d40a3c51e53cafc24328cec8456161256aaba5f8fdd0c5630f5cbea38db200b8977ccb32b481f6e2d4a924cae9a3629872d1f9272e2bae55e0a754ac376337693e7e33517e59cf316e7fb58ac8e5b3dc7ca7f52c4b62fa7be8c1e095b33ec41de3c5f17cfc175e649eabf7a719470dede5535848ec16a789019639956f667c1298cbd16ee7d8df64ea5904d3e9052ee2d12088308fe6e1630c0a785d4f1edcb46525a23209f4e4939cbb7fee4d22c9a764844ba2266c876c41cc8aa8dc3228e8708015506dc3e581d50f480db4036482bd0599b85ac10f1e402298a70092d0c65c7e8a10dcc146dc0486032e415a05208cb7e21630047805d602680611042dd501b121f07b7ac2d104e007240a6683f31f7019af117b9800a10f6fc4b402f481f7edb39a406dc3af7af309b4d8cce91c029f14248a6196410aa0c4698ad2e4846f12b6a36b74fb24f2a2dcbc0fc1b999b4f8d473c8110953187d90f8e6bbed8e753618198e5c980c999ae3057c3d1f43085267b6ca942ea87333ec96893adbda64b221cf6613663301a31818b5c2c1a28de4e9cc4a444db15e96f4a1dda86f6a52539b1b3621d93863a3366f866db4630e05da70b271d146946ea631c8103a7677e3b71ca57052da00cbcd3b73e69e334b89f60e24669b312230adbc938df568338d28c35b09c9290f0742377fe42c5d8e359c84d8ccdaaf997bf8d48b44166a6a71b4aab86071703208c060c29bc17bbcbfb95a6ab14a8665482700c92f9ee2cecb647266e4ee01e73dfe859d89d8ca911cc1e4cea00ed8b287f038c9998d4d6f26262b5fdd9c53c79010ebd6951025baa4c9604409dac7d033c634502313bac4174c1f2695f961b6edd3772c371f4774c74be9a8452463ab7071b3fa425f2a4e08fa48ac03f946e55a4c0858d3422c2107ca31051c7b136f1b6aa3122de08501cbb82d03c8cfff2ee13e0ee747281852df694b1a8020924b4351c4bdfbf457f001ad572a78ecae2e250f9517c356bb657f12dd7f65bcf48ea27ff3717f236da259dbe597ffe5f6873888e69ba52db5057a4be9ed11cb6ac7536741daf7cd3077cb2420e065dcf47a4dc6c08eeeae8d463b0cef0b2c81be4b38f8320b76f8e4d1c4a1cf44c44b38b9fa4caad5955bca240614da3072a6e0699a4984006bb69ddd0a7ac5018dee3fd3e03e5bce8d2b49d271a18c622c62ea627bf26f06b2c0de4c3fe5569aa9163904c55e085f40b15989d89e5774a1d6ac3613277651970c0e81cb4629ae908c808fa05166a451220c31ecd1e394f108c162190aa9518816fa69d56c75dec920050d300924558cc4992b6c02fdf649a6660df816649c0bef88a97622511d54ece9fd3a48f7891914c8fe5fd6c892f65d0ddf72c6a980cd5750d1f2765d416a576ace05299564c843e44f8a7792e9147fb63629d64da11c8f2f73de40a09605822f90af7e9c2c39599417e1cb2b6c44a8d59c669e709e99fb1b4096bf08556661c382cd3e70bfd91215e6385bae26aa52e0f28cc48c6ee16a2a8f0ae8ab64862a7c784638bce0a3ed5d090de6d9f2009277c9f7281a3ee36b35dc180e257ce05ef32387079dfc3bd4d46685000f37de32f5dc688c50c301c3b78458e082f90ef30524c78cb7b156a2825bfda5042fbc1fde036faabd4b14475073e8ef1330be51935055e1e1bbb4b27de23d9681e197c43cc17ec3195eb25c429238bd7b0f9037c7fb9edf8c5c32a0fd0c08d0718e701f5cf5bb04fc757c3ebc3c3c349c0717a477f84e1db6b3757b3df82634b0d63e0d3a8065f0b3d7d2a78ee002e1ce6b8e810ed33bccd0cb0cb48987e865216312be09073cb5af781ebc149e12bc4b1b31247e08fafa2533a41946383c2c193dc411172e7ce5fe5aa0e7927c1b8fbfdacc1ba618d2121e12ce030548f1b003f95ba18387f51d7dc12fbcff9f2485b730fe151757163b05c28d052ea54aa2f722917c65e400e6c075313b6641577940b73dd901859762342a8f95e84f9754cdd31fb6d8566f290504e7f6c791edd8392e5d5a77b13b89b88b6c2b78e5ce29f1c614f07b5dee45db781786d4fb8a3ad7bd6f55c5b9d7666b04ccc5e2fc7242865b21cb879a8075727d231b9f8ddbc011b99d499fdcca756962ae8b2fde0aa296f9691d4f13e30a45389d129515f033997ae56af8750f11ed70b0febc6601e62415c4ecfeaa9ad4aee523ca4614baa1c3b38864c304770aef9d6d5aa20abc069f054749053e1f6d2d5228a2fef36b01c946b170362d5ab0414b12b86a7ff746ce20cbb3150a673d0789be5c754d92d6794e174321ef63ce319aa47589050983e8504a6c2583c903499e542001a132aa639e3794891335596e2507bc5d8996233157bc1fb679f0dcb2bfb0eb96c5121628d948b3220bc2247c202c48d5716ca1647354366eefae48685cb0c84125a4e50a21148e8a0ca11f0908082af557ab3969a80e690126464948df9139a0e9710add1033be46947711b93bfd3110b526d69a8cae282210137244ee8705e59d2f915b250f7335ca512fba334f755bd01dcf71c30b1a71c02bac6ffcb0b5dd03a5e85232664ac23d948289dade01d194a04795c444ee934c7370f0bed9699c06d31185b060f756df8daada31a860d97886dbe7392a3493b61481c7fe78a4071eb8b1ce43e5fe043c33c43279ebc74bdad6dbee6ec4a8750d373a03f00ee5329421508effcaf122e2464321f3f9ca4104e57b79867a68ad0ce694ef1bbb411761ca77b1dce7ab2ca882545ffed36953b445cd06ccbd08139cae0a853915157d0754f852585973a4d64f2b50c3ca5da731e225faee35e6940387dde2f0aeb64411432d6991c249b594b13539ddcd40c14f443d61adcc416287d8b6424ad53cd3d26c6b26efb36696a896f76dd6046516d26381bb16ee6de1ebdfc210bceb7493670bd61d0b8348db60896f0bb97901ba18762c2d8c8523976f17d7a04b17147d6118b964e6afad1646aeb90c684d4fe1b8b113e7adc346ec8925b7765668dc9d12dc92d78037f0d0dc05b33529a7dcbf1692ff1eca40da66123d70ec6bc316534c0f1993bb686cf3cb8c7c63f4a455e4d2e68873c280e2bfbd814ff74e758d76e78176b2e73551fe2db6cae4d37cb02fcce613b9146b601e315dc8fc51a1bf14f545c5056a5b0d2cfb4f3ef59dd2cc08c74e7022d2a11a098c6dbbd5305f8dcc355f7a1cf8016fa93c7c80c7e3e9fca6d301054e201d8b1d5b42e9bd9f131f623bfbd939c5165ecab3c9894c79fa12d3c1103582272273ad7f78a5f58a6969a45da394eb0344090a1b7e4b1726e49710e576d62b79d655c0860cf8ecbce16cec8f208238749ba722195bccdadd636bbeb7c11c661606e77b49a88fe1517dda834545105e2c3a8b6e90f66d93d745b144038a047e46c758e7f295b8cf5e3dac51caaa90273e14417e1a55b64f2a27cb67fbff8045b97117abdf16dce1034fde6558a35491331b678f72a35834f5dfa788b19b36c4e2df5e2aedb74990d2a1ffd923f6883040acea6e88f62f6e04a0dc5ab5988aead19aa8817634623dbf1e493bd1b1d348f470a5530cdee1101add85162a5d7689eeb1caacd334d91bb82bc3db82c9c6887fd9f416d515b873ca55355c83122bd7e952004aac6e2e114a0e3f0a7346852685bea6d85edb8b7e1d61c0b6602cb6fd7b51cebcd57aac0fcf140e56eb91cf3c16815dce60400a62aaba007f153b1b338b2d3b6874e7a30d2f0df810d24ece117ccae9c5f3dbcd107f49dc3b9525a913cd54905d36896fd86d0e8b9cc10ebca8073700cc15252166336405ed03b1fe5520919a70467db8ddde77ba4f294599565acf003cd89806aa2d642fd7054c26539d34471a4e0afe43637431a06acf28563e029c837c48160a583553ece5f5d787e4e9c1cf44878146d6469636b24eb95c76a3909f42800a953dcc1004f3bdb87319223dd610fad4f4f35d03b3de156c57beb688675c560c681e7b9fe42595afca3c81376c604ac3ac4491265951681294e66c391bf16c9812db6c687b639e01c36f6b4e3609800f8b643575f7fd89743e3254cff51ac8a1cc1d9351c4e37ed8a4fddc40dea32acae7ff51cc7a742d55eb2fb1f8b6497aa080cd081fb24dc3528bf430343740ff7575319623898123442bb1395108e03ac5004d57b249a2305f35e977e6a35363c0c42a885b308a2bfe2cf9c60ef7b36ff00f317e00469271e030d46068c045020eeaebdf3e8d0edcd1f47712b7a83510e7a3756e14dbad61bc80e70d3aab95388ea6072d6bf84082725f0caae61748c41be76c872d9e89c104faec1db28ad1ba46ef7172499040a02e4a1129b0c5d49f8397f69410a7053f36a2468845ae41a9d19500a6c86c2a79d7923c49d9380679f28a177afe1db9f9ce6b305225a8202c221d83de4b1639c320afa45248191f0c119c2e108b3ae91fe548ffcd1a340c607dd4550772c8ae745943408b90d26962064db78e86767b608adf09567644a11c5e1351d883a64177eb581d5dbf32f2682b3b458c8624d2b236c23244bd9294c334cd4cd9e6244e9690301fb9c334b9a12c810a550c306377471a3a2a5d2914a266a9717f59dc215400f414a8416b67ebd96faefd0960cd2fa2d949d5237f88d590dc07ef0bbb9333aa72f8c4179394994bdba5cf5b5398949326f802404c4f2a2b297b5c4d9496cebcecbdaeccaea21232b854190f52ecd46c19ab1034b17d97009635115779f6193eb0944fd737a95672c635770c14a624162ae21cf0cb4bf3ce14cae00d4746f8573485c00a0490da33020023a343c3f12e2b6a1efa6056e5c8f8ab5b5d2a42ba8f66e3686c606cbefd7db6d0121ae8a2bd1b05b03bb33c9f74997ff2ba2c590622348b5b816e6e1ed4a5fec280714a0a260ce16801862bbefb33d6ffb45ff9db3461ce2d3fcd24a5f0359088c24cc14535ad1fac56ca1cbad5662f6a90e0e216abf71ef168116dc20b0102483dffb44addd7c3cbba1e5aae791484dc32066a96a9a66beb7de236f17d207eb9f8cde5e3fd365b7cfc8c6e9fe804107ce7e010f7c1d68f11a8d41a885e9eb03aa3f56309cc153670163afcfd6e058ff692952f69487935b0aa332624806a31943bee50e54eb80e3b112fed4a1dfa600404a2e43fa7322b20791c01554e1bd28559fbfd97b52266b5000c8a17a0ce4c93416d0c9079f36b8c186ee06b5eb266e1926e3b0cced82c4bfd7c6339b895425438c221109c095aafff79461e001376132cefc7b7df66a3041859670629d5184d673252d584eb1905b647766cdbfc72d00c54956bcf2e943d085093746b785711b4c4060a169b66b4ac310b0cbd5b0194f016c80bd620242d0eae6cfdccc09526f32f7e1086f3afc4fb3e28778ad222e6cab4a4a4696c90b900684d1e94114a26c952f69ea071a0a6d1d79f010556d96410fec1718bd2bb3b406c010cca5de736b36903606dd9d7611b7d07bb0a5961466b06863f45dc2b3135b95252046065f8f0602478b2f0f915104a120d44795ff4225c69ac51176aa563ed2390ec469b1abaa0972371d229c56b18debb1e88265c6bf09aaf74495dd14a52511532be852a854796cdd0075e5d88c93025f2880d68121b5b1d6ece2a53c8d6a8e796c1b34ee8398f5972f2497657cc814f091a8d076031b19420a6d743cb5e205fd64cd8c7c9fae864ee91f2e3f2a5237ed405fcae8b134af981ea985d5a3b70eacb3403ef47a1d14253b882e323961ab4ed5fb778ad8c77e6cf724dc9284d49f3ad49825b00cf663e3c8dadc0f09f22a973b448ac04b1312b5243b6662cf8afd52a80ffeee609d8f3cd0f913481ddbf5203c0409c29e162409e81b6cb8fe0021a6269cad359e76a3424e9317a9254a4884018c0a9ded8bff37c8edc849c76c2a2e0acfdb6643d79eab10f8bfc16d9dac101b860f1303040939dea2f6511c25b92ba0c759f21561e73edb6e83374c52d7f26eec92e37713295b14a9143938ebc5f10d515cf43be9da7c1fc6a625bc6dbb06231184adf818cca6da892891d82d284c04920ced850899e44975ab580cb60882f682712a2ffac264b683947cb005e04c71230d35661ca51ae6d01be4d5828bd3d1447f5a3a4545f61c77c042d28e4c77d408ccd10c210fce9f5cfe9944e4ea1e90fb82153034f8390d2ad3670b901109ca9cde7d5d204dba03fb4a42b879dad17b3470e13a9c8bbfaf9bed8aeba075e03630146c2dc54119529985b190ce7e292d07aafc65d33fd21fb6a3e81dcb83ee1dd5fe28816fa3c93afb46d80b0d084798fa41af0055baeb86913b644ce9c7badfd88badd33dcd95f4290d0f45ffdfbec0a37aa53f076b85c83389488ade83024e28648f9604027a6cadaaffa1e241a2602cdaca451ac5a8d1c0ee516bac5607aa40ac4c9ce98dddf3503e8ca793b2e04d7d07c5de43e2822e6fa945eac71226b0ee79f439f980b133d523513676605c8252c3723b20d15360717422cbdf171d052adec1404f571a58bd088804206181b2486d0e7d6789372026c7814b03b37d67126e3fc7de9ff7d1bbc8aea620403dbb3b7809f4e2d54116e61041b3d19daecc007826cd938660929b008eade8607f26bee9541634469521516641cbac2aee5031932e0c336e41daac68d9ecfe851b48ea4afcb82f657cda0e6595c57caf865f2ff7d210d0f56a0d5e71e877f9fdc1536ca1f5f4fd83995b9e96fa9b0f05da4218f6545b369d01f0ad2c2808ed2f28aa35f52f8ca8e05f5ba1317b9ed55d0eb9a5915b15941ba34a0e42d2d6611c2efe651de5a0853b55e21d377ba4eb25a8301de87194b887a8c74d915693a63fda02b41b304e65fc453f076c2addff8999f38d5f03219219f740d734e460637b5889a42ff783e57b48a2b154befb87dc527079280e6198ccef00465289da783c5efc7a4d20e4d8298c3689d08ed2bfaa2e6b1ffe110672de18f6e5119461dfab385be86333447619f2e1913f03ba166d038fa4f3c042f99c009387409d0fd851abe13b89c3502da44c8bcb1438d0549e0ace00a820504a110641381d37cce5b2019dd452748f9cb3432f312aad502aeaeb4d5c1f6603c818bff92328f53d25a13c755b3ca0083b9bab6017864e4a543270847a37cffea2ba7a83d4a886df2100788624d9047f440ce20020d32bc3ff5e382a7271ed4d3b233fad7c3438bb202249ee30207751ff1dd9e2921f7732e2c404062e379356b8c28842917428aa061089c40180f20e49dc02826642aae9269606d297762b770a6ec7cb62dc0b2593c81122b29bf6cb1950360ad841a66390df9eabfa63059264295778d3eac77db4470e609a288bc60b41f4a8bbda57f565fc630cdf88b00d7ef7196ed526b18dcdd2d9546cd146fa7fe73d9be9caffbf1f0fc217affe40de7d91f48c9513b92dd0f43bc726982ac54c0b0b502aab434ca4e11457d05141049e5a21c8c02b0d909a51f226187affcc8243962a2975c3947ce50e5105a4316155068f56548c482b5214fb518579fbcde7a0e6b96d3b7398d427fd466d3363cabfde8d856e3db3d92a0fe065f15cf02b55809c7e191c64fcc0b0be46aed077de1c9f4e670f2727fb3d5beb9dfea2c38b33600f041006bcb9ec6c496cf7cf0d4dd1409ac395d9b60f9d85bee7aa1a79e36c094666c7452c63f526bcc3d80d9b96ad51622f65c62e591359a12ad56697931bcf50d9d7d84f6606ad8acd7b024588d56c5a08d18776503148dd3627f3c3cc94e6742d20158e0d7f975ca44c8bd7794254941b98021be18b2d2f15c29982142d08417c55d1f7f7875173d7b29a15224764f58e4d0a8826371837cbcc2608b251560ad0b1c30c48c5d604edf9ecfa1786d13813c4045a03620e223c5f55ec0358fa00af9463da09c40f66c845b9630f506bb218d08045b2c7a598335eb5771ff8a89664ed4698a0d250860c57017e41002e521a2859cff6c5340f38cbb083c92c5af4079aa7af0a04d6fb42002af3464b74c6405a17e885332846207c8b77ef84f574b10411285ff3345225aa2c8e81abe705e2929a4390aa5b798685215dd4c21145712c20b97b50cda2ed8d5d98f8081baf526ab732c93bf86adfa9082ca423585186ae8e3dacf521b9510c242960b7910725dc892903042ee5921624302948d026e16847b0a3760b46439330f0f0f0f0f0f0f0f6f706b4968db1892209394924a6e3c99e26d4929c994644aa201daf7227b35fc45daf01769c35f88f106bf0a260a660ae8e694d51c3306a24d79e35bbc0e205acb312771cfa67e62a3a1b6caf1877d4d663fb431cc4fe5ab129a7feec33232b3f6c7b57c58935213fe1a6164a4031c7be8841eeb94a4dc3b3b3d5cee31cb43a71f6442d6a474bc535e900370e0e10ad9344528028e3bb4e7793e29fa66c61f2f1c05c8cb7639ecd075a9c435315b253e4543f76c8cad4395667a2162fef6a735be580062c00c2db40043035b54e002650065e0a0c30938e65038e4d0cba8860c93153c046884e10dd8420b1f38e2d05f8a9d49e4d216d999861a071cba6849f5b48632d714dbc840044c8eb151c46a8801c61538ded0cc46cca124a9145ac434d46a0311d0428b1a5f040e37f4f132ecffe71c37bdcc00638c1925f8e28b2ed4021b5b6c4460230311d88800a380a30d6df410632296841ccc67431fda638831f5645ae41fe05843abc174f40a498586da176294ad3dfac20b0570a8a1fd8b5fdadd44ce07d3d078cf04003be040437bb1795248d1945209318e337466f9634eaaac359a85861a0752031c66e84447ac7c412425ae7e19daec164d574c4964e8a37576fe470bb96534867ea286f0963a460c5d5072c34f66f998141386beff24c9d3d4893f5a30744a787cfb89cb3c26fd42977dc172cebc29c247bd90e4e42245c5a076a1b7982d942c39135753b8d04fca29222c754f26650bbae4941a2279d442937cf467f1340d224bb2d0e6bd4ca22dcc69fe170b5d442ffd0bdfaed2c815fa14b34cf848cc621e8715da6ce23ac54408be21aa429ba406b358154374c854e8b357448d3a97b46568048e29f441d44d26a614f37e34091c52e87c435297785984d60f1438a2d0c7cf62c963a65e781f0a5dcc9cd068328236a572c7f184f633b446430d55c0e1847ee3efcfe906ab3cb92229e0684223b1ba44343fd196c9467030a1ff770939c5ce27a083e0584233a3994152f8a0f363dc070e25341ed52c55c345129a981d2bb72d494842c5041c4868ab4fe65b5065157e81bc18034bc071842b6446cb3b40014c48c061842677ca21c9440d5a9c31022fb840781ce02802115af3f852393b2567353986d06e90a3624cb173616a5060021c42d86487c924729a100902dab356fa84181d430542422c33c8f7833a280d3283ca77c1858b91387cf0a8ec317ee3e841952d63a31c3c6877cfcd5ffb42432d8c1178170b581070eca0d11a296bcc162903870eda896b2a4c4e34079dd0d312964b069553a720a2060e1c74524e7ceccca4ce71311ccd0023396ed05ac8d13f899ce1b0412729ef054d53564a475ccc5881185c78e128f82ebec61d470d9aa01a13e3dd32838306a4a43cf5e98e865add0a386660454e0d5b399868a86d083864d05fe868a6442bf9c4a2197f3d0f17468652d9804597d93fd1adb292102f0d5170c5365ed1c7f292212e5cd1c6f0cd2534fa59650e0db5195f2055c2462bda9c25e3c47a76f64bace852f88826622c79d858457f3d7f21ffb344ce962a5a37195f249c4e2a9aebd2151ac206156d082969e69f9e0e397b8a4697fcd25c1df17367136c98a2cb9c2a4fcf748a6165b4518af6bc73b7af760ce71829ed6c90a26f534d161e64deb8308fa251d18f24aecf15459f33750c1d5783b94b42d16bd01fbe537bce912191d80045bb5fb96362afe7202d49feb0f1894e7952f2212159870d4f747e32bc85bd136d92a94b97b6f988ec2e273a29615535e86bd1fb6fa28d14f16318fd51a22835d147d15da57e29a7ffcd44eb99e35b3c638a91c7446f39c9259964061d3dbf44633a85fa76d6fc2eaa25fa929f10c37e961c8357893eac8baa8a311e3c5d4ab47dd9e3b924e9868d49b41eb36a257574ee0b5d20c9b021894ecfe5d22db21489bf42d73bf4e62521d1fc5588794212bad9c9473479b382ea333709ff3ba2b7cad14995b2ca0f2a8d68f3a726cd929f534aab850d46f4d163325529a7919416d178862da5c2fe569b47117d756ec9a8f325546412d1ccc6ac29090b2a2ca90511fdc7ec1339593a44eb3106b95749690d8d3144bf49c7e429c80f2d3153886672a7fcd71427449f3f9ff227914b6aca41742a73e916e42888b64bbd730812c2784c02d1492e95fc937e050b1b80e87d3f4589d9b08b2fbea8c05d61e30fada408bfd797c423cb1b7ee8633ffe7e94e0f7a1bde899b993f64ce15b3ef415a2b3a6ca90f2c3dc437b3977cc907221d8d043a7af2b89d3a81e57611e1ab96d7a942ed9ade48fa17868945b4bcea49741258cbc43ff49540e3ac61873de858bf1851887810d3bf47ffe39684ff620be44436d0535c428b3c0461d1a4bde15528fae12d8a043e77162489e5b0f6ccca1dfa4db212869ea7115b809e4d0ffc5b7f079f2647827b011875e3676543fdf9c3d35ec40b00187e6a4e8d6ea0d7deae925d1f21762f7620cedc0861b4ccf8a79b6a1d17e89979ba634630535c4400d6cb0a193d728295a5ac477c6796417d85843a7acb2c4f60ed15023ac862e93bcfe19cf3d5135330606340d9dee895a9245dfa2031b5b6c4860630b0c24e07f01e847b1818626650d115e32630535c4288688828d33345ab39eaa4ea5d9f268862ed5937b86d01aa729fdc246193a1127be42679e49ae36c8d08fee6865a231f888908d31747263865f195d0c6d65f30ced294bbb886c61230c9d9012f725f6a8143b250c2e34021b6068f4570c6d60e30b9dccef9b9de3261d6412a3b0e18526efac8e7ad03c494a36bad09bb545308c0f43035a6851890d2ef4b1f144fc93d201bc898d2d20731ebf30e33127b9b1c537600f1b5ae87cad424ffe53becefd2f000f1b59e862abe28276070b8dbfe7899e9025b6728556925e917965746766acd05ae44d7193971964b60a9db22c67b93d89e636a9d0864e139ed22253e8834c10ea13d332ce047931c602d8131b52e84788d39e3465a2d0e9903f9c483619f673283421c9089729c256a9ef85170e10c3c613da7c223189985427b497e36bae0a8fec7969425f22448289e9539a2513ba1c49fcc8e9e49a3ac2808d2534395e52dd915425595a850d25f4b13fe8064d6561e35701296c24a18f2242cee6a4fe417e24f41355b28ed2958d23f4714f865f2c913bd7dc3042571a4f6eac64a93c4c2cd0848d2234bbc9744fd774d221b2c5465160638b8d9ac0c6161b25818d2d362a021b5b6c1404362a10818d376c10a18927517f47641a6a5b6c25b131843ee9b09033960e62c6d2c2890d21341e4568c8d77b79cf7280185f7c718117e38b093c622308adfc6ca96c3e7225836603085d490d0d41b926213e51c0c60fda0fdb5fa692b42ed7fc182770302840ee011b3e584772de1c739e1e34c1748b76796689568db0c183642768ccc87ecac0c60efa0bc243f8563632c0802abaa183bed2cd7cc6ca2fac5d380d74c0460eda3ca17c72beb0290e1a8961fd4278aef406edf5c91cb13d558e1b43436d86175f10b3416cd8a0dfdc5151297274c511ffb051835c23e7ac1c43bb41833e67d80b29e5f84f6dd1504b402239c2878d1974e245538ee9c9b12de4ecb02183b63d2de8d8b214825eb28845234aa8e6f2cff2d67c16b038dd3a89b2784557aab334982e21e39559b8a28d49964412f17e4996452bdab84158999988bc04b360453f96179e327ab2584567d631ee84f1f5187216aa6853949d3fab6865d69851e38b1978a96852e72a8d9d0ab240459f7da278b5c5f9e88a218b53b4622a550ca1ef173dccc2146dc8967f338c454a626aa14515b228451bb38652ed2943438d579005291a19fc63f75e6b9cc5281af7687199628ed1ee31200b517471e3274f5a4e34d4b0025ea393105984a2919f644f65848a0a1a3e64018aaef36e8978d94ac6cf175af274e8e55faef2cc0691cf1cba14577a44cca46723874684a7dc1d92981034250e5d95f60ab1281e379be0d09af99bb824f768ba3734f2aa66f2f39f0e5a71439b59deb4eea9f84d6a4327f5ad4ff733e712b1a1cfa7276a5211314a6be87379c59c9ad28210a71abafc5a2dbb6d26433e0d9db9eff89e5910a3a1d77811eaa14b650b9da18f33327898bddc1c33b4f227db5b62484b192c43bf9257d74bc6890b91a1978fd13b8aaca8b142c6d068970c66b221abf562e82be40d91e730b4edb1224abb60d02f3439c51cb443968f92e285cef3e62e15329b78b10b5dc8cfe193f210628c1c17ba9c573e2adb6da18d4964bc8aaa26d14f5f48c265a4ea59684686d916110b8d8ad39b3aa449d1ccaed088c490132fb342e3598390182b7fe8bf0a5d9eb8eb7b890acd76aa145250d6c18253e854126d218f69d11c2929349b5762b4bc4987f3390aa6c50708000524c0135a8b257294dcf7099b75425772f4db540a95eed126741db354ec9d99d0a61cc5fdf4a5345a40802594dcc2ffb24b9212facff01b77469f8436bfc8474f991a4b7524741653bc98fcba6254f708cd5fce7d42668b11badc9ae76405e93e9b5284b62ae5514a3648842e89043f1171937f121942bb6d1e2c478742e8f325953e2ae711e29220349663e7247229ffa01f03080084dea3c6d62039a490dafb4117ba544dcf645329a53e0c1fb4e1437c311dbf22c4a01ef49373475f4e0bf9e4e1412bb2b4340711a2fc66078d5f5e10e235f306a9d1417f3994550eabe7a08b15b2c63995e3a0f3ac3d3204f752f2dd1bb4ada3640a23378789890ddacdea4e59218b10f91a341e75b453b508d3b9d1a0cb172733a662f61cff0c1aef4e22b865ad303902c8a0cd39860bf229138b2e4592d52958862961d1c659e593eb896bb1bea2b75442e205a53c665071453f17feb4b2a99224c35634ba3b25879c8415cdc791a3cbf20993a28c5520f26e2966ae1855b46142d8355d2afad24df720fc47279c50d106612a49b427b93153326520e3145df613e143bf24490c09b9820c442ca8a05962ac40414629fa1cf55d2f4e481efd438a4e6852151e3556599c47d177c6d8d93bb6fb89a2e844bf42c498722eed48287a119243851c178fa081a29f985fe545356912eb13cd9afabf5a5658d0ae0c4f741e746abd8a6dda82298c1a5fac08323ad17e852d6daa72b30519464106273a0d4931531279e793888c4d74c2f3cf42cc87d81baa418626fa70f19470799272cf973b838c4cf4e366498f6e5e1265d9d8e2b5ac0b323071d06ee25d927b892e825716b15a5264042dd1f7be8a0ed77a251a917d620c61bb7f43a344eb32b1cff3b509f3ca24bac8f99364b588c94356126df5e4b051241a89c65a3b8428d310368a90e8c325b99ba3f223da58fa2be9889f23ba4a7132a3e9a9117d5ed2ee3029618497187138f9395944a333c23c9f056141744534a79efe97aff54fc444b4d6496bc81ef3e6181a221af191295890dd215a511de3d462d64c863090618846e445f49953cb199142b4339f2b56dc144ae610a2cd2e1e46e4a457df4174b17a84070db2209a94b21a4f09d79c491988b6c3e8f5734b01449f448a7b4a792471faf387fe2d244f254e8e63c8f04317f445739719c94992fad0b6f8e8cd91f48e328d0f5d4510e626dedf432b1ae5544e65d143bbe6beda9784567c501e5aff0967fa5478e87388561419f39265f60e7d32cd41751cd9a1cde15386d88e6b396b75687df39f488ee9d0252d298c8cbb113f9ae6d04e929d8fec9a29fb91436fa62d77cf5adeaf3e0e7d653453cbff15c23232e0d024a173cc61cd2a28113241c61b5a373d324ecaa7d73d54820c3774d2fccf444cf4ea1cce1fc868439731cf299d4abf53ec65b0a1af701982704f1d1da38c3534674a9cc778212187ec63c00932d4d0a830af6d9a3c9872d3d005d116277a4ad1d0249d5208197333a855ced025df36113ddf5e0e6498a18ddf953ba7aa5611a232e43988cfd7a77299ce9561410619da121944d8a0c306c999316851e486ec21fbc94e4e0cad57ac67f74c593a7a183af3d011db73ddb50543b3a1739d53f6936e5fe8b443b0247762e5b4d80b8dccce945972b5ffb85db012c8e0826921630bfd8490c309195532aa163a4b2a84a6f8cd39a94643cd0b2e66a047637471bcf8e2a82b6464a1f7e095f258fcbfe8140b9dde08e233fb8f694dafd0f5aa4c38dfd20a9d1e4da662fe2cbba52af429680f3249cb494e16a9a086b8631529e8981e1a9a5b430c308620630abd596a954939a9925ced4086143a8ba753d2edf49890140535c4205d826486163ac88042334ac8111b25c7d2fa131a71551253e2a9202e4e684fb689533a7a3d254d131a79ea338191626d427f04194b68dc440c9f62cf4ae867f2450cb316da194e42a3ae2149c49d91d0f685d392375a1219b2476894e69cc276921dbf4d2334aa33e524634a4bc22274b979561e84564a9c08bd28159fab4a4c100b0ea1ff34f3b188ee298888101a2554d2781e2f376a178466354d68ea1c27e809844665848e31d9595734193f6873263797981e1ff4a54dce4a4abe6b3ad983362c58fbc9242b6388f0a0bda45f517662a74ace0edaf1b78e9dafb4f2373ae874789b65121fb17334078d6eb028223fc3012275a4ac92d5c2818c1ba4c38345923b96639836b04a3c3d62c7b8c904b10cadbc71734c460dfa983b5b56fd5d060dba1416a2b1a49e8230c0f01460c00b0ad040c60c9aa412929c94bdfa3387792043067dee17592a397de5ac1870c4a27189bdd62f423e23a5051cb0e8f5f27a24adf9bbb59281f417e078452751f3653f3e56458a3b030e5734d625e35b64b6d29d4443cda0196178591809d81770b4a2b18ab9373e7a6b86112bfaeeb0417ce4bdce2f59459b67dbf79455250bc9aaa2176bad0ebf705926e14885a5152ac5891aa2c7bb00c304af43e04045a36244b648fa533fe4148e5374593b66563dcf533669a86dc1618ad673cc98a232d253975b8a3e674a195e35778849478a46256542c9cd29fb3446178e8262a368e4426b8f8e906b84e1db00bc0b7088a29f1877454a1e1984e484a25392accf734c2923555074397ae584d9b0d06bd150628fbe98002ec727163911f2c40e8f86d298915609e0f044a3e5db674b473b640431a09dd8625e6408030e4edc26fa4b724a8eee20630e319a68df7d92577ec99189d6b26efb26971c3b781c986883c8159d8299e5cbd0251af1c947a80e2ae5529cc312bd856a27158448def1af447fed9664d6ab50a26f13655237c44ca291d57122a414f3b5bc92687d230915ad4ca87c950e2447243a75796df990a37b1621d1c6d3b9563956dbe4f3883e6f24cbcecd107f2758010e47f4ad93728913173c685023da2b2ddd25c2f9c70c35b05181087030c2ff4849ef59f28e8c51e38b2d46e0c50546e0c505ea9cc6164e630b2db4701ac7699c19c80bafc1b1884675a876914146b66ac760037028a28d3974c7b026938846678841cb48de5e13d6482d3810d1e5e8ab1c3d9679c13f441f622a632ecd3cce86687c4d93ec7e0add092a44a79d33b39ca49452cc09d16f4c31ea482df5d87910eda414477abe108316a920fad63cfbe67c125c444c96f2e842632a46d8ce2b4fba835c38ccafb54af95fa76ca11fb196bbaa2734140c7f1a5b1e5a303dc54a5eb932ca53077864a1936d2123ba9b328d1f0bbd448d331f41e28ce95c1c023caed0ca7ed2a9af19d7e2c70a9d2acbb313f5636638a304fe350c9a1835be0803d5a30aa6d4e472f263c8d0501ba3920aea3105529829392bf2904213f2e768228849c192360a5d049f49923265886128f4b923982935997d528ac7131a212321e5f844916f7938c1a3094d9024733a75855eced99d053c98d08e124188d4b87c9ae3b184f64b6f76159557426fb27f3246d1a1b145496836e82097af5246181109cd48b314fde462ca7972844e54782e9d496c5f05cd044705840a1e46209e18d11e56c28504f00c1e4568a3e9f8868a4468e24c90f35e9563653e061843e83bc92cca62ac3895230f21f4f1a5156244675ff462d8a5c023088669ea982d1a1322c6185fb085512e081e401883c70ffad22d29276fcb3d2b4de1e183ae42b6986e3abf87e936b6e084470fdaefccfa392733080f1ef45772c63da7a7d49d2484c70efae07ff1623ed5415b16c53463941e39e83a789e84ada467544e073c70d0e8eeef5c2104994b2578dca0ff53aa3709990ac2c306fdc44a2512b73584354fffe05183b6d46492cdf690d1471a34b1359a8ab36e041e33e853ee1713a66377cafdd5c14306edc5e599d6f8d7923bb61a8bbefb3cdc53fcac84a58b2e5c8dd0018b463bf5f5041dc1cd64960e1dafe84b365676488d713c270e1dae502b8705195278e6d0d18a3ec6d3159697c38a2eb56b0c22590a00e0d0b18a563be4a05490336632a98a2e06092a238fcb45472a3a31bab179c14477eea0a25377b150e163d4723c4fd107cdf1634c25739cf7d0146d6a1016636ef66ff4cce000e0a0a3148d889c32254b527964320cff2e66a00f3a48d1e8609dcf52236698d028da0da37166639268fadb193a44d1659325439a6ace734b051da1e882b058a6a478f4588d91a103148d8edf9ec96da9a562868e4fb456e571d4d43fc68f3cd1b6c6e9ffe4b8f2788da1a3135d1415838ac154e9c99ed3c1893e649f2715c722eeeb1fe8d844972f9899129741869cb459e8d044ab419586183b842c5aa358e8c804424f99f6157dd250834107265a51b91db2173fe85668a8f9257a9df114a654febd3ceab0441b2244fc6e52537d255aff1232e5ce123ae6130d35e71d7450a22b51b2933211a72b29d1505b1d744ca23725cdb2766ad39d241a6a92683d5e359a5684cd4185865a24bacea4de5971e213355fa0bc410724fa784b9a4197cc4156d018603ca2ab9c71720e7aae4c5f1a6a60180e3a1cd14c506119d7f93e568d37267813cc10e34760d7838e4634f9527c6bef91869ad7104305ff05239a1ca9334d5cfcca2dd25023566ad0b1887673680d224e76092b7eb11b1960c0c6165f021703035e50400b2d8800ead0a188f62abfe41c4e9f887f47f3e5927ca588a003119df7282509741ca251a9741c29c134c34e0c1d86682e6577ccd10b7d2a9dc0133a0ad127add7174394fece3184e834d6520e27456ee554c720fa8f9e2f3a458b17ca14801f7408a2499e933da836a5e3840270838e40981614e800846951818e3f749a5cac72e60e99e48661d1e107b3644ecfa0a71916fcd748351e055fa3a30f9d0e37a574ac5db47f68a8a9a2830f8dca99f164fe988e3d742277ce3929afb861f47a68f3759ee54e5995e39c873a72b2ec9b710474e0a12b397931fb9b4a89f93bf4a942aee6093713a2d7e03a74d8a1ddc897a1bf7f3647d5518726269349c850396b0a4a74e864c25c0cda4b73e8254efe146304cb174d72e862f65ea4d4d372d3c7a1fd32a159b307c1a1b768a5642839ebdc794327f9ff5d35e95c458f1b7af5399f2bb5cc39316de8644b6cd9fcde193f674317465a59525a53d0c95d439f75528fd231f732ae6ae8573c27dddd9ba29a34b421b6a256675d1d33d1d08686529d84521d4a7567e83c73a7aad06186b652f6c549b00ccd06eb8b99f927439b3b77f2eb498b31f93174fafe8ba1919647a8d60eff210585a1d9bc92aa93f534081118da8c15434624e80bbd8f100de2344f7cd3f14223bd555c36ea7ccc7b17faec49a6987424f924c48576645c95b0fa164cb157d6429fb3e36cb894b3d079ca0e55539164b43016fa1d9311b44fc6ad70afd04696386135e35e45d60afd8e90f7a4bf748544abd08889e6a353259d4d5fa8d0974cd627c25445cd32852ecb34e8d37ea13d2b526894d69c21e5bf28ffc928742664082621040a6d0a59a55f720e97b37c42bfd921491f59cac2864e68e545758855de49e59bd06b8a3a966487666b98d099ec16333fb910df25749623d973c994d0c867864a427549e84f4d8c142174895199143a90d0bf87b21c2182e5544147e8b5423ed349ad0e233496dc43cce5a93cad3a8ad084984290f0eb4944ac8ba711867f042c79d041844664664eb14dc91f9dd94b740ca191d1eb519e5384d05b89e78fa8c9a4856410baa4631e9d33983cfd11085d6faaf7860bd14474fca0d19d93ec988c5b55bcd81e3a7cd0e74eaa914ace9a89680fba343159d7248859065da18307bd5e90983aff6ade20a742c70efaed92fd25e45cce57b142870e9ae8232349ab92837e7e44e52037d6818376c44ccede9d21c3bd8e1bf42156653b6cd0674e5953e6988bdcb18e1a349f1b5ad9f26ac88fe9a04113d62ce2c8733b66d08e9e447d8f271a6a334ef061a8ed023a64d0e6deff0d614f983051d0c5171808e30b302630022fb858c1f11a6378f1261881d7a051016b030162d1c952c2622e5e9c20ba44818d2d36d208008bfe5fbb94d06b39cffe2b3a1d9496379daff72f2886177f5cd1080fb160e169322c1e81175cd0c8408dafc0b6a2099692d0b1e75f474859d1eba41421a60e20c02a1a19832c19b3b98820e5a38aae63d4f74cb921450a622a102015cd242d2f5d624a06714145a3a485e8173a6ebc89a7e88376c6a6088f254233459347466cec90b21bab1f010386408052b4d9536eec4f94146d4aa154c4128d00a3e882e82023a5205134faf22fc5d85c3223a1e8646c176d265a43e512289aa02e6e290613a152e913ad27b50af9f2e70b274f34da532cb95945a8ca74a2d54f5a548ca72432ce89bebcb4df42d6df8c6fa26b535af4f5ab89c6f3cabd456a55ce7d26daea2033c6f408139d5ece6a42f5e812fdc8593795a63b660659a2958fa1e4c5dce821a24a7416f3e8243c73c6c8214a742a421c97b85eddf93389d6946835a1f4fb67cf914473b919b3591646869c48347b1e83dca4a23b2707126d9fb6eccca355b13a8f68bc4b5b5a92a0470739a24979fa3d5b10a55b348d68b4bbc7a472181d63c430a2cf0c9d32245db592994574c1742f68550a1535a388fe227a0ee2b46ace299388ce6268b01063a798841c119d8e4156f52771cfd60fd18cc6ca6939e4eaceb921fa492545a24abc108d67c965cd721183e584e84275e624e3697fe60ca20b25b2460a1f3a9b1444fba2133d654f22a3c681e83af67b9f751810ad9ab064b12b6f12417fe8bda29888d9cdc762fcd068d36b1649585f69fbd07f2ea92364521135b504e043f3af7a9d91a45870f7d05e85ea5129d45dc7d543a784d4b8cad8e6a1499553c44ba2c9a249f170e999f19c2de90e6d18f9c14396b8b9233b34628484f0a62dab995a8736b586164be854901fa325f5054959015ca2004ae8940871d67341cb855c00952840121a9d3f584e737dcfa984842e24e127bbd7591dff113abf9420840ee531f9c8088d8fb470ed95a6742c8bd06f24edf124a80a40842e3f2ff9fc8987d068ea9e4f3a7c9e66ff86500021b4418a10163e8442284010ba98a921557c0c844eb9fc6b2c7d2d12827e8089385396820e0805f0419f34c7c827223de84cab6acc4146f60705e041abae39db53a6fc09a31db49644d6f87a3e294ed5413b22e72063ca9b265205c8419ba7f307f1c94b478e3c210e28000e3aa1e43ac58b7ddedd29c00dda5826824cd2bc0d3a77cf2e1205a841dbfd29a4f828132faf601480065df8c58bc57c96534e0f11059841a39ee4829c104ec2890f510019b461ba93f40ac9820a2aff118baef3b67c8ad02523665560d126e95ad65d9a37f28a762e357e6f66d2e0160db5306a9031a0f0e18ace4d43d0ec504db2a78f5674f9a2f65c4c7156743a88291d66397faca209c22a95d09d4badab0f557421f165e246980d1fa96892c7901e49e4594f96f9404523927f5f3661b249a7740d1fa768c782d2d9b47990e2da186af830451f72c5109390efbe9f82c347291aa5ca52ce512529daca0f398b5b1239150bf1318a4633cefc58507e88a231eb18ddce18f3a9e42314edbfe9ea31e95d00bcf0018afe4410c9e72bbe53d05c7d7ca24daf8e21553301171f9ee854b2fec9102fc7c5a4138de8554b1ae339a6d01d514e744a5b5f9c9ace31a7f0265a899fb2c41c160c68a1451847f7f0a189aefdb7c467e61025fa99683b2b97574a5aa14310136d882185f8ee8bc997ba44d2f425e71cdb6237b33a7c58a249912546d3e196ed9f4e7c54a215b1952c7e9279656428d1cfcbe49cb359774b7612ad78c8254c7c8cb39d93442f328ff09131168946364bce21944adaea42a27393e182d2bb3ea217cbb191aad7b235c5117da4a44d859cc455df3e1ad189204907fd97f43cc77c30a211a9d5d5aef2c7225af1b06cbaa694faa188465c97cec13b826ab1a07e24a22b4d9d135d15118d4ca93587b6e610bd8660ba459c95c82363884ea614c3988998698b3e0ad1acc8082244d7a72207f149cca8f820da31193c64cce8e14c2288f693f60bda1dbe625803d1ba494d95525736d91e10cde53ecf49b6475612fda1d1fa9da7e3c4cdd2c1f8f04393636ab13c5d392de94de05eccd0157cf4a109dea2dc3fc85d8b232f7e043e0351f0c1873e566c889b0aa2a1661f7b68e74cba7fb094af0461b81833ca6a7ce861bfa024a74eb93c341abe9276533af881874e2cb5778892d4537c68858f3b74662afc6b902f7a7eb3437f4274848855952a7cd4a1b5d2a264a6f0b8a9e220f8a0435f4a8952593b6638dfe6d00819b2e6a4abdb64b660e0430e9d4aea334245ccbf1163250e8d07eb11d3bf29965af8030ef7f18626c81c1e83e5e7b03e9202840f37b025e35cc65e8a79f0d18646c324219799f15d8cc00b2ec2b07bc1071bdacc1c454c57c1183e230c2ec8181f6b686499e72427e6ec99facff81a5fa437f7a1864654ff6810424c764ef691863e898a7af1a182867eb4c2568e9d64a4c5848f33343ba73a998e500b1f66687757e4f46f5a862e98e54a2b599317d506aef041864ee470f25f445c3ec6709c8c1fb46eec430c4df0cb3129b53ec2d0c6dc69498769e4030c6d5810ca2fb6cd44f95ff8f0429fa95af22b4ab0d4330a1f5d4833e814ceddbb0f2e3822478b1fb3de1ae1630b9d52b96c4105f9418b0e7df0a1855e26fa4ff4d53019b72c7c64a14fa56dc2fc471f58e89330d318445636b678c2c715bae416eb1b72966abc8b2e9c86185c7000cdf06185f64db589c81946fb8318868f2a74ad412d66f6d1905946808d021f5468c2b8e409b2a652aa58828f29f46b2a35695892cb3ad2d0b3314cc00593e0430a6dc50acd1a46ff1185de2d488d78da25cac60e121f50684d5a4b94d3d9fce44f6864189f7cda6304933ba1cba7a2673719cc74b609cd6ee6dc414499d0a4123a4da35f9f597e096d4e9ee2098f1e1ae62ba1ebd4377d25f249683be69057693284543948e84f5e720a494dcfb2e6089dcfc8edcac8d9f4334668bea4cfcaebc694a917a1f710e38928173ee28308cd9608d9a1fd4c87d287d08645bf6e99989a1a0ba15349f57b9c3783d0f7e5ac90dd0742632a47798d97d354f2833fa305211ff45b6eb224aa3de872ced54ecd1667c4833e78bcdc2bf9a12a2b3be824b75634d9491d741696f7627672d0e964c257f54270d069d5d894e3456ed0c58b1983c7cf6c9d6dd06b8a4d31872082b5a5066d858ca3dda5ca443b1af426ad4d37c6d8c70cdab1603a29f9116263fb90417f4994ce913a985a1c0b0f5834ca4f6829bf98577442889988a482e7841c57746ae2b299a956089bb5a22b1dbc738c14ef59b3ace86228d5a3e62937c26315fd66feab4a113d43ee8cf05045a3a72792f87a523247450b1ea9e867bfd2820c42ea6c9924111ea8307fae5c9623ff893fe0718a2ee78ad9fe1e61c1c314fd88d251437896a63c96a2fd0f2a4b959aa6c52039840729ba58da3af2aae690aa1c45a74509cd6f667988a2b718476786e885a25dbda02df1a4e86b388d30aae0018aae528a61cef39fca2bfa443b5e2da32fe99e68dc73ee0ded1ff31761824727fa32b55cf13563a2ab9ce843d65b3f3586ee3d0a1e9be8b36c0acd1db4268ba9133c34d15fcc399c88b9db158432d17c90a364322d3ab94a010f4cf491b453cbfc7a09f64d3797e4105d4bf49d44469016749aec180db5ad445f32fc249d924d5f630c30ae0b0f4a3452d42fddd3f2259dd0505b810ab84824f098449fe7babad57292e84465d038597b57747ea5e01189ce430797131f1b4b6cce3c20d178e5d01ae272f8e0f188de7ac3e7cdf70d22766c0f1e8ee82da378f966deb7886944172c2f7c45bf90e48519d1f6c994a26eb78be8325382f80c29a2b31cc763e82ce22fc744349bb22c660a1344b4399a56c9a7cafd35b5048f43343a7f7c5664cea51e433441c8b5e0922cb6e5268cc5834721d0a3e43de90b642ef02044674a4555e8d03fc16310cc85696c4bb0b0229334f33767abd6f6603a33be40697532f010446b3986ce73994ad3f740342296506d1a93889539207a49415bea8c9c3f7422cdf3524e914408a31f9adc2133f89970cbbfe943db2352e6b7cbf3c4c987763367bb7dbe2f69687be83b04f352e14354d1e8a14999e4e5f0d4300d6e1e7a399d4a786874c7880f1363c26ed21dfa28f99212526432070f3bf4e5f1a2b45d5b729cebd08b5011d473ad4329131dfa7822cb76eafca1f19f43ab31e7f698e25c9624ecc2cf1711505bc19780bbf00678c8a1095bca53fe240e1e71e87775c73f5aca030ebd7a6e92b73821fe346fe862e7d893e22955b1440f3774b1621016fe5494cb590682471bdacc18fd2433e991612c0ff060439b93deb2b04957498bb935f4a7a192757691a0c4440c2278a8a151b2e2a1af52b3443f0d9d8e514325af9c152df7050abe4605b4d0a208c1030d5d07091f4fe4f8199a9042be3b6fcc4bf898a1f1109de31bf2a1235ce8e051864694bcb8c162e84186d6fb734cc1abaf37788ca1d7a41554d6982f6f38d7430cade55a72d9a062e86a30a0c50e3cc2d0c8881d2f31b344887c0dc1030ced6739f127d6ae6b418f2ff421f4e7acef1e3b688c2f6ae8d1c88087179a038da6a8249165f2582c1288c3a1300e82181cdb1e00731308001838260e8562d1783c0e85dd0714000556301c46322e121c201a101c188985c2703010088602815020140e8682c160402c54b220a90750ec386e472a6aabeafa50d23831aa616191d6e96e4d392166cd79a1a9a40e8e8344c9a1f2dd5e6c7d119fc22020c62de6e276372b0ce63a91a585b50d4d787b24ecf877cb990642baec91219bd774fa128ad2a874537d90b5410e0669a0d6a0b0217e9e3876b91ece1b4c139990918b3e5e1a01b9a30176f9818a5f13a10d9bac4bbf6ad60d0b3741bc302414abb0d3f734a484adac51beaddf44b2754a24a8fd7d5b112b4dcdacc42916c4fbfb854ad8eb265f97089da8b4c689b9517a29905b344e041faf922df3be6e4234f2f96330878d796cf6e58ab32d8fccb73d5bba1cc197dd2f8ccc6649f6e014ccd477a3de545076a759a8ea2149c159d35bc40da68d0a4be05be8862f1ce4a752a01fbf6d9f3224563a8d4b40c5c05b1375538ca85d5a1ba0c2715cab1da79e08d1a4f4c4cc882070f8c57e3a02b3880ef265d6a7256eaa83547112a8185e9758cf9632c75ef7e356a9efa579973b185cdc68027fbaf913c5d41297129074062173f4589aaa465058e647680af0bc213df906e358f227578bfbfddb810e066cdbcd7520165689b08a4d0a2f2d0deb36ea685b3a6882601d22684a95b4099f35b63b551089971472ec59827b485051a47ef47355d2a73ef3e44f9e213010ddc62e8f024038871a4a0eaefd42a62508c39615ddd6cd8f1a70f883f6603b3fa09f50db054e9fe44bd8044bf3c73de01eae0f8068754750d98adeb374485f1850ea0b8f58e2a55f45a80778e683d929a3aa7b4059911cae67d65f71b1fafa30152e1bf83c2f04a5d2fe6d5595f754b1cba67066b11c4157d267e82ee806fff0f40d10dce8d2dc57aedaf2ce713c9c0492e8d81a6da84431ae9d413a301b94c32e3dc7092d8af0f4276b1b21dc2d1cd42244fa05419240e47117c756d67a479dbb7ff45546b0d08b5849430698e28455eab30dc96591d87e8b25c362ec62d6992df4564e9ce7a4a01575de356f0e0e430232d85d4ef107f2b15bb0a567c7cdf39f9663b433d79448299e30905e2a6fe12aff088455e1ad3d8126ba6aa39d7a59a3ea2509af36a14d74a76927e33c851b2f92e5c3957e64c1d39df94bf82f5ac55bf90e6cf6474b3a6f6704a0df3fdc57fe3f17a1ea87bdc6dd98f312cdce8ecddf6a41bd5364d87638ee11c9b951bd8d2464253c77f703d1dc74660b646f2c40642ff97530bc25631aa7f00273d21b0e96a25a2eb060d36c7910ddb59d2795a8a47a08a1a43028fd6debeb0764a86fa39c8e049fa9e483ed358dd868416cc12b4b0d10fcd51e2a77c61ad4a6b719ab685498e335060cadb8b1542498c860b03a07019c77fd3f7c784eaf916f31c083b561c3c2a4d901033870bf80682f78de86f4ca393c20cca1711b85a8f2a3b2111648041ff451d98ff34d077168e52f29816d41873ec18ee2e5a81a2850e6984db40a1bf339ab220493ff82864da4dbc8e7cbe7a1dc5c25aa2fbda50914de7d7b14f3ed2b25f36703196d70b7b8e262c79509fca843317cf03771ade9b69d03852bebd9f6078e66fc2771bab3df922a0e451905fb2f216c8803e177746364c27a2d452796c6fbfdfe322a66ff4a59f9a6665ce6587761d3241b64531bf0e04a859955acada6dc0ec8adecf14291af4835a000f1b7dcaf5ffc36a105c12412ffe68dbb8b88bd2a082e9c68607fc7d8c21a93914ed7db738552c37a59369fcbb79ec350ba5b19dd4af8f6edfea5035aff8b750858bb1ef326be57c9973241f42cc742b9e6896d9621ccc057d61548fc1071a8fe8b34b7fade00264297f510c62974af217078293edcfe522d5bf6ba978525edb810ca04cb01ffc2032f28ac908f87e6d4336cec40bb2c5e4fda21c1eb6fc1c36bcfc1e01ac1df6be789df73a5e09958940f4c97cddd7ac0455f17380cd7936e7cfca3837c7f1c3684f9cbd03e838a9228a59cb7dcf04a877eee97dce6fa71f8511d167689b484283626a99c617d2114aaacc90eeb4f0016c93942bc345e10cdb47cedc2376d4aedf15ebba71b2d14fe7f99aaa2430da088902694bbd0951b6bf5c8f23df10b74db639f61d46b1e2e4d6eed3f790eec3b3d92cdb6b5b01962a573a186146c873bce48a1670ac6c5b1b3d0b5e5fbc361506b36a80682a2156e278f98a9d0910a39a8d08d053afa8af0ab638ec682017a0062a4cc22ed4ecbc459f37b4a8549cf5aaf5845d29fe0b5785e096ee9cb8f83a786dceb30506a8692bb1f95a4f588ce7031502d0c89bf1b7ff992cdcb25c8eb3ba408904ebdb80decdab18c8d071138b954cdabaf99feed19685dc186153443161d92da43072ebf543b0ac17cff3fbc003e36ebf9e99af77c15a124f1666c789a65c4ea43861dacb7ff85bfe43ff9ff8dcfae1bc8ab80c942fdb5a9aef0b20c14ce6c8e39e0ac07f9c23abe0aa9aa7496b8f70efb3541c4a4e70d143b14e1345f156706b6047f94770a77ee9bd2deddf14f3c994c30b6be5928970a60fbecc07517a872443bc14fff7ccfdf4105cc7acd6cfba5ffa5a5f6d496de63d839e876ec23215a8d57b74a4a1f770740fa09094d200b4ecf89446af7c0fc96bf8a9d71b1de1f3c6e6433453c1c34981a87678b54604b1a373601cf6043e42278f8ab60582c1406a8b488a86b83793af5ca3494378c28e206eeed07bc5b70126d1b9447382b1e5b0b396167c5c323f6eca56b41b668ef381c58e97d8ac3772fcbe1db9f118fe15ae4220836434b3f46ba439b4ca85670f9a6797ab0246afd37a728b6ff8ddd2294584a79b6c691ec2a16f7c469bde28aa39579e1107b2408a86912315ac2066ca534e0e68e1e63df2f1b0713a43ede41e440aa8e4d5f64a724fcc470168dede661909d541c5122b40f828a5a68710f6dd543f7e279c1b4a281b3d62910558f577133cfc85a1c184d1d4834a41af039e5f318ca0471c34f65bf265e525be65fc641409780dfaad4a063c3d353c073f3801532be65009a217320a333fc17d02f38c47968c152fce5d9f8269d1c2dedbaf83521e28f54746894195b0bc1eb8954a3beb5f84bac90d6cc9a2a9b7ae18316e4f1ce82680823fdde5e8fd9a6cafa32bc5129919187e26111ab2480d60024780ad25af9a5134b368c1041f83e2319c69d4b1cb37d22c825fbeeadf561c541372c213faaf453cbee5a42a9d1786c8109dac898c459bfe62070c85fcc9b48960a134b90b9ef486ea9261063c1f410422c0408123a1b442b85e881cce45a049610641020a5dd91071504280853ab89c04e95cbe784618ad60849486e47b2046c8aa5f98014c45625558427f69042ab94084c2945b742ef538e746b095a99a2eac9622478f56ed852fc2428c0218464307d64ab69ac215c38c018025bc7956a43bca3f250da735d445f27de227916a31243bb949030819b0b10ca18a62b685772623b8762db36318667ca09d17e82f7f4107bb9d51fadbe2940fb1f2a9592afafa7bef390338e0e9e84b7ff5499d60a13dcd043211c328342acb138729bc8ac0861e9be0431c5c331a8a81597a49970b93a9d37214bd2cdb083c49fc71842bcf3da8dfb6c1877ac4bd6f92af45fbecef336691fbce7c2ae7447711bcb5245c4a0879a39022753abb10ff452144b38d1cc27501ac35c75615237912007f3b0420351a898a46c2d32cb2f09c914987750ab412334071895892786c93a082a3ea0a871f84b2aa7ea093b57dbb12b53b4865c310bc67fa80f75adc647894763b9639c280652608a1e5e35e467cd86d590e7358cb9c2f143580af6659e0803bb10aabf9f91ba74b73af4917c3b3551ea6fca084404a0247b12610b8729d92b07aec348db78b00a04a96fbe72e47206c2884b6024f5aac9effc0508e6fa91c48d9d004690184202ab72228d4bbb3e256c54c21f1a8da119b4183b422e268af373a9e011037ec56dfe239b12e181b39b4fc7a8493373fb68f6e9a5748ee8df4c5784587f6ecc84bb5bf31c3a7be367687e93060f22e18222d22e69c7240d3b8e1e80fd7f9a92c1a49b9552624a2091fedd614aff7af6ad02de3aba81cf088bc3e2e56cb48f60ea4abb78c848020cea4ad6cda683922a5e4b4428978bb11e11412e852670a1bb6f5a6b4a6a8ca01b0868cda4a1209f2207116073ae7aff78a1f7c1c92a804082c3723a1ba94fd1357093d742ef458490deb2a02705fdf58977f9201a8cacab9cfc5a40892952266229620e8fb671749468d3509d084e72b39ae33e3e97fa7044e500286367603345ab0613c30183821143fa546c67c98d75c95731f6e02917c62ec9bc93885721e96ca2b0b3e684c3328b6e3743c14e6396f1c598591760c886c978ff410b4f8319ee52b26711a950848bc8cc6b2485a5dd6db421baf58cf409e2d93064056e1d3dc35b89b037f9be5660293e88fa5183430c16d967f809b421e183985842769d0e9ff6e8d26724f009aba063b57157cdc82e57a42d99ac1e423283e7c81e7d983622acc34cdd9a315c6b889e465684b2cc9fa48c8cda646abbc1c563e1de6f2b72e3ee57e56393a627768c945f1a6162f29122ae3560d26f10bfa721760c1e8641f1c0b42ca7ae6ed04d600dd9b9889661e10e9be6e0fde9be0ac4942aa60d80e5a47f5fb04b1ac61305360c73eed7953e54f39106e564886f6e0ce9682ab224cda9a302047b83bbdd9205b7eec0ae5f2e8ccffa5cc5422b69449408da3870fb81ff94564239de6df6fee3e9ca05c4cfee87ecf9655c4dcf8c8036ed32157be99281f0374e71d2dfd6fe2cd62ebc6762a30a8ed697727ee0818e1672ca59d077a8980ecd6df160de88b92701f36165634aa18986c63f5191b83d2309602038386f3325e92028d760003d318688f0dbbe78d7d1f1c91a02ee8670af2d20cc3711c14acd75acd641f7d6a37e217784ba4ef31a8d185b6c6c16169b54b7e6504d76151a980a69af9ae183a1e83f47f1e0a2ac5debc23673a266360851407d2545002f7555e4e340015d5eabf8ec519be47c7bae9d3094f17d1648b891c08f832b251671c667584c794852a588a4989f6e703924f4eeaa871361228458832ad1aebdc821834cc213b5a751f95936a78051294e922d4a9f5018df50e3989982581a2053eb9457a9b40ad525cb79566e2ce91e8c4c00595aa473f5969b7b407ec27dbdd73e449a64c23e2142750df5affa950f1782f451d390e7499281146f6689699f099cdb7e5faaa5728962d6649b28beac8466067e91403878772869a788ce28f560d5f187b321ae653507c0e6044c3b8678d093ad775cc1c30f8cdd772b1c4d6f3e3423cb6d1793c8c4e0f00c7bb15c09d31104f8424bfc4196f4ab2a5113e93cacfd01521fc0bd5c228927df9806442cc205c380d864379b7ecb3bb4d77a61edcb84d85c745540c676c56ed57cf21f5775066014a08a5d9d7f234be218b6d7ee017145e32fe685a161a214ca4631e28a6067f3549217171c48506c8dee31909e8e54ab7789e4e8827d50593f3916b9e23b766c65295b0e503914e7a185f05cd3682d61a7b1869cbdbcc056206cd87037490e2db8cc3fdf64ff90113e1d9a16ce503c21b27008407266a143feaa32193be781b043762e2cab94e487016c2506752fe9ee745d0867321fdbde50c0f56dc9a0ce4d49c18f34c98770ab8c06a541945327576bd12ed3663bc36af068c6c8d48f71eec939d7f6e9ab1b8a308e642826d964ae5d27abbfb93758118851213de837e0e36b296138a101a804b2940b05e1251edbb5165216c766f550b915d988b1e6f21c107d8f46d8d7c055740f60d7f463b3e9bfe0b197e89a339e0114e698cbfb1f3a9545f1d801c6374e292c12fa55a31c03e94855078134817c4bd90332f52fd198d08abd46a82b4428d9a7cb4ca244043129f601b8ee896836f9445c565255624a77361e268937cd7a6022e0bbcb4caff85ad8a82327e4503bd368ccacb46b92a2a97ecc06764934b435318cee234ae39d256b5e9039ac8890e1c20c33dfd3a5dbd80aa5b81b01af2c8a17b8a60e7a0d2ba007f0b4c35c946738918cae775183b36b542649f083a563491d092224afbc2589eaf88c745f2e91c9842012ba917024798e4496961868721f099e2a51754e1abb880faa74930c7139a68c10723c6daba71e005982442da755f2506478bce35f5a90e4e54ccf7b2ceab18be831a986cdf1f6e8b1bd07a17c50c7434e7a0ef299f790d36d1227c49364058bef719c42063aa5ab87de3c58f5c0e5418ec7fd3d74f570c6e3d8fdb11de7b83a6054102f1ecceb8326430a30e47b0339fd262dbe1e0d3989ba3cc64dc4e3743d40aa6b87420709b8c06ac89ddb2851730bd6b466182f06e1803c1c5cadf4568bb183acf24580c28c7d04a95b78e99d3233670071a5e248b1caa70e5addaff92e3b3f3a24234034dfe19525669361f544ebb0e71362a03594d2ddf0d5c58ce966d46d462f5c50deb074d9adcb6ed0713a2a061a9227768616a06e1b79cf30114677858eac01f172056b70eae2dbf1b6f3a128c2658719c344a3354192642d6596ec8ac2495219e08815f5c929b83bab853fb942f04e6e19464fa154213c4be41ae2e0b3fdb3b8df4e2202cca11a605360ea20289106ae11080c340ce2bef8a9b8030ba624fcddbc92bf03ee1264193a41a98b07b90e30c2089a37ca68a20fc31431b316cc35c02261f7a3a14921530b5dfdfec568e5e66155e177b15ca4647aaca803d8739417546000472eb8914ccb559d80d71899f27e83f8891d5a51b4f89e0e8e46056880fb4aa482585cee629f764de49c9ac013e98ac3734528aace492ea7d255317260d100bff2912b329b284b7da01330e6263d76feee41fecedcf21408614b55ba770ee4513971ace73f7adcbbfe89c5af5cfeb180e33a328a1b16279b8bf9a815f154f0d8420ba6b057c93783fae1778cd40d221751fb50b1c41e448b5f405d03006a80b9016010519725d5442aef34c3cd77654f31e9aa32d6c16e938d9a7ff95c73af321feca56dca8095cc6784b28ab529c7bbf24859f9bd4ae1798a86df17111056d4296c124fa60156c2a652657503a9ff492b5866a704931450a17566418f4bdf104714f298a5c2d23a89814476a99a7449da20f17c22880225a1263194e649aa94ac4a7b12fe44ca73ac901883a4c89da65de1a1d8dff4974f3396424dbf4df58f64150e2a6cdd63b11a572aa2a73fecbbd066587afe742d7d5470eb972b06d4fc4fd7ae70a0f86e0194403aa1a71e6f12cbca82d4d51666131f5f05291b0f6aa5c5ac890c8afa27a85d056c390fac084205789c8c4a3656fb360e0dac166a915d948884542aa91851cc9af497be02395427b2427a3cc90e85fa8ffb301a95a77e50e95f0e64c9c4cb94f4ab3f4e716ba82a7581f7803f682d0146013894520fcf074b823cd2ac0eaffb8a55be934cb814e079864a0b411578e5a98efcb81c062ded932885396782a18b8cd0c429985dcc1b6ef2ff02172ac4c8758d6fa8dfddc7cb9c387508417f58c633730f5d25f03288c177f0cad94c8bfc0b6c31fcd910e610cec69f40f3574c7ae61dd11daed9cc48ccb8bc39ca8f3840173b40586be07f9b99b2f11b14f4a1c030f28634997f0c666a7c08b7475c406ffc708430d43d12722fd0e76440fdc1fb1d2e3db170e445ae08da3a398240d404c279876257285a99469448d4514863280a2587a25939d437486d9a2ae8d10ab06a277e3bb7b2582dec850e811846254b4995b62a4491be3f036accbe57c6afc60ca607e66d019917fc315866314e8b11e759d471f5687ad2ea5f5ae46513f79ac0697ae20e8dac531dc126131d82e36d4812ed0b1e526ff038ef0cbe33d5ac8967279494322077ba9df19802bdd80c9f22bf875c38a674516d91f5c464d75617e46fb4d37943afc799471489bf896ebcb09c27475416e8fcf697a7d57a04405825495d364ceb94a1acad6003269448b683a30f7ece280759c54a780325fc9038a82193090bd7702df524cb03b121baf6193299938e341f6f103c0a1038a5dd994643e0c84504d57760c46d5680b2a46176309f312310d162dd558cf0588d2475c458413cf0ab5bec01b2038ed18eb64579598d3131d8f2b16cc70838f4d1d3748c407ecdc3500b0e21cf4ec73ca7c77e610728098cc804d97795d2da905be09a1781f33ae1eafbda39ec337d967f101335447e941c0a40225743cb03d698ec6c9735ee55e4ccd9b50e86f577b096b31fc92a1da2f6fcf329a5cf1c77f62135c2cc69e71b3f7bb2f0553fb1e39c9d7dc399f29a0141a4337f4ad05e5fe1fc70487746ccf9ad6a9a1401732de0699315192fcf3b22e4c28d8a1a076078e2c8048792a05efd7a9286c7dd01d6818f9dc8dc2f326e065e288b48382cbdf5958dbffe7a69b4ba10c9da59c30eee5be2899dc46e16285cd66a882f31c82ad9d68560b4de412abe808618e6c09848dd9315f2a4bf305c0caa1801bd9af4d0555daec46ec0fe0914cc9e97d6d1fe482dee9483e721f2ed98a8663c00a896d4f48fa1fe385777eef90dd94a92a8aea8959a2d8d625f218cc36518e530a28610c0b8b36739f2d5c3902fc76f369f436a809d24f76b434eb04b64f11b1f9b58a549b962f5c5079ccb7307d47e4e29d5823feea5284802fa1c9f6dac5b8034f611703263a80dfdd16d6b3f2708037edcb18906697b6d6c4419c23e5c325cd7e0d6d85924370bfa3aa1f0ba2d7c2ed4f4e268a74ead5401e9007a3b6d541af48dda45da5f311ebfb84fe36d170079e671caae6de67e3d3c3a9a12f639f35e3a1ab573a71e2f381da5a3d070eb402a51a30c94f35de10cde1aae56d792e79f4864d44cc87a1200101f506805896f05ee80b5894992f1df3e9a244aa9690bb37e249a8bf61925f86d48751dfa08859f946a2a434b62b247542b31c9ee79c8997107ba070ffc448d4c75fb654263d5c1c5d48bea7ba98dfa0b8894abdaf81119b9a81c54bb90801aadb348d07a4254de36c7036c843a11fb3debd84e8b1f7ef0a4898a59317fc7190a22f82efed3ff97f7b9d78af6253dc172e902e6faf9aa0bb99c0449130ba1ba907cdf74d7403cd5941e0c621c14e62c54e95ebe488ea7ef7501ae0832e5f1b96054f3d4e913e4bc538186662eda8a09028acf3aaae205cb2d3f6afeaf71312974b630f01122e8b4bce79b6c46947613ce93e869fe33d87f72440a03645c70b86ea46fa1084f785a44c88f173da83bb4fece5d341e2f9a3a7ff71058f13508efb1ddc1eb9477ecb2d7d9b0a3a501b776fab7f62481626b1af45b4f6362d20e6827a80157d96d1cdb038a61064727d7ee8895db29b008aae6d83447ab2539ae02111d0b1089e8002f54a1a752a79ed606aacf40a08b885054c9edc592ad3aa0ce857dc48f7019992d604754d53c403b1e5afb2428161790b712f63c4f9b54d3e25c02a9c11d3e46dc614d63dc8b830f1f071b929e18d706f06142308ec6aab9b0862146605546269c31594ef92d1eff5ad258d210d92b7deb1c066540f7330fdc12f2bb8c9094d7f8870487147d96f10c901d4baf7e902e1d4d7603b8fb5e9dc228af3e73ddbe0f25650554d2a73aa2f3042b6a5a10f37cf321babeb46f4a68738bd7c17c78cfd67d6df08565842f1d4b7851b8a94403f8cf02a1554f21331bb92df5a2f8cf85e57e11b28a527b87994fb77e31d13d8721d2e5eeed34d77bf2dedc2e4dfdf74f99ea53b30a968edd04c735313dbf7f953ee44375b792f6fe24a860d13e5063bf10ae20a1324f823461c80daf9e56936c7b47a21f9ac0625de469feaca073d0a58cf419178c70ab5ae866867ae03f215024f9c22dc7e7220a56b91aabaa46848b9d9115c1135cec0084819b24b669fe633857e0501497166b8391e0321c0de4bd58b8e8660d179f7a2b69b1b7d2dcc8c0a93aa200fa08c1e9a94437cae0547f426c3f40bc86be1b72f5f42b603d628b95d39ec2a01e1af02009caee4f78fa4c8b68c13418bc216bdea02fefa41d740c19baf3087b96f79237edd2c2824a01a1c5b8715a2c9dd4b46a7e960c0194ff29d323f8fa63fcbe963084f480af51123a78a54b5f63c1fdc03722f804fd349fe3fae93ffd69985b5b0e6d8d24fc6342d927f67fda830f4d8211aa7546ec649be996c5f7c65608104a5bda31c2eb3cc49b083c66454e40e0794fc788cdf1445cae2ad717aa865f893cdcf0e1c3750d0876c2d00ceb64feee7e628a3cb23c4bc68823a103a6e71a0eeb49bd37be4e27b4d8cace7cfaadbec30e4bcfb4e5f14e63c77aa890a80d31b443bdfdf0cb22fbdb2dfa6be06d64c705b5f0c48bd713147d34a6de0d6029097efe6abdddbc86ef410157dc5d209658d9fbfc7ea5c8815603013a82e0f4a05669f6de31c03859758447b28b846d83952e9c1aface06fd0767886f5246ec7705bb0b93b140673c963e2d95cdda911ea4f46845b56c143488df76c4bbb89329521831e49d450143efc52b94625e0aa96ddd7f4a43c88616a66be8cc0a870d6f5eff9b27e95e0a00bbb212d15f6f566f2b8a4a676cd3bdb6a10cd7c04e069b262cd0bd8a69b5d08ba11e5857990485144b0f8f1f806735d900278e94ca363b0e9264140ad177230cc8a185cf62a457cc9bfe9820cd75a8e855c2dec5e3c2b567e0087d71e830e21f5ae65b7496e53c5cb06cc311f4a20c09b2771384159f34e603d6634913f15f898febba299d84988730b2bfd004d2c1de574a8bcb359cad78422b19ba5eea528095fefcfb99aeb93daf598b0f22111ff58e72ce5919652c0335e838142554833c2ff3a8c85241f5f6de7f90e37a04d6de211062e5a856c6734009bda6fc67bb01d09db3582a2517233cfc15a7fd749c33dbd6c863ef7e051368f7787fc087c15e4422231a9c1c3441925d5936c62563dd64c1c92b3614bcfec534cddefa5e5966213559ec7ce9b2c81eccf6f7b4c58d49a84272f59e361e4b4d24b15307addac65d879e25df2a95b1b7db975ad5dfeca0dbc9f9b46ac74c2bec643dae93e51a3fbf1975a40def506ad397898e0b92a72a8e55af5b236d5e4247dadefc9e3668659a8e2490e9a1c62f70d8d10c28207518126ee4aa15c93dffa83519a39d0d02a99fcccdcb538af3c8c37ad9556a403649b0ff924e657798167423904f68b298fc7d64c342e6626683738e4a64f512eb86fb1b9a29ba1827d250d43217142aba17962664b8396c4a0376e3e44fd984a10151ea5dde63dd8bc5c95bf11b2dcb8177ab3065bf8ce270ff9fe11f56fc55e11c0a3a269241b2d950b99d4dcf8f50a307618c3c1922dd67ea2c6451a272a7b28f006c9ff6328725624d7817c0873c411a18b0d87bea27ed7663438b38ccec4b087b11b3f74a96b8d8a236b367bf590b8f22ad3ddecff1e9d44c281220346ea2f0e25e0796343697f14b5598b0096781d873d3c438ed49f7b481a7853746141055997a5e24b41f1926914a1463640a5a4d9c57905135d49afd341cace552a740622cb2a82f84a8355f3d154ca930014f1f63f8aba8809e8225251883a53ad52f2ad98423801f3e58f12da67ab2f80c5c600aec6123aa9359c033e4677ac11e0ac3e379a76e80fd930c2895a10b82f5106f75b31ce035db43925b5ea2ac68078006d6e33918f09752a75323820187d0603653ab211c53a4b2e5f96e44af8ecbe657cbb49637a922e2a37f57b1c01caf42ef2bbd5999323639bc786ecc2b80384bdb0bdb4d5fa2f272cb5bdba4121aa94ad6d2af4ae48edb55599731c79a467daa8d89c4996018833bec627a05fb6e9e2576a7351065e84b0c1310698dfb18266890b89815c798362baada62911422a310c11fe0a5f2cd7b5b931b05f88f6a9dcc028e5743c92093103ec2d21851d761d6aa0ba3839a10833c14875fb5085db2d7f8b7d552b7dd5b99b8c3dd101bb72f0d35fa451f3856b08425c7c637f9661a5265f6b577ebf59a6075b4020b0d2e10161580c39aad306d083860d8d53825815d0254ca455ab7364908ce9e1b50e63098ef0803ab032650d8e198d8fc53e93a1a5a5fe1fc4c46b6ed1442650d952f5ee5aa8ba937ec45858d55ae1ceff44ea351c9919516ac0a5dadb20d2abaf60764bb8633a292558fbc46e9119676ddeadd256e478e0214688145094d41792de5d8cb4153cbb36ffe70c2a06daa4a840bcdff51d55462a5b5bd961339ad82ae368a25ac6cc54bc146dfbb44aa408a2de3e5b837cf688a283b21751b0fb15f4a6d6e1d3148ace6876ec82e34b5baf19920c5f521970131cbd9284dc0ef080b9449b9410706649ec5a6c73a87a3b4a3628ccc8d23ba346612af38bfb5cb1fdc47ec8f43859beb9bebd742fa1ac0faf79552e47c0a3ec7a3773da1a1f43362d7ef59895fd9accaab5116f346e3d0d98c0afbd0297c96100e086888cc0ef9fa31711bc59629e4f2317af42357bf97d5b500f530738871bbcfc130713ac1bbcb43f4dce650e05e0ee6419a8b3227804e106dd388262a5eb1f101dedfc08e36719f0d941b9f64924ff40f63d834b0db8d16ccdf833577246a38ffcf0eccb337f4875a4eb819314f77783ee8a8e23da71c927a6386c9d064539c3261a163c527bd2522993d9e4a365cb3f8b7fa64696f7ab1242c4d4f1ec14f595e8597d134841a362cf8e04fc6def6fee0ae626fb44eb399ee45ea6532ce896211a0a1faed716531dbcde3c98c332785a3946cb39705bca5a962f862395d5839694b82d5c19f814cb6abc15bfc3a91bfba51b80daed8d57f48c2af2cae346032a1e5ba000c555c86ab446cbc3c343c30b0ef8b4c953f8cdc5718374ea74b9b62c5fd0e017fd23c1b23c531aff13ff2f90750e8dbe2cc8fbfa71073520f0c175bb2a5a194e46127d9f359e4aa4b973ec64c4486aee98c96cb5e3808e4078c8674dc5001501e92acd3ebd7921eed3907ec5d01a62b3e916d440a3f582a1db28a068d87bda80a29f2ba147512755bf706e0e80c9b4a421bb91b6146fba20100debd239486a15096446c15a7f004c57db9a7dc83c296148ca24a2300bbd96473a6f07f9f3c6998879f7db18030bb6c7119a66a0d10d016f0e5b182c10894b91343598052bb665e9f22b84f990d5c1a619a8b895df11c15c6dfd51b099f95edd47d87a46d26ad350a4506637f146a8ade715003aa86271cf016af97b1ab0a27ee3b8b533beb1e6d33ddb21aa377d80ca536d3cad13de0c9db42fa8add3ea1846e0767c220165827ce3722149a0596e7a8abeb66d3c607ccb093841ac836d86766f14ddb1a5f39b139a558cc6923ede80044856ae413746209b78d92f3d188449c61563358c4e08499f1e89c1f8f2dc9d8a8d0cb792e20ac3b331327a97ce4435adc93d83c35b5199f3115217eff80f7efa60d0b99c6974195a730cc1611482c741643fcc24f02cb8af1deba893da11c099e40b0f8421e47f4c1fb018752621b93855f7881fdc1d494524c4c85d4ca6d052deb526380732bc495b9f5a971689866f69085a1e215b728538f668c5c334a35fa498ad0a560a99bed1f7faba6bc2ef60a1006687e34dc527a6dcf96a494f12fa2b31d061bb39525f22a68e771ae06c5af3d982a25edbb6bb5c71c5eb6480a830d2665f5661e81df31091f0b5db55fb7cd88388983499b7c6fea22861aec29b349a2960014778bff94a95b55928f66b360fd1769e09a2462c18c9fd270aac8e0738c3343705bc4ab9ddff16243707b9415a4120209f4fb485835e1c32a34ffc214622839761c6b31269e2d45274df063ea2ad8d946c634552dab13f6bec0e1fff4b026aced9a32b9fd975302db979cb391e225e138d6919cd67ee4934cb28736068e54d0aecd975cc440e5409b2f825f66ac87a9d42603585a186648e2f4f9918c8c5e703ccf636ae0686d1827a23055ba6fb5b73c36d2196ec3d16298d1884b433740d3646895c25a7ea4a9577c835dca19d88e4c453c4bd5306ccc794c89ca2a09cb84c23a23d0f6cc308dde34471c9fa018b39e41ceeb65133aa5a604a6af97c683676442419cdc961d3001ae2816f27fb4aeb9b5264d50039472fbe168d60c241b4634b34f3c2bf9195e5925009d34968c9d8558d9651c1c9aa0b1e5fa342eb6ba75b39112af205ebc115908c07b1cd88b82c08ddc5ef4c005122f47824de923ebbc7cd6abbb7570e1144a2a0e1928ca74d12c0f0f13b8fc21f6e3468fa7a9ceb7eca5f067abbd38f7cd1521ba6c83e7f6d6209dc46d6a8ad08615d17cfdb572d48f18160c9fade36a3506d31c9d3ab4702040471235d49e18d3f612f5fdd9cd036cc302fe59c4170b3460b25472ff000000000000000000c098d5946d51a5e9a624e402a80f858057a4ac8848b9093778cd3b7807efe01dbc832d460504390bf90a3c0b495ec7f13c5b42a25a30a71695c833bdfa51b360f41cd747be8ec3b35549002c18724a0cef9f90b38b7a05d3549af81244b58239cb05f578ede8efa982b14b522c7a5430b95f9e488ed76c3b5330a7648f24d9aeea722998a287a9193a12c78e82e9543aec473514ccedf9224dad85b2399f60debde853edb71b0f2718d3d3ca871dd6cf2fd90453f214a472c8293acd67692000138c6f919ff3e5079225b90453c7e77b93efbaf65782297f3a5965497b590a19418024183d8d5b78fa4830c7af152ec4fd0886d02a9f23cf8d60b0c88c96969036ef22983aa40e62caa58c9508e6f8ea6285b6698f780886100f9f24729c63f8ee000208c1a85f612e58dd447804c1a4399a123a403056f037c966d9425e7e60f2b07591a51adfaf0fcc217fba551792ff1e18b743c57f492edd260fcc3979ff63497660bc943c694142001d183e08f92484656d8a8400393085641f677abaf4142502e0c0a8bebb691621c00d4cae13ceee3d8a2f1721800d8c1d7f4708d767953eb5305da41016232f9d7d68619addaac91ea2d4f3b3c02d6461f02fd3b5900e46f2b13079dada894a291da4616172efaec9eee4b3f32bcc41df9e89a956ea0e5798e62cd9b4f5e5945b61d4d2cb7233d2972cac3077f2dcaa93ff0eb2ab30569f57fcfbf03c5215269b20293edde5eb3415e6387e5eb508d1b30515a68e72a8321f75ca71790a73c4fabbfcf9be9b98c2101e27f6f57176bb1406dd8e3e963c29ccd9638e887ed816a330470e3bda3c148549a33de7ae2f14a60b2613452e54c50d14c6d0ccb4b4147e8220c92677afef09e3c95c149b79f9d077c2e01e3e549670412bce0973b8fc2872ed5dc7de84e1b5ccf3634bdf7f68c294531fee5b454ddf4c18afb365c45131616c4f217a4729472fc9254cdef15896a8a3250c1f47ea5d1e2b610a36e99235d299659430ba755c792efac53e0943a4f0f92f9b8fb77492305aad870ee3e27e942e1286fe487b152f64b51f128acc64e9f4f9230c57b136dc7e3b3c8e3045e409ee7f37c2e8d166c8990723cc9d93dc7fc81d7d14da2dc214e6267faa993c63518439ba4ca4368b12d126c250fda15ff8e41de724224c71aaaa1a512bfa4398ece4d4e388863087b81c5369d25b14c22cb1d472cb5c7cca11c2e0a66f314e3b47e5d9208cbf9683a9becb6f9d09c228365f29baa3e8c03f65d8221086b1886f926b9d3f00616abdbda9911c86f90773aeccfcc9c1c5f1fc600ee257b454d607835cbb445dae9e330992cac4167c30f707cf8b1f1d6e93edc11c29e9a9ee63f2e47a3047e1953be7fb5fdb7930670fb6a39c113f79a7dbb0051e4c1fd7fa546424eb90c28504f4015bdcc160214e9944db0ee61042ca952ccb52751dcc66af7dd1b3d773a68361f5c38e3dc77330476fa313cfe5fee3908321c7fb78a392844e471ccc1d7b050ec6ba0b36361dead2bec158592a1f7f965b59dd60ca3904cb716cae69d8065399b47c7b8e193a1b8ce9336e5241fa33aa3518f7f4762b470d46cbff1c44928c3cf93418e4aa24564a231accf1f6747cf73398ca3fb263c5f3a86306739c3d2c4931bdc316653054e538e510f1f66793c16ce11f2b4cbac8378fc164391ecbe3436e3b6705b785184c216946927784c114d5ba237e98bfbe6c74d8020c0693c8aa74f70be6ec39ad7e687ac1f051ae52f3bb60705dc921b992336cc105aa2db660b4f071143a9248f9205a30c7510af1382f0be6fad877e663c114428fa78997cf17bb82d1caf36c07e9e85159618b2a18a524dde78a4d0f2a98627f9ef7587e1042f482a30b1a35c2e0a2055b5d1c615c5d608b29983a2d89fea7764f5e19cbc0165230e9770ec43feb778790ffa2020358c000beb07183015e5460000b18000464c89021e3bb602e4ce0c50a4e148cf3293cc4e6a582b7a40b5b40c1e061648b1dfe22ed07edb4b0c51362b08513cc71b494e9f1b209a68faaffd22bbe8794230c5b30c1641176eb83fae86205c7799fc2164b30a5d78966569f27156f610b2518d5264775093d09c7122e1d5fcecff3c15fd8020906b7dce61237fe2687d4d0a2f161ace08be783778b23e0164668f5d22fece5300b5b146171cb213ee272d406a0010710620b2298c6bf83b5101de70e72c2f0e2c300830b30bcb0d15e4020095b0c810f72847f095b08c1d8b29f3a4831e27b8e83c2164130845c28eb91ec41ee3811b6008241f4a3c71e22a8876b61c16317071729b0e1c50bbab06181ff06ac81d1802d7e90cf4618f71ce56ce103532e6b29bbc9d9256e41d8a207e68deb20625677fad82e40d88207468fe3f073d4d212f5293dd86207e6c8a3f0dcf519bb1cfb600b1df456793d871a72da29ed850d2f2a115be4e08ca83877b7c0c1163cbc8bebff28de4e6c71833b3dcf637bedeb0edec206a87cac4b926389752d901c99e67b103f8d69719a6019b29f6265167f7d9aef70ac7d95851e8844980db7e9b762d1c71bfee993443a0a2ceef99734dd9b2ed9739688f18a27c97c36afc8514dc8ce10c31584b41b31625cc444a7156787a8b86e072be838fe0829fce4f0744118b90a2a66b4f5aaa788aaf073f6b493c8b9e0a5025f978e5b2dbe24c941c5371f7a36a5f724e753289652557c5d7d1453bc17c97d3bb090214629f6f9702a3b342c5d52d0da7160f9be448c5190b324044b9ba81ea2289ac6c6e408c5f17e781d821030c1a18030c0e0a20b2c33c4004591cfa3f0f2b9fe937b8f88f1892c566ee5d4a3b36b952186275ebffc09ee91952648c4e844151e3b45ca3e5e2d12313841d6ddd9fb78e6e64d3c6baf9ebe42469489261ab5949649f51f57993032b363ff52f7ac9848774cc4729c4b98f7a6f4e3b4d812ead427ab88ee215e09a2c40b1f84e5a051a48841893b6e7a0a9e1ed60e22c6241af9b8d24935e4639504af1bf7a972d26d2691306e5df2e0eb3a9d4ba79f05a586189038942cc95348d80d1ff1e55cefde71a84b9f23f81c215de769a68b84188d38c7510e92e3b0ae3a8c60c205e9e0928a5b16615a671b97a022cca84a9de291dd4d2776d81e27c2e01dedc30f7e1271af14d8b8d1802ec44084213d8ea27df2bab00f740f428c4398f2c7f1357e62734c8c610872b023fec1c6c506310a61ac7871ba3b96da44b7c42084714e3eec9ed864cd8e1c84c93b274f79278f87d986114310669b8e363c47a417b704c230d926c16ec2b6e3d82e6cfc0d05445b29f97ecc3e738f72c4f88329fe6d3f0832f77f0186175c5420861fc8113fe6e2cb27461f4cb93d2877a9b9460c3e9c9d7371ab3d18267cc4978f9b1eac9f5ced0862e4c1349f7dc26989a758a38fa6c28339be465eec2cbd1c79773069a5f839a4761cf964566807b3f4f5a4d78e915120461dccb11fd54e7d48fa1a113a982ee8747ccad17a2c360793dc67c9dda15fd72487d6639bec0d1b5c601ccc95f4bc3d7cc7f695b5ba06c48083313652848c77f059d53718ff434f0e19953b4e5edc5841187f83861787460ac0385a10868d30ee1410c30d265bf370f696b315724c10a30da6ecd0a1ffec44ca4b712ffe461e5f64200c2e1480e6c5f105180f88c1064ff27ed9487684d30b2e6c58e00531d6f0d4bcbf7f2426710584918206c0e0052d70410c3598badfd7239329ab06a00107a4c1143472ce92474583a963d9b4bc547c7818e30c268dcbf6ee2fa0b9e8028c23410c339893b647e99f7797634f8c32940c03c420837962533fa7384b5b7d8c3198c385d4b18cd4623056eac0febdcb72f47261408c30d093e9361d421b030c6c94dcfd765fe85eaca1953588e1858dd105e3c21dc45c4e498bb185e3f373b1ed17701e03189021a36f68e16264c160fde551e7f1d6a5b586ee186260c134ed39525f89edaf1f85ae60ca418eccc3f48bfcfa5ac16254c11c4fde8f2a9dd3c85a16f05cdcd00052c114a62c59aa8fe4a5a618534024f476cc0a6693d730b848410c29ec9ef2256fbfcf3025b08209288001cfc50d0d6c1519c48882c78082d13edeeecb6515cc3a0610e7cb608897f3e3a43fd1bf4d3298e473ccf3ecf4bda96e30630ca693ce71d8b1b23b454ac1c10c319863fb4e57be130693dda889bb9de4e83cc060ae8abeab1c962f184c42655f4bd2a1730e2f1827769e4e1d077b92ad0d6674c17c2176f46817463f5433b860b2ea388c8ad0b7602c8fba1c68840833b460d6daaef22887ecd1e5c88229afccddd97eaef02158300769776aaaae17227805937a88a567f3cdb082219ee5884f7760216654c19436a6f2c707b9aa43501033a8602c3df5fe701f16a2650ac6a81017eefd713c340331430a861c4908fd81fd8c87c46146144cd9b36cf7e3efeb08ea0c28982accc22e5659434b04339e6014c9eaf97a632b29aea1559c60b41c767071cbefc3f5c68c2618527e0ff593759634da173398600e2687b4e913cb9f820777d1c571689413cc5882692d745acb1e77dfe76628a164e88c24cc4082e1c4e378d34143af30e308e66a95991c9d52c5fb6a689133c308a64ab3fc9a2c7f12db456246114ce3b169f97f882f920168c0011648c40c2218e268e4c95b1f42891943305a0829428ad50fac3f5012338460eac0232c9d981104b34ca9c4cf396fe7396b68bda06968e5c9600610cc9683a073f9ef8184687f60160d4f7aa3e92266f8c0dc711ea767a58898d1034378cae1722c9e2266f0c010f9eda179f5efe7750786d8a1fe29c2de47617288193a30577f102547e5c050792721aabf14dcf80c63060ecc51d6b162223fcaf560ccb881052430c306469509e99cb35343cb8b159ca940462d4ce9fa514efde40964d0c254f1f427ea429d5e9d0719b3305c4e99d2792be23f3c2b2490210b73db76d2f934378bd31d64c4c2d86e6a973dc751062ccc1e751ccce484f515a6d8a8b614cd3ac8dec5d1ef0517295030c87085a1e38f53a86b4fe31d5b4365b4c268e6db593cf0682ec038d86a0b325861ae28cb13a9a372105e19ab304c7bca1d56a4d3889e1a5a302813830c55185654a2ae471397acd7d0f2028ca3c617990a737798292968a586162a0ce5b1488ea867b1d50f858c5318274d0e2b64fb386ab5a630fcc7c5f071cbec3856186494c228973eda8c78ea511d5298a3dcd94356728cc2aa924c14c6f86cf9539a381466adbb1cbeae0285c9c2c2e688bb8f6fc7274cd9574c5e27f67ef784f9db43cdff8fd20973acdaf53957faf452e184f13df2502d7f30056014199b30777496ad9bfe41fc9a307f0e1ef677c6781c2513a68febf34be5fbad2d268c1de63d88be189b1f4d43c6254cd2b9c2c37fbb9f485ac27c7d49bc3a142b619eed18e99e2dfa3da1842942d9641246cb297cdc58f49fed2461f8f01ee4345bb3795924cca925e5384b7efc512f4898d2e3bdac7bf3c82492f108c36ccd95ec24c311c6358fe53efc3052b0310b321a61b624611eb4fb549c560623d0f7d8a2accbb190b108b357f88b1f5829431146f150de632b09d5eec948846132247a0e15958188c7fe2b5bfa8628e310e6d097a2e7f6c4bba840d128c2022bb49061887daba3560bbdce4146210c15dfa2030fc35f321b1c309341085390bcfc2562b993e90419833079059f903fab166408c26cba71339f1e739d0ce399068db3c3808c4098aa269be9cf7fc3c6175f94ca0084d1e3e4b5f89e5b848c3f9883e71ce7ee2865f8c19083079523afb3eae2f0820b13dc3826b0d907c37f59b66462b9cd8364f0c1e47156e914c1a33812eec13021f5616dd352f0482eda059f32f460ce1dedcb85856d6dee850d2e4a7140461e8c2b1397923eca56d1b5ec800c3c183b8e139f3e5fe6e7957107e374f03bdbef90610743e545c4d0dc0b651a59838c3a98d327eb841c78b75a44061d4c2355da390efa111e2f630ec63889f46178c8c1fcb97f54b2c4f6d14c461c4c79b267ad905d527c30f60132e060941e97c871f294cd426a68e51bccd3bfb16e39e560a708c8708329e6bbeee4fd6883d13d5be8ed20df7e2e185cdcb0d1051f6890c106a37dc6bfa5bcebc108038c722e630da61c4773ff9185d8e5b00c3598fbe3dcc1c769aa6d2784a08c341856d75f3b9e6029a686a501098471e359e0858d94830c3498feee63fa2297869d32ce60f4e47174c9afa67d4086190c12c383e9e05306d36f7eca7e8456eec96088eb28a47d0a0b790ca61c7ab4c82945ed8718cce75bf51fa47b5d1d0653de55a77e3af71c07180cd142656544a473ec17cc162fce5f4aecc3d40be6c9f591e3b2bc6c66174c1fe48bf97ab960dc8e2d983ec77d66a1d4b4afa30543487e168c6921d3276d2c18c2756d34dfb37bf0150cf1b2fc87ed68f66305739214274ac7510553c497fb38b73ae40a15cc1693a6927bd27e85c89882f14c353f64f76548c11c2a313cae6c3ad2be8c2898c3ccec38be7b4f1ee46540c1b4feb199ac764b84308d4fc87882d142eca495ee9d602c8f733f0a93cefe34c16cff5fc9d633a2e7996076eff65c8f3dfbe3976016cd9576d9cb15b7128cbfb3df1e73124cfe765f219dc7e13112ccb9720e739623987debd6ec423a6a690473189b92c8412f8279cefcb244e84430dbd47cb4581d543c1f82a94d23467ecfc9552e04e3fd66c7e6f484fc04c1609d7b1ecae54fec4906104c395927e1237a94e3fcc07c2be75ec9373dd207e6c0030b15297a74ae07c64ab9f915ef1e4ece03a3a6c7dcce1d3b30e89da79b6cd181397adce99e6b0e0c2339f8f051c9c681612375246597e31e3730e5c90e39bda465d8c01c9d526bf5d6b530b6deb430f54795132d751ca7ccc2fcc17efc1f6a779847168620d172b695b68f8d8529c79125c9c18785713a07d1d4c3fc0a634d5e54995859f5b22bcca9444ba7cb554ce55618d4b7b4a77527c713c20ae3441fa930ead3b955984ebf42b8bd760f535598e67ce3dfc3e754692a0c9b3fb1b3127e69820a73f493c2edcf851c484e61f6fa28c63afad8595398d45a721c6cc71eb7580a435ca98a36968314e6b01f7b0e2dae5b5f3c0a93b8e7dc56cea230e7c4f5a449db71421c0a837a740b2efe923a0714e69bbefcdfc1774a399f305545e7b5e0f184296fb59484be9fed3b61ca619d5396b42c7a4e9883f9283ab116e6c33761900b1d94e78ff352ae0973f5a5081fd599305dce13df225a7a0ec684c9524c979cf541bde312a7e41f6efc7558c220294cbbb6b784eaa884e953983191303bc1a58439fa3821d557b2f49193307e1c050f2356766012499854c27dbe6b0f257c91306f64d317fd0ed276903067eb87f74d0b0f628f30f9c78f9453e60873e4e71f1e3cc73d698421fd8ea7ed1861f8f012b9f4e0aad522cc95d633f75184394a845cdb750f3e1d893044f30a71443a1061a895ce716ea99a528f0087304577e93c5e663bb721cc7a1d4d8f648530eb74e471ec10c21cff477aa62231da1c8429ee3aa7ef485cb71484a13d36b34a92c33d2410c68f3f4bc6a47d460410e64995bce7c33b0bf10fe68fdd27a63a3f98c4a2aae558a454b73e9872da4e93d3c60783841046ae2ddf47d983e183cd5ee8ae1056e9c1f069e2e84af0d419953c98bf5b357f7cc2e267098007a3a478dc9ffda3aa7f097007a3fe05f9683aebf32f01ec609ed7df93cb2221894a803a98e3f83b4ff623c1723a9824e4a0c2caabe54fcfc16839dfdbf6450ea6b493ef903fbabcc7c114c2876ca9cfeceb8583f1e6f306737c713fee94fe5210c00de670971869c27c7bda60d68ee711125b27ad6c6892c7f16b30eea73bf71ed5608ab157f1f3360d8629a9d21b0f0da68b1f7a8a0a57f1d59ec110b32b49b3bd623a66304c9876d49d9741698b6e913f9a0cc64af10ae21f6330ef79fa143dda90481183293d9a7d14be72a3120643fcb7e50bab97f2e2040083c1a2cd99d5a85f30eb7fb4913ef2a05356bd60f0b760a9afa3325bb50b468baefa134644ff542e98e2b493c4c484bf44b76030c7a9fa0f7d7a3a238928271852b228d1e379770a6e13cc816aec79e49fb8b017130cf79a16526a42c77d2dc1683152e33d0aea361e02885282a9d358ac685c8af1619504b3c54fa1ad563fce7b6ed4088354a290600a4bcb3d194b1e414ad411cc6312cbbaf2c974878d60482e31c2c3a5c5c81e24aa0886f08fba6db299021b371ae05e70d1001932445144307b2441c672c87c3ef3c3b0c1011932720d3504a36709be5ad17639ee1082a1c3e4ed6d7f8360f675bd12fd38dcf71c100cee16bf83f3fc39320901f503d3e6071f8778faddeb7d609290fa9f731c6b47f5f6c090b331eaee761e9d583c30e6a84769970517503b30770ee22d5d2a51bd1c30b848004aa074608ecae72ce7df0e5a5e0abc30011704059503f3fb9c8f4974f8a3e0082303c745e1c03036a96e2698e430923730979bdec48d89bc9f05eac0a83241d9c0d87942b05fcb41b530e7be47a9420853261e440b53ae7ae8796d274ca559184ffd52f2bc33c9c25cea21215dbcd309140b83a6a499186d6d2d2e8d4a5818625afa0edd59bf8264000b18c0a7e0a0400664c8a05179a057985344a7cfaef482a65fac0005366c48205d618e362b3cf4b9ed5ac8817ca0569892c47964565773d1215618faf3855f8eeb2dc2a25518b2ed8ec712f25a5a23551853427a0e453a7cdc1fa5c23c35b13ea99af31d8c0a43e518aa91737d053a85f9a2ba1090294cdf973e8c1ffd961442a5305e8ebfabfd425ee71091c2d8ae8551bca05198fc2a664d7e94272ac78dffc2868ac2a02e3988f4101a5028cc417b904d35cf25ec07288c1ee7b0e3e439ab5fa04f985dc3d34e46aebeb92817c813068bec38c6428e4e18b4d2ee59a4fad4d1e584f9726d8b88acb409e3498894238d10a2676e77481366c9d973301dbe3c98b286561860acc06998c00b2e962913c62f9d94b303111366b124c1f4e2ae4c90037409d34692d5df71310a640943b8df5ff197ea1c5d953007ffee6a69b289c4094409e34dfcb8e6b52c4d050a3409e348e8ff1c6325d5a3650b2409f3fc7cb48458d2c0c2801100588122618c9437fcdd2d5aa7bdea090409738a337fb14225956a35b41e618e5a5feb3e9adcc44e0ec81146fdd0c7a27585c9351100d8801a610e3d451bc9b08ee6631a66046284d147d7e5c277a998498b304ca476fefca02d8728a294621f5243cbca079408b36e4b0e3b5fb0778f22820e610ef395d4c4262d7c28990319c21ceeef513a8fa35e290b61c8ca37a939e998e12142182cca7fcae6e791ff85ca40833069697a34a16e3c965c10e688316a93e621761805c29cdf96fde3b1c89c0c10860eb33edecfc37a747f3086b4f86db4bf84d47e30a975fa997f5697bd0fa60faea7fb64c73db6f9600a12d3efc1902ec13df04897e2133d9872ca397aa98a06ca83295b4578c7d68ec3ed82075394f4f76d359f78f51d0c954bae26690763fcabfecde76ccfb93a98b5de2a8795a31f9bbc407430f4a67ce8a0567f953e680ea608c9252efa5776ae1c0cda35971ee9e3af0b691ccc56d9935b479df348100ee6fefda05ee9c63d9a107a03933c1ab5df8675416e30a49e903bfc28525c09a161c3c986406d307cfce8b6ca75bf27ca06b3d9e7f8ace7301d44346cf81a4c39da7e9ba80f4605e3c6db700502a9c13011ed738e4d2687d260b4ebd9b89cbe180f743f68d8f0fc01a1c11c244ba5bae7166e1713f8777106730e76627ab2bc10611b5d48c0cc60ee388a8ea3a4c8efde6b6871e1822350198c9f27a5cfe929ec420e190cf3d9e14348e81c51aed1a5031a8329f85a85646ece8aa40b2e50402406f3e4d8dcb2488ec2c8594369786183aa8b2e56d0806f2fe24061307668698f36b235b42e0066203018d4c6d5dea36df38fe378405f30b748f2d0ee5144f1ce8d378109b8b811068d17dc08a31479c11042d6e32323471ec25d30ec55e867ddfe40ab5317003210174c21782cad8b30357d16b405730c198f746dece2fd1a5a5cf8dff8c214415a307a6487ae59ececf474109485523e3377b6b354681c1b34ec8bc482e123c9514b4a99466890aa0b045dc1b86ee1e3fd783eacfc8281ac608a2a7926a4e3cb7f175405d387e838b63a3331f9dc0b1bac06a282d13f8f8b47f5dfb58b1b61348d230c30a660baa096b7d7c2bc4aa886d649a178d9e1cbe760fe2a325014cc6a1fb78510b7671f47503007a296e2788a316b2e3dc14916732368759815c80986b3e41d87b50f2c5013ccd164957ddc9fa8d09860ae8e52f547f7414b30ebdcc5ced1c7f9b7626509a404a3d487a6e79c94404930c72e91a3655c47785006909060ca694bbd13d743d38eba33d011cc79bf3bae94a84cb3c7404630c67edcc17d799bceaa052a8229fda744c831725db0b2404430c7d9a93c8e52a12198a45afa3c924908e6b379f77d15d749a930b8b8d1823b2550104c61facbdf2db7e3acb9e815f8df5019326c090404538eb36529e4844e4e3b81bc7174b10213fc0ddd0dd00fcc3943c2e46446a38b2f52c0c5150084403e304ebb78fcf214a807a68f429c091f6ca7902b0f4c531fcbe37c25146807a68b9be51d5d987460f0f06ec6b3ec3fb75505ca81f143c27b78ce1a957429100ecc9d3c4827b1f2a59205c6d1c5026ebc05cc403730b78edb459eca0364039376dccf66b1db3e592a30a316a67c9e2fe5e02cd56a575a182e564ef73895a7c9c92c8cd9fbbb1fa28b245716861cb95f08b52a9344b458cc8085c1542c54d49f35b48c05335ef1e7b826d4f2e46c292bc20c5718ecdb420ab9225e3c0bfe462b8cbf923fc48dc759697871b4b1c278f92e8574ade7729219ab30eef58aa87a96c7bfd5d0a25285c1a3e9f5ec462c15476f9f545f2a21cc4085217885a41e824ee50e5f14669c62bbf83cb9ef363a630d2d53182ad75a77b41f9ffb430312184029cc512686a9458ccfe10c529845dca3db25f507d7868d1ba3307bf47c59ec43a4d5ee4595cd108539ec346b160e85c1bbe683c4ec7b10a51a5af86633406156c9619b4d4acdf884a9e311c99e4c2cccf0c4a71663425be7555298d1098384ab0f0d30b8e084397fea385744985e98b109f3999c6c5f49bae86205c72a4b1356ecb2e8551e924c2a444aad942af8c7295dca16c83bccc884693aca431fbd08f760c26c2fd1ed2935bd5b42332e619a141d6787de29d4796a681d6186250c16d6732813afa18546985109836ca9a48a170b0fc11a5a288719943044b9ff0f23679330de87e19103fb54d2510dadac0168c0013264a8618624cc1e63245cea5c95cc120943e8e710cda3089e3b88026640c2143f2ef2c57a26c5f511a61c55b0bfccabf095bf812581198e307baa509fa7340db4e0c040590b8e1b0b98d18852089752ba53610623d80ee4425c3a4f98b1883edd7ddf7f080127cc50c493e2f1b2c28c44182e96e7b9a7e6e5c8e306035a70dc902183063310614a396e73ad83df4ed17208b394b9c5e8f03006330c610e3eb4fc98a89c2a7d210c73a9f54cebc37489cef15d9419cc2084396de551fb951af3388328277ff1f3b35910e6145f2b7ff4396a68398de28319813085ef935fd9c83b3d01611e4f7561ab2347a1e3300733fe60d8a8ec683d6de3627e30c7c588719f392122d20783c9eadc440a9583af35b4de6db48d05bcdba040a12ac00c3edcf28c3d9843da79cfab1c5a0e827a30054f09d172c78f486b461e0cff73d66198a586da08c30b08e0c11c7ab9c7710a1121b504c18c3b983aac9063a4d4396ebb1dcc992775923bb80e5e0757b12b39e8603e89d0d5aa36d9213b0753c4ea0b963de7725e0e46c9e192912c84c68c38183d7cfa38dcf6e46fde0206c0810158400203a00008686460061cca8c3798fd42429e74f7cf699ee1063e9a9310e771ed35a30da69d7f30122b2b8d54434b6f0f33d8f089743c9798f38c3518236de24fba5f0dc68ffab5721c466c879c06f37b58d57144d674d0a2c164a9e641f4d0d1c2a4669cc1e4e9f34dbcce7efdb90e6698c1905a1e2e74b8172d0120a071807382b12c072d7ab237000303b82698c623564c3a3a9ff698600af9e71f1dec4efde5805b42991255615d5119c9dbbce3546127b78405a70443c6e5c6e7b0def134b34b825923072124448f438239e46b4fb48a8f1ce772057704e3bcaa86df87a838df9f114c6d39753e5de98ae0a4d073edec99e088604a9d3f08398ebf9e7e43307e8a0e1d21797cec92aae08420830b021e10b8a87dee51f0f9cbb91f9c0f4cfb3b937268a16fa91b07ae07a6d8816ce48f3fce1ef9450a7a4b703cb0dbc1962a92a418512c976b250b8f326c3bba713a306a47071d98256d2d079703f36705f9f05f4bb23e6787037387be1f9d9287f33fdf0d8c7912fb4ebb0c9c0d8c23ebd69e729ec8b56b61faa99cb56bc13b6f4c0b731cc761b330fcf489e7b46ec995928521bbe791a90fcd144f2c4c9fd5a3840e75e4dc020b436ea5aef8f6d8a5a2dd81bdc29c6dfdbeebd269c485d0c05c61d6eb099522841edde800d60a63664627bbac91aa6c5618e23c22e5bebcaf925761caf1f75a5ec897af8a2acc514893d8908da4b7a6c218fe5b15af23adb7890a733cc13a1649eb173d4e61b22c1e8794831ced4789290c6f23e5b186570ad386c78f9472470a43f08e733ef73874906c14e6bf8eb3b2924267bd44618ed4ccdd72750e62b15018cc3df4f408f1dbe310280cc1839fff9eb97c3157fb04a135c3baa324494472899c3ebf965ccd85e609739cd37cc4b4f868a61bb04e98f672aa3e304f1fb1a3140ce384219f449aafcaf238961cb04d98bde5c366e7a22f304d982bfbeec743f9579ae4179609b3852c9dbd8e364c985385fcd7396f1d471959609730c7bb64affd7b8a3a530004343e6096306dde9e5f05914d9510ab84ed651a9153d77b09b8e098c0aea00118184015188c1226c99e7235f737d9e39330c7da2fcbaefea91c25e1cd6ce7a4146eec348b84d135c4e28e7fdafe09244c21b5e3f45ab7dca1f808b378ad4ebcf4b83c9d39c2903a8e6f3d56eed27fa55137ac11a5c42a8976b5c84821faa92aecff1a5a7b238c2f82608c30b4fea7c8adba08f36b5b49da6e4fb192354598363e70d7b33fad8852db1e91c262883007398aecaed39b1d2275f7f8f78cb40feb8019c2581f4b9af170b74298a3891eac84f60961a8547a1da77c1065d30ed3ff5210e6f5993699348d2c1f23b04018cc738ccfd1e58bddd20061cc0ffb336352f607338284c830f5a8685ad153fc0539fdb2201ee607d3a73c39ea14d751bf5304eb83c1abf42a27755c128c0fe610b43775532edfd22fb8a10125d81e0c314ed3d3e7363d18dea223847413b63c7930c771f08e53d6120f061befa87adab98339ec09e2416d4dea388e1d4c1eea3bc5ab5ddaaf83218667c5b5a8b9e1d2c1d075e9ff92763469cec114f282e59b7230ab6795b9749d2c483e0ea61c7b7c747e3898f73bc8219efe06f34b99e428a4b81b0c3af3575d6b1bcc96bdbeaa1e76cbc90643f7e4e5d4d3db9ef91a8ce11f7eaeab90ce8f1a0c761de4b2e0a7c1fc3a971dce8c06b2dae3fa8fd233983dc85fdb6a159f3683a93b4e39858edb72342983d9653c7d54e397354206a3d8a76a93e8205cd763304a67b58f546a311873b4e63d66a8661f0673071d42881983c13caedfe16a55b68bf117ccafa2baea29f682b1ad62ffc8d7e7a647170c97334423480717cce5361f32553d295fb6608e2c92e51f450ba6c859e452d75cc7a364c154712a2eca7d48b3162c98f543e59492761c4b7f0543ee74207a177175b782397deef0a32fc3e3bb0ac6ec0b9e6b96438ef652c1f4a2174f5ee2071fef149e1c5968a560a834c9c1b78e48e46814ccf13d4523522f99af5030a5b06a6d1ffa047359c717795e7de2552798c23c3a8f4f22dc45b509a6cf08b17ad483ed3d99608e0f3ae894f3c30e3d720926aff496e2944a305fa5d0e53f2955c59260ce8ef6ee672a645e8904d364558ffb117142fe0806add8d1bf7860ddb911cc9fa7fe238f9fe32c17c1389ffbfe2e4a04b4f2c4896c12320473e5b434322142305c0e35f6a3e7ff0a49104cbb13daf253e8ec39020473e039d28f7beec8e23f3025c929e4263ece15f681c1d259258d8f1e98673d55229e86eb0479600ed2577e9e77600a9fa32a0b93dde3203a308a45eb943f96186ae5c0649e52e7aea96d48880363bd8bb9fba7f58e7203b3ae87753d9b0dccfd777596db5e3fb416a6f9b579bb8c8c28d2c2ecee9de27c6e16a610e52bf1dac9c2b06d967e2b6eb130c7c1d7a68795e338cb060b835a87d87e49d29db5579873ac62ad26952cfeae30eb579470eb358fe75618ff4227b7cf3fe40f5698c76276fcfc1da4b9d42acc17d623ca3a946e5baa3069e59e728f71de51a5c2a0f78178bed78f2407158650e11d9227ed10fe294c717624477b9bc2104267911cc78ba71486ab2cd13bf592742185d923071b7927aa511845429a7417bc42a5578a50a23059e750bcbc52d84a9708150a83c4fab72e9d58698f100a14860b9715359f3bec8f1e17ea13a6f5be8f9d6763ff2558284f1842ff79ca4c3f0f33e913aa13e699bf949053bc48284e98e320d84c969011111111efeeeeeeeeccccccccccaaaaaaaa8aaaace652000434042085da8449e6a38f23f7f0e2f5ac09f357fead68f72a13c6da34f90e22294c98837f7c6449df13d425a86494256c355095a0c49611bae4bfdc23845e9370247ecc2d4306312a942488ae78fbc9f7f5b48a84214fd6b71cf8bcdee80509f3458afb615979ce71af47983a791ca69fd582b6a11f28479823ee6457dd32e97eff467aa01a61a8dabdf0b8157f35308a11c68e14b9d62e3f947817515a508a304bcabb99933d0e391acf82038cab0d54220caddbe7f1b5e595cbf1376c74894221c21cc874aadeffc5cff94318352582e4929ca30a1bc26ca9911a5a36b60a61300bd1e1e15f4556534258271f1d6f3508c35c4c7d3c99a8fbd1305660e353c0451882305c4a1e8d57dcae453710868a7349dda3f9322f6d84610508e3a465f5af10724821eb42fde120b247c5f59a3668786183c6172bf08329ca638bea1c7b20d15243c3b0718346e142f5c11c87ba2129dbc495a93485e283695ece72daf0d4b2ef1e68507a307bc8d9e396fb2077b8d6d0e2e28b83300f86bbf0d1274ba778305b8a8dd189f62aadeb0e76304bba8971ad9f37afd6d0052ca00e26d17f0b4b316662ba557430cb847a88a2bd39a1e161bc09146b0ea614febb428e3a1d6ea95a283998a62fe59c53cc8a83a9d43b2d73be2f48c161dd53f98e3d272172a8376cb5a19e53eec38b2a3718d3426acfd9fc2778a822a2da602eab10f2295267c448b1c13c29d7d6b51e6b309d79f4edb97d8bd8a9516a306507afd4ea1f576930544e578df7aa1c47586830d55aa85e54a786960dc726d419ccdd213f76b0cbcbf999c12c72ba5d162f3f54190cfa913a9fc5cd62646b0f450673241693a320f93caefa5063307a10ba42ee4f1d68878bc1dcd17739f6dc764ab61e2a0ca654c96ffe3fb6b4456030e4897596d3d65dd4174c398a0739555a49177997174c3293aafda23b74c7c6a1ba60d40eff39c3a307a13e3814178c1f39cdcdc5f528c75c300daa2d9826eeee5eb6490d2d3068e027c098505a30bb9956f66d85fc49b54065c114a5a562562776deb861c3ce505830c7b3547793763fe51cf7c286a3d515888f3eec1829446a68d108c3868d1b250c6505f3efa795f8713c899cf34ec386df16aa0aa6ad9024e639ff5592555430fce5c4acbb58a82998efd268e7ca55234a0aa6d7b58f26d6768fad8aa828182a2287243139f92d85110505b3e77e91ef684a26d74f301e8f34a8c47086226128128803c2380ac22034730f73130000000c1a91c742c158341c4bb2ea0314800457281c48382e14201c1c14221e8906a2502014088603816020000a0642a160901ccf731ed50053456744eecb71fdfe283c69334f00b71b1efc7be8fa4c361d6e4621451e02331d47eaef91c860c022b7775379f0d4087f37bc3a929f0691469de313f9773f095580ab2d4ea20c0a50417a7acaa58403b9ea9710c7e058bf383c5c7180d0045c3434b820ba42ba79d500a333ad52750175e7233d477a671ec8e7411a2db6f53204991a5825890458bf07d2b7ae2a6ba1a39a21065efc45d5380fa34c0d0fc72ca0e5d68ecc902e2dac632594c1b4408416f41c37c4a7ca8f77088229dc55f092b3006096e2ab1bfe20080698a876b4fac709752786795b49210e6044a272f13db13c75a48b5de94ee2308c1925a054b2510b2f9b396ca53d002b921d8ad010ca709b1c96ef2e22f93c275827fed9905c9c71adb22decf5530a85bf9e0153f35b20081a9ebb0d52d917c55ae8fc8830ae402bb4dceafbd37cc4cb2b06eb35bb177e0d8a99c20dad56db6335e3ba509a8a3214199235ddeb450130fa3daa0ef045a134550187840398f563ef67098489d9dc50f4a68f8f7e121a5e6754945621a394ac85d25fed7532244b1bfb295acd58f5f2d01ff62fa3df8336cdca45f90e442c91a8191e04af2050e412e321a52c353b82b08e7bff92a235378ab1495ff7efca3590c903d8a34956ebc0942217dd27bd20d5412fa76e3fd6a09426702ec1a0dfee3524c4fd5d76c8ae4b5bb52fac00ffebdaac58c403647fb601ccd38ca330c85cc1d2dc1ad3f67d29d90d12b0f372a5fb2f4daa53f251213bf9a2e2a5f0c0f777bdfa68bb4db2870fc3b9a68b6d310f15b73106dd50574f0e70829c6ba2fe7813601649d81d862d0405798aa3c2ecf25697ec0dc7c2024139ce20b96c489b45d8725332a598d8c2f415d1ca112446044e963c982c19d1d21877b8970ab8470193f106b1e7f3499f0c5a45e3ba72b12abba4107c98c9d057883f58cf36e27ed54ae052994c16a2cb6560855942f5e4bb421c67c99eb34f35e44b11924fe135a7d842a5fbebe28862416881829045900f8e0a93aa30b97bff655f3bb470572bf16bfe652a0f880920ca8c1eee8bb3d1ef2222c012b134a4febb40400de945b08dea75e289a8e273b5bde93910d3a358ebdaa10f1d8db8fe927a3ebaf4ee655152e31e6811bc45383eb1792c0b30f038844836f38cecfab6295a9ab4f45ae5428aa76b387812c918405ec41d6d301fb0fa754c1df0284ba60b75285fe80c112b83c1aa59bc992b2f0df19b20f16105cb255256660ff42cb81201634aa45a4862bdcb3d5f0105a08fc5caf7dc36efe1e5031de0fceb4d581938d9ff46f7a12789512a49a290dfaad56cae251bd301395327017455dc18f5abe11b2e2e95fd5680dab6f20d84d0b858b0b0fdd0e2f06944b180cdfa49a3a131834630802c1d586e3c1de066807ddf3401d6040d3ae451eedb633f6ced7779d134d4f501e4098422400ab0c402bbbe15cd58f57f04705a50a7626eeb284c9f9ccf22598f8b8f7a17a450b7952d323d4bbe32633a64eee8d5f31cec76f016c77affbf806bbbb128a02a5c1df40d32a218d31e9d32b10b8f0a46d19001a01ca061a129372cb2d54058582c900b1057e28ac12859f33d3f0b5421d415df3ecf802828a800df0808c74d500500d070cd2b5fbd4d5a059f43741790a99f5070a127ca490b8d2806def257332cd1a23a895d0306e309360567333e02585220936959c0163df7267a4794090e04805bc042a075e87259b7ff6a54000d8c27e41d08349d6995eec17bf2d32112030c86777968a82beaee6a4471df97b5853dc26ae5cb19341bc810bdbf56d0fcd8b0e3f4f43f94dffaf54d39d5849aef9d5c62433a7756c784aa93ccb3a6ee523b34d4f4a3b0dd14d2b6f8a0ef1355ef71231d045aaa3927d1cb7074f6363bda9c3143a8addf3303705a547816f6ce9f0275c4026196927dea78fdd232fa1bcba7d71a4d41f8e48252895f6ac53474bb2b4d5d8a2b591545aa44113514ae2a35d5ad8a12d5a5b8925551a44a145143e1aa5253752d5765b9d2cad5a95c3165250ea94ff5148a2654c9136a955e40b3858454e1e9016cb4270db53baaa01bac0912cd20f21d92f88defa41d639cd9a23ebb72350911110ec99c10b5db6e4061f783cf9b39d484ccee6a2913c8fcdb51ec1ae2d106bfd3ab6e3885a243b1974dc63b5036724ec1d032581b56acdec0b44d2221c2b4142fd08fc71d17cf61ebbab3e48f55c330458238f99967e39fc27d2cb24c477dd4d0c7e1a3efdb602b75de9317aaea0f62a515056229d3db6a67a54c8ffdd4459e815625f587a254b13a6efb3d6232acf617135d92041ffd8eae48193cd3ba98fd4f9f78404e7179e5ff80c088dacbecb58eeb2f70e017f9ec004255d5c6e781d39ea6a43b08a349d6316cdf1bfc63a88ac434a094b13f639b1b227a0da5db5a8b9399ed3d7aaa6025ec0e4d8cae75ec3ade87ec850461c5afda53b20b18f01b5df4fa7ed6d475ecd10bc0e576e1bf15e5007617a019a7961c24f663c8708397f667b0541919e0fc4f2d703d6bffb5fc710fead1b85c7b51bc6db38bc3fbc5bbdcc9c79c2a342505228609e46498360c94329480c70d177d7a0db7409fb8143748b78aa9a7206cb98c8db38e134334e7680f4576091851692c31188e74c751acd031bfb5a4d6761db5ac82ed6773879e62d638845b0d42e1acf4d28805be93056910f6becbaf5195a4bba991aa513d8bc9f37d11c0fb44a31297b854ef8c4f9886807aaee2b06e37b0fa4dcef331a82c9c4cb3febf7ceebd64a3b4c3d8e541948cb1fef6ef1781b496cbb6a3a88ea87aa86924921d4c8ee988082d30488226cc9fd5b4e4ae3404c8bd7194dc36f0fb43f1312fe6b1b1dfd48ec78e3c233e95b1d4bcc5455b932083d0ec291d39d0bdfcbaa6b50d3ade66af3ae3a2b857e07d716f558a70198af61d81fae9378ae86ce62a2c49da146c9488b171ee9f086dca3e393192b5a42b4b8aa4b0bde51f90985bf6809818c9384984ddd71da52cf122422ed0f87e9e394aaf4e2eba88220dee224a9da985f9e82798f0c308ee7140b431b228596d615257834af58164745346d33a8b540f1280841424ff9159ab4918ba1f4bec8901d80131108d208b10b832f36c84e5a6dcf01f0071f7d9ed9e251b04f377625cf5cbf4fc78b5aebc6f8d6ab2a7fa56e5579f463cd764f1929915de9ce46e5f5ec5d2a7d7a06a4339fc03a2342ed0f5359f9b742ddead5b2fa97eb17bec3fe323a61f98acee92bb15930cf34bbd35d126241dbd64b4e107ae37c855e28e08763066cb50a41aa50b9977d66f7533918e213301e15e58fbf1312e1189da560082ab06a46438560faf9881e1f50660f9e3aa165489253341dfc75e006c58b76cc889414c73dc5d69577ae1d6c41b4764585d7a1e95ff18ef702ed9b2ffa77651cf7bf59be2db32d14325d04dbfdbc962e7c0d4b542817f81ddcc449086b0ee905d28b32f74d3b2b1293bdb57827de1c5c3c15f47b331471f5fff360544668c7437197e4d4f6ad8c3c2b4836c86c76452f1f983ac91a719200bb3269bba2df8a8fe66010bfedd6d64c317122fe9f67dd480a8cb02d5672ccd8f4aed5d71d8f1720bb05b4f35299664d0f16292b07c95b867e449b55eb9e88312e3c235ae1686e847b5ce75d3e9674b960f26385b387da43672780bdf595064972dea15d83568fca2e4eb1f383202861be16d06d91a02284208b5ea6b80ecee35bf99011c63519e138da2107628259d8d6da727e906c6e37717deaf9c123192cc0d6af301c9406c70f15d8dafa1e5afb70f950b5c09494d581aee1a8879cfc59c4551f87085224638472856c720a16955bce4f6cbd0d88472bdfccf8e93ac8d7361026d54b53eb329179e97b2f3b1f11227bcb08b1efdeba9a8b93ac92d1316c56a93cc9d335d42eaa47fe9a5a34885bcfa25e0b7726918f3e00890ab010439f7cd71eb741ff97295d5426de6b320bf467ad6035645d771af440699c6423480a58529153a54492a285275e03fc4e5b7c6225086c9c5f952568e0414ae4f630bd8b2b5861d7e2bc580e7b6875625056cf5b8df01478e3f73b9ce68150d2e89424453d660579bd92411c23091a12cba2ed434c9552bdd5c3333cdaa61bbb19a969f58a741337296032b8e2382aa210be0b995c4abb730bb25e374d1dd677d7377157ce2fdaa4df1f2d997e5c69fdaaf8bb9834f7ddf26933ca811dc378b5b6f45914f0deb5bc90c4ac7bc211c6c51427a4c17ba51a84cf3fd8e15b39ff59b525288007c522bce02f5101c15fe3d9b79284dfcbc1cc5fd804497d96bccd4c8eb811bf088a3ae954a84ee8c4c030ce690c26f41e95f8c3bd19cc651e88573ab18f88070e3c40cc65983c0beab6de963dc4b359432e6af033610408d9764b4ceeae0c37bce2205d0b1596101bb7a5c47f64e23de7d1444539f6bceac4a4408efc98d3d04c95e60f6e0013524c4294f3137514cd958a232af1eb76a34f4d26b26ac54d252c60ea33d78342a1fd13b80b76e5dfc552e7ec34150783b3b66830ec09583cb742e03b6d368c59fce35bff97653d8c0af916672df4a3815ef789391b16ddd9456326624f3e59964f51f4017c92f006c81e3c7ac6b59b23ee003a7e585d594034b21c0502ed73ac85dcbde136486d7d166e35e92bb54f6415408f299b234ce607547fabf1d326d1401d614cf8b465a1dd8dab6ac238d619afe0298e7061bd59c05d5d2c16b263dc22f9e91ea8bdff14209cf502f12695b4678634c61f9f19f6ed24a0980bf865643fd902354febb3bc0a5ce342df4ca1475c306d6154d61b73a34bb4f72126f9a3312d5901b294b7a46c0268d3c6051e670029ea0b01a7ab8c58b7709cc9bc4fb506f68cfa1ce29a7a8f203ae672d65847c3a7590a22650019031cc9be5c8340d33820af3befa2beb30aac8f1e22dc4bbf03bb7b941a2a0579256cefcd215d8f31ffa17593a8b749aefa4fb9303df28c6149b613955f9b82f6819b65a0afdfe6735d4a915a024d6dc0d8b320c0b345fdf87a1a5d8242327d561761faf00f4e8cce8ce0a7831664d8b7c405a6cad043dd3f121d2f0863a30aeabbee20c7c6ac24f1672f290cb565cd7f76c538ee031a84942d37bc95c7182d2b2cef92bbe165db0d50bb0892061a4910ebda48e5de0d82577d6c946777368d777ab0d181a2fe4f8011f8524a61914dbf601fd090496062b3365b6c366115d64a50c2debd6d970ff735b563e6082404bfdda87aa4c7d8b6d21369db1a61781414fba38999206d926f868cce260f3e4a4e8b73f0e0475ab7df5d842de177a4da16f18822b791ae3f5a1b79ac0575c5c23919e85ce8d506eb5c9746192c361b9e65ca34bf5c5ad99ab992ac7f6c3e033daf524da6b1f6709fd14995e33c6972d58e35de4e8ca45fb7d793dfaf8c1e35e027e6215cc42dba130ff3acd1e6714d5f9f5072a82a9ee2ccca558b77b1b4eaa6203d7a98294a085d0fabc153243c914a743c0cc092198b695e3c09fb329a0b5a4e233c6e786a34ae3a356c60e889f8b6448e4736d0179c96609ede424df6dfb25fd4a0acdcae3622285cb6377046fa24a571d7ba96ec72987da201bb6cfe74e79b68015e524a8ae76229af3f02712795a3088891711097fcb4a64100d11676a7fac4188cc2ec513324ab41ea3911683f5a715aaa3ea0679231b07f1e5306238e572f83a4a2ea0c220467f32067cf595775e250e1786a764ef851d0a22ca4376d1240ec9c7c497a2ad951d4157afc505896d9a5969cc65f3b3d522bcb1c0a605e1ae73eebc0346d0ee7b7def4834b170ac377b0391c1c264a2f0e37e9739cb34882295893e8bbcd6b83b1ccb308c2a5ccae8aef0d7d0a77c7619f0b1b75863b2bdc92bdc5bec4d0421546a167307cd5a20f39feedf70d0db25c475f41e93342670a09b4c37c03403b204011ef1628120e8875f898e1e6bdd373652c4b11ab5d059555351fec5e60c82374dc38cd18cf8291628fe4894d156ee311154b5a157df0ef6820c87b76b2f2d24cef676db146524ffafc6676d7c8a2ec936b7556f5a895193ec04aefda891a8b9d63f84d50fc41b2151e84987bf939a8cb66bdc7cef4586daf82e3b780e78bfbefdab827d1444327ea26e3be1c7b6c5931e5cad1e098031fbb63c1ccf7bb1232fb4b0052e9653cad4d15bd5e3427ec45f1bf916bd334e462f9f0667a415b0d858b0f451565f0d490fe7e1df53fe807329c6cd379649f3c21b7f4afd716bb18aa48be950cb94cf236fb10203a6749e6c416d16e1c55c9dce664acb992e302a5bdbd0b652286b12f3415e47a1ae523c0afeda01ae82213e3013bc423c7a38ba0ad482f244b252d11da67d9220c76385b44674cec5bca9bb97848b25796dae4af9896524b3108e2dd6ed00d7c289d57493874dd5d5501048b1afa2c261a7ef5a929316d8994a71b0e308765ae2736c46822961e71a04aa5e11bd66b23eee428d4b2b5308019260a504c2832e2750950b752ec78265822e3f0b1c3dc28b0b296f225550ae1ce8b72c3dd44735af620439b878cb4bde34ad384fee4eaf7117a1c2b406a9a88e619b7997223206b69ce6c700f9ec4576d425bd081cb7bb6bc507feffb1441ace94fde9c8112056790f01cfc943162b99d03a80fc4d4a4d4ba2690f49de42d6681fdf5d614d8913e1dc2243b55695a1393c6ea2ed3adf28539f57296fde3ad0dceba47be1e1701b56376afa91a27dbc4a080900f9b263107134d8c9ddc19cbf0dd4bb44d8ba1d67c03ee3e0edfbd1c348ec1fadd6f2dfce54c7bf83b13356c3ad9108c90903373bf6045c5b71da2e855c7d1d7ed51fbf1d89d59d76dd37ff5103cf6ac783868c3eb95c0724547a7b3f58b5c03b636ee8e22f796532c0996d4a9869c5a9f73a0ef99086f3b6e0d3d93bdcee6ed3f41699ebdd5509a22f7a06e0bf42a756a138bc51db98548b891a067b6d3bafc06e969e6298c3a87f2d92f372d682fdb36a20de906073a094d58546ebb56d2c43b01fc6a835f93ab29afccbcc9be48fabdcf617d5dd6fd87ab36426c1d662da22d73f8249a67c133bf1d20a94c0ed5bbbeb171c7f45d5495191f5494ed41848ffe468b84e8d455f27d6cbcde676a6d8a1332616b58f6a2a36ba583d5f6e654e1442a2f756720d4bd71106eee02e54572af4781a1366fcefd7e18f65815cd57b601249b2245aa12112fffa1b0a174b2a72f0f8358687bc3524819e2c216b22d8a024d1578bb1c478cd8cb4385ddfc329b654f97658adad0568f2e6e8bf00da4321668453510188313411af758a4e4414ac64ab6cf0c79a980f4ab4e1c2addb24faf73e0be83407c1befe121dcaabcaa24395bebb6e688d9ba2781ab3192caf3be95ba243f66cb93141ba46ca7a9092ecd458abd25ffb0e12e251f436f960cb0f35e4ddf359aeaae9e15214dcfa8c99a23dfcc1d2b71d00d19c003494717fe8bc308813efd0fe6580681299228128d401c264efb3f62b657a1b9e0fe18a5376ef68d4fc72f16bc3ed7e474ac763146aca026ec6fa2382226aef4cf4feb1b0898a2639bbfd62664e01672adfd10d942747109046e724408eacd77eefa210cdd3d90096a08e1cdbd1b5ed159eee8010c08b7538c14b6d7775b8fb77234a9093f01f5ab2b23196f3cfb3c49680a53ae31b23e623e14760c5f12bf56488f78a03e5f81a7e1f43747f8f5a8f067472f1781df93e98565c746092defecae82c188561b57b1840032ffc5bdead56e4bc85672b08d4abeec09688b94c469570cf4f270f405817990e8257a409efa60227b5c1f230e9b03c57c4e6b0f420ef181e36b604a7eecf7e3209f5487cc81c690879ecbf1e0d6e2eebb52359d0fb00d1c303767b4acdd0ae28f0f74d92b9ca2a97dd14c126133f1008db671cfdf2a78d859d821792b7cb2386640e5a708d75b7804b65357b78129655f181a6978a3296e4b352681e7f888efca94e8243411711593a53e2b7077b118e0b7bd5a86e39a0ca0783ce99db48205facf141bc58641063ae0e8caf7c325151e3ad91a436aca42894d469bc9a6485aa67756c819ec75791bb6fd37e0ba8e0975351effc9954a488de5971255928d0c06d8e9bcd0959de2c5cab555d87314686140f9f014c1472c8de66b1003530a922f9c6f823807c55f2b23cdb9986fcad91bd670946183d680deea0abc2706c3ab5ef032fb2750717e07d34b7917754484224eb50061c9f976f52b1d06133618841db7d67f0203deffdde917bba0b1ac7b76de7ad97a64d854529a8a4b5ca2f958016bee72715080b5e5b3dfa57a6889e40588f8131424706343ae455594216bb15f0a7d9240f4f0fcef891794b38e20f03351fbe5a099ef836b59a63fa2895b2cdb486c339d84be11a328f26a5f8f2b12a56aa862ec9603636c3eb941fa300075ed7aa6cc2d2cbd1c4ab0975da7026184849ac2d80565b0c1868ff02d148166a5affca9ddf3a2c2fc0b822754e65bcb2604335d490c312649812462ccfdbaae171fa4db4f6012f9a065898cac752ef38cfd05b54d750d37209979e13e34de30f7f9025d55306573d7fe960bb91411977d36b5328f027d7bb1b05a3e2496d5213794cf9aea1ac6aa5d4672a0ea0360822dc4a6062b3505942c8e1c40d60e6aaac648516296a32145d4e50b7c1c3842573368db9652b3806916626385694b61dbefd01dcb3d85863e97a07ca640b6edc667ba0dbcee1d112b22e1092423ac981127bc97d0facc68a2c4e77a03932134977807ba5e79507685ed19cd8a574af641c4e8f2b4d8dad2577a2f03cecb2dfb65aca1153df839d810f115cc650624b910749fdc7e98afc7c6a071296b4b8680c51179b533668c9e9a48f5a28e1ca3af25a13202097301710dffd374fe70e6c79f1b3865977d98daba9387d93b293640762a88f1b74b74aec0be38f63b5002557c819f3bbec13cd7bd3ccdfd653b686b36b23ee74e0b5f589c31a1c75eab6a30031d6090c33742843b21dd5b0decda1e4afebda5b18eabdfff6bf7350c12b799974cb4da78787c7be3c97b0b6f0b4a39709b4aa77bf99e4ea99e3b6fc27ca52b2162161bf6151441f85980c97c9dc1a17c8b7a1bded8aac4ad1e02a2e61d259f3c418be7f590446214aa4c80f88f33e796a90ebfac952aeddc9f38f2b86ae2c57985088689f89ec556dd0ef0d32c7f77e703dde127ad74cf8415c77851c46a80555341a6dfc857581844041fe32f252937142addae6cd5f7992abd6b0f6b5f57a07843c30b11899e241225f12b7e6b42450816fad4e1b4b14483077450c8c49a0544d9645d9ad88dd7bf4a2efdc4aee18946a8805a1135a569fb1850d9e1c36562e5e84174a4745c4976f406c5ed51abafb6363df320a7b92c7863a249a960f6daf860185f53a128d4b3920e71a1776523a0e26eba030d394d2ec890e9cc721b9e60a99d5e154641221cec5352e4e85b928a088434ae26125e75eaf1bb39cf39a9876f9207705293c6002ac2411fdbf23541793ac5da8e13ba362940b24faf88767dd6e28cf42d87d4a72a4efc8b523224de302030f8ba254c7e22e7a60489dcceb504bf728ccdf202c56af11ae4ada15e2577074453512452ce438469569086c088d3ca708a5232ae7bab8b05f1f5b11a4bb35aacdb388f7e87de549e1409df375e5264be418bcd7e5674d476d76e01f91ee50111c1c56575a9d2488fa7e0aebffe096936034c5788cfd23d35c06e52e5c955cad41cfacd427eea36631b374442a8ff23e362dc5929f2ab4e02abcdb057d5693246c7a720f30ecd6243bdde889a39f35247795ca76a6a8940d8e57df82f9361373c1ffba288ad7c8413e8446567afe9183ba0175ba432084138be444765ff2306bf32413261c08d0f5ac0a9da348deaea9883bda08b8999ecd59d37cf4e932a58f540dcf389f7251b72ca8b22f8470a823336aca6b18c3eff3b3a770bab40dfcd4be6618e3f54c504629b77df4af562059e0715e17c9505604cbfe1a60dda7921b87573ffed8030e1a8f81741d7dbdd6932dcb2a122a8ec889017eb7173f258994e804a12b7510c5b3e045c09d7576f4cdfaaab401b8204d547fdc6a45819ac52e59565b9007bb90e04c01c88d7e17185539822e4f2f08575f40a8c1d86f4581b222a77332bf285b1e6044b00ac998556350a3625cabb2372551c50adc4064029bcd0d3a3c78e03595c8d6631d955a7fbea54cc974a8819e0b83efcbc190e652f29ab612a47b8a7b7a431b16989d2652a1c204775c8bdbf09d343855273e2171b380b5c93d8d2e7c5c32dcdd55971861570d47091f18542d5956e28237c6c0c7fe3948896815eaf6ab9c9cc7ca0f90b79595f8b871dbeaef3b988be8384efacf7f59f86be2452ee45910c13709fde45fb18ade7631a9c651d0d0731748c2c009106cd090e897199592969b85007bb5e844ff1cbef2faf7624e237bfff131355ac1d9e704cd4194d0f4540ae6710af1cbf97ecb2ab8d35c7173b0674710ee252dd640c029f4b190e083d0a2eb2bb30237042b265c9b068d739ca838317a1fd300418a0812c6ffbfe6522fe59e67d1d05f406a42343c1f61c754e5a5e2a7e411d83d1351c617614fe98b0f79022611d178cb0c0485c11f2aa97d3b34a08ef3d4cb6a7cf73122a74e7ced77e1e0ae3e47fe49ee91ab5c6b50294608267c8f6a0bb3296933a51137b893448214b1ff2518dd02ea61ba9e02bc530a7394aa1ab8ff2172fd2177e62fe55ab0ad4e512e0a9ca2558448e9e8cf780b717ef416ee7841514e9942cd5cf3b4c6a206c2da0ac49cc34c3841d35531ed53540b354bd6a386f7118c87e37f115492c43566a7fc235d0ceb334e80797071c4bfd340151a6b952ea1e39fb969f28541dcbd25a356b856c21e4eff17443a403e71ce0bdefc14973fb3f62f0370c12f3ad8c75f688a3dc30435f3dfa512dbba7e6a9e25cba0bbb903c3e4a47409cb68bfde7b3b3eab2c97102e1dfba297db18667d9329899cb2e7dfc4d6098159b8bc07d23d9f04cc2fb779b44054ef9e8c68e08ecf200200930062a02dcc13d413440a2771b812e50cab0e583540891fe767dd856492fc6bae82698608031162f8fbd43cd71b0970c13b1a57046e7919570c57f84166e01ad6d0cff072ef2e3bab3dbfc649bc035f4cd7213d449cb478c7d62c7ce05ce58726bdca001afd3fc3c40569c3198515836c8f0423781958f578b645840acc16b8743fae41e7cda76e3b873e67f662fa3f3c3a41e76a474e548e803a620c83dac335f0a72052547069c376b7ed13f857499bc140422fe60418579239db89f67bc50017b055339fbaeb9911ca5393ac148736d201cf27ae884fed1eba0dfef35e0e5ba82e1aee1a5f05321c158c7f04de8bd908713a730825ac1ade1328015e0cd26658fd68380dd913ed7eeadf2468d5fa9c9f81304fc3a906f7b91465a01b4614160e403afc27e8fb41fe036303e760d43b7f53ea699026e0130814d008700b74c2e0012b55834b4a0cb84db1929cbfc8d7c30cc0e5a04b481e6871595b5e88dbf651aaf7e1f0c83fbbed7ba9eca88d39c95e266e2e21607641861ab840da44f403ec079f0c69ea0dc10bec040a01eb61508195d28d7ec105fd074c712934692c049a0cf2009fe257519bcf9d5b4dde78c16a53c3008cf6367384418081bfe535c0ec20e8629c36b872190553c338991a303c8718038aa4a113848458fe094b1c02ba3fe2e1aa32c6a14cb8b2491633b58779780857e6d868a66d830bced5d920fcff5f00857c0de0151f220d9781a7e710035c8ea6a6aff417d07495d673d025bc1ca8027ae2a4691b61e8ee73d81649b7bdcd997c86b175555005563e37b82c35f2b9317c7f3c977d5efe2c47d4690c2e7ac0ee5db35c309eb8e99dd4961757c6efab450878d39cefa2c35457e5b4ce792c2d4003a88f71d1607d1870ad568fb3059013870d317a474b7a04bafa2826a272ecb2d57177b2bf1900d098101cb3ebaf132aec702add330205ca7afbd4d03fa80fc170ad748bd20a3afcfd93dec522c34d0057c675a02094cbdb3fdb51900a4e2191e9c888f169000e7869a8c670ad1b3a1905142e1d15cf3262d2af363a88816937cca07976b57695c3d903bdebb44583dd09db040c835a40cda4fa850d153815810d8e8f661e8df9157044d5fa32b81caa06de03e98c17c2ed80d3413d80a54123700d0c1de2ea1ee05ea8221492553fc0f83378f2b053804608ac1436118dc8a005c295dfa4a482f5fb0cf78480c1a0d9004ea040807a002b606548e956020ed9015cfa74f77156f8fd4b72c123a0d0133842ccc689fcf400ab54b7840e08cc5be26a7d435f2cf590073e897add0d64871863bf2760e719b2b1493e2a61067d28c475c1adcf9c605300028036e342fe57474a3d1075bc2a672cfb3e5fddda4626c02026fb74e92dae3ec5c78ac4058a032d24f496e94a41aefbdb1fb7fbde09651811b221869f622713650a1837ae63ce164f74e3e68bf2d689827a6f3517d9e29046a2ea364bff5e235b7832af00c3ffaf86f783021b7ebcca41d4fca5ce1a2ef421e0a9ecdd7a0d51cd418570e0405a7db309e3990fe8e5155c26373865832be19f0002e13f14c649a22d001c7813d88fe12a11412d1b3b078b02c781aaa07df12dff8a774ef4f0dae38a1c8061a8b2b7e83520481beebb32eb536da4d75385697039d083e3cbf8b9ce08b87054fdd2f8363d7d8e9942b58f68ca340f81bf008cd1c26cb09af46dfb4e56dbcb8533fce49ea5ac8f6c313d3e60664086380194d060ecbd2404546dbb4d9afa46ea3928c336c07c047538c3e5b737d0ef089703fd42074281a9bfeb64de5370e564b7fb37437ffed50ea9771abae76a32260ca84b1e481834246666f21d0e2b5c8a3a71b4860d564def9cca3f47ce844b0930ae773ab354189b306c02dece43ae0b63eea9081b8a324835e71a9b916f2cbb799f1e384f5b0f4a6dc4999585ebb041250c453d8440f4a50bfdf5a73d771eaca869d6ff45d356b3c15eb896a597a73a05382c06fcbbf4e9a5a4195d77c7583f86573c60598d342592d820f7ca7959d24f685c94fe652a133432591b242b7f9c97dd0218df1b24ac5a514af62af8ff1d300121921554366596c2409ccd2cb0595a6c1e80a373a7b0c57b3b47efef57afb45d99a70db2f060c02b5f29060da88d9b328787e74edffc8731f73bf74b14b4366a901637de2d30ae2f9180250309fb534371a4be955acff8b901665c5d127b931a1b5c74428fb2dc50099abe2081de8c6b0319fc9d526a6c97aeec5a162367958a5f3525132df5430a732e06a8b1347ab53487edb9e4912b9f63617ab1de5f7312006e73537cf6e88425e94a1bd4fa2c6617aeca6f4116db88d69ba1c8823dc1555dea62b62b34ad6b674b14e90fb24c5ea6616fca482ae440dcce5d8f33434268d29c7f184219e735d9657fc4f7a832f79180984be20964574b5366a4209446967e01c90b2d523f9208613294d078d8fd5f5133b6f5e7d2f0ab7cac61720a51978636bc49a780c50181234d05a101884ad0fe51971983cb2274a8f7283b4c98ea8b193a4f0efd8b1a23313a78bd6324bb507a696c9c5b12a0e182594c2cc96157f7f77d6c15e4b7ba87cde754c2200a9847bb8e77431ae97eb68d1aca4f2f85eb2926239ee8d5f623b192a57bbc954e776d47f05a3df1d154879242f7b08d809eb203c6b223118e053af00fa3f4ce197044a8557a8037342b78e88e4c54c558b784d22d5b23bfb21a8ea82f0b3a66d406f76b517aa6b12e7ea0a35233987d494d54cc246876594c6d8b9ae896f57664b6a81e29f2d692b219fb7df4e8ed5cf1f69a385860779e3c530458d57c1dba2b70e0b77f072ae31ea704c5697e2aa5405b75ce51971b95c2a7ad3b773ef22baf65546ae9e06e34c574d2b4a7510056ffeec2c12625e2525338e13b3f901ac24429042182212e030efab18687c398b922b64724e24c1d49182ac7e340814462b2d9956636fb63e704f90fd06295a61af2ad917b30e3a3ba4db4fbe2a2cb30295107881b99268e48113d1addd90ea5c64e0f0c65944fb58e2c032268d89cca178b2cc00903520c75e81fd25f7cf9d2039c6ae330dd01528b62ba976b1b997564c424f94c92c313c88d86e6e683d96d59a7778466394c1b12c40c68d924f8c8140cb7aab15070371004fe9edad9111925b1dff765a8bd57fccea8e45a9599709f5b7dedbf179d7ed9136da0d5606473623ce9b509a7ec588cfc1cb48d5804606c080d39a2e29008c4203efa4874bd4378dd28a0e15d902667b56c89bf8c68204a883a8d00ba8fa80ba27acd2ff143ed84c5ee1712b25c2617afa0bcfe55df32e254aa439ffd9be1cf0210ead62fc4d7a6397142ce01e49badfabe9d7e37aa66c6df7893570d42a781ba69e5f7876b3de0e0dae35144a7eed2db970c798276c67ad5ef81b21f3814a2250fcc602f7429e74a8a533c59a41c91a662e46b3152ec4bf0c516a9c84536f214c7a57847bd3338a2c38cfc6491c89ed3823912960cb37b760d134159576e4b99a247ac059b0364c21b68f01d3028a00258e17ad254323b4ddeedf9ba28705bae04766f24f3b97b2e0fe41004cb635d221fa7d8a0d8e5dd241b32d88a033043eca759832481c9725233298b5d057278cf30fccf929ed800259c00d729c0956ad41f664c44cbe31345582591f66ee79a5d2dcbe32cc351868421b7c4e758b54af4152ff6ac07f0e98baa8f2ed7f5b514a647a1092490dde3f299b5d4e4a990e3d2438ab1dd51370183a4365bfb709c7ad5b42517f3f0e8e937a0a4b42ca06c91793582033cdcec727e08c3eab5be9b2eefb687329168d131ea19710391948d98aaacb28853d3ee9c09d3b9f35932c3716f0129680e5af428c5fa55d0765ec55aaeaf0ccd675a3640a17febfc731e0b5e7e4b32907fc159f5d7dc0cb23dd3150f2852226a5e631a6c1c684a706d2e19600eefe717f25dc6a0c3a1e3d01f44f71183669c4f68873e1fa46b9951a1a551c03c6ce7d893e6a2c6bdf24abc548683ba68cb6507f7675ed9b94ae8b92e46c11295994e2d9eb9270af8fda2fbc696d18aa255c9ad351388e2be524d1d9c6128ec7ce3ad32cf5d152168c90dd251cb7e71e25ee4514304b7a16c741064d4e911f45ca66353bb6e7a9b4159a6d2fc9b4657756acedaed3d7cf8a0bc9102fd99e6702404063886a43980d9f2b4327d259cca0f1cf190aa6560726476f342acd27cb51fe5eb1ce049eb76a3ab96e4192c429b8476440c0f140c774cf7cd1136c0a99cb1b37c970c6bacd2cfb3acf5950ac3cfbeea080069eab4e045d07f41a49e0403548c5048daabff589c2391c9567ac4252d5afe964707a2438dff23251ef46fc9b87ecd36394a43c604f407d9c369389e835a09194078603836c3a0e077e1f0cfe299528710cf0af624af069f581c54b508d4c8aade5d6a28c6ecc4461f46b09b17eac5fe4f6e87cbb454280e3a370581b9bc7f8ec04ed7c3355e2460ebcdf1520130b660f33701e6d445ab4dd544fb1f09a890a4c90cd02ca990f412e6314b56c725ef5ba3bb520c36b041b397a8ed6f7e7dcdabf49a465a58a7b2a2863122610d3788a2d667e435cb0b968bb75e5b3984e5ee2763942538b439c23472f011c857dc707bf010e08d7cc4c41978482c95c33a957cf2354332431bc4944df110561ce5986d581dec90b24a0cf42dc56828491836ac0ae3a7ee0ff56c084219287df999ea96ce67029b6baf62ab1939d00fd4405686f90958bff3bf5a71df7de038d42733bb49a5eb2a48473a0d16fe1fd7057da000eafb64293260b62c07ffffffffffffff7f3fa51fb2b79140a278a2a494523245a9a939bfc3654a49a694d2c12d3d67a463ddd29cfa21017402690281026d7573af3922293fb5fb29f2745cc7de439243a7a97475587b8aa921095f32bbb5f05f6d21e97dcb57ada896f5fbfb610949aaa9a6af50b3ef20a97d61a7b7fedd1524293262cb5dbfb3811c40123b75e49e3d2a6e77ce1fc91dfdb7bd6243c6f9fc48cfbcd89e29978e9dd9ccf691543eb75aea7e1ee683bc2c1fe99dab866c57f59d3b5533768f96cd9b3ffe3787c5ea919e43c656b7a5989b477aebbd7376ae159dc5bc8be75fb078a4cc5c471fe6d2861c7231ec1d49357de7eda1a3f828fa2e9ecdb5579b3c61ed488fa1624f9b4389da3b6aeb486725b63afd4df9d95c74a4c7ca6d764375efe63947ebfbe75ea3c6c49537ac1ce99dbe964b257bdeda6a98c1c6911833536ab585bf0b476be7debaedcaf5467aa7ed5bf8dab675232d66897a9fc34e3d4c39b16d2475f99ad79f7364bca8cc1a1172d938afac992fc796ff2c985d23699fdda30a99aa91dc6ad7cdac9edaf5f6692477dcc71bf3f78fed381aa91d297cab15730b3dd6f68cf41e4a85cdfab95febda0cc478dab9771989dfc273c848fd966aab4db3db35d43e862ba2bf9e7c5d84faca8dd0c3c59abc6224f6d8f4ade4739ae266982bc48691d8fbea8291d66137e57c66566d5f24a48da1b51e1dd78b848e21545dd7ad8bb4de2573ce3c1917e9e5c1f5dc3b63ceaa6b8bf48d8b59d35eaa85fae9f38cf0b34f37d7506b47b9f3f983cd82e942c773b14808d75a6ff79e355ad47bc5af1569b1c59ab1913a236f4acf569158d17bf1a2a5ca1967cdcc4b0b960ac6f8fc52a699bf64f00283cc97992e3733344c81bb07557191f3fafa54746fb92d62ce5f5829d26b96c77757e25a37b3186c14295b5f7333a5ae5a5b4a7991b999f102c606d306d9a04b5f2d5b65221272c57b1e7aac290f915c277a7775fba956a5b210c913157b66ee74958348e799c3b6e8301ec6b80c44c62cfe216deedb863c15aad3bebb78dacc1ce5807d48cbf0553a660ef5f63a5516b887e45e4b6f6745ec658a9887b4fc7bb8a166737538577987a4dd9a3a6e2bd6f97d56590784f6f0ea7be261e71c92539efcefa0a5ca38a486eebd5c6ebe4e99a95bd696c4dfc9729ff2f673bad7d69294fa3ad67616d192945f7f3baff68fe6e39d25bdf59a43ecdbad2c29175beabfdde3b26abdb11c6e3fed0d4b6287bb9753afadff5df44a4ac650735407f1ef59b4aea4cfcceda6d4e15652af760e1172a7f02c645692f676fbed5fdcced8ae92de233c9947335795c45c33b48baf9b8b4d25f93aa610626cdfee128b4a42ebac3bddb231e4faec298953f520fade8654d9f0663ead29e9ad7daede1f3d8514bdab01a31e6c29e9214fc656fbb4252539b5ce7ba8cfa21de5a0edfeed3ef8a8aa487945d10d25b93e27d7bd83eca741492c9bfdea3b8d3e49a8d235e7795d9e24f51873f4b8dcea2431a4ce419a98271f9fd3633949ad54327ac7595bffb037498cbd479b9fcbf5f3d2d524bdf7468a316ede87b965929e1bbd3fad4a7df7314c12f343b67c3957d8fe7a493ac82dc2d330dd5a92d0fe3bfb0c296afc0fb792a4cfedf9e61e5a8f9867104b49628baa946aeaaba1f43349c2954dd15a889ccad34fc14a92d86bca177eaa6e2489ed5acce834b7633d084962a88f5d262ab67059ea3e92dcc367d89e1f62ac1cea2feb48ca862adf7a54ddd4ff37927a39cfc63811eb0cca48eab6ec385f7f527791d40b1d5dabab484a657fea612be44c1b3d129b485acf54737e54ef9c75da5a8524e68ba16afdbc29769918302149652fe5d6c4ff601085ac9df32c487268356763cb7539908489faac528e310664ff583fd23b7fa265ac38b3fbf4c147eab687b11e749ae1ead6e0be9c0e6adc4d070e861a07c26a8dbb99d9a3196922dcc3cdce2ad57bcb31f4e956b6a2c07a24c4d071ca8feb570b17cb233d65ff1c991f5eb5bef0480edd1d913b1ddee176a4a38833791f393bcb5d474287e821b6a8cb747c1e948f1e3df2b32227236ab7b8bf280243c3e648cfd5357dc6eeed2ec44a0013a6b90d96e5c8982f1c47e247c63dea919fb7620c47c6bc91316e681b1ac3460bbc466aaed3add7563b6a2467d6efbd7bb6103f434e23639686d1e83d23a9976fb5a5075fae4cf66c46c69491312e301909b1e43d4f99ebfb8792c7484d21b6b89527bad54b35c26238f3223ad8ac9db8c7d1596875f772cf57ad30527b88eca16ae6188ce4faed71dfbf97af2dee49f88bd4cc8f56bb667fe5cfb1170973ed6aafddda45ec1f4021dc45e264dea99cbf513bb7b848ff8d9d43bbda798bb434177b479ece2adf6991d6b9c37e903aecdef17633c3dadc171a44f081b3480fa9d50f9d6bbe0d9d61915e5b6e8f182bc577dcf2156965b6d778bd6d56246f6ba9852e75ab488da965bede4a5f8ba9464542c8fcd858632ad551dd8fa748ead67a36ff86dc6b3329d22f624ef725274791f03bffdde25a8c0f1f8aa4d6da7dc850732af809d698a9f7aaec445a7cd8719deab063478f2a3791d82b96a711af5d4da6ae473013a999428ffccddf9babb344d2c590fd34b5eb9bab2a2b918e42ecdaf27348f5074e22f5f27577dc51b58e8f541989f4dc39ab3da5bdd88947f1118917eaa3dcef428ef0f784b082cc086c44c6a4c04524ec7fd70f5b0b129c9c20730255d4079eabf50f5145f0800d5e6ebac030f365028cf18e4908d88d4d981a0200b0614dd05c025c6e6cc2903000000e061506862fcacb0101d8d43816a6c95003068113b800c0850b1706de0105904d020044c2c5c666c50c1080ba512d860000d48d6a32d4c418c045800610a871313075632323a300170016302025a25d78fe1c55d43d5dfc35c368d2b81a9919669a4c73286fbecc8c7078d8999b9a2e365e646410383baccc014787024e0e3b7333c34c0b232383c0c1010c0dbbc500b665676e9a0c3461646410e05ad2e1ddb5eadc1752e94d4b42fd6ddd71a56f5bae6649eba8cb4507a9546c9e348ec69b6549ebcb164bf4368061348ea6691ccdda7c99b9f1321366c9d4382f33606864608763b97136348ea6008665676ebc74b151323208f02b09f51e75e83974256573a45e33a71ab779732ba9a1852c593fd458497ad6f9d5e36bb95f25a9b5acd669cb5d913542aaa466c7a9d695ba5f280fa7925c43e8bc750c17a392beed42aabdbfa7176f17cf2f3334eb7c4ada97ec2c6ea2ebe2b9c2d8e4056c8a3aaeee653eb794aec0a5a45c3ccfb9d78fcb96e6064c4a7a3e08196a47eee2e915f028e9308610b766cea66d346051d2e347baf9123f94d7b0a163e5d8435e8741e14fd4938ce9449b13dde435d14cb23191676f33ea57f978b29b63675d0cca04c624355e6cfdee7d9edbf625c9799762dbd052b624b9fffae7581e57496299fb0d559b1d765e5355604ad25be6eb47bda3ca83cc93fc663e3f62e4e72077e4db08d5236a7e164b921443abeab1556a21d58b23498e976ae7b5902b3ad5500e0cc9739ddd16b32f55defc11a4cd99d9fbac9d8daca9d4ba86f068738f123992f617bd7ccf75abcb6c8d24f7de7bb7db773db99091b4dcb2cf6a87d022893547847d14ab228931a58ab1c5ae1349aff998a8df91db316444125a8e9b3acb8e968fa20c1f925af66e72e25bad1de46c487ac48326a8a2830e53c61863883146449a490772091404e4308e03499873926a371240303108c4380c632008a2288a184208218410a308218410326264d40db36806b60fb9d042b1cf85de9d9bdb3d958d62f61ac029fd71a1756e58e49e76de1d385c2b243d8fc511abc6d0e6bb45ce053d7f03ebcad03157c9e33dc3ae8655b2a788764dc2b86023eeda3ccceb90a804aa551a5ee590644848d025f962f1e8c0f5fb0858109d3d97c1b4f0de8b2ef86df02d4d6fb8ea253ed19df785b08b4eed065b32480759c96f71159ab84d06a2bcce77c21f24da1604f9450c81861aef61b8c05e883879cc43360ea86d55236f6e8bc70107d38b28c54b58655bbc2c94fec83a058250c9cd83238bec06260a61244dbe6bf54a7fa24e897f6f9c0a6ef3d71565c936f98a294e0bd8c75d5c81057008c5d25bc0db6e25c053360a61b61fc65c4fea7b6956bf1b729bf3d898a8950e58dbf7218b1d6f91b5601bc39aa6588d696b802cb50507e6078fafa8d0e7481dea2f1407dfd6acac665c129a33f39978974426361597b99c23607a72f714284d18c3960faf571ba6df6c1d3bb26565e7c1676a738ca0dd55bb5595c01098fb35f94de05695195935887ff7219b13102fa8b2ea11987e0de9790f21b68f51ed1429a865c29760feb97ef24a8ec2e284d75f205492a5e6826aeccc85766dd90ae328ca48f437a982b82527041fa3715eb8ab5cd367e2c22892a25db476fb5fc7050d93128183b8b5a8f834c925da776b33e9a6ba9049f07cffbefe1dfc678c951619d082e66f1e2f33bccd81a9515c5b293ecaf2de43a0b193e594e4d73b819f3f3dbb1eb89d0a88948d924fb0cf993ee94f284db453659fe6aa2a02a12dd032faafca08e52fbe31e8a9164b671b0da6e5fb7ae39b50d089ff296b6d40037dfc39231f8daf1ef17757e460a5386ede16b828a276ecb5d5cc15995360d29b769ae463b6630a988b29e09c4d143896dba059ca697a2e6647f65db433f03cf100f310f336084d0fbb68f121f5f979a836e7a64ac9490fbd5d1694a82b7c5dac1a1f2e574080340b571b943a671c7fb208612f9460921aef1c0892749cf547af2c086924c00fd114929a1c7fb2b2b229e3f2bfad945cd24827108224f78019ca1693009e947d7bc95c0d540494b854d9893aed2812e5db9bf316236921737a606fdce3c231d3d2f51c5563d565b61fb9bc8dd68bfc669a6b81e783abd5c41b146a9ff7df6a949484d664a9d8172a667556201aedaa7c2362ec85654edf3f026b2b69a46e3003630507b196c5364958ad7bd8541b470ff89dc6f0c0f1dbc06c9205f5c11fc49188e09bd788aaa8309b94064be202ca235aa519bcf3560eddbbdeb2117194fbf3e41453ac930c6cd7d0a8de503a30f49b0485245a84c950740661a5d13650765c81a3f01325bff8ada9ae155bfeae1040a86b0983f1fdea5a7a1febc819073d22f039cc59667aa540f9094fc406ea8f595d8f8e515566554cf8b84c539e061b155a1869a3dede11863d27321fbafe54892ec05a973d929635882fccbe0ea452168ad37c83bb841742a048beba325f994009083fdee8925466a990dd9dc3da54027253273104d8f3c3205e2e97253f14f4029d6843eaf8fda626cdc41880fc0fa9023ef000a18121dca938f0aab9263988207da514b0edd2e48eaf7a5cb4b06376e44ba544bb25cafcc8e78f6e25d07625ad8ebfb19595b0f5ca4393215bcb5bfc5378d04624a1eb134e810463c741af4e4285596668629bea0d0dc34c44d2168e14fca44f439ffb5186b9d240ddde5f07a0fd0e8b717a9d6011f44fc43a80a20ac1e94a21a9aa638449e3bb68629029ef7c8bf90d26831371018b1ed224b8f3e23d0ad7c4a3dbe48d8e4b439c4c4e25277b0dea7aba17965dafbeda1b6d40ed31395ad0f25e36bda3f13b009e05557cc411e39374332c73be6859eb781eadbfd1fcbece553fbf58729eda8946864a0a164f32bd5efc5d823906b7f0c1f404df50639be89b451b81bd0e0816c8adf695f92bddaf6cba4128980b8c112f600ca1676290810f1381908aa147b0e6daf51b7260aae120715c595e22bc6bdb64d85d5a034bee11e0262338d8a8918cbcdd54de1b7f3dac8e07da040235b5dc72db6c5075ebab0e99de4a8329b5d17c66b4f3d1455a027a84e91e936e64842a5284e45e112eb92cd9892314b31e90d503e09d45a64431c509eae2b11332698d1a5e2114b7ce2b26ec0a47cb65893a39610c63889b94928bd880a39bf6e811f1fe2b07658b1f70279474ee4f7de8016db1575e06951b2710b8147f6551b6709a78c68ff5f499c011e098209d3d8b3a18673e04968bfb25a6b87c631e54e22267c961cd7b15fffaab5d385a9d2692222f320d1d56b8a39724f34fc7ea6032a79117e1e0062b585f3526906411bb4a42e124aa2a01c38e949c26363985e87cf0e24375bb5e7ab474050847d3311549420461d5e0713795b5b0851fdc2dfab322cd23bb51a0a8e2132533009aad5e6b2b99b40c5d0511eac836a8054fa291249f132ccc3a92fd5f1d02c70723cd6a1ef60d0099d0de09cfb1f17eda6cd1686343051cb619f8aed9d071a19538cd7885c81b24b78a12ad52562b509c1e3f7592e0408f8d58441e3d0a0cf0c5c65bcd8e348b10c0feb6119e9ae0acdc9985755cc92793ded212caa01749082d7eff5f2152f2c341015cfe5588380ba58319fcdd2348f6f0e11b69f6c080e4184b35d6c11924ab0e068e7acd2c15ef23f7ced68e1ceaecff99243aa0b4093c03fc63544116d4e512bd60f09bffc23edb9919da6a2f1d76f48ee0df7c79178a9a495f78b1820724bd19f930232042266a490d8acda1fcf6a56d87749a3564221d8b588eea63601606cd8b87c7f03ba979b69499f98e44a7d9bbace3ef5e240ad01213937960043facd69616509e05e379de488855f77291aa1523c5c65766a5840a38ac256de93ad64e423e951dd23f3b90122d2a6a1b7f2eb2c2eb2d0177dc22e01adec7cfd669218efc69055bfc7f161b5c0a38e0fbe72ff89a668306e0c86a01427cb1ed1be9a4ce7914f5c0b4415c467404f7e013a78d7a503f966aae841513c23d3a0e2e3f814055b1d6f517c6881845602e7d19fbf8d13ac59058170f5e73bd10bd0601afcca7b2b100b8411a9aad2c45ce841a433ba943572e26a24ffc314c7f71034642b8092a8a9fc54b8ef62b415e6583e77484293a7ab5042ccf7bca1e7e3343aa39bb37dc817853bd5d31ad10c08410e287b827249d5c678e89033997cf0049284877d750075e3fae2295435d26d3581a19ea43d1d4931cb597e06d49ad1afe2a22248580b4a6785240680900ab5efe80ba30b7c6918097682ae9296a3447e40f4071f0129c63558c1ed198cfcf0fc186ef62de2e0bddb25c8f136cfe3765c37e75d140f56a89ab69e784f762310da4bfe7081537378116e40887288c8852e9a8bbad37848e2b48a4139a019339d835a22c583008", - "0x3a65787472696e7369635f696e646578": "0x00000000", - "0x3a6772616e6470615f617574686f726974696573": "0x0110094f736c315addde86b7cd5adac7984cd10b1dc187364e92f7ac901a5447609f0100000000000000cee75bb8d02be946f52be595adfd9e4a8ce0343a9894c5e2471429193926765301000000000000005e7084c57d9f04eaa7c22a86d33757cdef9bbcb6607dab1a7c2262dd1293d7ce01000000000000009ee080484f0429022dda72f19bc76cd0b142689d2782c0a68682bba5c5fb156e0100000000000000", - "0x3d9cad2baf702e20b136f4c8900cd8024e7b9012096b41c4eb3aaf947f6ea429": "0x0100", - "0x3db7a24cfdc9de785974746c14a99df94e7b9012096b41c4eb3aaf947f6ea429": "0x0400", - "0x3f1467a096bcd71a5b6a0c8155e208104e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0x3fba98689ebed1138735e0e7a5a790ab4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0x3fba98689ebed1138735e0e7a5a790abee99a84ccbfb4b82e714617e5e06f6f7": "0xd0070000", - "0x42b50b77ef717947e7043bb52127d6654e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0x4da2c41eaffa8e1a791c5d65beeefd1f028685274e698e781f7f2766cba0cc8300000000": "0x1003000000010000000000000002000000abc3f086f5ac20eaab792c75933b2e196307835a61a955be82aa63bc0ff9617a0600000010ce68cabd54aaa5c1e9870f89645ee0f2b3cfafc58089b15387b1e87f59ec3d7e701aa8e4ebae70f627b5cca9726c5ac67133b9295eacdfd5f22a3e44297c4e3b866bd4b14f3f67a056b09c6834375bdc6d0b2d7ae387f8568f67afd1db9b8a1bacf21938aa46cda6a2eca3134629bfb201bf45cc62514672daeb4c55f6b2f332000000000000000000000000000000000000000100000000000000", - "0x4da2c41eaffa8e1a791c5d65beeefd1f4e5747352ae927817a9171156fb3da7f00000000": "0x00", - "0x4da2c41eaffa8e1a791c5d65beeefd1f4e7b9012096b41c4eb3aaf947f6ea429": "0x0100", - "0x4da2c41eaffa8e1a791c5d65beeefd1f5762b52ec4f696c1235b20491a567f8500000000": "0x00", - "0x4da2c41eaffa8e1a791c5d65beeefd1fff4a51b74593c3708682038efe5323b5": "0x00000000", - "0x50e709b04947c0cd2f04727ef76e88f64e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0x5c0d1176a568c1f92944340dbfed9e9c4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0x5c0d1176a568c1f92944340dbfed9e9c530ebca703c85910e7164cb7d1c9e47b": "0x9ed7705e3c7da027ba0583a22a3212042f7e715d3c168ba14f1424e2bc111d00", - "0x5f27b51b5ec208ee9cb25b55d8728243308ce9615de0775a82f8a94dc3d285a1": "0x01", - "0x5f27b51b5ec208ee9cb25b55d87282434e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0x5f3e4907f716ac89b6347d15ececedca0b6a45321efae92aea15e0740ec7afe7": "0x00000000", - "0x5f3e4907f716ac89b6347d15ececedca138e71612491192d68deab7e6f563fe1": "0x0a000000", - "0x5f3e4907f716ac89b6347d15ececedca28dccb559b95c40168a1b2696581b5a7": "0x00000000000000000000000000000000", - "0x5f3e4907f716ac89b6347d15ececedca3ed14b45ed20d054f05e37e2542cfe705e5f82ad672e896be4c1d81c7e8df384e1ae9667a668825c56e95b0f7d3b1ba2f7539d4c470abb1b": "0x388f7ac281acf72b7782ada96bf0c0d3c09f9276c6f4b7c6271c375fa3a28716", - "0x5f3e4907f716ac89b6347d15ececedca3ed14b45ed20d054f05e37e2542cfe70ad47afdd1ab6146118caf23bb6b8cda7f3f90c1dc2b2f6c2b7143b13c956ef0a12dfe486e83cb758": "0xb2858dfa47e91328dc2f41334228a288d19a853ce0e981cd0115c406f001225f", - "0x5f3e4907f716ac89b6347d15ececedca3ed14b45ed20d054f05e37e2542cfe70ca9d64ddf2c4bc4afa6a6474ec1a9234888f7b20d3978a706d386d0f16344765faa48cb376db331c": "0x18484954a9f3547cf962d6dec822c6353042b56776ec58316a5558d75e304f31", - "0x5f3e4907f716ac89b6347d15ececedca3ed14b45ed20d054f05e37e2542cfe70dd959ae783e3505c005203395fd92c2b411136466e88ef74dd6327c0a6f32b3be7e38d56a2e1324f": "0xf628104fc1f6314effd92cd12cfdfb5ee5c913605174e76ec501797254c61d19", - "0x5f3e4907f716ac89b6347d15ececedca422adb579f1dbf4f3886c5cfa3bb8cc410675ed593218347060fc977d4c87a2318484954a9f3547cf962d6dec822c6353042b56776ec58316a5558d75e304f31": "0xfa6a6474ec1a9234888f7b20d3978a706d386d0f16344765faa48cb376db331c0b00407a10f35a0b00407a10f35a0000", - "0x5f3e4907f716ac89b6347d15ececedca422adb579f1dbf4f3886c5cfa3bb8cc43c9981354ec1409d0ef80e92fad06bf6388f7ac281acf72b7782ada96bf0c0d3c09f9276c6f4b7c6271c375fa3a28716": "0xe4c1d81c7e8df384e1ae9667a668825c56e95b0f7d3b1ba2f7539d4c470abb1b0b00407a10f35a0b00407a10f35a0000", - "0x5f3e4907f716ac89b6347d15ececedca422adb579f1dbf4f3886c5cfa3bb8cc488021e4d172831d344e0aa9a1b9bc22ab2858dfa47e91328dc2f41334228a288d19a853ce0e981cd0115c406f001225f": "0x18caf23bb6b8cda7f3f90c1dc2b2f6c2b7143b13c956ef0a12dfe486e83cb7580b00407a10f35a0b00407a10f35a0000", - "0x5f3e4907f716ac89b6347d15ececedca422adb579f1dbf4f3886c5cfa3bb8cc4b5b6969754a268a0612ebdf3fad88e97f628104fc1f6314effd92cd12cfdfb5ee5c913605174e76ec501797254c61d19": "0x005203395fd92c2b411136466e88ef74dd6327c0a6f32b3be7e38d56a2e1324f0b00407a10f35a0b00407a10f35a0000", - "0x5f3e4907f716ac89b6347d15ececedca42982b9d6c7acc99faa9094c912372c2b4def25cfda6ef3a000000005e5f82ad672e896be4c1d81c7e8df384e1ae9667a668825c56e95b0f7d3b1ba2f7539d4c470abb1b": "0x0b00407a10f35a0b00407a10f35a00", - "0x5f3e4907f716ac89b6347d15ececedca42982b9d6c7acc99faa9094c912372c2b4def25cfda6ef3a00000000ad47afdd1ab6146118caf23bb6b8cda7f3f90c1dc2b2f6c2b7143b13c956ef0a12dfe486e83cb758": "0x0b00407a10f35a0b00407a10f35a00", - "0x5f3e4907f716ac89b6347d15ececedca42982b9d6c7acc99faa9094c912372c2b4def25cfda6ef3a00000000ca9d64ddf2c4bc4afa6a6474ec1a9234888f7b20d3978a706d386d0f16344765faa48cb376db331c": "0x0b00407a10f35a0b00407a10f35a00", - "0x5f3e4907f716ac89b6347d15ececedca42982b9d6c7acc99faa9094c912372c2b4def25cfda6ef3a00000000dd959ae783e3505c005203395fd92c2b411136466e88ef74dd6327c0a6f32b3be7e38d56a2e1324f": "0x0b00407a10f35a0b00407a10f35a00", - "0x5f3e4907f716ac89b6347d15ececedca487df464e44a534ba6b0cbb32407b587": "0x0000000000", - "0x5f3e4907f716ac89b6347d15ececedca4e7b9012096b41c4eb3aaf947f6ea429": "0x0d00", - "0x5f3e4907f716ac89b6347d15ececedca5579297f4dfb9609e7e4c2ebab9ce40a": "0x10fa6a6474ec1a9234888f7b20d3978a706d386d0f16344765faa48cb376db331c005203395fd92c2b411136466e88ef74dd6327c0a6f32b3be7e38d56a2e1324fe4c1d81c7e8df384e1ae9667a668825c56e95b0f7d3b1ba2f7539d4c470abb1b18caf23bb6b8cda7f3f90c1dc2b2f6c2b7143b13c956ef0a12dfe486e83cb758", - "0x5f3e4907f716ac89b6347d15ececedca666fdcbb473985b3ac933d13f4acff8d": "0x00000000000000000000000000000000", - "0x5f3e4907f716ac89b6347d15ececedca682db92dde20a10d96d00ff0e9e221c0b4def25cfda6ef3a000000005e5f82ad672e896be4c1d81c7e8df384e1ae9667a668825c56e95b0f7d3b1ba2f7539d4c470abb1b": "0x0000", - "0x5f3e4907f716ac89b6347d15ececedca682db92dde20a10d96d00ff0e9e221c0b4def25cfda6ef3a00000000ad47afdd1ab6146118caf23bb6b8cda7f3f90c1dc2b2f6c2b7143b13c956ef0a12dfe486e83cb758": "0x0000", - "0x5f3e4907f716ac89b6347d15ececedca682db92dde20a10d96d00ff0e9e221c0b4def25cfda6ef3a00000000ca9d64ddf2c4bc4afa6a6474ec1a9234888f7b20d3978a706d386d0f16344765faa48cb376db331c": "0x0000", - "0x5f3e4907f716ac89b6347d15ececedca682db92dde20a10d96d00ff0e9e221c0b4def25cfda6ef3a00000000dd959ae783e3505c005203395fd92c2b411136466e88ef74dd6327c0a6f32b3be7e38d56a2e1324f": "0x0000", - "0x5f3e4907f716ac89b6347d15ececedca6ddc7809c6da9bb6093ee22e0fda4ba8": "0x04000000", - "0x5f3e4907f716ac89b6347d15ececedca88dcde934c658227ee1dfafcd6e169035e5f82ad672e896be4c1d81c7e8df384e1ae9667a668825c56e95b0f7d3b1ba2f7539d4c470abb1b": "0x0000", - "0x5f3e4907f716ac89b6347d15ececedca88dcde934c658227ee1dfafcd6e16903ad47afdd1ab6146118caf23bb6b8cda7f3f90c1dc2b2f6c2b7143b13c956ef0a12dfe486e83cb758": "0x0000", - "0x5f3e4907f716ac89b6347d15ececedca88dcde934c658227ee1dfafcd6e16903ca9d64ddf2c4bc4afa6a6474ec1a9234888f7b20d3978a706d386d0f16344765faa48cb376db331c": "0x0000", - "0x5f3e4907f716ac89b6347d15ececedca88dcde934c658227ee1dfafcd6e16903dd959ae783e3505c005203395fd92c2b411136466e88ef74dd6327c0a6f32b3be7e38d56a2e1324f": "0x0000", - "0x5f3e4907f716ac89b6347d15ececedca8bde0a0ea8864605e3b68ed9cb2da01bb4def25cfda6ef3a000000005e5f82ad672e896be4c1d81c7e8df384e1ae9667a668825c56e95b0f7d3b1ba2f7539d4c470abb1b": "0x0b00407a10f35a0b00407a10f35a00", - "0x5f3e4907f716ac89b6347d15ececedca8bde0a0ea8864605e3b68ed9cb2da01bb4def25cfda6ef3a00000000ad47afdd1ab6146118caf23bb6b8cda7f3f90c1dc2b2f6c2b7143b13c956ef0a12dfe486e83cb758": "0x0b00407a10f35a0b00407a10f35a00", - "0x5f3e4907f716ac89b6347d15ececedca8bde0a0ea8864605e3b68ed9cb2da01bb4def25cfda6ef3a00000000ca9d64ddf2c4bc4afa6a6474ec1a9234888f7b20d3978a706d386d0f16344765faa48cb376db331c": "0x0b00407a10f35a0b00407a10f35a00", - "0x5f3e4907f716ac89b6347d15ececedca8bde0a0ea8864605e3b68ed9cb2da01bb4def25cfda6ef3a00000000dd959ae783e3505c005203395fd92c2b411136466e88ef74dd6327c0a6f32b3be7e38d56a2e1324f": "0x0b00407a10f35a0b00407a10f35a00", - "0x5f3e4907f716ac89b6347d15ececedca9220e172bed316605f73f1ff7b4ade985e5f82ad672e896be4c1d81c7e8df384e1ae9667a668825c56e95b0f7d3b1ba2f7539d4c470abb1b": "0x00", - "0x5f3e4907f716ac89b6347d15ececedca9220e172bed316605f73f1ff7b4ade98ad47afdd1ab6146118caf23bb6b8cda7f3f90c1dc2b2f6c2b7143b13c956ef0a12dfe486e83cb758": "0x00", - "0x5f3e4907f716ac89b6347d15ececedca9220e172bed316605f73f1ff7b4ade98ca9d64ddf2c4bc4afa6a6474ec1a9234888f7b20d3978a706d386d0f16344765faa48cb376db331c": "0x00", - "0x5f3e4907f716ac89b6347d15ececedca9220e172bed316605f73f1ff7b4ade98dd959ae783e3505c005203395fd92c2b411136466e88ef74dd6327c0a6f32b3be7e38d56a2e1324f": "0x00", - "0x5f3e4907f716ac89b6347d15ececedcaa141c4fe67c2d11f4a10c6aca7a79a04b4def25cfda6ef3a00000000": "0x0000e941cc6b01000000000000000000", - "0x5f3e4907f716ac89b6347d15ececedcaad811cd65a470ddc5f1d628ff0550982b4def25cfda6ef3a00000000": "0x00000000", - "0x5f3e4907f716ac89b6347d15ececedcab49a2738eeb30896aacb8b3fb46471bd": "0x04000000", - "0x5f3e4907f716ac89b6347d15ececedcac0d39ff577af2cc6b67ac3641fa9c4e7": "0x01000000", - "0x5f3e4907f716ac89b6347d15ececedcac29a0310e1bb45d20cace77ccb62c97d": "0x00e1f505", - "0x5f3e4907f716ac89b6347d15ececedcaea07de2b8f010516dca3f7ef52f7ac5a": "0x040000000000000000", - "0x5f3e4907f716ac89b6347d15ececedcaed441ceb81326c56263efbb60c95c2e4": "0x00000000000000000000000000000000", - "0x5f3e4907f716ac89b6347d15ececedcaf7dad0317324aecae8744b87fc95f2f3": "0x02", - "0x5f3e4907f716ac89b6347d15ececedcafab86d26e629e39b4903db94786fac74": "0xffffffffffffffff0000000000000000", - "0x5f9cc45b7a00c5899361e1c6099678dc4e7b9012096b41c4eb3aaf947f6ea429": "0x0400", - "0x5f9cc45b7a00c5899361e1c6099678dc8a2d09463effcc78a22d75b9cb87dffc": "0x0000000000000000", - "0x5f9cc45b7a00c5899361e1c6099678dcd47cb8f5328af743ddfb361e7180e7fcbb1bdbcacd6ac9340000000000000000": "0x00000000", - "0x63f78c98723ddc9073523ef3beefda0c4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0x6a0da05ca59913bc38a8630590f2627c2a351b6a99a5b21324516e668bb86a57": "0x00", - "0x6a0da05ca59913bc38a8630590f2627c4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0x6ac983d82528bf1595ab26438ae5b2cf4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0x6cf4040bbce30824850f1a4823d8c65f4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0x7474449cca95dc5d0c00e71735a6d17d4e7b9012096b41c4eb3aaf947f6ea429": "0x0100", - "0x74dd702da46f77d7acf77f5a48d4af7d4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0x74dd702da46f77d7acf77f5a48d4af7d62556a85fcb7c61b2c6c750924846b155e5f82ad672e896be4c1d81c7e8df384e1ae9667a668825c56e95b0f7d3b1ba2f7539d4c470abb1b": "0xe4c1d81c7e8df384e1ae9667a668825c56e95b0f7d3b1ba2f7539d4c470abb1b01005203395fd92c2b411136466e88ef74dd6327c0a6f32b3be7e38d56a2e1324f0118caf23bb6b8cda7f3f90c1dc2b2f6c2b7143b13c956ef0a12dfe486e83cb758e7f10262de5b000000407a10f35a0000", - "0x74dd702da46f77d7acf77f5a48d4af7d62556a85fcb7c61b2c6c750924846b15ad47afdd1ab6146118caf23bb6b8cda7f3f90c1dc2b2f6c2b7143b13c956ef0a12dfe486e83cb758": "0x18caf23bb6b8cda7f3f90c1dc2b2f6c2b7143b13c956ef0a12dfe486e83cb75801e4c1d81c7e8df384e1ae9667a668825c56e95b0f7d3b1ba2f7539d4c470abb1b00e7f10262de5b000000407a10f35a0000", - "0x74dd702da46f77d7acf77f5a48d4af7d62556a85fcb7c61b2c6c750924846b15ca9d64ddf2c4bc4afa6a6474ec1a9234888f7b20d3978a706d386d0f16344765faa48cb376db331c": "0xfa6a6474ec1a9234888f7b20d3978a706d386d0f16344765faa48cb376db331c0001005203395fd92c2b411136466e88ef74dd6327c0a6f32b3be7e38d56a2e1324fe7f10262de5b000000407a10f35a0000", - "0x74dd702da46f77d7acf77f5a48d4af7d62556a85fcb7c61b2c6c750924846b15dd959ae783e3505c005203395fd92c2b411136466e88ef74dd6327c0a6f32b3be7e38d56a2e1324f": "0x005203395fd92c2b411136466e88ef74dd6327c0a6f32b3be7e38d56a2e1324f01fa6a6474ec1a9234888f7b20d3978a706d386d0f16344765faa48cb376db331c01e4c1d81c7e8df384e1ae9667a668825c56e95b0f7d3b1ba2f7539d4c470abb1be7f10262de5b000000407a10f35a0000", - "0x74dd702da46f77d7acf77f5a48d4af7d7a6dc62e324093ba1331bf49fdb2f24a": "0x04000000", - "0x74dd702da46f77d7acf77f5a48d4af7de5c03730c8f59f00941607850b6633d8dec683721ac60452e7f10262de5b0000": "0x01fa6a6474ec1a9234888f7b20d3978a706d386d0f16344765faa48cb376db331c0118caf23bb6b8cda7f3f90c1dc2b2f6c2b7143b13c956ef0a12dfe486e83cb758", - "0x7a6d38deaa01cb6e76ee69889f1696272be9a4e88368a2188d2b9100a9f3cd43": "0x00000000000000000000000000000000", - "0x7a6d38deaa01cb6e76ee69889f16962730256ea2c545a3e5e3744665ffb2ed28": "0x00020000", - "0x7a6d38deaa01cb6e76ee69889f1696273f0d64e1907361c689834a9c1cb0fbe0": "0x20000000", - "0x7a6d38deaa01cb6e76ee69889f16962749d67997de33812a1cc37310f765b82e": "0x00000000000000000000000000000000", - "0x7a6d38deaa01cb6e76ee69889f1696274e7b9012096b41c4eb3aaf947f6ea429": "0x0400", - "0x7a6d38deaa01cb6e76ee69889f169627ba93302f3b868c50785e6ade45c6a1d8": "0x10000000", - "0x94eadf0156a8ad5156507773d0471e4a16973e1142f5bd30d9464076794007db": "0x00", - "0x94eadf0156a8ad5156507773d0471e4a1e8de4295679f32032acb318db364135": "0x00", - "0x94eadf0156a8ad5156507773d0471e4a4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0x94eadf0156a8ad5156507773d0471e4a64fb6e378f53d72f7859ad0e6b6d8810": "0x0000000000", - "0x94eadf0156a8ad5156507773d0471e4a9ce0310edffce7a01a96c2039f92dd10": "0x01000000", - "0x94eadf0156a8ad5156507773d0471e4ab8ebad86f546c7e0b135a4212aace339": "0x00", - "0xa2ce73642c549ae79c14f0a671cf45f94e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0xb341e3a63e58a188839b242d17f8c9f82586833f834350b4d435d5fd269ecc8b": "0x1003000000010000000000000002000000", - "0xb341e3a63e58a188839b242d17f8c9f84e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0xb341e3a63e58a188839b242d17f8c9f87a50c904b368210021127f9238883a6e": "0x10ce68cabd54aaa5c1e9870f89645ee0f2b3cfafc58089b15387b1e87f59ec3d7e701aa8e4ebae70f627b5cca9726c5ac67133b9295eacdfd5f22a3e44297c4e3b866bd4b14f3f67a056b09c6834375bdc6d0b2d7ae387f8568f67afd1db9b8a1bacf21938aa46cda6a2eca3134629bfb201bf45cc62514672daeb4c55f6b2f332", - "0xb341e3a63e58a188839b242d17f8c9f8b5cab3380174032968897a4c3ce57c0a": "0x00000000", - "0xc2261276cc9d1f8598ea4b6a74b15c2f218f26c73add634897550b4003b26bc69a0d9ba64d584162e7d1fc85d6d19ad1005203395fd92c2b411136466e88ef74dd6327c0a6f32b3be7e38d56a2e1324f": "0x047374616b696e672000407a10f35a0000000000000000000002", - "0xc2261276cc9d1f8598ea4b6a74b15c2f218f26c73add634897550b4003b26bc6a1e0293801ecda3bccddad286cfce679fa6a6474ec1a9234888f7b20d3978a706d386d0f16344765faa48cb376db331c": "0x047374616b696e672000407a10f35a0000000000000000000002", - "0xc2261276cc9d1f8598ea4b6a74b15c2f218f26c73add634897550b4003b26bc6e39abd9d6d25130391c9ff6fc64a35ef18caf23bb6b8cda7f3f90c1dc2b2f6c2b7143b13c956ef0a12dfe486e83cb758": "0x047374616b696e672000407a10f35a0000000000000000000002", - "0xc2261276cc9d1f8598ea4b6a74b15c2f218f26c73add634897550b4003b26bc6f4c6172605184c65d6c162727408dc0be4c1d81c7e8df384e1ae9667a668825c56e95b0f7d3b1ba2f7539d4c470abb1b": "0x047374616b696e672000407a10f35a0000000000000000000002", - "0xc2261276cc9d1f8598ea4b6a74b15c2f4e7b9012096b41c4eb3aaf947f6ea429": "0x0100", - "0xc2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80": "0x0040c7f9727de20d0000000000000000", - "0xca32a41f4b3ed515863dc0a38697f84e4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0xcd710b30bd2eab0352ddcc26417aa1944e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0xcd710b30bd2eab0352ddcc26417aa1949f4993f016e2d2f8e5f43be7bb259486": "0x00", - "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb35e5f82ad672e896be4c1d81c7e8df384e1ae9667a668825c56e95b0f7d3b1ba2f7539d4c470abb1b": "0x5e7084c57d9f04eaa7c22a86d33757cdef9bbcb6607dab1a7c2262dd1293d7cee6b8162c3e767f8e61892f7fcd06d27041d806e5e0335c59dcdafa5c8e181c5bded28f03696a0c9f9dec223f3cbc44c4895d8b243ebe5cee12f9f02bf0c5043cacf21938aa46cda6a2eca3134629bfb201bf45cc62514672daeb4c55f6b2f332b2174a8685bb3c874484978b71c55b45c4057e290c57c0a076ba9aeb7b6618025ed9fdbd8dffeb5324935a7fafc536de96d62abee0a05d7eefa961c1cf3de266", - "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb3ad47afdd1ab6146118caf23bb6b8cda7f3f90c1dc2b2f6c2b7143b13c956ef0a12dfe486e83cb758": "0xcee75bb8d02be946f52be595adfd9e4a8ce0343a9894c5e2471429193926765322371e9715d00b3a21c9a899ba3eafd11f5143b821b159b864025ba1eabdb631ce83a2b5c733f98b4018856a1fb0bdf0138dd883cc93a883f97de48b762d6b12701aa8e4ebae70f627b5cca9726c5ac67133b9295eacdfd5f22a3e44297c4e3bd815b1a9dc0077cdf10a4cd3bedc7dd0b5de4b873f9932ae8f8b9d147f43d3000e93248544c963f34bb9cde63c97f85ef7a1939d3c9075907b26edf368fe846e", - "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb3ca9d64ddf2c4bc4afa6a6474ec1a9234888f7b20d3978a706d386d0f16344765faa48cb376db331c": "0x9ee080484f0429022dda72f19bc76cd0b142689d2782c0a68682bba5c5fb156e585a72774ca9465ba0e7407e4e66d239febbe906cbf090169b6cfa15dd44e5779e3e67bfc0daed31db022fce484b2cf0d757e9aafded1988293da74301275b38ce68cabd54aaa5c1e9870f89645ee0f2b3cfafc58089b15387b1e87f59ec3d7e62f0e85adce6f9782769ae007691df98557e3a04452ac0be90309f88f513f55dca24971e2ec596d510c673f4f8d36d0a8a407b59ffd0643f621369973a335656", - "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb3dd959ae783e3505c005203395fd92c2b411136466e88ef74dd6327c0a6f32b3be7e38d56a2e1324f": "0x094f736c315addde86b7cd5adac7984cd10b1dc187364e92f7ac901a5447609f006078f6e6a00db1f40097f0d07953008b04cda71ad831e70f37e93eb2b404314a611c52c43142e11767e4443eb56b908babae266b4f446271d11ffaaafbb16e866bd4b14f3f67a056b09c6834375bdc6d0b2d7ae387f8568f67afd1db9b8a1bca5ff4e343aa58559db1467ab84f5241f95baf8fd4bbc4d90856089e74d32669b691bfd2cd584abd1531b7deff6d0e34893960b59ae550348c33abd76af4cb49", - "0xcec5070d609dd3497f72bde07fc96ba04e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa195005b6dcd704a27908696d6f6e804a611c52c43142e11767e4443eb56b908babae266b4f446271d11ffaaafbb16e": "0x005203395fd92c2b411136466e88ef74dd6327c0a6f32b3be7e38d56a2e1324f", - "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa19500a7d72b76c2ec9b06173676e8062f0e85adce6f9782769ae007691df98557e3a04452ac0be90309f88f513f55d": "0xfa6a6474ec1a9234888f7b20d3978a706d386d0f16344765faa48cb376db331c", - "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950110db7b3540f726061756469805ed9fdbd8dffeb5324935a7fafc536de96d62abee0a05d7eefa961c1cf3de266": "0xe4c1d81c7e8df384e1ae9667a668825c56e95b0f7d3b1ba2f7539d4c470abb1b", - "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa195014a98987c6f4654c6261626580e6b8162c3e767f8e61892f7fcd06d27041d806e5e0335c59dcdafa5c8e181c5b": "0xe4c1d81c7e8df384e1ae9667a668825c56e95b0f7d3b1ba2f7539d4c470abb1b", - "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950270bb04a2a9e106e696d6f6e80ce83a2b5c733f98b4018856a1fb0bdf0138dd883cc93a883f97de48b762d6b12": "0x18caf23bb6b8cda7f3f90c1dc2b2f6c2b7143b13c956ef0a12dfe486e83cb758", - "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa195031063675260bb8076261626580006078f6e6a00db1f40097f0d07953008b04cda71ad831e70f37e93eb2b40431": "0x005203395fd92c2b411136466e88ef74dd6327c0a6f32b3be7e38d56a2e1324f", - "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950413ab9d61fa646a76772616e80094f736c315addde86b7cd5adac7984cd10b1dc187364e92f7ac901a5447609f": "0x005203395fd92c2b411136466e88ef74dd6327c0a6f32b3be7e38d56a2e1324f", - "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa19505404aed8a9c40e507061726180866bd4b14f3f67a056b09c6834375bdc6d0b2d7ae387f8568f67afd1db9b8a1b": "0x005203395fd92c2b411136466e88ef74dd6327c0a6f32b3be7e38d56a2e1324f", - "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950597968238adfa9af6772616e80cee75bb8d02be946f52be595adfd9e4a8ce0343a9894c5e24714291939267653": "0x18caf23bb6b8cda7f3f90c1dc2b2f6c2b7143b13c956ef0a12dfe486e83cb758", - "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa19505b639c79d4e8330c696d6f6e809e3e67bfc0daed31db022fce484b2cf0d757e9aafded1988293da74301275b38": "0xfa6a6474ec1a9234888f7b20d3978a706d386d0f16344765faa48cb376db331c", - "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa195061ccbc794cd1e95c6175646980b691bfd2cd584abd1531b7deff6d0e34893960b59ae550348c33abd76af4cb49": "0x005203395fd92c2b411136466e88ef74dd6327c0a6f32b3be7e38d56a2e1324f", - "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa19507f0621f339620f26696d6f6e80ded28f03696a0c9f9dec223f3cbc44c4895d8b243ebe5cee12f9f02bf0c5043c": "0xe4c1d81c7e8df384e1ae9667a668825c56e95b0f7d3b1ba2f7539d4c470abb1b", - "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950949420096ce6b2176772616e805e7084c57d9f04eaa7c22a86d33757cdef9bbcb6607dab1a7c2262dd1293d7ce": "0xe4c1d81c7e8df384e1ae9667a668825c56e95b0f7d3b1ba2f7539d4c470abb1b", - "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa19509af54e7103657a4c7061726180701aa8e4ebae70f627b5cca9726c5ac67133b9295eacdfd5f22a3e44297c4e3b": "0x18caf23bb6b8cda7f3f90c1dc2b2f6c2b7143b13c956ef0a12dfe486e83cb758", - "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950a359a745f65c1e456261626580585a72774ca9465ba0e7407e4e66d239febbe906cbf090169b6cfa15dd44e577": "0xfa6a6474ec1a9234888f7b20d3978a706d386d0f16344765faa48cb376db331c", - "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950af162637344b36a96173676e80d815b1a9dc0077cdf10a4cd3bedc7dd0b5de4b873f9932ae8f8b9d147f43d300": "0x18caf23bb6b8cda7f3f90c1dc2b2f6c2b7143b13c956ef0a12dfe486e83cb758", - "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950b229c28236e354a26173676e80b2174a8685bb3c874484978b71c55b45c4057e290c57c0a076ba9aeb7b661802": "0xe4c1d81c7e8df384e1ae9667a668825c56e95b0f7d3b1ba2f7539d4c470abb1b", - "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950b6b8ce596a13561b7061726180acf21938aa46cda6a2eca3134629bfb201bf45cc62514672daeb4c55f6b2f332": "0xe4c1d81c7e8df384e1ae9667a668825c56e95b0f7d3b1ba2f7539d4c470abb1b", - "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950bee8d7df4d460d9d61756469800e93248544c963f34bb9cde63c97f85ef7a1939d3c9075907b26edf368fe846e": "0x18caf23bb6b8cda7f3f90c1dc2b2f6c2b7143b13c956ef0a12dfe486e83cb758", - "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950c02fd5bdeb0ec6f06175646980ca24971e2ec596d510c673f4f8d36d0a8a407b59ffd0643f621369973a335656": "0xfa6a6474ec1a9234888f7b20d3978a706d386d0f16344765faa48cb376db331c", - "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950d9ebe2452f14a591626162658022371e9715d00b3a21c9a899ba3eafd11f5143b821b159b864025ba1eabdb631": "0x18caf23bb6b8cda7f3f90c1dc2b2f6c2b7143b13c956ef0a12dfe486e83cb758", - "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950e322099b0a5bb5836772616e809ee080484f0429022dda72f19bc76cd0b142689d2782c0a68682bba5c5fb156e": "0xfa6a6474ec1a9234888f7b20d3978a706d386d0f16344765faa48cb376db331c", - "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950e6ddf4dc42f9b1b66173676e80ca5ff4e343aa58559db1467ab84f5241f95baf8fd4bbc4d90856089e74d32669": "0x005203395fd92c2b411136466e88ef74dd6327c0a6f32b3be7e38d56a2e1324f", - "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950f8bc7112b190dae97061726180ce68cabd54aaa5c1e9870f89645ee0f2b3cfafc58089b15387b1e87f59ec3d7e": "0xfa6a6474ec1a9234888f7b20d3978a706d386d0f16344765faa48cb376db331c", - "0xcec5070d609dd3497f72bde07fc96ba088dcde934c658227ee1dfafcd6e16903": "0x10005203395fd92c2b411136466e88ef74dd6327c0a6f32b3be7e38d56a2e1324f18caf23bb6b8cda7f3f90c1dc2b2f6c2b7143b13c956ef0a12dfe486e83cb758e4c1d81c7e8df384e1ae9667a668825c56e95b0f7d3b1ba2f7539d4c470abb1bfa6a6474ec1a9234888f7b20d3978a706d386d0f16344765faa48cb376db331c", - "0xcec5070d609dd3497f72bde07fc96ba0e0cdd062e6eaf24295ad4ccfc41d4609": "0x10005203395fd92c2b411136466e88ef74dd6327c0a6f32b3be7e38d56a2e1324f094f736c315addde86b7cd5adac7984cd10b1dc187364e92f7ac901a5447609f006078f6e6a00db1f40097f0d07953008b04cda71ad831e70f37e93eb2b404314a611c52c43142e11767e4443eb56b908babae266b4f446271d11ffaaafbb16e866bd4b14f3f67a056b09c6834375bdc6d0b2d7ae387f8568f67afd1db9b8a1bca5ff4e343aa58559db1467ab84f5241f95baf8fd4bbc4d90856089e74d32669b691bfd2cd584abd1531b7deff6d0e34893960b59ae550348c33abd76af4cb4918caf23bb6b8cda7f3f90c1dc2b2f6c2b7143b13c956ef0a12dfe486e83cb758cee75bb8d02be946f52be595adfd9e4a8ce0343a9894c5e2471429193926765322371e9715d00b3a21c9a899ba3eafd11f5143b821b159b864025ba1eabdb631ce83a2b5c733f98b4018856a1fb0bdf0138dd883cc93a883f97de48b762d6b12701aa8e4ebae70f627b5cca9726c5ac67133b9295eacdfd5f22a3e44297c4e3bd815b1a9dc0077cdf10a4cd3bedc7dd0b5de4b873f9932ae8f8b9d147f43d3000e93248544c963f34bb9cde63c97f85ef7a1939d3c9075907b26edf368fe846ee4c1d81c7e8df384e1ae9667a668825c56e95b0f7d3b1ba2f7539d4c470abb1b5e7084c57d9f04eaa7c22a86d33757cdef9bbcb6607dab1a7c2262dd1293d7cee6b8162c3e767f8e61892f7fcd06d27041d806e5e0335c59dcdafa5c8e181c5bded28f03696a0c9f9dec223f3cbc44c4895d8b243ebe5cee12f9f02bf0c5043cacf21938aa46cda6a2eca3134629bfb201bf45cc62514672daeb4c55f6b2f332b2174a8685bb3c874484978b71c55b45c4057e290c57c0a076ba9aeb7b6618025ed9fdbd8dffeb5324935a7fafc536de96d62abee0a05d7eefa961c1cf3de266fa6a6474ec1a9234888f7b20d3978a706d386d0f16344765faa48cb376db331c9ee080484f0429022dda72f19bc76cd0b142689d2782c0a68682bba5c5fb156e585a72774ca9465ba0e7407e4e66d239febbe906cbf090169b6cfa15dd44e5779e3e67bfc0daed31db022fce484b2cf0d757e9aafded1988293da74301275b38ce68cabd54aaa5c1e9870f89645ee0f2b3cfafc58089b15387b1e87f59ec3d7e62f0e85adce6f9782769ae007691df98557e3a04452ac0be90309f88f513f55dca24971e2ec596d510c673f4f8d36d0a8a407b59ffd0643f621369973a335656", - "0xd57bce545fb382c34570e5dfbf338f5e4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0xd5c41b52a371aa36c9254ce34324f2a54e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0xd5e1a2fa16732ce6906189438c0a82c64e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0xd8bbe27baf3aa64bb483afabc240f68e4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0xd8f314b7f4e6b095f0f8ee4656a448254e7b9012096b41c4eb3aaf947f6ea429": "0x0100", - "0xede8e4fdc3c8b556f0ce2f77fc2575e34e7b9012096b41c4eb3aaf947f6ea429": "0x0100", - "0xf0c365c3cf59d671eb72da0e7a4113c44e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0xf5207f03cfdce586301014700e2c25934e7b9012096b41c4eb3aaf947f6ea429": "0x0100", - "0xf5a4963e4efb097983d7a693b0c1ee454e7b9012096b41c4eb3aaf947f6ea429": "0x0100" - }, - "childrenDefault": {} - } - } -} diff --git a/polkadot/node/service/src/chain_spec.rs b/polkadot/node/service/src/chain_spec.rs index d377a75f106..fe360e7b8c7 100644 --- a/polkadot/node/service/src/chain_spec.rs +++ b/polkadot/node/service/src/chain_spec.rs @@ -16,16 +16,6 @@ //! Polkadot chain configurations. -#[cfg(feature = "westend-native")] -use pallet_staking::Forcing; -use polkadot_primitives::{AccountId, AccountPublic, AssignmentId, ValidatorId}; -use sc_consensus_grandpa::AuthorityId as GrandpaId; -use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId; -use sp_consensus_babe::AuthorityId as BabeId; -use sp_consensus_beefy::ecdsa_crypto::AuthorityId as BeefyId; - -#[cfg(feature = "westend-native")] -use polkadot_primitives::SchedulerParams; #[cfg(feature = "rococo-native")] use rococo_runtime as rococo; use sc_chain_spec::ChainSpecExtension; @@ -34,14 +24,12 @@ use sc_chain_spec::ChainType; #[cfg(any(feature = "westend-native", feature = "rococo-native"))] use sc_telemetry::TelemetryEndpoints; use serde::{Deserialize, Serialize}; -use sp_core::{sr25519, Pair, Public}; -use sp_runtime::traits::IdentifyAccount; -#[cfg(feature = "westend-native")] -use sp_runtime::Perbill; #[cfg(feature = "westend-native")] use westend_runtime as westend; -#[cfg(feature = "westend-native")] -use westend_runtime_constants::currency::UNITS as WND; + +use polkadot_primitives::{AccountId, AccountPublic}; +use sp_core::{Pair, Public}; +use sp_runtime::traits::IdentifyAccount; #[cfg(feature = "westend-native")] const WESTEND_STAGING_TELEMETRY_URL: &str = "wss://telemetry.polkadot.io/submit/"; @@ -110,269 +98,6 @@ pub fn rococo_config() -> Result { RococoChainSpec::from_json_bytes(&include_bytes!("../chain-specs/rococo.json")[..]) } -/// This is a temporary testnet that uses the same runtime as rococo. -pub fn wococo_config() -> Result { - RococoChainSpec::from_json_bytes(&include_bytes!("../chain-specs/wococo.json")[..]) -} - -/// The default parachains host configuration. -#[cfg(feature = "westend-native")] -fn default_parachains_host_configuration( -) -> polkadot_runtime_parachains::configuration::HostConfiguration -{ - use polkadot_primitives::{ - node_features::FeatureIndex, ApprovalVotingParams, AsyncBackingParams, MAX_CODE_SIZE, - MAX_POV_SIZE, - }; - - polkadot_runtime_parachains::configuration::HostConfiguration { - validation_upgrade_cooldown: 2u32, - validation_upgrade_delay: 2, - code_retention_period: 1200, - max_code_size: MAX_CODE_SIZE, - max_pov_size: MAX_POV_SIZE, - max_head_data_size: 32 * 1024, - max_upward_queue_count: 8, - max_upward_queue_size: 1024 * 1024, - max_downward_message_size: 1024 * 1024, - 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_channel_max_message_size: 1024 * 1024, - hrmp_max_parachain_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, - minimum_validation_upgrade_delay: 5, - async_backing_params: AsyncBackingParams { - max_candidate_depth: 3, - allowed_ancestry_len: 2, - }, - node_features: bitvec::vec::BitVec::from_element( - 1u8 << (FeatureIndex::ElasticScalingMVP as usize) | - 1u8 << (FeatureIndex::EnableAssignmentsV2 as usize), - ), - scheduler_params: SchedulerParams { - lookahead: 2, - group_rotation_frequency: 20, - paras_availability_period: 4, - ..Default::default() - }, - approval_voting_params: ApprovalVotingParams { max_approval_coalesce_count: 5 }, - ..Default::default() - } -} - -#[cfg(feature = "westend-native")] -#[test] -fn default_parachains_host_configuration_is_consistent() { - default_parachains_host_configuration().panic_if_not_consistent(); -} - -#[cfg(feature = "westend-native")] -fn westend_session_keys( - babe: BabeId, - grandpa: GrandpaId, - para_validator: ValidatorId, - para_assignment: AssignmentId, - authority_discovery: AuthorityDiscoveryId, - beefy: BeefyId, -) -> westend::SessionKeys { - westend::SessionKeys { - babe, - grandpa, - para_validator, - para_assignment, - authority_discovery, - beefy, - } -} - -#[cfg(feature = "westend-native")] -fn westend_staging_testnet_config_genesis() -> serde_json::Value { - use hex_literal::hex; - use sp_core::crypto::UncheckedInto; - - // Following keys are used in genesis config for development chains. - // DO NOT use them in production chains as the secret seed is public. - // - // SECRET_SEED="slow awkward present example safe bundle science ocean cradle word tennis earn" - // subkey inspect -n polkadot "$SECRET_SEED" - let endowed_accounts = vec![ - // 15S75FkhCWEowEGfxWwVfrW3LQuy8w8PNhVmrzfsVhCMjUh1 - hex!["c416837e232d9603e83162ef4bda08e61580eeefe60fe92fc044aa508559ae42"].into(), - ]; - // SECRET=$SECRET_SEED ./scripts/prepare-test-net.sh 4 - let initial_authorities: Vec<( - AccountId, - AccountId, - BabeId, - GrandpaId, - ValidatorId, - AssignmentId, - AuthorityDiscoveryId, - BeefyId, - )> = vec![ - ( - //5EvydUTtHvt39Khac3mMxNPgzcfu49uPDzUs3TL7KEzyrwbw - hex!["7ecfd50629cdd246649959d88d490b31508db511487e111a52a392e6e458f518"].into(), - //5HQyX5gyy77m9QLXguAhiwjTArHYjYspeY98dYDu1JDetfZg - hex!["eca2cca09bdc66a7e6d8c3d9499a0be2ad4690061be8a9834972e17d13d2fe7e"].into(), - //5G13qYRudTyttwTJvHvnwp8StFtcfigyPnwfD4v7LNopsnX4 - hex!["ae27367cb77850fb195fe1f9c60b73210409e68c5ad953088070f7d8513d464c"] - .unchecked_into(), - //5Eb7wM65PNgtY6e33FEAzYtU5cRTXt6WQvZTnzaKQwkVcABk - hex!["6faae44b21c6f2681a7f60df708e9f79d340f7d441d28bd987fab8d05c6487e8"] - .unchecked_into(), - //5FqMLAgygdX9UqzukDp15Uid9PAKdFAR621U7xtp5ut2NfrW - hex!["a6c1a5b501985a83cb1c37630c5b41e6b0a15b3675b2fd94694758e6cfa6794d"] - .unchecked_into(), - //5DhXAV75BKvF9o447ikWqLttyL2wHtLMFSX7GrsKF9Ny61Ta - hex!["485051748ab9c15732f19f3fbcf1fd00a6d9709635f084505107fbb059c33d2f"] - .unchecked_into(), - //5GNHfmrtWLTawnGCmc39rjAEiW97vKvE7DGePYe4am5JtE4i - hex!["be59ed75a72f7b47221ce081ba4262cf2e1ea7867e30e0b3781822f942b97677"] - .unchecked_into(), - //5DA6Z8RUF626stn94aTRBCeobDCYcFbU7Pdk4Tz1R9vA8B8F - hex!["0207e43990799e1d02b0507451e342a1240ff836ea769c57297589a5fd072ad8f4"] - .unchecked_into(), - ), - ( - //5DFpvDUdCgw54E3E357GR1PyJe3Ft9s7Qyp7wbELAoJH9RQa - hex!["34b7b3efd35fcc3c1926ca065381682b1af29b57dabbcd091042c6de1d541b7d"].into(), - //5DZSSsND5wCjngvyXv27qvF3yPzt3MCU8rWnqNy4imqZmjT8 - hex!["4226796fa792ac78875e023ff2e30e3c2cf79f0b7b3431254cd0f14a3007bc0e"].into(), - //5CPrgfRNDQvQSnLRdeCphP3ibj5PJW9ESbqj2fw29vBMNQNn - hex!["0e9b60f04be3bffe362eb2212ea99d2b909b052f4bff7c714e13c2416a797f5d"] - .unchecked_into(), - //5FXFsPReTUEYPRNKhbTdUathcWBsxTNsLbk2mTpYdKCJewjA - hex!["98f4d81cb383898c2c3d54dab28698c0f717c81b509cb32dc6905af3cc697b18"] - .unchecked_into(), - //5CZjurB78XbSHf6SLkLhCdkqw52Zm7aBYUDdfkLqEDWJ9Zhj - hex!["162508accd470e379b04cb0c7c60b35a7d5357e84407a89ed2dd48db4b726960"] - .unchecked_into(), - //5DkAqCtSjUMVoJFauuGoAbSEgn2aFCRGziKJiLGpPwYgE1pS - hex!["4a559c028b69a7f784ce553393e547bec0aa530352157603396d515f9c83463b"] - .unchecked_into(), - //5GsBt9MhGwkg8Jfb1F9LAy2kcr88WNyNy4L5ezwbCr8NWKQU - hex!["d464908266c878acbf181bf8fda398b3aa3fd2d05508013e414aaece4cf0d702"] - .unchecked_into(), - //5DtJVkz8AHevEnpszy3X4dUcPvACW6x1qBMQZtFxjexLr5bq - hex!["02fdf30222d2cb88f2376d558d3de9cb83f9fde3aa4b2dd40c93e3104e3488bcd2"] - .unchecked_into(), - ), - ( - //5E2cob2jrXsBkTih56pizwSqENjE4siaVdXhaD6akLdDyVq7 - hex!["56e0f73c563d49ee4a3971c393e17c44eaa313dabad7fcf297dc3271d803f303"].into(), - //5D4rNYgP9uFNi5GMyDEXTfiaFLjXyDEEX2VvuqBVi3f1qgCh - hex!["2c58e5e1d5aef77774480cead4f6876b1a1a6261170166995184d7f86140572b"].into(), - //5Ea2D65KXqe625sz4uV1jjhSfuigVnkezC8VgEj9LXN7ERAk - hex!["6ed45cb7af613be5d88a2622921e18d147225165f24538af03b93f2a03ce6e13"] - .unchecked_into(), - //5G4kCbgqUhEyrRHCyFwFEkgBZXoYA8sbgsRxT9rY8Tp5Jj5F - hex!["b0f8d2b9e4e1eafd4dab6358e0b9d5380d78af27c094e69ae9d6d30ca300fd86"] - .unchecked_into(), - //5CS7thd2n54WfqeKU3cjvZzK4z5p7zku1Zw97mSzXgPioAAs - hex!["1055100a283968271a0781450b389b9093231be809be1e48a305ebad2a90497e"] - .unchecked_into(), - //5DSaL4ZmSYarZSazhL5NQh7LT6pWhNRDcefk2QS9RxEXfsJe - hex!["3cea4ab74bab4adf176cf05a6e18c1599a7bc217d4c6c217275bfbe3b037a527"] - .unchecked_into(), - //5CaNLkYEbFYXZodXhd3UjV6RNLjFGNLiYafc8X5NooMkZiAq - hex!["169faa81aebfe74533518bda28567f2e2664014c8905aa07ea003336afda5a58"] - .unchecked_into(), - //5ERwhKiePayukzZStMuzGzRJGxGRFpwxYUXVarQpMSMrXzDS - hex!["03429d0d20f6ac5ca8b349f04d014f7b5b864acf382a744104d5d9a51108156c0f"] - .unchecked_into(), - ), - ( - //5H6j9ovzYk9opckVjvM9SvVfaK37ASTtPTzWeRfqk1tgLJUN - hex!["deb804ed2ed2bb696a3dd4ed7de4cd5c496528a2b204051c6ace385bacd66a3a"].into(), - //5DJ51tMW916mGwjMpfS1o9skcNt6Sb28YnZQXaKVg4h89agE - hex!["366da6a748afedb31f07902f2de36ab265beccee37762d3ae1f237de234d9c36"].into(), - //5CSPYDYoCDGSoSLgSp4EHkJ52YasZLHG2woqhPZkdbtNQpke - hex!["1089bc0cd60237d061872925e81d36c9d9205d250d5d8b542c8e08a8ecf1b911"] - .unchecked_into(), - //5ChfdrAqmLjCeDJvynbMjcxYLHYzPe8UWXd3HnX9JDThUMbn - hex!["1c309a70b4e274314b84c9a0a1f973c9c4fc084df5479ef686c54b1ae4950424"] - .unchecked_into(), - //5D8C3HHEp5E8fJsXRD56494F413CdRSR9QKGXe7v5ZEfymdj - hex!["2ee4d78f328db178c54f205ac809da12e291a33bcbd4f29f081ce7e74bdc5044"] - .unchecked_into(), - //5GxeTYCGmp1C3ZRLDkRWqJc6gB2GYmuqnygweuH3vsivMQq6 - hex!["d88e40e3c2c7a7c5abf96ffdd8f7b7bec8798cc277bc97e255881871ab73b529"] - .unchecked_into(), - //5DoGpsgSLcJsHa9B8V4PKjxegWAqDZttWfxicAd68prUX654 - hex!["4cb3863271b70daa38612acd5dae4f5afcb7c165fa277629e5150d2214df322a"] - .unchecked_into(), - //5G1KLjqFyMsPAodnjSRkwRFJztTTEzmZWxow2Q3ZSRCPdthM - hex!["03be5ec86d10a94db89c9b7a396d3c7742e3bec5f85159d4cf308cef505966ddf5"] - .unchecked_into(), - ), - ]; - - const ENDOWMENT: u128 = 1_000_000 * WND; - const STASH: u128 = 100 * WND; - - serde_json::json!({ - "balances": { - "balances": endowed_accounts - .iter() - .map(|k: &AccountId| (k.clone(), ENDOWMENT)) - .chain(initial_authorities.iter().map(|x| (x.0.clone(), STASH))) - .collect::>(), - }, - "session": { - "keys": initial_authorities - .iter() - .map(|x| { - ( - x.0.clone(), - x.0.clone(), - westend_session_keys( - x.2.clone(), - x.3.clone(), - x.4.clone(), - x.5.clone(), - x.6.clone(), - x.7.clone(), - ), - ) - }) - .collect::>(), - }, - "staking": { - "validatorCount": 50, - "minimumValidatorCount": 4, - "stakers": initial_authorities - .iter() - .map(|x| (x.0.clone(), x.0.clone(), STASH, westend::StakerStatus::::Validator)) - .collect::>(), - "invulnerables": initial_authorities.iter().map(|x| x.0.clone()).collect::>(), - "forceEra": Forcing::ForceNone, - "slashRewardFraction": Perbill::from_percent(10), - }, - "babe": { - "epochConfig": Some(westend::BABE_GENESIS_EPOCH_CONFIG), - }, - "sudo": { "key": Some(endowed_accounts[0].clone()) }, - "configuration": { - "config": default_parachains_host_configuration(), - }, - "registrar": { - "nextFreeParaId": polkadot_primitives::LOWEST_PUBLIC_ID, - }, - }) -} - /// Westend staging testnet config. #[cfg(feature = "westend-native")] pub fn westend_staging_testnet_config() -> Result { @@ -383,7 +108,7 @@ pub fn westend_staging_testnet_config() -> Result { .with_name("Westend Staging Testnet") .with_id("westend_staging_testnet") .with_chain_type(ChainType::Live) - .with_genesis_config_patch(westend_staging_testnet_config_genesis()) + .with_genesis_config_preset_name("staging_testnet") .with_telemetry_endpoints( TelemetryEndpoints::new(vec![(WESTEND_STAGING_TELEMETRY_URL.to_string(), 0)]) .expect("Westend Staging telemetry url is valid; qed"), @@ -442,148 +167,6 @@ pub fn versi_staging_testnet_config() -> Result { .build()) } -/// Helper function to generate a crypto pair from seed -pub fn get_from_seed(seed: &str) -> ::Public { - TPublic::Pair::from_string(&format!("//{}", seed), None) - .expect("static values are valid; qed") - .public() -} - -/// Helper function to generate an account ID from seed -pub fn get_account_id_from_seed(seed: &str) -> AccountId -where - AccountPublic: From<::Public>, -{ - AccountPublic::from(get_from_seed::(seed)).into_account() -} - -/// Helper function to generate stash, controller and session key from seed -pub fn get_authority_keys_from_seed( - seed: &str, -) -> ( - AccountId, - AccountId, - BabeId, - GrandpaId, - ValidatorId, - AssignmentId, - AuthorityDiscoveryId, - BeefyId, -) { - let keys = get_authority_keys_from_seed_no_beefy(seed); - (keys.0, keys.1, keys.2, keys.3, keys.4, keys.5, keys.6, get_from_seed::(seed)) -} - -/// Helper function to generate stash, controller and session key from seed -pub fn get_authority_keys_from_seed_no_beefy( - seed: &str, -) -> (AccountId, AccountId, BabeId, GrandpaId, ValidatorId, AssignmentId, AuthorityDiscoveryId) { - ( - get_account_id_from_seed::(&format!("{}//stash", seed)), - get_account_id_from_seed::(seed), - get_from_seed::(seed), - get_from_seed::(seed), - get_from_seed::(seed), - get_from_seed::(seed), - get_from_seed::(seed), - ) -} - -#[cfg(feature = "westend-native")] -fn testnet_accounts() -> Vec { - vec![ - get_account_id_from_seed::("Alice"), - get_account_id_from_seed::("Bob"), - get_account_id_from_seed::("Charlie"), - get_account_id_from_seed::("Dave"), - get_account_id_from_seed::("Eve"), - get_account_id_from_seed::("Ferdie"), - get_account_id_from_seed::("Alice//stash"), - get_account_id_from_seed::("Bob//stash"), - get_account_id_from_seed::("Charlie//stash"), - get_account_id_from_seed::("Dave//stash"), - get_account_id_from_seed::("Eve//stash"), - get_account_id_from_seed::("Ferdie//stash"), - ] -} - -/// Helper function to create westend runtime `GenesisConfig` patch for testing -#[cfg(feature = "westend-native")] -pub fn westend_testnet_genesis( - initial_authorities: Vec<( - AccountId, - AccountId, - BabeId, - GrandpaId, - ValidatorId, - AssignmentId, - AuthorityDiscoveryId, - BeefyId, - )>, - root_key: AccountId, - endowed_accounts: Option>, -) -> serde_json::Value { - let endowed_accounts: Vec = endowed_accounts.unwrap_or_else(testnet_accounts); - - const ENDOWMENT: u128 = 1_000_000 * WND; - const STASH: u128 = 100 * WND; - - serde_json::json!({ - "balances": { - "balances": endowed_accounts.iter().map(|k| (k.clone(), ENDOWMENT)).collect::>(), - }, - "session": { - "keys": initial_authorities - .iter() - .map(|x| { - ( - x.0.clone(), - x.0.clone(), - westend_session_keys( - x.2.clone(), - x.3.clone(), - x.4.clone(), - x.5.clone(), - x.6.clone(), - x.7.clone(), - ), - ) - }) - .collect::>(), - }, - "staking": { - "minimumValidatorCount": 1, - "validatorCount": initial_authorities.len() as u32, - "stakers": initial_authorities - .iter() - .map(|x| (x.0.clone(), x.0.clone(), STASH, westend::StakerStatus::::Validator)) - .collect::>(), - "invulnerables": initial_authorities.iter().map(|x| x.0.clone()).collect::>(), - "forceEra": Forcing::NotForcing, - "slashRewardFraction": Perbill::from_percent(10), - }, - "babe": { - "epochConfig": Some(westend::BABE_GENESIS_EPOCH_CONFIG), - }, - "sudo": { "key": Some(root_key) }, - "configuration": { - "config": default_parachains_host_configuration(), - }, - "registrar": { - "nextFreeParaId": polkadot_primitives::LOWEST_PUBLIC_ID, - }, - }) -} - -#[cfg(feature = "westend-native")] -fn westend_development_config_genesis() -> serde_json::Value { - westend_testnet_genesis( - vec![get_authority_keys_from_seed("Alice")], - get_account_id_from_seed::("Alice"), - None, - ) -} - /// Westend development config (single validator Alice) #[cfg(feature = "westend-native")] pub fn westend_development_config() -> Result { @@ -594,7 +177,7 @@ pub fn westend_development_config() -> Result { .with_name("Development") .with_id("westend_dev") .with_chain_type(ChainType::Development) - .with_genesis_config_patch(westend_development_config_genesis()) + .with_genesis_config_preset_name(sp_genesis_builder::DEV_RUNTIME_PRESET) .with_protocol_id(DEFAULT_PROTOCOL_ID) .build()) } @@ -609,7 +192,7 @@ pub fn rococo_development_config() -> Result { .with_name("Development") .with_id("rococo_dev") .with_chain_type(ChainType::Development) - .with_genesis_config_preset_name("development") + .with_genesis_config_preset_name(sp_genesis_builder::DEV_RUNTIME_PRESET) .with_protocol_id(DEFAULT_PROTOCOL_ID) .build()) } @@ -624,36 +207,11 @@ pub fn versi_development_config() -> Result { .with_name("Development") .with_id("versi_dev") .with_chain_type(ChainType::Development) - .with_genesis_config_preset_name("development") + .with_genesis_config_preset_name(sp_genesis_builder::DEV_RUNTIME_PRESET) .with_protocol_id("versi") .build()) } -/// Wococo development config (single validator Alice) -#[cfg(feature = "rococo-native")] -pub fn wococo_development_config() -> Result { - const WOCOCO_DEV_PROTOCOL_ID: &str = "woco"; - Ok(RococoChainSpec::builder( - rococo::WASM_BINARY.ok_or("Wococo development wasm not available")?, - Default::default(), - ) - .with_name("Development") - .with_id("wococo_dev") - .with_chain_type(ChainType::Development) - .with_genesis_config_preset_name("development") - .with_protocol_id(WOCOCO_DEV_PROTOCOL_ID) - .build()) -} - -#[cfg(feature = "westend-native")] -fn westend_local_testnet_genesis() -> serde_json::Value { - westend_testnet_genesis( - vec![get_authority_keys_from_seed("Alice"), get_authority_keys_from_seed("Bob")], - get_account_id_from_seed::("Alice"), - None, - ) -} - /// Westend local testnet config (multivalidator Alice + Bob) #[cfg(feature = "westend-native")] pub fn westend_local_testnet_config() -> Result { @@ -665,7 +223,7 @@ pub fn westend_local_testnet_config() -> Result { .with_name("Westend Local Testnet") .with_id("westend_local_testnet") .with_chain_type(ChainType::Local) - .with_genesis_config_patch(westend_local_testnet_genesis()) + .with_genesis_config_preset_name(sp_genesis_builder::LOCAL_TESTNET_RUNTIME_PRESET) .with_protocol_id(DEFAULT_PROTOCOL_ID) .build()) } @@ -680,22 +238,7 @@ pub fn rococo_local_testnet_config() -> Result { .with_name("Rococo Local Testnet") .with_id("rococo_local_testnet") .with_chain_type(ChainType::Local) - .with_genesis_config_preset_name("local_testnet") - .with_protocol_id(DEFAULT_PROTOCOL_ID) - .build()) -} - -/// Wococo local testnet config (multivalidator Alice + Bob + Charlie + Dave) -#[cfg(feature = "rococo-native")] -pub fn wococo_local_testnet_config() -> Result { - Ok(RococoChainSpec::builder( - rococo::WASM_BINARY.ok_or("Rococo development wasm (used for wococo) not available")?, - Default::default(), - ) - .with_name("Wococo Local Testnet") - .with_id("wococo_local_testnet") - .with_chain_type(ChainType::Local) - .with_genesis_config_preset_name("wococo_local_testnet") + .with_genesis_config_preset_name(sp_genesis_builder::LOCAL_TESTNET_RUNTIME_PRESET) .with_protocol_id(DEFAULT_PROTOCOL_ID) .build()) } @@ -714,3 +257,18 @@ pub fn versi_local_testnet_config() -> Result { .with_protocol_id("versi") .build()) } + +/// Helper function to generate a crypto pair from seed +pub fn get_from_seed(seed: &str) -> ::Public { + TPublic::Pair::from_string(&format!("//{}", seed), None) + .expect("static values are valid; qed") + .public() +} + +/// Helper function to generate an account ID from seed +pub fn get_account_id_from_seed(seed: &str) -> AccountId +where + AccountPublic: From<::Public>, +{ + AccountPublic::from(get_from_seed::(seed)).into_account() +} diff --git a/polkadot/node/service/src/lib.rs b/polkadot/node/service/src/lib.rs index dd35423e18e..79424356880 100644 --- a/polkadot/node/service/src/lib.rs +++ b/polkadot/node/service/src/lib.rs @@ -290,9 +290,6 @@ pub trait IdentifyVariant { /// Returns if this is a configuration for the `Rococo` network. fn is_rococo(&self) -> bool; - /// Returns if this is a configuration for the `Wococo` test network. - fn is_wococo(&self) -> bool; - /// Returns if this is a configuration for the `Versi` test network. fn is_versi(&self) -> bool; @@ -316,9 +313,6 @@ impl IdentifyVariant for Box { fn is_rococo(&self) -> bool { self.id().starts_with("rococo") || self.id().starts_with("rco") } - fn is_wococo(&self) -> bool { - self.id().starts_with("wococo") || self.id().starts_with("wco") - } fn is_versi(&self) -> bool { self.id().starts_with("versi") || self.id().starts_with("vrs") } @@ -332,7 +326,7 @@ impl IdentifyVariant for Box { Chain::Kusama } else if self.is_westend() { Chain::Westend - } else if self.is_rococo() || self.is_versi() || self.is_wococo() { + } else if self.is_rococo() || self.is_versi() { Chain::Rococo } else { Chain::Unknown @@ -778,7 +772,6 @@ pub fn new_full< let mut backoff = sc_consensus_slots::BackoffAuthoringOnFinalizedHeadLagging::default(); if config.chain_spec.is_rococo() || - config.chain_spec.is_wococo() || config.chain_spec.is_versi() || config.chain_spec.is_dev() { @@ -801,8 +794,6 @@ pub fn new_full< let overseer_connector = OverseerConnector::default(); let overseer_handle = Handle::new(overseer_connector.handle()); - let chain_spec = config.chain_spec.cloned_box(); - let keystore = basics.keystore_container.local_keystore(); let auth_or_collator = role.is_authority() || is_parachain_node.is_collator(); @@ -1327,7 +1318,7 @@ pub fn new_full< runtime: client.clone(), key_store: keystore_opt.clone(), network_params, - min_block_delta: if chain_spec.is_wococo() { 4 } else { 8 }, + min_block_delta: 8, prometheus_registry: prometheus_registry.clone(), links: beefy_links, on_demand_justifications_handler: beefy_on_demand_justifications_handler, @@ -1458,10 +1449,7 @@ pub fn new_chain_ops( { config.keystore = sc_service::config::KeystoreConfig::InMemory; - if config.chain_spec.is_rococo() || - config.chain_spec.is_wococo() || - config.chain_spec.is_versi() - { + if config.chain_spec.is_rococo() || config.chain_spec.is_versi() { chain_ops!(config, jaeger_agent, None) } else if config.chain_spec.is_kusama() { chain_ops!(config, jaeger_agent, None) diff --git a/polkadot/node/test/service/src/chain_spec.rs b/polkadot/node/test/service/src/chain_spec.rs index 8add51b0752..00d62af0b27 100644 --- a/polkadot/node/test/service/src/chain_spec.rs +++ b/polkadot/node/test/service/src/chain_spec.rs @@ -20,7 +20,8 @@ use pallet_staking::Forcing; use polkadot_primitives::{ AccountId, AssignmentId, SchedulerParams, ValidatorId, MAX_CODE_SIZE, MAX_POV_SIZE, }; -use polkadot_service::chain_spec::{get_account_id_from_seed, get_from_seed, Extensions}; +use polkadot_service::chain_spec::Extensions; +pub use polkadot_service::chain_spec::{get_account_id_from_seed, get_from_seed}; use polkadot_test_runtime::BABE_GENESIS_EPOCH_CONFIG; use sc_chain_spec::{ChainSpec, ChainType}; use sc_consensus_grandpa::AuthorityId as GrandpaId; diff --git a/polkadot/runtime/common/src/assigned_slots/mod.rs b/polkadot/runtime/common/src/assigned_slots/mod.rs index dd39789e10c..96c98c45954 100644 --- a/polkadot/runtime/common/src/assigned_slots/mod.rs +++ b/polkadot/runtime/common/src/assigned_slots/mod.rs @@ -186,6 +186,7 @@ pub mod pallet { pub struct GenesisConfig { pub max_temporary_slots: u32, pub max_permanent_slots: u32, + #[serde(skip)] pub _config: PhantomData, } diff --git a/polkadot/runtime/rococo/src/genesis_config_presets.rs b/polkadot/runtime/rococo/src/genesis_config_presets.rs index 17792cff186..c237dfd967f 100644 --- a/polkadot/runtime/rococo/src/genesis_config_presets.rs +++ b/polkadot/runtime/rococo/src/genesis_config_presets.rs @@ -16,10 +16,13 @@ //! Genesis configs presets for the Rococo runtime -use crate::{SessionKeys, BABE_GENESIS_EPOCH_CONFIG}; +use crate::{ + BabeConfig, BalancesConfig, ConfigurationConfig, RegistrarConfig, RuntimeGenesisConfig, + SessionConfig, SessionKeys, SudoConfig, BABE_GENESIS_EPOCH_CONFIG, +}; #[cfg(not(feature = "std"))] use alloc::format; -use alloc::vec::Vec; +use alloc::{vec, vec::Vec}; use polkadot_primitives::{AccountId, AccountPublic, AssignmentId, SchedulerParams, ValidatorId}; use rococo_runtime_constants::currency::UNITS as ROC; use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId; @@ -27,6 +30,7 @@ use sp_consensus_babe::AuthorityId as BabeId; use sp_consensus_beefy::ecdsa_crypto::AuthorityId as BeefyId; use sp_consensus_grandpa::AuthorityId as GrandpaId; use sp_core::{sr25519, Pair, Public}; +use sp_genesis_builder::PresetId; use sp_runtime::traits::IdentifyAccount; /// Helper function to generate a crypto pair from seed @@ -178,12 +182,12 @@ fn rococo_testnet_genesis( const ENDOWMENT: u128 = 1_000_000 * ROC; - serde_json::json!({ - "balances": { - "balances": endowed_accounts.iter().map(|k| (k.clone(), ENDOWMENT)).collect::>(), + let config = RuntimeGenesisConfig { + balances: BalancesConfig { + balances: endowed_accounts.iter().map(|k| (k.clone(), ENDOWMENT)).collect::>(), }, - "session": { - "keys": initial_authorities + session: SessionConfig { + keys: initial_authorities .iter() .map(|x| { ( @@ -200,13 +204,12 @@ fn rococo_testnet_genesis( ) }) .collect::>(), + ..Default::default() }, - "babe": { - "epochConfig": Some(BABE_GENESIS_EPOCH_CONFIG), - }, - "sudo": { "key": Some(root_key.clone()) }, - "configuration": { - "config": polkadot_runtime_parachains::configuration::HostConfiguration { + babe: BabeConfig { epoch_config: BABE_GENESIS_EPOCH_CONFIG, ..Default::default() }, + sudo: SudoConfig { key: Some(root_key.clone()) }, + configuration: ConfigurationConfig { + config: polkadot_runtime_parachains::configuration::HostConfiguration { scheduler_params: SchedulerParams { max_validators_per_core: Some(1), ..default_parachains_host_configuration().scheduler_params @@ -214,10 +217,14 @@ fn rococo_testnet_genesis( ..default_parachains_host_configuration() }, }, - "registrar": { - "nextFreeParaId": polkadot_primitives::LOWEST_PUBLIC_ID, - } - }) + registrar: RegistrarConfig { + next_free_para_id: polkadot_primitives::LOWEST_PUBLIC_ID, + ..Default::default() + }, + ..Default::default() + }; + + serde_json::to_value(config).expect("Could not build genesis config.") } // staging_testnet @@ -439,44 +446,32 @@ fn rococo_staging_testnet_config_genesis() -> serde_json::Value { const ENDOWMENT: u128 = 1_000_000 * ROC; const STASH: u128 = 100 * ROC; - serde_json::json!({ - "balances": { - "balances": endowed_accounts + let config = RuntimeGenesisConfig { + balances: BalancesConfig { + balances: endowed_accounts .iter() .map(|k: &AccountId| (k.clone(), ENDOWMENT)) .chain(initial_authorities.iter().map(|x| (x.0.clone(), STASH))) .collect::>(), }, - "session": { - "keys": initial_authorities + session: SessionConfig { + keys: initial_authorities .into_iter() - .map(|x| { - ( - x.0.clone(), - x.0, - rococo_session_keys( - x.2, - x.3, - x.4, - x.5, - x.6, - x.7, - ), - ) - }) + .map(|x| (x.0.clone(), x.0, rococo_session_keys(x.2, x.3, x.4, x.5, x.6, x.7))) .collect::>(), + ..Default::default() }, - "babe": { - "epochConfig": Some(BABE_GENESIS_EPOCH_CONFIG), - }, - "sudo": { "key": Some(endowed_accounts[0].clone()) }, - "configuration": { - "config": default_parachains_host_configuration(), - }, - "registrar": { - "nextFreeParaId": polkadot_primitives::LOWEST_PUBLIC_ID, + babe: BabeConfig { epoch_config: BABE_GENESIS_EPOCH_CONFIG, ..Default::default() }, + sudo: SudoConfig { key: Some(endowed_accounts[0].clone()) }, + configuration: ConfigurationConfig { config: default_parachains_host_configuration() }, + registrar: RegistrarConfig { + next_free_para_id: polkadot_primitives::LOWEST_PUBLIC_ID, + ..Default::default() }, - }) + ..Default::default() + }; + + serde_json::to_value(config).expect("Could not build genesis config.") } //development @@ -512,28 +507,12 @@ fn versi_local_testnet_genesis() -> serde_json::Value { ) } -/// Wococo is a temporary testnet that uses almost the same runtime as rococo. -//wococo_local_testnet -fn wococo_local_testnet_genesis() -> serde_json::Value { - rococo_testnet_genesis( - Vec::from([ - get_authority_keys_from_seed("Alice"), - get_authority_keys_from_seed("Bob"), - get_authority_keys_from_seed("Charlie"), - get_authority_keys_from_seed("Dave"), - ]), - get_account_id_from_seed::("Alice"), - None, - ) -} - /// Provides the JSON representation of predefined genesis config for given `id`. -pub fn get_preset(id: &sp_genesis_builder::PresetId) -> Option> { +pub fn get_preset(id: &PresetId) -> Option> { let patch = match id.try_into() { - Ok("local_testnet") => rococo_local_testnet_genesis(), - Ok("development") => rococo_development_config_genesis(), + Ok(sp_genesis_builder::LOCAL_TESTNET_RUNTIME_PRESET) => rococo_local_testnet_genesis(), + Ok(sp_genesis_builder::DEV_RUNTIME_PRESET) => rococo_development_config_genesis(), Ok("staging_testnet") => rococo_staging_testnet_config_genesis(), - Ok("wococo_local_testnet") => wococo_local_testnet_genesis(), Ok("versi_local_testnet") => versi_local_testnet_genesis(), _ => return None, }; @@ -543,3 +522,13 @@ pub fn get_preset(id: &sp_genesis_builder::PresetId) -> Option Vec { + vec![ + PresetId::from(sp_genesis_builder::LOCAL_TESTNET_RUNTIME_PRESET), + PresetId::from(sp_genesis_builder::DEV_RUNTIME_PRESET), + PresetId::from("staging_testnet"), + PresetId::from("versi_local_testnet"), + ] +} diff --git a/polkadot/runtime/rococo/src/lib.rs b/polkadot/runtime/rococo/src/lib.rs index 6ec49c5830f..e5396034a44 100644 --- a/polkadot/runtime/rococo/src/lib.rs +++ b/polkadot/runtime/rococo/src/lib.rs @@ -2582,13 +2582,7 @@ sp_api::impl_runtime_apis! { } fn preset_names() -> Vec { - vec![ - PresetId::from("local_testnet"), - PresetId::from("development"), - PresetId::from("staging_testnet"), - PresetId::from("wococo_local_testnet"), - PresetId::from("versi_local_testnet"), - ] + genesis_config_presets::preset_names() } } } diff --git a/polkadot/runtime/westend/Cargo.toml b/polkadot/runtime/westend/Cargo.toml index 83c0eb037f4..28ffd9fb150 100644 --- a/polkadot/runtime/westend/Cargo.toml +++ b/polkadot/runtime/westend/Cargo.toml @@ -17,11 +17,13 @@ scale-info = { features = ["derive"], workspace = true } log = { workspace = true } serde = { workspace = true } serde_derive = { optional = true, workspace = true } +serde_json = { features = ["alloc"], workspace = true } smallvec = { workspace = true, default-features = true } sp-authority-discovery = { workspace = true } sp-consensus-babe = { workspace = true } sp-consensus-beefy = { workspace = true } +sp-consensus-grandpa = { workspace = true } binary-merkle-tree = { workspace = true } sp-inherents = { workspace = true } sp-offchain = { workspace = true } @@ -103,7 +105,7 @@ pallet-election-provider-support-benchmarking = { optional = true, workspace = t pallet-nomination-pools-benchmarking = { optional = true, workspace = true } pallet-offences-benchmarking = { optional = true, workspace = true } pallet-session-benchmarking = { optional = true, workspace = true } -hex-literal = { optional = true, workspace = true, default-features = true } +hex-literal = { workspace = true, default-features = true } polkadot-runtime-common = { workspace = true } polkadot-primitives = { workspace = true } @@ -116,7 +118,6 @@ xcm-builder = { workspace = true } xcm-runtime-apis = { workspace = true } [dev-dependencies] -hex-literal = { workspace = true, default-features = true } tiny-keccak = { features = ["keccak"], workspace = true } sp-keyring = { workspace = true, default-features = true } serde_json = { workspace = true, default-features = true } @@ -203,6 +204,7 @@ std = [ "scale-info/std", "serde/std", "serde_derive", + "serde_json/std", "sp-api/std", "sp-application-crypto/std", "sp-arithmetic/std", @@ -210,6 +212,7 @@ std = [ "sp-block-builder/std", "sp-consensus-babe/std", "sp-consensus-beefy/std", + "sp-consensus-grandpa/std", "sp-core/std", "sp-genesis-builder/std", "sp-inherents/std", @@ -236,7 +239,6 @@ runtime-benchmarks = [ "frame-support/runtime-benchmarks", "frame-system-benchmarking/runtime-benchmarks", "frame-system/runtime-benchmarks", - "hex-literal", "pallet-asset-rate/runtime-benchmarks", "pallet-babe/runtime-benchmarks", "pallet-bags-list/runtime-benchmarks", diff --git a/polkadot/runtime/westend/src/genesis_config_presets.rs b/polkadot/runtime/westend/src/genesis_config_presets.rs new file mode 100644 index 00000000000..f59bacce31b --- /dev/null +++ b/polkadot/runtime/westend/src/genesis_config_presets.rs @@ -0,0 +1,459 @@ +// Copyright (C) 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 . + +//! Genesis configs presets for the Westend runtime + +use crate::{ + BabeConfig, BalancesConfig, ConfigurationConfig, RegistrarConfig, RuntimeGenesisConfig, + SessionConfig, SessionKeys, StakingConfig, SudoConfig, BABE_GENESIS_EPOCH_CONFIG, +}; +#[cfg(not(feature = "std"))] +use alloc::format; +use alloc::{vec, vec::Vec}; +use pallet_staking::{Forcing, StakerStatus}; +use polkadot_primitives::{AccountId, AccountPublic, AssignmentId, SchedulerParams, ValidatorId}; +use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId; +use sp_consensus_babe::AuthorityId as BabeId; +use sp_consensus_beefy::ecdsa_crypto::AuthorityId as BeefyId; +use sp_consensus_grandpa::AuthorityId as GrandpaId; +use sp_core::{sr25519, Pair, Public}; +use sp_genesis_builder::PresetId; +use sp_runtime::{traits::IdentifyAccount, Perbill}; +use westend_runtime_constants::currency::UNITS as WND; + +/// Helper function to generate a crypto pair from seed +fn get_from_seed(seed: &str) -> ::Public { + TPublic::Pair::from_string(&format!("//{}", seed), None) + .expect("static values are valid; qed") + .public() +} + +/// Helper function to generate an account ID from seed +fn get_account_id_from_seed(seed: &str) -> AccountId +where + AccountPublic: From<::Public>, +{ + AccountPublic::from(get_from_seed::(seed)).into_account() +} + +/// Helper function to generate stash, controller and session key from seed +fn get_authority_keys_from_seed( + seed: &str, +) -> ( + AccountId, + AccountId, + BabeId, + GrandpaId, + ValidatorId, + AssignmentId, + AuthorityDiscoveryId, + BeefyId, +) { + let keys = get_authority_keys_from_seed_no_beefy(seed); + (keys.0, keys.1, keys.2, keys.3, keys.4, keys.5, keys.6, get_from_seed::(seed)) +} + +/// Helper function to generate stash, controller and session key from seed +fn get_authority_keys_from_seed_no_beefy( + seed: &str, +) -> (AccountId, AccountId, BabeId, GrandpaId, ValidatorId, AssignmentId, AuthorityDiscoveryId) { + ( + get_account_id_from_seed::(&format!("{}//stash", seed)), + get_account_id_from_seed::(seed), + get_from_seed::(seed), + get_from_seed::(seed), + get_from_seed::(seed), + get_from_seed::(seed), + get_from_seed::(seed), + ) +} + +fn testnet_accounts() -> Vec { + Vec::from([ + get_account_id_from_seed::("Alice"), + get_account_id_from_seed::("Bob"), + get_account_id_from_seed::("Charlie"), + get_account_id_from_seed::("Dave"), + get_account_id_from_seed::("Eve"), + get_account_id_from_seed::("Ferdie"), + get_account_id_from_seed::("Alice//stash"), + get_account_id_from_seed::("Bob//stash"), + get_account_id_from_seed::("Charlie//stash"), + get_account_id_from_seed::("Dave//stash"), + get_account_id_from_seed::("Eve//stash"), + get_account_id_from_seed::("Ferdie//stash"), + ]) +} + +fn westend_session_keys( + babe: BabeId, + grandpa: GrandpaId, + para_validator: ValidatorId, + para_assignment: AssignmentId, + authority_discovery: AuthorityDiscoveryId, + beefy: BeefyId, +) -> SessionKeys { + SessionKeys { babe, grandpa, para_validator, para_assignment, authority_discovery, beefy } +} + +fn default_parachains_host_configuration( +) -> polkadot_runtime_parachains::configuration::HostConfiguration +{ + use polkadot_primitives::{ + node_features::FeatureIndex, ApprovalVotingParams, AsyncBackingParams, MAX_CODE_SIZE, + MAX_POV_SIZE, + }; + + polkadot_runtime_parachains::configuration::HostConfiguration { + validation_upgrade_cooldown: 2u32, + validation_upgrade_delay: 2, + code_retention_period: 1200, + max_code_size: MAX_CODE_SIZE, + max_pov_size: MAX_POV_SIZE, + max_head_data_size: 32 * 1024, + max_upward_queue_count: 8, + max_upward_queue_size: 1024 * 1024, + max_downward_message_size: 1024 * 1024, + 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_channel_max_message_size: 1024 * 1024, + hrmp_max_parachain_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, + minimum_validation_upgrade_delay: 5, + async_backing_params: AsyncBackingParams { + max_candidate_depth: 3, + allowed_ancestry_len: 2, + }, + node_features: bitvec::vec::BitVec::from_element( + 1u8 << (FeatureIndex::ElasticScalingMVP as usize) | + 1u8 << (FeatureIndex::EnableAssignmentsV2 as usize), + ), + scheduler_params: SchedulerParams { + lookahead: 2, + group_rotation_frequency: 20, + paras_availability_period: 4, + ..Default::default() + }, + approval_voting_params: ApprovalVotingParams { max_approval_coalesce_count: 5 }, + ..Default::default() + } +} + +#[test] +fn default_parachains_host_configuration_is_consistent() { + default_parachains_host_configuration().panic_if_not_consistent(); +} + +/// Helper function to create westend runtime `GenesisConfig` patch for testing +fn westend_testnet_genesis( + initial_authorities: Vec<( + AccountId, + AccountId, + BabeId, + GrandpaId, + ValidatorId, + AssignmentId, + AuthorityDiscoveryId, + BeefyId, + )>, + root_key: AccountId, + endowed_accounts: Option>, +) -> serde_json::Value { + let endowed_accounts: Vec = endowed_accounts.unwrap_or_else(testnet_accounts); + + const ENDOWMENT: u128 = 1_000_000 * WND; + const STASH: u128 = 100 * WND; + + let config = RuntimeGenesisConfig { + balances: BalancesConfig { + balances: endowed_accounts.iter().map(|k| (k.clone(), ENDOWMENT)).collect::>(), + }, + session: SessionConfig { + keys: initial_authorities + .iter() + .map(|x| { + ( + x.0.clone(), + x.0.clone(), + westend_session_keys( + x.2.clone(), + x.3.clone(), + x.4.clone(), + x.5.clone(), + x.6.clone(), + x.7.clone(), + ), + ) + }) + .collect::>(), + ..Default::default() + }, + staking: StakingConfig { + minimum_validator_count: 1, + validator_count: initial_authorities.len() as u32, + stakers: initial_authorities + .iter() + .map(|x| (x.0.clone(), x.0.clone(), STASH, StakerStatus::::Validator)) + .collect::>(), + invulnerables: initial_authorities.iter().map(|x| x.0.clone()).collect::>(), + force_era: Forcing::NotForcing, + slash_reward_fraction: Perbill::from_percent(10), + ..Default::default() + }, + babe: BabeConfig { epoch_config: BABE_GENESIS_EPOCH_CONFIG, ..Default::default() }, + sudo: SudoConfig { key: Some(root_key) }, + configuration: ConfigurationConfig { config: default_parachains_host_configuration() }, + registrar: RegistrarConfig { + next_free_para_id: polkadot_primitives::LOWEST_PUBLIC_ID, + ..Default::default() + }, + ..Default::default() + }; + + serde_json::to_value(config).expect("Could not build genesis config.") +} + +// staging_testnet +fn westend_staging_testnet_config_genesis() -> serde_json::Value { + use hex_literal::hex; + use sp_core::crypto::UncheckedInto; + + // Following keys are used in genesis config for development chains. + // DO NOT use them in production chains as the secret seed is public. + // + // SECRET_SEED="slow awkward present example safe bundle science ocean cradle word tennis earn" + // subkey inspect -n polkadot "$SECRET_SEED" + let endowed_accounts = vec![ + // 15S75FkhCWEowEGfxWwVfrW3LQuy8w8PNhVmrzfsVhCMjUh1 + hex!["c416837e232d9603e83162ef4bda08e61580eeefe60fe92fc044aa508559ae42"].into(), + ]; + // SECRET=$SECRET_SEED ./scripts/prepare-test-net.sh 4 + let initial_authorities: Vec<( + AccountId, + AccountId, + BabeId, + GrandpaId, + ValidatorId, + AssignmentId, + AuthorityDiscoveryId, + BeefyId, + )> = Vec::from([ + ( + //5EvydUTtHvt39Khac3mMxNPgzcfu49uPDzUs3TL7KEzyrwbw + hex!["7ecfd50629cdd246649959d88d490b31508db511487e111a52a392e6e458f518"].into(), + //5HQyX5gyy77m9QLXguAhiwjTArHYjYspeY98dYDu1JDetfZg + hex!["eca2cca09bdc66a7e6d8c3d9499a0be2ad4690061be8a9834972e17d13d2fe7e"].into(), + //5G13qYRudTyttwTJvHvnwp8StFtcfigyPnwfD4v7LNopsnX4 + hex!["ae27367cb77850fb195fe1f9c60b73210409e68c5ad953088070f7d8513d464c"] + .unchecked_into(), + //5Eb7wM65PNgtY6e33FEAzYtU5cRTXt6WQvZTnzaKQwkVcABk + hex!["6faae44b21c6f2681a7f60df708e9f79d340f7d441d28bd987fab8d05c6487e8"] + .unchecked_into(), + //5FqMLAgygdX9UqzukDp15Uid9PAKdFAR621U7xtp5ut2NfrW + hex!["a6c1a5b501985a83cb1c37630c5b41e6b0a15b3675b2fd94694758e6cfa6794d"] + .unchecked_into(), + //5DhXAV75BKvF9o447ikWqLttyL2wHtLMFSX7GrsKF9Ny61Ta + hex!["485051748ab9c15732f19f3fbcf1fd00a6d9709635f084505107fbb059c33d2f"] + .unchecked_into(), + //5GNHfmrtWLTawnGCmc39rjAEiW97vKvE7DGePYe4am5JtE4i + hex!["be59ed75a72f7b47221ce081ba4262cf2e1ea7867e30e0b3781822f942b97677"] + .unchecked_into(), + //5DA6Z8RUF626stn94aTRBCeobDCYcFbU7Pdk4Tz1R9vA8B8F + hex!["0207e43990799e1d02b0507451e342a1240ff836ea769c57297589a5fd072ad8f4"] + .unchecked_into(), + ), + ( + //5DFpvDUdCgw54E3E357GR1PyJe3Ft9s7Qyp7wbELAoJH9RQa + hex!["34b7b3efd35fcc3c1926ca065381682b1af29b57dabbcd091042c6de1d541b7d"].into(), + //5DZSSsND5wCjngvyXv27qvF3yPzt3MCU8rWnqNy4imqZmjT8 + hex!["4226796fa792ac78875e023ff2e30e3c2cf79f0b7b3431254cd0f14a3007bc0e"].into(), + //5CPrgfRNDQvQSnLRdeCphP3ibj5PJW9ESbqj2fw29vBMNQNn + hex!["0e9b60f04be3bffe362eb2212ea99d2b909b052f4bff7c714e13c2416a797f5d"] + .unchecked_into(), + //5FXFsPReTUEYPRNKhbTdUathcWBsxTNsLbk2mTpYdKCJewjA + hex!["98f4d81cb383898c2c3d54dab28698c0f717c81b509cb32dc6905af3cc697b18"] + .unchecked_into(), + //5CZjurB78XbSHf6SLkLhCdkqw52Zm7aBYUDdfkLqEDWJ9Zhj + hex!["162508accd470e379b04cb0c7c60b35a7d5357e84407a89ed2dd48db4b726960"] + .unchecked_into(), + //5DkAqCtSjUMVoJFauuGoAbSEgn2aFCRGziKJiLGpPwYgE1pS + hex!["4a559c028b69a7f784ce553393e547bec0aa530352157603396d515f9c83463b"] + .unchecked_into(), + //5GsBt9MhGwkg8Jfb1F9LAy2kcr88WNyNy4L5ezwbCr8NWKQU + hex!["d464908266c878acbf181bf8fda398b3aa3fd2d05508013e414aaece4cf0d702"] + .unchecked_into(), + //5DtJVkz8AHevEnpszy3X4dUcPvACW6x1qBMQZtFxjexLr5bq + hex!["02fdf30222d2cb88f2376d558d3de9cb83f9fde3aa4b2dd40c93e3104e3488bcd2"] + .unchecked_into(), + ), + ( + //5E2cob2jrXsBkTih56pizwSqENjE4siaVdXhaD6akLdDyVq7 + hex!["56e0f73c563d49ee4a3971c393e17c44eaa313dabad7fcf297dc3271d803f303"].into(), + //5D4rNYgP9uFNi5GMyDEXTfiaFLjXyDEEX2VvuqBVi3f1qgCh + hex!["2c58e5e1d5aef77774480cead4f6876b1a1a6261170166995184d7f86140572b"].into(), + //5Ea2D65KXqe625sz4uV1jjhSfuigVnkezC8VgEj9LXN7ERAk + hex!["6ed45cb7af613be5d88a2622921e18d147225165f24538af03b93f2a03ce6e13"] + .unchecked_into(), + //5G4kCbgqUhEyrRHCyFwFEkgBZXoYA8sbgsRxT9rY8Tp5Jj5F + hex!["b0f8d2b9e4e1eafd4dab6358e0b9d5380d78af27c094e69ae9d6d30ca300fd86"] + .unchecked_into(), + //5CS7thd2n54WfqeKU3cjvZzK4z5p7zku1Zw97mSzXgPioAAs + hex!["1055100a283968271a0781450b389b9093231be809be1e48a305ebad2a90497e"] + .unchecked_into(), + //5DSaL4ZmSYarZSazhL5NQh7LT6pWhNRDcefk2QS9RxEXfsJe + hex!["3cea4ab74bab4adf176cf05a6e18c1599a7bc217d4c6c217275bfbe3b037a527"] + .unchecked_into(), + //5CaNLkYEbFYXZodXhd3UjV6RNLjFGNLiYafc8X5NooMkZiAq + hex!["169faa81aebfe74533518bda28567f2e2664014c8905aa07ea003336afda5a58"] + .unchecked_into(), + //5ERwhKiePayukzZStMuzGzRJGxGRFpwxYUXVarQpMSMrXzDS + hex!["03429d0d20f6ac5ca8b349f04d014f7b5b864acf382a744104d5d9a51108156c0f"] + .unchecked_into(), + ), + ( + //5H6j9ovzYk9opckVjvM9SvVfaK37ASTtPTzWeRfqk1tgLJUN + hex!["deb804ed2ed2bb696a3dd4ed7de4cd5c496528a2b204051c6ace385bacd66a3a"].into(), + //5DJ51tMW916mGwjMpfS1o9skcNt6Sb28YnZQXaKVg4h89agE + hex!["366da6a748afedb31f07902f2de36ab265beccee37762d3ae1f237de234d9c36"].into(), + //5CSPYDYoCDGSoSLgSp4EHkJ52YasZLHG2woqhPZkdbtNQpke + hex!["1089bc0cd60237d061872925e81d36c9d9205d250d5d8b542c8e08a8ecf1b911"] + .unchecked_into(), + //5ChfdrAqmLjCeDJvynbMjcxYLHYzPe8UWXd3HnX9JDThUMbn + hex!["1c309a70b4e274314b84c9a0a1f973c9c4fc084df5479ef686c54b1ae4950424"] + .unchecked_into(), + //5D8C3HHEp5E8fJsXRD56494F413CdRSR9QKGXe7v5ZEfymdj + hex!["2ee4d78f328db178c54f205ac809da12e291a33bcbd4f29f081ce7e74bdc5044"] + .unchecked_into(), + //5GxeTYCGmp1C3ZRLDkRWqJc6gB2GYmuqnygweuH3vsivMQq6 + hex!["d88e40e3c2c7a7c5abf96ffdd8f7b7bec8798cc277bc97e255881871ab73b529"] + .unchecked_into(), + //5DoGpsgSLcJsHa9B8V4PKjxegWAqDZttWfxicAd68prUX654 + hex!["4cb3863271b70daa38612acd5dae4f5afcb7c165fa277629e5150d2214df322a"] + .unchecked_into(), + //5G1KLjqFyMsPAodnjSRkwRFJztTTEzmZWxow2Q3ZSRCPdthM + hex!["03be5ec86d10a94db89c9b7a396d3c7742e3bec5f85159d4cf308cef505966ddf5"] + .unchecked_into(), + ), + ]); + + const ENDOWMENT: u128 = 1_000_000 * WND; + const STASH: u128 = 100 * WND; + + let config = RuntimeGenesisConfig { + balances: BalancesConfig { + balances: endowed_accounts + .iter() + .map(|k: &AccountId| (k.clone(), ENDOWMENT)) + .chain(initial_authorities.iter().map(|x| (x.0.clone(), STASH))) + .collect::>(), + }, + session: SessionConfig { + keys: initial_authorities + .iter() + .map(|x| { + ( + x.0.clone(), + x.0.clone(), + westend_session_keys( + x.2.clone(), + x.3.clone(), + x.4.clone(), + x.5.clone(), + x.6.clone(), + x.7.clone(), + ), + ) + }) + .collect::>(), + ..Default::default() + }, + staking: StakingConfig { + validator_count: 50, + minimum_validator_count: 4, + stakers: initial_authorities + .iter() + .map(|x| (x.0.clone(), x.0.clone(), STASH, StakerStatus::::Validator)) + .collect::>(), + invulnerables: initial_authorities.iter().map(|x| x.0.clone()).collect::>(), + force_era: Forcing::ForceNone, + slash_reward_fraction: Perbill::from_percent(10), + ..Default::default() + }, + babe: BabeConfig { epoch_config: BABE_GENESIS_EPOCH_CONFIG, ..Default::default() }, + sudo: SudoConfig { key: Some(endowed_accounts[0].clone()) }, + configuration: ConfigurationConfig { config: default_parachains_host_configuration() }, + registrar: RegistrarConfig { + next_free_para_id: polkadot_primitives::LOWEST_PUBLIC_ID, + ..Default::default() + }, + ..Default::default() + }; + + serde_json::to_value(config).expect("Could not build genesis config.") +} + +//development +fn westend_development_config_genesis() -> serde_json::Value { + westend_testnet_genesis( + Vec::from([get_authority_keys_from_seed("Alice")]), + get_account_id_from_seed::("Alice"), + None, + ) +} + +//local_testnet +fn westend_local_testnet_genesis() -> serde_json::Value { + westend_testnet_genesis( + Vec::from([get_authority_keys_from_seed("Alice"), get_authority_keys_from_seed("Bob")]), + get_account_id_from_seed::("Alice"), + None, + ) +} + +/// Provides the JSON representation of predefined genesis config for given `id`. +pub fn get_preset(id: &PresetId) -> Option> { + let patch = match id.try_into() { + Ok(sp_genesis_builder::LOCAL_TESTNET_RUNTIME_PRESET) => westend_local_testnet_genesis(), + Ok(sp_genesis_builder::DEV_RUNTIME_PRESET) => westend_development_config_genesis(), + Ok("staging_testnet") => westend_staging_testnet_config_genesis(), + _ => return None, + }; + Some( + serde_json::to_string(&patch) + .expect("serialization to json is expected to work. qed.") + .into_bytes(), + ) +} + +/// List of supported presets. +pub fn preset_names() -> Vec { + vec![ + PresetId::from(sp_genesis_builder::LOCAL_TESTNET_RUNTIME_PRESET), + PresetId::from(sp_genesis_builder::DEV_RUNTIME_PRESET), + PresetId::from("staging_testnet"), + ] +} diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index d0c1cd89de3..6719a037384 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -120,8 +120,6 @@ use xcm_runtime_apis::{ pub use frame_system::Call as SystemCall; pub use pallet_balances::Call as BalancesCall; pub use pallet_election_provider_multi_phase::{Call as EPMCall, GeometricDepositBase}; -#[cfg(feature = "std")] -pub use pallet_staking::StakerStatus; use pallet_staking::UseValidatorsMap; pub use pallet_timestamp::Call as TimestampCall; use sp_runtime::traits::Get; @@ -137,6 +135,7 @@ use westend_runtime_constants::{ }; mod bag_thresholds; +mod genesis_config_presets; mod weights; pub mod xcm_config; @@ -2756,11 +2755,11 @@ sp_api::impl_runtime_apis! { } fn get_preset(id: &Option) -> Option> { - get_preset::(id, |_| None) + get_preset::(id, &genesis_config_presets::get_preset) } fn preset_names() -> Vec { - vec![] + genesis_config_presets::preset_names() } } } diff --git a/polkadot/xcm/xcm-executor/integration-tests/Cargo.toml b/polkadot/xcm/xcm-executor/integration-tests/Cargo.toml index e669e5d2b23..7e6bfe967b9 100644 --- a/polkadot/xcm/xcm-executor/integration-tests/Cargo.toml +++ b/polkadot/xcm/xcm-executor/integration-tests/Cargo.toml @@ -13,14 +13,12 @@ workspace = true [dependencies] codec = { workspace = true, default-features = true } frame-support = { workspace = true } -frame-system = { workspace = true, default-features = true } futures = { workspace = true } pallet-transaction-payment = { workspace = true, default-features = true } pallet-xcm = { workspace = true, default-features = true } polkadot-test-client = { workspace = true } polkadot-test-runtime = { workspace = true } polkadot-test-service = { workspace = true } -polkadot-service = { workspace = true, default-features = true } sp-consensus = { workspace = true, default-features = true } sp-keyring = { workspace = true, default-features = true } sp-runtime = { workspace = true } diff --git a/polkadot/xcm/xcm-executor/integration-tests/src/lib.rs b/polkadot/xcm/xcm-executor/integration-tests/src/lib.rs index 7683c602539..6f44cc0a75d 100644 --- a/polkadot/xcm/xcm-executor/integration-tests/src/lib.rs +++ b/polkadot/xcm/xcm-executor/integration-tests/src/lib.rs @@ -18,13 +18,12 @@ use codec::Encode; use frame_support::{dispatch::GetDispatchInfo, weights::Weight}; -use polkadot_service::chain_spec::get_account_id_from_seed; use polkadot_test_client::{ BlockBuilderExt, ClientBlockImportExt, DefaultTestClientBuilderExt, InitPolkadotBlockBuilder, TestClientBuilder, TestClientBuilderExt, }; use polkadot_test_runtime::{pallet_test_notifier, xcm_config::XcmConfig}; -use polkadot_test_service::construct_extrinsic; +use polkadot_test_service::{chain_spec::get_account_id_from_seed, construct_extrinsic}; use sp_core::sr25519; use sp_runtime::traits::Block; use sp_state_machine::InspectState; diff --git a/prdoc/pr_5327.prdoc b/prdoc/pr_5327.prdoc new file mode 100644 index 00000000000..a3821790590 --- /dev/null +++ b/prdoc/pr_5327.prdoc @@ -0,0 +1,43 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "Moved presets to the testnet runtimes" + +doc: + - audience: Runtime Dev + description: | + This PR migrates the genesis config presets from `polkadot-parachain-bin` to the relevant runtimes. + +crates: + - name: polkadot-runtime-common + bump: patch + - name: rococo-runtime + bump: patch + - name: westend-runtime + bump: patch + - name: parachains-common + bump: patch + - name: testnet-parachains-constants + bump: patch + - name: asset-hub-rococo-runtime + bump: patch + - name: asset-hub-westend-runtime + bump: patch + - name: bridge-hub-rococo-runtime + bump: patch + - name: bridge-hub-westend-runtime + bump: patch + - name: collectives-westend-runtime + bump: patch + - name: polkadot-parachain-bin + bump: patch + - name: polkadot-runtime-parachains + bump: none + - name: polkadot-service + bump: major + - name: polkadot-cli + bump: patch + - name: sc-chain-spec + bump: none + - name: sp-genesis-builder + bump: none diff --git a/substrate/client/chain-spec/src/genesis_config_builder.rs b/substrate/client/chain-spec/src/genesis_config_builder.rs index 13a2f3c072f..66989495d42 100644 --- a/substrate/client/chain-spec/src/genesis_config_builder.rs +++ b/substrate/client/chain-spec/src/genesis_config_builder.rs @@ -27,6 +27,7 @@ use sp_core::{ traits::{CallContext, CodeExecutor, Externalities, FetchRuntimeCode, RuntimeCode}, }; use sp_genesis_builder::{PresetId, Result as BuildResult}; +pub use sp_genesis_builder::{DEV_RUNTIME_PRESET, LOCAL_TESTNET_RUNTIME_PRESET}; use sp_state_machine::BasicExternalities; use std::borrow::Cow; diff --git a/substrate/client/chain-spec/src/lib.rs b/substrate/client/chain-spec/src/lib.rs index 5451428d348..43639ffb5aa 100644 --- a/substrate/client/chain-spec/src/lib.rs +++ b/substrate/client/chain-spec/src/lib.rs @@ -347,7 +347,9 @@ pub use self::{ construct_genesis_block, resolve_state_version_from_wasm, BuildGenesisBlock, GenesisBlockBuilder, }, - genesis_config_builder::GenesisConfigBuilderRuntimeCaller, + genesis_config_builder::{ + GenesisConfigBuilderRuntimeCaller, DEV_RUNTIME_PRESET, LOCAL_TESTNET_RUNTIME_PRESET, + }, json_patch::merge as json_merge, }; pub use sc_chain_spec_derive::{ChainSpecExtension, ChainSpecGroup}; @@ -357,9 +359,9 @@ use sc_telemetry::TelemetryEndpoints; use sp_core::storage::Storage; use sp_runtime::BuildStorage; -/// The type of a chain. +/// The type of chain. /// -/// This can be used by tools to determine the type of a chain for displaying +/// This can be used by tools to determine the type of chain for displaying /// additional information or enabling additional features. #[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Clone)] #[cfg_attr(feature = "clap", derive(clap::ValueEnum))] diff --git a/substrate/primitives/genesis-builder/src/lib.rs b/substrate/primitives/genesis-builder/src/lib.rs index b33609464fc..07317bc4cb5 100644 --- a/substrate/primitives/genesis-builder/src/lib.rs +++ b/substrate/primitives/genesis-builder/src/lib.rs @@ -64,8 +64,16 @@ pub type PresetId = sp_runtime::RuntimeString; /// The default `development` preset used to communicate with the runtime via /// [`GenesisBuilder`] interface. +/// +/// (Recommended for testing with a single node, e.g., for benchmarking) pub const DEV_RUNTIME_PRESET: &'static str = "development"; +/// The default `local_testnet` preset used to communicate with the runtime via +/// [`GenesisBuilder`] interface. +/// +/// (Recommended for local testing with multiple nodes) +pub const LOCAL_TESTNET_RUNTIME_PRESET: &'static str = "local_testnet"; + sp_api::decl_runtime_apis! { /// API to interact with RuntimeGenesisConfig for the runtime pub trait GenesisBuilder { @@ -89,7 +97,7 @@ sp_api::decl_runtime_apis! { /// /// Otherwise function returns a JSON representation of the built-in, named /// `RuntimeGenesisConfig` preset identified by `id`, or `None` if such preset does not - /// exists. Returned `Vec` contains bytes of JSON blob (patch) which comprises a list of + /// exist. Returned `Vec` contains bytes of JSON blob (patch) which comprises a list of /// (potentially nested) key-value pairs that are intended for customizing the default /// runtime genesis config. The patch shall be merged (rfc7386) with the JSON representation /// of the default `RuntimeGenesisConfig` to create a comprehensive genesis config that can diff --git a/templates/parachain/node/Cargo.toml b/templates/parachain/node/Cargo.toml index c782888a3e8..c0c81d8222a 100644 --- a/templates/parachain/node/Cargo.toml +++ b/templates/parachain/node/Cargo.toml @@ -51,6 +51,7 @@ sp-block-builder = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } sp-consensus-aura = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } +sp-genesis-builder = { workspace = true, default-features = true } sp-keystore = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } diff --git a/templates/parachain/node/src/chain_spec.rs b/templates/parachain/node/src/chain_spec.rs index cd02bca466f..7cdd362dffb 100644 --- a/templates/parachain/node/src/chain_spec.rs +++ b/templates/parachain/node/src/chain_spec.rs @@ -42,7 +42,7 @@ pub fn development_config() -> ChainSpec { .with_name("Development") .with_id("dev") .with_chain_type(ChainType::Development) - .with_genesis_config_preset_name("development") + .with_genesis_config_preset_name(sp_genesis_builder::DEV_RUNTIME_PRESET) .build() } @@ -65,7 +65,7 @@ pub fn local_testnet_config() -> ChainSpec { .with_name("Local Testnet") .with_id("local_testnet") .with_chain_type(ChainType::Local) - .with_genesis_config_preset_name("local_testnet") + .with_genesis_config_preset_name(sc_chain_spec::LOCAL_TESTNET_RUNTIME_PRESET) .with_protocol_id("template-local") .with_properties(properties) .build() diff --git a/templates/parachain/runtime/src/genesis_config_presets.rs b/templates/parachain/runtime/src/genesis_config_presets.rs index 80b763d5bd8..fec53d17394 100644 --- a/templates/parachain/runtime/src/genesis_config_presets.rs +++ b/templates/parachain/runtime/src/genesis_config_presets.rs @@ -1,44 +1,18 @@ use cumulus_primitives_core::ParaId; -pub use sp_consensus_aura::sr25519::AuthorityId as AuraId; - -use crate::{AccountId, SessionKeys, Signature, EXISTENTIAL_DEPOSIT}; -use alloc::{format, vec, vec::Vec}; +use crate::{ + AccountId, BalancesConfig, CollatorSelectionConfig, ParachainInfoConfig, PolkadotXcmConfig, + RuntimeGenesisConfig, SessionConfig, SessionKeys, SudoConfig, EXISTENTIAL_DEPOSIT, +}; +use alloc::{vec, vec::Vec}; +use parachains_common::{genesis_config_helpers::*, AuraId}; use serde_json::Value; -use sp_core::{sr25519, Pair, Public}; +use sp_core::sr25519; use sp_genesis_builder::PresetId; -use sp_runtime::traits::{IdentifyAccount, Verify}; - -/// Preset configuration name for a local testnet environment. -pub const PRESET_LOCAL_TESTNET: &str = "local_testnet"; - -type AccountPublic = ::Signer; /// The default XCM version to set in genesis config. const SAFE_XCM_VERSION: u32 = xcm::prelude::XCM_VERSION; -/// Helper function to generate a crypto pair from seed -pub fn get_from_seed(seed: &str) -> ::Public { - TPublic::Pair::from_string(&format!("//{}", seed), None) - .expect("static values are valid; qed") - .public() -} - -/// Generate collator keys from seed. -/// -/// This function's return type must always match the session keys of the chain in tuple format. -pub fn get_collator_keys_from_seed(seed: &str) -> AuraId { - get_from_seed::(seed) -} - -/// Helper function to generate an account ID from seed -pub fn get_account_id_from_seed(seed: &str) -> AccountId -where - AccountPublic: From<::Public>, -{ - AccountPublic::from(get_from_seed::(seed)).into_account() -} - /// Generate the session keys from individual elements. /// /// The input must be a tuple of individual keys (a single arg for now since we have just one key). @@ -52,19 +26,22 @@ fn testnet_genesis( root: AccountId, id: ParaId, ) -> Value { - serde_json::json!({ - "balances": { - "balances": endowed_accounts.iter().cloned().map(|k| (k, 1u64 << 60)).collect::>(), + let config = RuntimeGenesisConfig { + balances: BalancesConfig { + balances: endowed_accounts + .iter() + .cloned() + .map(|k| (k, 1u128 << 60)) + .collect::>(), }, - "parachainInfo": { - "parachainId": id, + parachain_info: ParachainInfoConfig { parachain_id: id, ..Default::default() }, + collator_selection: CollatorSelectionConfig { + invulnerables: invulnerables.iter().cloned().map(|(acc, _)| acc).collect::>(), + candidacy_bond: EXISTENTIAL_DEPOSIT * 16, + ..Default::default() }, - "collatorSelection": { - "invulnerables": invulnerables.iter().cloned().map(|(acc, _)| acc).collect::>(), - "candidacyBond": EXISTENTIAL_DEPOSIT * 16, - }, - "session": { - "keys": invulnerables + session: SessionConfig { + keys: invulnerables .into_iter() .map(|(acc, aura)| { ( @@ -73,13 +50,18 @@ fn testnet_genesis( template_session_keys(aura), // session keys ) }) - .collect::>(), + .collect::>(), + ..Default::default() }, - "polkadotXcm": { - "safeXcmVersion": Some(SAFE_XCM_VERSION), + polkadot_xcm: PolkadotXcmConfig { + safe_xcm_version: Some(SAFE_XCM_VERSION), + ..Default::default() }, - "sudo": { "key": Some(root) } - }) + sudo: SudoConfig { key: Some(root) }, + ..Default::default() + }; + + serde_json::to_value(config).expect("Could not build genesis config.") } fn local_testnet_genesis() -> Value { @@ -88,11 +70,11 @@ fn local_testnet_genesis() -> Value { vec![ ( get_account_id_from_seed::("Alice"), - get_collator_keys_from_seed("Alice"), + get_collator_keys_from_seed::("Alice"), ), ( get_account_id_from_seed::("Bob"), - get_collator_keys_from_seed("Bob"), + get_collator_keys_from_seed::("Bob"), ), ], vec![ @@ -120,11 +102,11 @@ fn development_config_genesis() -> Value { vec![ ( get_account_id_from_seed::("Alice"), - get_collator_keys_from_seed("Alice"), + get_collator_keys_from_seed::("Alice"), ), ( get_account_id_from_seed::("Bob"), - get_collator_keys_from_seed("Bob"), + get_collator_keys_from_seed::("Bob"), ), ], vec![ @@ -149,7 +131,7 @@ fn development_config_genesis() -> Value { /// Provides the JSON representation of predefined genesis config for given `id`. pub fn get_preset(id: &PresetId) -> Option> { let patch = match id.try_into() { - Ok(PRESET_LOCAL_TESTNET) => local_testnet_genesis(), + Ok(sp_genesis_builder::LOCAL_TESTNET_RUNTIME_PRESET) => local_testnet_genesis(), Ok(sp_genesis_builder::DEV_RUNTIME_PRESET) => development_config_genesis(), _ => return None, }; @@ -164,6 +146,6 @@ pub fn get_preset(id: &PresetId) -> Option> { pub fn preset_names() -> Vec { vec![ PresetId::from(sp_genesis_builder::DEV_RUNTIME_PRESET), - PresetId::from(PRESET_LOCAL_TESTNET), + PresetId::from(sp_genesis_builder::LOCAL_TESTNET_RUNTIME_PRESET), ] } -- GitLab From 128f6c79fcbbc06bd3c507f6b08d0af98a492192 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Sun, 22 Sep 2024 23:04:33 +0200 Subject: [PATCH 089/125] Fix RPC relay chain interface (#5796) Use `sp_core::Bytes` as `payload` to encode the values correctly as `hex` string. --- .../src/rpc_client.rs | 34 ++++++++++--------- prdoc/pr_5796.prdoc | 8 +++++ 2 files changed, 26 insertions(+), 16 deletions(-) create mode 100644 prdoc/pr_5796.prdoc diff --git a/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs b/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs index 381f4a046a4..d8e5abaddc6 100644 --- a/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs +++ b/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs @@ -23,7 +23,7 @@ use jsonrpsee::{ rpc_params, }; use prometheus::Registry; -use serde::de::DeserializeOwned; +use serde::{de::DeserializeOwned, Serialize}; use serde_json::Value as JsonValue; use std::collections::{btree_map::BTreeMap, VecDeque}; use tokio::sync::mpsc::Sender as TokioSender; @@ -122,6 +122,9 @@ pub async fn create_client_and_start_light_client_worker( Ok(client) } +#[derive(Serialize)] +struct PayloadToHex<'a>(#[serde(with = "sp_core::bytes")] &'a [u8]); + /// Client that maps RPC methods and deserializes results #[derive(Clone)] pub struct RelayChainRpcClient { @@ -153,27 +156,26 @@ impl RelayChainRpcClient { &self, method_name: &str, hash: RelayHash, - payload_bytes: &[u8], + payload: &[u8], ) -> RelayChainResult { + let payload = PayloadToHex(payload); + let params = rpc_params! { method_name, - payload_bytes, + payload, hash }; - let res = self - .request_tracing::("state_call", params, |err| { - tracing::trace!( - target: LOG_TARGET, - %method_name, - %hash, - error = %err, - "Error during call to 'state_call'.", - ); - }) - .await?; - - Ok(res) + self.request_tracing::("state_call", params, |err| { + tracing::trace!( + target: LOG_TARGET, + %method_name, + %hash, + error = %err, + "Error during call to 'state_call'.", + ); + }) + .await } /// Call a call to `state_call` rpc method. diff --git a/prdoc/pr_5796.prdoc b/prdoc/pr_5796.prdoc new file mode 100644 index 00000000000..76958e3db4f --- /dev/null +++ b/prdoc/pr_5796.prdoc @@ -0,0 +1,8 @@ +title: "Fix RPC relay chain interface" + +doc: + +crates: + - name: cumulus-relay-chain-rpc-interface + bump: none + validate: false -- GitLab From 2e4e5bf2fd0ae19fa38951c7e5f495dd1453b2bb Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Mon, 23 Sep 2024 00:28:38 +0200 Subject: [PATCH 090/125] [benchmarking] Reset to genesis storage after each run (#5655) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The genesis state is currently partially provided via `OverlayedChanges`, but these changes are reset by the runtime after the first repetition, causing the second repitition to use an invalid genesis state. Changes: - Provide the genesis state as a `Storage` without any `OverlayedChanges` to make it work correctly with repetitions. - Add `--genesis-builder-preset` option to use different genesis preset names. - Improve error messages. --------- Signed-off-by: Oliver Tale-Yazdi Co-authored-by: ggwpez Co-authored-by: Bastian Köcher Co-authored-by: Branislav Kontur --- prdoc/pr_5655.prdoc | 15 ++ .../node/cli/tests/benchmark_pallet_works.rs | 35 +++ .../benchmarking/pov/src/benchmarking.rs | 31 +++ .../benchmarking-cli/src/pallet/command.rs | 228 +++++++----------- .../frame/benchmarking-cli/src/pallet/mod.rs | 16 +- .../benchmarking-cli/src/pallet/types.rs | 22 +- .../benchmarking-cli/src/pallet/writer.rs | 7 +- 7 files changed, 197 insertions(+), 157 deletions(-) create mode 100644 prdoc/pr_5655.prdoc diff --git a/prdoc/pr_5655.prdoc b/prdoc/pr_5655.prdoc new file mode 100644 index 00000000000..bfa2e295d15 --- /dev/null +++ b/prdoc/pr_5655.prdoc @@ -0,0 +1,15 @@ +title: '[benchmarking] Reset to genesis storage after each run' +doc: +- audience: Runtime Dev + description: |- + The genesis state is currently partially provided via `OverlayedChanges`, but these changes are reset by the runtime after the first repetition, causing the second repitition to use an invalid genesis state. + + Changes: + - Provide the genesis state as a `Storage` without any `OverlayedChanges` to make it work correctly with repetitions. + - Add `--genesis-builder-preset` option to use different genesis preset names. + - Improve error messages. +crates: +- name: frame-benchmarking-cli + bump: major +- name: frame-benchmarking-pallet-pov + bump: patch diff --git a/substrate/bin/node/cli/tests/benchmark_pallet_works.rs b/substrate/bin/node/cli/tests/benchmark_pallet_works.rs index 8441333429b..d913228881a 100644 --- a/substrate/bin/node/cli/tests/benchmark_pallet_works.rs +++ b/substrate/bin/node/cli/tests/benchmark_pallet_works.rs @@ -33,6 +33,31 @@ fn benchmark_pallet_works() { benchmark_pallet(20, 50, true); } +#[test] +fn benchmark_pallet_args_work() { + benchmark_pallet_args(&["--list", "--pallet=pallet_balances"], true); + benchmark_pallet_args(&["--list", "--pallet=pallet_balances"], true); + benchmark_pallet_args( + &["--list", "--pallet=pallet_balances", "--genesis-builder=spec-genesis"], + true, + ); + benchmark_pallet_args( + &["--list", "--pallet=pallet_balances", "--chain=dev", "--genesis-builder=spec-genesis"], + true, + ); + + // Error because the genesis runtime does not have any presets in it: + benchmark_pallet_args( + &["--list", "--pallet=pallet_balances", "--chain=dev", "--genesis-builder=spec-runtime"], + false, + ); + // Error because no runtime is provided: + benchmark_pallet_args( + &["--list", "--pallet=pallet_balances", "--chain=dev", "--genesis-builder=runtime"], + false, + ); +} + fn benchmark_pallet(steps: u32, repeat: u32, should_work: bool) { let status = Command::new(cargo_bin("substrate-node")) .args(["benchmark", "pallet", "--dev"]) @@ -51,3 +76,13 @@ fn benchmark_pallet(steps: u32, repeat: u32, should_work: bool) { assert_eq!(status.success(), should_work); } + +fn benchmark_pallet_args(args: &[&str], should_work: bool) { + let status = Command::new(cargo_bin("substrate-node")) + .args(["benchmark", "pallet"]) + .args(args) + .status() + .unwrap(); + + assert_eq!(status.success(), should_work); +} diff --git a/substrate/frame/benchmarking/pov/src/benchmarking.rs b/substrate/frame/benchmarking/pov/src/benchmarking.rs index bf3d406d0b2..d52fcc2689c 100644 --- a/substrate/frame/benchmarking/pov/src/benchmarking.rs +++ b/substrate/frame/benchmarking/pov/src/benchmarking.rs @@ -26,6 +26,11 @@ use frame_support::traits::UnfilteredDispatchable; use frame_system::{Pallet as System, RawOrigin}; use sp_runtime::traits::Hash; +#[cfg(feature = "std")] +frame_support::parameter_types! { + pub static StorageRootHash: Option> = None; +} + #[benchmarks] mod benchmarks { use super::*; @@ -392,6 +397,32 @@ mod benchmarks { } } + #[benchmark] + fn storage_root_is_the_same_every_time(i: Linear<0, 10>) { + #[cfg(feature = "std")] + let root = sp_io::storage::root(sp_runtime::StateVersion::V1); + + #[cfg(feature = "std")] + match (i, StorageRootHash::get()) { + (0, Some(_)) => panic!("StorageRootHash should be None initially"), + (0, None) => StorageRootHash::set(Some(root)), + (_, Some(r)) if r == root => {}, + (_, Some(r)) => + panic!("StorageRootHash should be the same every time: {:?} vs {:?}", r, root), + (_, None) => panic!("StorageRootHash should be Some after the first iteration"), + } + + // Also test that everything is reset correctly: + sp_io::storage::set(b"key1", b"value"); + + #[block] + { + sp_io::storage::set(b"key2", b"value"); + } + + sp_io::storage::set(b"key3", b"value"); + } + impl_benchmark_test_suite!(Pallet, super::mock::new_test_ext(), super::mock::Test,); } diff --git a/substrate/utils/frame/benchmarking-cli/src/pallet/command.rs b/substrate/utils/frame/benchmarking-cli/src/pallet/command.rs index 47191981520..f1499f41c2e 100644 --- a/substrate/utils/frame/benchmarking-cli/src/pallet/command.rs +++ b/substrate/utils/frame/benchmarking-cli/src/pallet/command.rs @@ -19,7 +19,7 @@ use super::{ types::{ComponentRange, ComponentRangeMap}, writer, ListOutput, PalletCmd, }; -use crate::pallet::{types::FetchedCode, GenesisBuilder}; +use crate::pallet::{types::FetchedCode, GenesisBuilderPolicy}; use codec::{Decode, Encode}; use frame_benchmarking::{ Analysis, BenchmarkBatch, BenchmarkBatchSplitResults, BenchmarkList, BenchmarkParameter, @@ -27,7 +27,7 @@ use frame_benchmarking::{ }; use frame_support::traits::StorageInfo; use linked_hash_map::LinkedHashMap; -use sc_chain_spec::json_patch::merge as json_merge; +use sc_chain_spec::GenesisConfigBuilderRuntimeCaller; use sc_cli::{execution_method_from_cli, ChainSpec, CliConfiguration, Result, SharedParams}; use sc_client_db::BenchmarkingState; use sc_executor::{HeapAllocStrategy, WasmExecutor, DEFAULT_HEAP_ALLOC_STRATEGY}; @@ -36,18 +36,17 @@ use sp_core::{ testing::{TestOffchainExt, TestTransactionPoolExt}, OffchainDbExt, OffchainWorkerExt, TransactionPoolExt, }, - traits::{CallContext, CodeExecutor, ReadRuntimeVersionExt, WrappedRuntimeCode}, + traits::{CallContext, CodeExecutor, ReadRuntimeVersionExt}, Hasher, }; use sp_externalities::Extensions; -use sp_genesis_builder::{PresetId, Result as GenesisBuildResult}; use sp_keystore::{testing::MemoryKeystore, KeystoreExt}; use sp_runtime::traits::Hash; -use sp_state_machine::{OverlayedChanges, StateMachine}; +use sp_state_machine::StateMachine; +use sp_storage::{well_known_keys::CODE, Storage}; use sp_trie::{proof_size_extension::ProofSizeExt, recorder::Recorder}; use sp_wasm_interface::HostFunctions; use std::{ - borrow::Cow, collections::{BTreeMap, BTreeSet, HashMap}, fmt::Debug, fs, @@ -162,9 +161,6 @@ generate the genesis state is deprecated. Please remove the `--chain`/`--dev`/`- point `--runtime` to your runtime blob and set `--genesis-builder=runtime`. This warning may \ become a hard error any time after December 2024."; -/// The preset that we expect to find in the GenesisBuilder runtime API. -const GENESIS_PRESET: &str = "development"; - impl PalletCmd { /// Runs the command and benchmarks a pallet. #[deprecated( @@ -214,9 +210,7 @@ impl PalletCmd { return self.output_from_results(&batches) } - let (genesis_storage, genesis_changes) = - self.genesis_storage::(&chain_spec)?; - let mut changes = genesis_changes.clone(); + let genesis_storage = self.genesis_storage::(&chain_spec)?; let cache_size = Some(self.database_cache_size as usize); let state_with_tracking = BenchmarkingState::::new( @@ -262,7 +256,7 @@ impl PalletCmd { Self::exec_state_machine( StateMachine::new( state, - &mut changes, + &mut Default::default(), &executor, "Benchmark_benchmark_metadata", &(self.extra).encode(), @@ -347,7 +341,6 @@ impl PalletCmd { for (s, selected_components) in all_components.iter().enumerate() { // First we run a verification if !self.no_verify { - let mut changes = genesis_changes.clone(); let state = &state_without_tracking; // Don't use these results since verification code will add overhead. let _batch: Vec = match Self::exec_state_machine::< @@ -357,7 +350,7 @@ impl PalletCmd { >( StateMachine::new( state, - &mut changes, + &mut Default::default(), &executor, "Benchmark_dispatch_benchmark", &( @@ -389,7 +382,6 @@ impl PalletCmd { } // Do one loop of DB tracking. { - let mut changes = genesis_changes.clone(); let state = &state_with_tracking; let batch: Vec = match Self::exec_state_machine::< std::result::Result, String>, @@ -398,7 +390,7 @@ impl PalletCmd { >( StateMachine::new( state, // todo remove tracking - &mut changes, + &mut Default::default(), &executor, "Benchmark_dispatch_benchmark", &( @@ -432,7 +424,6 @@ impl PalletCmd { } // Finally run a bunch of loops to get extrinsic timing information. for r in 0..self.external_repeat { - let mut changes = genesis_changes.clone(); let state = &state_without_tracking; let batch = match Self::exec_state_machine::< std::result::Result, String>, @@ -441,7 +432,7 @@ impl PalletCmd { >( StateMachine::new( state, // todo remove tracking - &mut changes, + &mut Default::default(), &executor, "Benchmark_dispatch_benchmark", &( @@ -567,19 +558,19 @@ impl PalletCmd { Ok(benchmarks_to_run) } - /// Produce a genesis storage and genesis changes. + /// Build the genesis storage by either the Genesis Builder API, chain spec or nothing. /// - /// It would be easier to only return one type, but there is no easy way to convert them. - // TODO: Re-write `BenchmarkingState` to not be such a clusterfuck and only accept - // `OverlayedChanges` instead of a mix between `OverlayedChanges` and `State`. But this can only - // be done once we deprecated and removed the legacy interface :( - fn genesis_storage( + /// Behaviour can be controlled by the `--genesis-builder` flag. + fn genesis_storage( &self, chain_spec: &Option>, - ) -> Result<(sp_storage::Storage, OverlayedChanges)> { - Ok(match (self.genesis_builder, self.runtime.is_some()) { - (Some(GenesisBuilder::None), _) => Default::default(), - (Some(GenesisBuilder::Spec), _) | (None, false) => { + ) -> Result { + Ok(match (self.genesis_builder, self.runtime.as_ref()) { + (Some(GenesisBuilderPolicy::None), Some(_)) => return Err("Cannot use `--genesis-builder=none` with `--runtime` since the runtime would be ignored.".into()), + (Some(GenesisBuilderPolicy::None), None) => Storage::default(), + (Some(GenesisBuilderPolicy::SpecGenesis | GenesisBuilderPolicy::Spec), Some(_)) => + return Err("Cannot use `--genesis-builder=spec-genesis` with `--runtime` since the runtime would be ignored.".into()), + (Some(GenesisBuilderPolicy::SpecGenesis | GenesisBuilderPolicy::Spec), None) | (None, None) => { log::warn!("{WARN_SPEC_GENESIS_CTOR}"); let Some(chain_spec) = chain_spec else { return Err("No chain spec specified to generate the genesis state".into()); @@ -589,111 +580,73 @@ impl PalletCmd { .build_storage() .map_err(|e| format!("{ERROR_CANNOT_BUILD_GENESIS}\nError: {e}"))?; - (storage, Default::default()) + storage }, - (Some(GenesisBuilder::Runtime), _) | (None, true) => - (Default::default(), self.genesis_from_runtime::()?), - }) - } + (Some(GenesisBuilderPolicy::SpecRuntime), Some(_)) => + return Err("Cannot use `--genesis-builder=spec` with `--runtime` since the runtime would be ignored.".into()), + (Some(GenesisBuilderPolicy::SpecRuntime), None) => { + let Some(chain_spec) = chain_spec else { + return Err("No chain spec specified to generate the genesis state".into()); + }; - /// Generate the genesis changeset by the runtime API. - fn genesis_from_runtime(&self) -> Result> { - let state = BenchmarkingState::::new( - Default::default(), - Some(self.database_cache_size as usize), - false, - false, - )?; + self.genesis_from_spec_runtime::(chain_spec.as_ref())? + }, + (Some(GenesisBuilderPolicy::Runtime), None) => return Err("Cannot use `--genesis-builder=runtime` without `--runtime`".into()), + (Some(GenesisBuilderPolicy::Runtime), Some(runtime)) | (None, Some(runtime)) => { + log::info!("Loading WASM from {}", runtime.display()); + + let code = fs::read(&runtime).map_err(|e| { + format!( + "Could not load runtime file from path: {}, error: {}", + runtime.display(), + e + ) + })?; - // Create a dummy WasmExecutor just to build the genesis storage. - let method = - execution_method_from_cli(self.wasm_method, self.wasmtime_instantiation_strategy); - let executor = WasmExecutor::<( - sp_io::SubstrateHostFunctions, - frame_benchmarking::benchmarking::HostFunctions, - F, - )>::builder() - .with_execution_method(method) - .with_allow_missing_host_functions(self.allow_missing_host_functions) - .build(); + self.genesis_from_code::(&code)? + } + }) + } - let runtime = self.runtime_blob(&state)?; - let runtime_code = runtime.code()?; + /// Setup the genesis state by calling the runtime APIs of the chain-specs genesis runtime. + fn genesis_from_spec_runtime( + &self, + chain_spec: &dyn ChainSpec, + ) -> Result { + log::info!("Building genesis state from chain spec runtime"); + let storage = chain_spec + .build_storage() + .map_err(|e| format!("{ERROR_CANNOT_BUILD_GENESIS}\nError: {e}"))?; - // We cannot use the `GenesisConfigBuilderRuntimeCaller` here since it returns the changes - // as `Storage` item, but we need it as `OverlayedChanges`. - let genesis_json: Option> = Self::exec_state_machine( - StateMachine::new( - &state, - &mut Default::default(), - &executor, - "GenesisBuilder_get_preset", - &None::.encode(), // Use the default preset - &mut Self::build_extensions(executor.clone(), state.recorder()), - &runtime_code, - CallContext::Offchain, - ), - "build the genesis spec", - )?; + let code: &Vec = + storage.top.get(CODE).ok_or("No runtime code in the genesis storage")?; - let Some(base_genesis_json) = genesis_json else { - return Err("GenesisBuilder::get_preset returned no data".into()) - }; - - let base_genesis_json = serde_json::from_slice::(&base_genesis_json) - .map_err(|e| format!("GenesisBuilder::get_preset returned invalid JSON: {:?}", e))?; - - let dev_genesis_json: Option> = Self::exec_state_machine( - StateMachine::new( - &state, - &mut Default::default(), - &executor, - "GenesisBuilder_get_preset", - &Some::(GENESIS_PRESET.into()).encode(), // Use the default preset - &mut Self::build_extensions(executor.clone(), state.recorder()), - &runtime_code, - CallContext::Offchain, - ), - "build the genesis spec", - )?; + self.genesis_from_code::(code) + } - let mut genesis_json = serde_json::Value::default(); - json_merge(&mut genesis_json, base_genesis_json); + fn genesis_from_code(&self, code: &[u8]) -> Result { + let genesis_config_caller = GenesisConfigBuilderRuntimeCaller::<( + sp_io::SubstrateHostFunctions, + frame_benchmarking::benchmarking::HostFunctions, + EHF, + )>::new(code); + let preset = Some(&self.genesis_builder_preset); - if let Some(dev) = dev_genesis_json { - let dev: serde_json::Value = serde_json::from_slice(&dev).map_err(|e| { - format!("GenesisBuilder::get_preset returned invalid JSON: {:?}", e) + let mut storage = + genesis_config_caller.get_storage_for_named_preset(preset).inspect_err(|e| { + let presets = genesis_config_caller.preset_names().unwrap_or_default(); + log::error!( + "Please pick one of the available presets with \ + `--genesis-builder-preset=`. Available presets ({}): {:?}. Error: {:?}", + presets.len(), + presets, + e + ); })?; - json_merge(&mut genesis_json, dev); - } else { - log::warn!( - "Could not find genesis preset '{GENESIS_PRESET}'. Falling back to default." - ); - } - - let json_pretty_str = serde_json::to_string_pretty(&genesis_json) - .map_err(|e| format!("json to string failed: {e}"))?; - - let mut changes = Default::default(); - let build_res: GenesisBuildResult = Self::exec_state_machine( - StateMachine::new( - &state, - &mut changes, - &executor, - "GenesisBuilder_build_state", - &json_pretty_str.encode(), - &mut Extensions::default(), - &runtime_code, - CallContext::Offchain, - ), - "populate the genesis state", - )?; - if let Err(e) = build_res { - return Err(format!("GenesisBuilder::build_state failed: {}", e).into()) - } + storage.top.insert(CODE.into(), code.into()); - Ok(changes) + Ok(storage) } /// Execute a state machine and decode its return value as `R`. @@ -737,19 +690,10 @@ impl PalletCmd { &self, state: &'a BenchmarkingState, ) -> Result, H>> { - if let Some(runtime) = &self.runtime { - log::info!("Loading WASM from {}", runtime.display()); - let code = fs::read(runtime)?; - let hash = sp_core::blake2_256(&code).to_vec(); - let wrapped_code = WrappedRuntimeCode(Cow::Owned(code)); - - Ok(FetchedCode::FromFile { wrapped_code, heap_pages: self.heap_pages, hash }) - } else { - log::info!("Loading WASM from genesis state"); - let state = sp_state_machine::backend::BackendRuntimeCode::new(state); - - Ok(FetchedCode::FromGenesis { state }) - } + log::info!("Loading WASM from state"); + let state = sp_state_machine::backend::BackendRuntimeCode::new(state); + + Ok(FetchedCode { state }) } /// Allocation strategy for pallet benchmarking. @@ -990,19 +934,25 @@ impl PalletCmd { if let Some(output_path) = &self.output { if !output_path.is_dir() && output_path.file_name().is_none() { - return Err("Output file or path is invalid!".into()) + return Err(format!( + "Output path is neither a directory nor a file: {output_path:?}" + ) + .into()) } } if let Some(header_file) = &self.header { if !header_file.is_file() { - return Err("Header file is invalid!".into()) + return Err(format!("Header file could not be found: {header_file:?}").into()) }; } if let Some(handlebars_template_file) = &self.template { if !handlebars_template_file.is_file() { - return Err("Handlebars template file is invalid!".into()) + return Err(format!( + "Handlebars template file could not be found: {handlebars_template_file:?}" + ) + .into()) }; } diff --git a/substrate/utils/frame/benchmarking-cli/src/pallet/mod.rs b/substrate/utils/frame/benchmarking-cli/src/pallet/mod.rs index ebf737be1db..a507c1d1f54 100644 --- a/substrate/utils/frame/benchmarking-cli/src/pallet/mod.rs +++ b/substrate/utils/frame/benchmarking-cli/src/pallet/mod.rs @@ -19,7 +19,7 @@ mod command; mod types; mod writer; -use crate::{pallet::types::GenesisBuilder, shared::HostInfoParams}; +use crate::{pallet::types::GenesisBuilderPolicy, shared::HostInfoParams}; use clap::ValueEnum; use sc_cli::{ WasmExecutionMethod, WasmtimeInstantiationStrategy, DEFAULT_WASMTIME_INSTANTIATION_STRATEGY, @@ -177,9 +177,17 @@ pub struct PalletCmd { /// How to construct the genesis state. /// - /// Uses `GenesisBuilder::Spec` by default and `GenesisBuilder::Runtime` if `runtime` is set. - #[arg(long, value_enum)] - pub genesis_builder: Option, + /// Uses `GenesisBuilderPolicy::Spec` by default and `GenesisBuilderPolicy::Runtime` if + /// `runtime` is set. + #[arg(long, value_enum, alias = "genesis-builder-policy")] + pub genesis_builder: Option, + + /// The preset that we expect to find in the GenesisBuilder runtime API. + /// + /// This can be useful when a runtime has a dedicated benchmarking preset instead of using the + /// default one. + #[arg(long, default_value = sp_genesis_builder::DEV_RUNTIME_PRESET)] + pub genesis_builder_preset: String, /// DEPRECATED: This argument has no effect. #[arg(long = "execution")] diff --git a/substrate/utils/frame/benchmarking-cli/src/pallet/types.rs b/substrate/utils/frame/benchmarking-cli/src/pallet/types.rs index 2bb00d66560..ce37be455e8 100644 --- a/substrate/utils/frame/benchmarking-cli/src/pallet/types.rs +++ b/substrate/utils/frame/benchmarking-cli/src/pallet/types.rs @@ -18,13 +18,13 @@ //! Various types used by this crate. use sc_cli::Result; -use sp_core::traits::{RuntimeCode, WrappedRuntimeCode}; +use sp_core::traits::RuntimeCode; use sp_runtime::traits::Hash; /// How the genesis state for benchmarking should be build. #[derive(clap::ValueEnum, Debug, Eq, PartialEq, Clone, Copy)] #[clap(rename_all = "kebab-case")] -pub enum GenesisBuilder { +pub enum GenesisBuilderPolicy { /// Do not provide any genesis state. /// /// Benchmarks are advised to function with this, since they should setup their own required @@ -32,16 +32,19 @@ pub enum GenesisBuilder { None, /// Let the runtime build the genesis state through its `BuildGenesisConfig` runtime API. Runtime, + // Use the runtime from the Spec file to build the genesis state. + SpecRuntime, /// Use the spec file to build the genesis state. This fails when there is no spec. + SpecGenesis, + /// Same as `SpecGenesis` - only here for backwards compatibility. Spec, } /// A runtime blob that was either fetched from genesis storage or loaded from a file. // NOTE: This enum is only needed for the annoying lifetime bounds on `RuntimeCode`. Otherwise we // could just directly return the blob. -pub enum FetchedCode<'a, B, H> { - FromGenesis { state: sp_state_machine::backend::BackendRuntimeCode<'a, B, H> }, - FromFile { wrapped_code: WrappedRuntimeCode<'a>, heap_pages: Option, hash: Vec }, +pub struct FetchedCode<'a, B, H> { + pub state: sp_state_machine::backend::BackendRuntimeCode<'a, B, H>, } impl<'a, B, H> FetchedCode<'a, B, H> @@ -51,14 +54,7 @@ where { /// The runtime blob. pub fn code(&'a self) -> Result> { - match self { - Self::FromGenesis { state } => state.runtime_code().map_err(Into::into), - Self::FromFile { wrapped_code, heap_pages, hash } => Ok(RuntimeCode { - code_fetcher: wrapped_code, - heap_pages: *heap_pages, - hash: hash.clone(), - }), - } + self.state.runtime_code().map_err(Into::into) } } diff --git a/substrate/utils/frame/benchmarking-cli/src/pallet/writer.rs b/substrate/utils/frame/benchmarking-cli/src/pallet/writer.rs index df7d81b2822..34f31734da6 100644 --- a/substrate/utils/frame/benchmarking-cli/src/pallet/writer.rs +++ b/substrate/utils/frame/benchmarking-cli/src/pallet/writer.rs @@ -484,7 +484,12 @@ pub(crate) fn write_results( benchmarks: results.clone(), }; - let mut output_file = fs::File::create(&file_path)?; + let file_path = fs::canonicalize(&file_path).map_err(|e| { + format!("Could not get absolute path for: {:?}. Error: {:?}", &file_path, e) + })?; + let mut output_file = fs::File::create(&file_path).map_err(|e| { + format!("Could not write weight file to: {:?}. Error: {:?}", &file_path, e) + })?; handlebars .render_template_to_write(&template, &hbs_data, &mut output_file) .map_err(|e| io_error(&e.to_string()))?; -- GitLab From 86dc1ab36cdaf0554fbf11ef9556759bcbdda38c Mon Sep 17 00:00:00 2001 From: Alexandru Gheorghe <49718502+alexggh@users.noreply.github.com> Date: Mon, 23 Sep 2024 09:10:40 +0300 Subject: [PATCH 091/125] bitfield_distribution: Move on blocking pool and use custom capacity (#5787) ## Description Details and rationale explained here: https://github.com/paritytech/polkadot-sdk/issues/5657#issuecomment-2363076080 Fixes: https://github.com/paritytech/polkadot-sdk/issues/5657 --------- Signed-off-by: Alexandru Gheorghe --- polkadot/node/overseer/src/lib.rs | 2 +- prdoc/pr_5787.prdoc | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 prdoc/pr_5787.prdoc diff --git a/polkadot/node/overseer/src/lib.rs b/polkadot/node/overseer/src/lib.rs index 26a6a907e32..23adf4f4d8a 100644 --- a/polkadot/node/overseer/src/lib.rs +++ b/polkadot/node/overseer/src/lib.rs @@ -521,7 +521,7 @@ pub struct Overseer { ])] bitfield_signing: BitfieldSigning, - #[subsystem(BitfieldDistributionMessage, sends: [ + #[subsystem(blocking, message_capacity: 8192, BitfieldDistributionMessage, sends: [ RuntimeApiMessage, NetworkBridgeTxMessage, ProvisionerMessage, diff --git a/prdoc/pr_5787.prdoc b/prdoc/pr_5787.prdoc new file mode 100644 index 00000000000..59d4118f190 --- /dev/null +++ b/prdoc/pr_5787.prdoc @@ -0,0 +1,13 @@ +title: "Move bitfield_distribution to blocking task pool and set capacity to 8192" + +doc: + - audience: Node Dev + description: | + This is moving bitfield_distribution to the blocking task pool because it does cpu + intensive work and to make it snappier. Additionally, also increase the message + capacity of the subsystem to make sure the queue does not get full if there is a + burst of messages. + +crates: + - name: polkadot-overseer + bump: patch -- GitLab From b9eb68bcb5ab93e58bcba4425975ad00374da2bc Mon Sep 17 00:00:00 2001 From: Alin Dima Date: Mon, 23 Sep 2024 10:42:50 +0300 Subject: [PATCH 092/125] elastic scaling: add core selector to cumulus (#5372) Partially implements https://github.com/paritytech/polkadot-sdk/issues/5048 - adds a core selection runtime API to cumulus and a generic way of configuring it for a parachain - modifies the slot based collator to utilise the claim queue and the generic core selection What's left to be implemented (in a follow-up PR): - add the UMP signal for core selection into the parachain-system pallet View the RFC for more context: https://github.com/polkadot-fellows/RFCs/pull/103 --------- Co-authored-by: command-bot <> --- Cargo.lock | 1 + cumulus/client/consensus/aura/Cargo.toml | 1 + .../consensus/aura/src/collators/lookahead.rs | 4 +- .../consensus/aura/src/collators/mod.rs | 48 ++--- .../slot_based/block_builder_task.rs | 174 +++++++++++------- .../aura/src/collators/slot_based/mod.rs | 7 +- cumulus/client/consensus/common/src/tests.rs | 11 +- cumulus/client/network/src/tests.rs | 15 +- cumulus/client/pov-recovery/src/tests.rs | 11 +- .../src/lib.rs | 18 +- .../client/relay-chain-interface/src/lib.rs | 24 ++- .../src/blockchain_rpc_client.rs | 56 ++---- .../relay-chain-rpc-interface/src/lib.rs | 9 + cumulus/pallets/parachain-system/src/lib.rs | 39 +++- cumulus/pallets/parachain-system/src/mock.rs | 1 + cumulus/pallets/xcmp-queue/src/mock.rs | 1 + .../assets/asset-hub-rococo/src/lib.rs | 9 +- .../assets/asset-hub-westend/src/lib.rs | 9 +- .../cumulus_pallet_parachain_system.rs | 2 +- .../bridge-hubs/bridge-hub-rococo/src/lib.rs | 9 +- .../bridge-hubs/bridge-hub-westend/src/lib.rs | 9 +- .../collectives-westend/src/lib.rs | 9 +- .../contracts/contracts-rococo/src/lib.rs | 9 +- .../coretime/coretime-rococo/src/lib.rs | 9 +- .../coretime/coretime-westend/src/lib.rs | 9 +- .../glutton/glutton-westend/src/lib.rs | 17 +- .../runtimes/people/people-rococo/src/lib.rs | 9 +- .../runtimes/people/people-westend/src/lib.rs | 9 +- .../runtimes/starters/seedling/src/lib.rs | 8 + .../runtimes/starters/shell/src/lib.rs | 9 +- .../runtimes/testing/penpal/src/lib.rs | 9 +- .../testing/rococo-parachain/src/lib.rs | 9 +- .../polkadot-parachain-lib/src/common/mod.rs | 4 +- .../src/fake_runtime_api/utils.rs | 7 + .../polkadot-parachain-lib/src/service.rs | 3 +- cumulus/primitives/core/src/lib.rs | 11 ++ cumulus/test/runtime/src/lib.rs | 8 + cumulus/test/service/src/lib.rs | 1 - docs/sdk/src/polkadot_sdk/cumulus.rs | 1 + prdoc/pr_5372.prdoc | 71 +++++++ .../parachain/runtime/src/configs/mod.rs | 1 + 41 files changed, 490 insertions(+), 181 deletions(-) create mode 100644 prdoc/pr_5372.prdoc diff --git a/Cargo.lock b/Cargo.lock index 89e0b87c864..251b472352b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4067,6 +4067,7 @@ dependencies = [ "parking_lot 0.12.3", "polkadot-node-primitives", "polkadot-node-subsystem", + "polkadot-node-subsystem-util", "polkadot-overseer", "polkadot-primitives", "sc-client-api", diff --git a/cumulus/client/consensus/aura/Cargo.toml b/cumulus/client/consensus/aura/Cargo.toml index 47e2d8572c3..0bb2de6bb9b 100644 --- a/cumulus/client/consensus/aura/Cargo.toml +++ b/cumulus/client/consensus/aura/Cargo.toml @@ -53,6 +53,7 @@ cumulus-client-collator = { workspace = true, default-features = true } polkadot-primitives = { workspace = true, default-features = true } polkadot-node-primitives = { workspace = true, default-features = true } polkadot-node-subsystem = { workspace = true, default-features = true } +polkadot-node-subsystem-util = { workspace = true, default-features = true } polkadot-overseer = { workspace = true, default-features = true } [features] diff --git a/cumulus/client/consensus/aura/src/collators/lookahead.rs b/cumulus/client/consensus/aura/src/collators/lookahead.rs index 0be1e0a23ca..322baaa0149 100644 --- a/cumulus/client/consensus/aura/src/collators/lookahead.rs +++ b/cumulus/client/consensus/aura/src/collators/lookahead.rs @@ -36,7 +36,7 @@ use cumulus_client_collator::service::ServiceInterface as CollatorServiceInterfa use cumulus_client_consensus_common::{self as consensus_common, ParachainBlockImportMarker}; use cumulus_client_consensus_proposer::ProposerInterface; use cumulus_primitives_aura::AuraUnincludedSegmentApi; -use cumulus_primitives_core::{CollectCollationInfo, PersistedValidationData}; +use cumulus_primitives_core::{ClaimQueueOffset, CollectCollationInfo, PersistedValidationData}; use cumulus_relay_chain_interface::RelayChainInterface; use polkadot_node_primitives::{PoV, SubmitCollationParams}; @@ -260,6 +260,8 @@ where relay_parent, params.para_id, &mut params.relay_client, + // Use depth 0, to preserve behaviour. + ClaimQueueOffset(0), ) .await .get(0) diff --git a/cumulus/client/consensus/aura/src/collators/mod.rs b/cumulus/client/consensus/aura/src/collators/mod.rs index 7d430ecdc72..89070607fba 100644 --- a/cumulus/client/consensus/aura/src/collators/mod.rs +++ b/cumulus/client/consensus/aura/src/collators/mod.rs @@ -26,11 +26,12 @@ use cumulus_client_consensus_common::{ self as consensus_common, load_abridged_host_configuration, ParentSearchParams, }; use cumulus_primitives_aura::{AuraUnincludedSegmentApi, Slot}; -use cumulus_primitives_core::{relay_chain::Hash as ParaHash, BlockT}; +use cumulus_primitives_core::{relay_chain::Hash as ParaHash, BlockT, ClaimQueueOffset}; use cumulus_relay_chain_interface::RelayChainInterface; +use polkadot_node_subsystem_util::runtime::ClaimQueueSnapshot; use polkadot_primitives::{ - AsyncBackingParams, CoreIndex, CoreState, Hash as RelayHash, Id as ParaId, - OccupiedCoreAssumption, ValidationCodeHash, + AsyncBackingParams, CoreIndex, Hash as RelayHash, Id as ParaId, OccupiedCoreAssumption, + ValidationCodeHash, }; use sc_consensus_aura::{standalone as aura_internal, AuraApi}; use sp_api::ProvideRuntimeApi; @@ -126,50 +127,33 @@ async fn async_backing_params( } } -// Return all the cores assigned to the para at the provided relay parent. +// Return all the cores assigned to the para at the provided relay parent, using the claim queue +// offset. +// Will return an empty vec if the provided offset is higher than the claim queue length (which +// corresponds to the scheduling_lookahead on the relay chain). async fn cores_scheduled_for_para( relay_parent: RelayHash, para_id: ParaId, relay_client: &impl RelayChainInterface, + claim_queue_offset: ClaimQueueOffset, ) -> Vec { - // Get `AvailabilityCores` from runtime - let cores = match relay_client.availability_cores(relay_parent).await { - Ok(cores) => cores, + // Get `ClaimQueue` from runtime + let claim_queue: ClaimQueueSnapshot = match relay_client.claim_queue(relay_parent).await { + Ok(claim_queue) => claim_queue.into(), Err(error) => { tracing::error!( target: crate::LOG_TARGET, ?error, ?relay_parent, - "Failed to query availability cores runtime API", + "Failed to query claim queue runtime API", ); return Vec::new() }, }; - let max_candidate_depth = async_backing_params(relay_parent, relay_client) - .await - .map(|c| c.max_candidate_depth) - .unwrap_or(0); - - cores - .iter() - .enumerate() - .filter_map(|(index, core)| { - let core_para_id = match core { - CoreState::Scheduled(scheduled_core) => Some(scheduled_core.para_id), - CoreState::Occupied(occupied_core) if max_candidate_depth > 0 => occupied_core - .next_up_on_available - .as_ref() - .map(|scheduled_core| scheduled_core.para_id), - CoreState::Free | CoreState::Occupied(_) => None, - }; - - if core_para_id == Some(para_id) { - Some(CoreIndex(index as u32)) - } else { - None - } - }) + claim_queue + .iter_claims_at_depth(claim_queue_offset.0 as usize) + .filter_map(|(core_index, core_para_id)| (core_para_id == para_id).then_some(core_index)) .collect() } diff --git a/cumulus/client/consensus/aura/src/collators/slot_based/block_builder_task.rs b/cumulus/client/consensus/aura/src/collators/slot_based/block_builder_task.rs index b70cfe3841b..e75b52aeebd 100644 --- a/cumulus/client/consensus/aura/src/collators/slot_based/block_builder_task.rs +++ b/cumulus/client/consensus/aura/src/collators/slot_based/block_builder_task.rs @@ -20,10 +20,13 @@ use cumulus_client_collator::service::ServiceInterface as CollatorServiceInterfa use cumulus_client_consensus_common::{self as consensus_common, ParachainBlockImportMarker}; use cumulus_client_consensus_proposer::ProposerInterface; use cumulus_primitives_aura::AuraUnincludedSegmentApi; -use cumulus_primitives_core::{CollectCollationInfo, PersistedValidationData}; +use cumulus_primitives_core::{ + GetCoreSelectorApi, PersistedValidationData, DEFAULT_CLAIM_QUEUE_OFFSET, +}; use cumulus_relay_chain_interface::RelayChainInterface; use polkadot_primitives::{ + vstaging::{ClaimQueueOffset, CoreSelector}, BlockId, CoreIndex, Hash as RelayHash, Header as RelayHeader, Id as ParaId, OccupiedCoreAssumption, }; @@ -31,16 +34,16 @@ use polkadot_primitives::{ use futures::prelude::*; use sc_client_api::{backend::AuxStore, BlockBackend, BlockOf, UsageProvider}; use sc_consensus::BlockImport; -use sp_api::ProvideRuntimeApi; +use sp_api::{ApiExt, ProvideRuntimeApi}; use sp_application_crypto::AppPublic; use sp_blockchain::HeaderBackend; -use sp_consensus_aura::{AuraApi, Slot, SlotDuration}; -use sp_core::crypto::Pair; +use sp_consensus_aura::{AuraApi, Slot}; +use sp_core::{crypto::Pair, U256}; use sp_inherents::CreateInherentDataProviders; use sp_keystore::KeystorePtr; -use sp_runtime::traits::{Block as BlockT, Header as HeaderT, Member}; +use sp_runtime::traits::{Block as BlockT, Header as HeaderT, Member, One}; use sp_timestamp::Timestamp; -use std::{sync::Arc, time::Duration}; +use std::{collections::BTreeSet, sync::Arc, time::Duration}; use super::CollatorMessage; use crate::{ @@ -87,8 +90,6 @@ pub struct BuilderTaskParams< pub authoring_duration: Duration, /// Channel to send built blocks to the collation task. pub collator_sender: sc_utils::mpsc::TracingUnboundedSender>, - /// Slot duration of the relay chain - pub relay_chain_slot_duration: Duration, /// Drift every slot by this duration. /// This is a time quantity that is subtracted from the actual timestamp when computing /// the time left to enter a new slot. In practice, this *left-shifts* the clock time with the @@ -102,7 +103,6 @@ pub struct BuilderTaskParams< struct SlotInfo { pub timestamp: Timestamp, pub slot: Slot, - pub slot_duration: SlotDuration, } #[derive(Debug)] @@ -153,11 +153,7 @@ where let time_until_next_slot = time_until_next_slot(slot_duration.as_duration(), self.drift); tokio::time::sleep(time_until_next_slot).await; let timestamp = sp_timestamp::Timestamp::current(); - Ok(SlotInfo { - slot: Slot::from_timestamp(timestamp, slot_duration), - timestamp, - slot_duration, - }) + Ok(SlotInfo { slot: Slot::from_timestamp(timestamp, slot_duration), timestamp }) } } @@ -177,7 +173,7 @@ where + Sync + 'static, Client::Api: - AuraApi + CollectCollationInfo + AuraUnincludedSegmentApi, + AuraApi + GetCoreSelectorApi + AuraUnincludedSegmentApi, Backend: sc_client_api::Backend + 'static, RelayClient: RelayChainInterface + Clone + 'static, CIDP: CreateInherentDataProviders + 'static, @@ -205,7 +201,6 @@ where code_hash_provider, authoring_duration, para_backend, - relay_chain_slot_duration, slot_drift, } = params; @@ -233,18 +228,42 @@ where return; }; - let Some(expected_cores) = - expected_core_count(relay_chain_slot_duration, para_slot.slot_duration) + let Ok(relay_parent) = relay_client.best_block_hash().await else { + tracing::warn!(target: crate::LOG_TARGET, "Unable to fetch latest relay chain block hash."); + continue + }; + + let Some((included_block, parent)) = + crate::collators::find_parent(relay_parent, para_id, &*para_backend, &relay_client) + .await else { - return + continue }; + let parent_hash = parent.hash; + + // Retrieve the core selector. + let (core_selector, claim_queue_offset) = + match core_selector(&*para_client, &parent).await { + Ok(core_selector) => core_selector, + Err(err) => { + tracing::trace!( + target: crate::LOG_TARGET, + "Unable to retrieve the core selector from the runtime API: {}", + err + ); + continue + }, + }; + let Ok(RelayChainData { relay_parent_header, max_pov_size, - relay_parent_hash: relay_parent, scheduled_cores, - }) = relay_chain_fetcher.get_relay_chain_data().await + claimed_cores, + }) = relay_chain_fetcher + .get_mut_relay_chain_data(relay_parent, claim_queue_offset) + .await else { continue; }; @@ -252,23 +271,32 @@ where if scheduled_cores.is_empty() { tracing::debug!(target: LOG_TARGET, "Parachain not scheduled, skipping slot."); continue; + } else { + tracing::debug!( + target: LOG_TARGET, + ?relay_parent, + "Parachain is scheduled on cores: {:?}", + scheduled_cores + ); } - let core_index_in_scheduled: u64 = *para_slot.slot % expected_cores; - let Some(core_index) = scheduled_cores.get(core_index_in_scheduled as usize) else { - tracing::debug!(target: LOG_TARGET, core_index_in_scheduled, core_len = scheduled_cores.len(), "Para is scheduled, but not enough cores available."); + let core_selector = core_selector.0 as usize % scheduled_cores.len(); + let Some(core_index) = scheduled_cores.get(core_selector) else { + // This cannot really happen, as we modulo the core selector with the + // scheduled_cores length and we check that the scheduled_cores is not empty. continue; }; - let Some((included_block, parent)) = - crate::collators::find_parent(relay_parent, para_id, &*para_backend, &relay_client) - .await - else { + if !claimed_cores.insert(*core_index) { + tracing::debug!( + target: LOG_TARGET, + "Core {:?} was already claimed at this relay chain slot", + core_index + ); continue - }; + } let parent_header = parent.header; - let parent_hash = parent.hash; // We mainly call this to inform users at genesis if there is a mismatch with the // on-chain data. @@ -315,7 +343,7 @@ where parent_head: parent_header.encode().into(), relay_parent_number: *relay_parent_header.number(), relay_parent_storage_root: *relay_parent_header.state_root(), - max_pov_size, + max_pov_size: *max_pov_size, }; let (parachain_inherent_data, other_inherent_data) = match collator @@ -394,34 +422,17 @@ where } } -/// Calculate the expected core count based on the slot duration of the relay and parachain. -/// -/// If `slot_duration` is smaller than `relay_chain_slot_duration` that means that we produce more -/// than one parachain block per relay chain block. In order to get these backed, we need multiple -/// cores. This method calculates how many cores we should expect to have scheduled under the -/// assumption that we have a fixed number of cores assigned to our parachain. -fn expected_core_count( - relay_chain_slot_duration: Duration, - slot_duration: SlotDuration, -) -> Option { - let slot_duration_millis = slot_duration.as_millis(); - u64::try_from(relay_chain_slot_duration.as_millis()) - .map_err(|e| tracing::error!("Unable to calculate expected parachain core count: {e}")) - .map(|relay_slot_duration| (relay_slot_duration / slot_duration_millis).max(1)) - .ok() -} - /// Contains relay chain data necessary for parachain block building. #[derive(Clone)] struct RelayChainData { /// Current relay chain parent header. pub relay_parent_header: RelayHeader, - /// The cores this para is scheduled on in the context of the relay parent. + /// The cores on which the para is scheduled at the configured claim queue offset. pub scheduled_cores: Vec, /// Maximum configured PoV size on the relay chain. pub max_pov_size: u32, - /// Current relay chain parent header. - pub relay_parent_hash: RelayHash, + /// The claimed cores at a relay parent. + pub claimed_cores: BTreeSet, } /// Simple helper to fetch relay chain data and cache it based on the current relay chain best block @@ -443,30 +454,39 @@ where /// Fetch required [`RelayChainData`] from the relay chain. /// If this data has been fetched in the past for the incoming hash, it will reuse /// cached data. - pub async fn get_relay_chain_data(&mut self) -> Result { - let Ok(relay_parent) = self.relay_client.best_block_hash().await else { - tracing::warn!(target: crate::LOG_TARGET, "Unable to fetch latest relay chain block hash."); - return Err(()) - }; - + pub async fn get_mut_relay_chain_data( + &mut self, + relay_parent: RelayHash, + claim_queue_offset: ClaimQueueOffset, + ) -> Result<&mut RelayChainData, ()> { match &self.last_data { - Some((last_seen_hash, data)) if *last_seen_hash == relay_parent => { + Some((last_seen_hash, _)) if *last_seen_hash == relay_parent => { tracing::trace!(target: crate::LOG_TARGET, %relay_parent, "Using cached data for relay parent."); - Ok(data.clone()) + Ok(&mut self.last_data.as_mut().expect("last_data is Some").1) }, _ => { tracing::trace!(target: crate::LOG_TARGET, %relay_parent, "Relay chain best block changed, fetching new data from relay chain."); - let data = self.update_for_relay_parent(relay_parent).await?; - self.last_data = Some((relay_parent, data.clone())); - Ok(data) + let data = self.update_for_relay_parent(relay_parent, claim_queue_offset).await?; + self.last_data = Some((relay_parent, data)); + Ok(&mut self.last_data.as_mut().expect("last_data was just set above").1) }, } } /// Fetch fresh data from the relay chain for the given relay parent hash. - async fn update_for_relay_parent(&self, relay_parent: RelayHash) -> Result { - let scheduled_cores = - cores_scheduled_for_para(relay_parent, self.para_id, &self.relay_client).await; + async fn update_for_relay_parent( + &self, + relay_parent: RelayHash, + claim_queue_offset: ClaimQueueOffset, + ) -> Result { + let scheduled_cores = cores_scheduled_for_para( + relay_parent, + self.para_id, + &self.relay_client, + claim_queue_offset, + ) + .await; + let Ok(Some(relay_parent_header)) = self.relay_client.header(BlockId::Hash(relay_parent)).await else { @@ -488,10 +508,32 @@ where }; Ok(RelayChainData { - relay_parent_hash: relay_parent, relay_parent_header, scheduled_cores, max_pov_size, + claimed_cores: BTreeSet::new(), }) } } + +async fn core_selector( + para_client: &Client, + parent: &consensus_common::PotentialParent, +) -> Result<(CoreSelector, ClaimQueueOffset), sp_api::ApiError> +where + Client: ProvideRuntimeApi + Send + Sync, + Client::Api: GetCoreSelectorApi, +{ + let block_hash = parent.hash; + let runtime_api = para_client.runtime_api(); + + if runtime_api.has_api::>(block_hash)? { + Ok(runtime_api.core_selector(block_hash)?) + } else { + let next_block_number: U256 = (*parent.header.number() + One::one()).into(); + + // If the runtime API does not support the core selector API, fallback to some default + // values. + Ok((CoreSelector(next_block_number.byte(0)), ClaimQueueOffset(DEFAULT_CLAIM_QUEUE_OFFSET))) + } +} diff --git a/cumulus/client/consensus/aura/src/collators/slot_based/mod.rs b/cumulus/client/consensus/aura/src/collators/slot_based/mod.rs index 0fe49d58d25..7453d3c89d0 100644 --- a/cumulus/client/consensus/aura/src/collators/slot_based/mod.rs +++ b/cumulus/client/consensus/aura/src/collators/slot_based/mod.rs @@ -34,7 +34,7 @@ use cumulus_client_collator::service::ServiceInterface as CollatorServiceInterfa use cumulus_client_consensus_common::{self as consensus_common, ParachainBlockImportMarker}; use cumulus_client_consensus_proposer::ProposerInterface; use cumulus_primitives_aura::AuraUnincludedSegmentApi; -use cumulus_primitives_core::CollectCollationInfo; +use cumulus_primitives_core::GetCoreSelectorApi; use cumulus_relay_chain_interface::RelayChainInterface; use polkadot_primitives::{ CollatorPair, CoreIndex, Hash as RelayHash, Id as ParaId, ValidationCodeHash, @@ -82,8 +82,6 @@ pub struct Params { pub collator_key: CollatorPair, /// The para's ID. pub para_id: ParaId, - /// The length of slots in the relay chain. - pub relay_chain_slot_duration: Duration, /// The underlying block proposer this should call into. pub proposer: Proposer, /// The generic collator service used to plug into this consensus engine. @@ -113,7 +111,7 @@ where + Sync + 'static, Client::Api: - AuraApi + CollectCollationInfo + AuraUnincludedSegmentApi, + AuraApi + GetCoreSelectorApi + AuraUnincludedSegmentApi, Backend: sc_client_api::Backend + 'static, RClient: RelayChainInterface + Clone + 'static, CIDP: CreateInherentDataProviders + 'static, @@ -151,7 +149,6 @@ where collator_service: params.collator_service, authoring_duration: params.authoring_duration, collator_sender: tx, - relay_chain_slot_duration: params.relay_chain_slot_duration, slot_drift: params.slot_drift, }; diff --git a/cumulus/client/consensus/common/src/tests.rs b/cumulus/client/consensus/common/src/tests.rs index 794ce30de3e..94e2304011b 100644 --- a/cumulus/client/consensus/common/src/tests.rs +++ b/cumulus/client/consensus/common/src/tests.rs @@ -24,7 +24,7 @@ use cumulus_primitives_core::{ CumulusDigestItem, InboundDownwardMessage, InboundHrmpMessage, }; use cumulus_relay_chain_interface::{ - CommittedCandidateReceipt, OccupiedCoreAssumption, OverseerHandle, PHeader, ParaId, + CommittedCandidateReceipt, CoreIndex, OccupiedCoreAssumption, OverseerHandle, PHeader, ParaId, RelayChainInterface, RelayChainResult, SessionIndex, StorageValue, ValidatorId, }; use cumulus_test_client::{ @@ -41,7 +41,7 @@ use sp_blockchain::Backend as BlockchainBackend; use sp_consensus::{BlockOrigin, BlockStatus}; use sp_version::RuntimeVersion; use std::{ - collections::{BTreeMap, HashMap}, + collections::{BTreeMap, HashMap, VecDeque}, pin::Pin, sync::{Arc, Mutex}, time::Duration, @@ -269,6 +269,13 @@ impl RelayChainInterface for Relaychain { unimplemented!("Not needed for test") } + async fn claim_queue( + &self, + _: PHash, + ) -> RelayChainResult>> { + unimplemented!("Not needed for test"); + } + async fn call_runtime_api( &self, _method_name: &'static str, diff --git a/cumulus/client/network/src/tests.rs b/cumulus/client/network/src/tests.rs index 686943063bb..4b347364521 100644 --- a/cumulus/client/network/src/tests.rs +++ b/cumulus/client/network/src/tests.rs @@ -16,7 +16,7 @@ use super::*; use async_trait::async_trait; -use cumulus_primitives_core::relay_chain::BlockId; +use cumulus_primitives_core::relay_chain::{BlockId, CoreIndex}; use cumulus_relay_chain_inprocess_interface::{check_block_in_chain, BlockCheckStatus}; use cumulus_relay_chain_interface::{ OverseerHandle, PHeader, ParaId, RelayChainError, RelayChainResult, @@ -45,7 +45,11 @@ use sp_keystore::{testing::MemoryKeystore, Keystore, KeystorePtr}; use sp_runtime::RuntimeAppPublic; use sp_state_machine::StorageValue; use sp_version::RuntimeVersion; -use std::{borrow::Cow, collections::BTreeMap, time::Duration}; +use std::{ + borrow::Cow, + collections::{BTreeMap, VecDeque}, + time::Duration, +}; fn check_error(error: crate::BoxedError, check_error: impl Fn(&BlockAnnounceError) -> bool) { let error = *error @@ -327,6 +331,13 @@ impl RelayChainInterface for DummyRelayChainInterface { }) } + async fn claim_queue( + &self, + _: PHash, + ) -> RelayChainResult>> { + unimplemented!("Not needed for test"); + } + async fn call_runtime_api( &self, _method_name: &'static str, diff --git a/cumulus/client/pov-recovery/src/tests.rs b/cumulus/client/pov-recovery/src/tests.rs index 539f7f33ad3..94dec32485c 100644 --- a/cumulus/client/pov-recovery/src/tests.rs +++ b/cumulus/client/pov-recovery/src/tests.rs @@ -18,7 +18,7 @@ use super::*; use assert_matches::assert_matches; use codec::{Decode, Encode}; use cumulus_primitives_core::relay_chain::{ - BlockId, CandidateCommitments, CandidateDescriptor, CoreState, + BlockId, CandidateCommitments, CandidateDescriptor, CoreIndex, CoreState, }; use cumulus_relay_chain_interface::{ InboundDownwardMessage, InboundHrmpMessage, OccupiedCoreAssumption, PHash, PHeader, @@ -43,7 +43,7 @@ use sp_runtime::{generic::SignedBlock, Justifications}; use sp_version::RuntimeVersion; use std::{ borrow::Cow, - collections::BTreeMap, + collections::{BTreeMap, VecDeque}, ops::Range, sync::{Arc, Mutex}, }; @@ -488,6 +488,13 @@ impl RelayChainInterface for Relaychain { unimplemented!("Not needed for test"); } + async fn claim_queue( + &self, + _: PHash, + ) -> RelayChainResult>> { + unimplemented!("Not needed for test"); + } + async fn call_runtime_api( &self, _method_name: &'static str, diff --git a/cumulus/client/relay-chain-inprocess-interface/src/lib.rs b/cumulus/client/relay-chain-inprocess-interface/src/lib.rs index 0455c03fc4d..4fea055203d 100644 --- a/cumulus/client/relay-chain-inprocess-interface/src/lib.rs +++ b/cumulus/client/relay-chain-inprocess-interface/src/lib.rs @@ -14,14 +14,19 @@ // You should have received a copy of the GNU General Public License // along with Cumulus. If not, see . -use std::{collections::btree_map::BTreeMap, pin::Pin, sync::Arc, time::Duration}; +use std::{ + collections::{BTreeMap, VecDeque}, + pin::Pin, + sync::Arc, + time::Duration, +}; use async_trait::async_trait; use cumulus_primitives_core::{ relay_chain::{ runtime_api::ParachainHost, Block as PBlock, BlockId, BlockNumber, - CommittedCandidateReceipt, CoreState, Hash as PHash, Header as PHeader, InboundHrmpMessage, - OccupiedCoreAssumption, SessionIndex, ValidationCodeHash, ValidatorId, + CommittedCandidateReceipt, CoreIndex, CoreState, Hash as PHash, Header as PHeader, + InboundHrmpMessage, OccupiedCoreAssumption, SessionIndex, ValidationCodeHash, ValidatorId, }, InboundDownwardMessage, ParaId, PersistedValidationData, }; @@ -303,6 +308,13 @@ impl RelayChainInterface for RelayChainInProcessInterface { .map(|receipt| receipt.into()) .collect::>()) } + + async fn claim_queue( + &self, + hash: PHash, + ) -> RelayChainResult>> { + Ok(self.full_client.runtime_api().claim_queue(hash)?) + } } pub enum BlockCheckStatus { diff --git a/cumulus/client/relay-chain-interface/src/lib.rs b/cumulus/client/relay-chain-interface/src/lib.rs index 8d172e423eb..2eed71c4d7d 100644 --- a/cumulus/client/relay-chain-interface/src/lib.rs +++ b/cumulus/client/relay-chain-interface/src/lib.rs @@ -14,7 +14,11 @@ // You should have received a copy of the GNU General Public License // along with Cumulus. If not, see . -use std::{collections::BTreeMap, pin::Pin, sync::Arc}; +use std::{ + collections::{BTreeMap, VecDeque}, + pin::Pin, + sync::Arc, +}; use futures::Stream; use polkadot_overseer::prometheus::PrometheusError; @@ -29,8 +33,9 @@ use sp_api::ApiError; use cumulus_primitives_core::relay_chain::{BlockId, Hash as RelayHash}; pub use cumulus_primitives_core::{ relay_chain::{ - BlockNumber, CommittedCandidateReceipt, CoreState, Hash as PHash, Header as PHeader, - InboundHrmpMessage, OccupiedCoreAssumption, SessionIndex, ValidationCodeHash, ValidatorId, + BlockNumber, CommittedCandidateReceipt, CoreIndex, CoreState, Hash as PHash, + Header as PHeader, InboundHrmpMessage, OccupiedCoreAssumption, SessionIndex, + ValidationCodeHash, ValidatorId, }, InboundDownwardMessage, ParaId, PersistedValidationData, }; @@ -233,6 +238,12 @@ pub trait RelayChainInterface: Send + Sync { &self, relay_parent: PHash, ) -> RelayChainResult>>; + + /// Fetch the claim queue. + async fn claim_queue( + &self, + relay_parent: PHash, + ) -> RelayChainResult>>; } #[async_trait] @@ -380,6 +391,13 @@ where async fn version(&self, relay_parent: PHash) -> RelayChainResult { (**self).version(relay_parent).await } + + async fn claim_queue( + &self, + relay_parent: PHash, + ) -> RelayChainResult>> { + (**self).claim_queue(relay_parent).await + } } /// Helper function to call an arbitrary runtime API using a `RelayChainInterface` client. diff --git a/cumulus/client/relay-chain-minimal-node/src/blockchain_rpc_client.rs b/cumulus/client/relay-chain-minimal-node/src/blockchain_rpc_client.rs index 06f19941165..7d6b5bfe3ec 100644 --- a/cumulus/client/relay-chain-minimal-node/src/blockchain_rpc_client.rs +++ b/cumulus/client/relay-chain-minimal-node/src/blockchain_rpc_client.rs @@ -19,6 +19,7 @@ use std::{ pin::Pin, }; +use cumulus_primitives_core::{InboundDownwardMessage, ParaId, PersistedValidationData}; use cumulus_relay_chain_interface::{RelayChainError, RelayChainResult}; use cumulus_relay_chain_rpc_interface::RelayChainRpcClient; use futures::{Stream, StreamExt}; @@ -132,7 +133,7 @@ impl RuntimeApiSubsystemClient for BlockChainRpcClient { ) -> Result< ( Vec>, - polkadot_primitives::GroupRotationInfo, + polkadot_primitives::GroupRotationInfo, ), sp_api::ApiError, > { @@ -142,27 +143,16 @@ impl RuntimeApiSubsystemClient for BlockChainRpcClient { async fn availability_cores( &self, at: Hash, - ) -> Result< - Vec>, - sp_api::ApiError, - > { + ) -> Result>, sp_api::ApiError> { Ok(self.rpc_client.parachain_host_availability_cores(at).await?) } async fn persisted_validation_data( &self, at: Hash, - para_id: cumulus_primitives_core::ParaId, + para_id: ParaId, assumption: polkadot_primitives::OccupiedCoreAssumption, - ) -> Result< - Option< - cumulus_primitives_core::PersistedValidationData< - Hash, - polkadot_core_primitives::BlockNumber, - >, - >, - sp_api::ApiError, - > { + ) -> Result>, sp_api::ApiError> { Ok(self .rpc_client .parachain_host_persisted_validation_data(at, para_id, assumption) @@ -172,14 +162,11 @@ impl RuntimeApiSubsystemClient for BlockChainRpcClient { async fn assumed_validation_data( &self, at: Hash, - para_id: cumulus_primitives_core::ParaId, + para_id: ParaId, expected_persisted_validation_data_hash: Hash, ) -> Result< Option<( - cumulus_primitives_core::PersistedValidationData< - Hash, - polkadot_core_primitives::BlockNumber, - >, + PersistedValidationData, polkadot_primitives::ValidationCodeHash, )>, sp_api::ApiError, @@ -197,7 +184,7 @@ impl RuntimeApiSubsystemClient for BlockChainRpcClient { async fn check_validation_outputs( &self, at: Hash, - para_id: cumulus_primitives_core::ParaId, + para_id: ParaId, outputs: polkadot_primitives::CandidateCommitments, ) -> Result { Ok(self @@ -216,7 +203,7 @@ impl RuntimeApiSubsystemClient for BlockChainRpcClient { async fn validation_code( &self, at: Hash, - para_id: cumulus_primitives_core::ParaId, + para_id: ParaId, assumption: polkadot_primitives::OccupiedCoreAssumption, ) -> Result, sp_api::ApiError> { Ok(self.rpc_client.parachain_host_validation_code(at, para_id, assumption).await?) @@ -225,7 +212,7 @@ impl RuntimeApiSubsystemClient for BlockChainRpcClient { async fn candidate_pending_availability( &self, at: Hash, - para_id: cumulus_primitives_core::ParaId, + para_id: ParaId, ) -> Result>, sp_api::ApiError> { Ok(self .rpc_client @@ -243,24 +230,19 @@ impl RuntimeApiSubsystemClient for BlockChainRpcClient { async fn dmq_contents( &self, at: Hash, - recipient: cumulus_primitives_core::ParaId, - ) -> Result< - Vec>, - sp_api::ApiError, - > { + recipient: ParaId, + ) -> Result>, sp_api::ApiError> { Ok(self.rpc_client.parachain_host_dmq_contents(recipient, at).await?) } async fn inbound_hrmp_channels_contents( &self, at: Hash, - recipient: cumulus_primitives_core::ParaId, + recipient: ParaId, ) -> Result< std::collections::BTreeMap< - cumulus_primitives_core::ParaId, - Vec< - polkadot_core_primitives::InboundHrmpMessage, - >, + ParaId, + Vec>, >, sp_api::ApiError, > { @@ -329,7 +311,7 @@ impl RuntimeApiSubsystemClient for BlockChainRpcClient { async fn validation_code_hash( &self, at: Hash, - para_id: cumulus_primitives_core::ParaId, + para_id: ParaId, assumption: polkadot_primitives::OccupiedCoreAssumption, ) -> Result, sp_api::ApiError> { Ok(self @@ -424,7 +406,7 @@ impl RuntimeApiSubsystemClient for BlockChainRpcClient { async fn para_backing_state( &self, at: Hash, - para_id: cumulus_primitives_core::ParaId, + para_id: ParaId, ) -> Result, ApiError> { Ok(self.rpc_client.parachain_host_para_backing_state(at, para_id).await?) } @@ -448,14 +430,14 @@ impl RuntimeApiSubsystemClient for BlockChainRpcClient { async fn claim_queue( &self, at: Hash, - ) -> Result>, ApiError> { + ) -> Result>, ApiError> { Ok(self.rpc_client.parachain_host_claim_queue(at).await?) } async fn candidates_pending_availability( &self, at: Hash, - para_id: cumulus_primitives_core::ParaId, + para_id: ParaId, ) -> Result>, sp_api::ApiError> { Ok(self .rpc_client diff --git a/cumulus/client/relay-chain-rpc-interface/src/lib.rs b/cumulus/client/relay-chain-rpc-interface/src/lib.rs index 77dc1d7318a..f53cdeffea9 100644 --- a/cumulus/client/relay-chain-rpc-interface/src/lib.rs +++ b/cumulus/client/relay-chain-rpc-interface/src/lib.rs @@ -272,4 +272,13 @@ impl RelayChainInterface for RelayChainRpcInterface { ) -> RelayChainResult>> { self.rpc_client.parachain_host_availability_cores(relay_parent).await } + + async fn claim_queue( + &self, + relay_parent: RelayHash, + ) -> RelayChainResult< + BTreeMap>, + > { + self.rpc_client.parachain_host_claim_queue(relay_parent).await + } } diff --git a/cumulus/pallets/parachain-system/src/lib.rs b/cumulus/pallets/parachain-system/src/lib.rs index 882dcb68fbb..9dc41aa03d9 100644 --- a/cumulus/pallets/parachain-system/src/lib.rs +++ b/cumulus/pallets/parachain-system/src/lib.rs @@ -31,12 +31,16 @@ extern crate alloc; use alloc::{collections::btree_map::BTreeMap, vec, vec::Vec}; use codec::{Decode, Encode}; -use core::cmp; +use core::{cmp, marker::PhantomData}; use cumulus_primitives_core::{ - relay_chain, AbridgedHostConfiguration, ChannelInfo, ChannelStatus, CollationInfo, - GetChannelInfo, InboundDownwardMessage, InboundHrmpMessage, ListChannelInfos, MessageSendError, + relay_chain::{ + self, + vstaging::{ClaimQueueOffset, CoreSelector}, + }, + AbridgedHostConfiguration, ChannelInfo, ChannelStatus, CollationInfo, GetChannelInfo, + InboundDownwardMessage, InboundHrmpMessage, ListChannelInfos, MessageSendError, OutboundHrmpMessage, ParaId, PersistedValidationData, UpwardMessage, UpwardMessageSender, - XcmpMessageHandler, XcmpMessageSource, + XcmpMessageHandler, XcmpMessageSource, DEFAULT_CLAIM_QUEUE_OFFSET, }; use cumulus_primitives_parachain_inherent::{MessageQueueChain, ParachainInherentData}; use frame_support::{ @@ -51,8 +55,9 @@ use frame_system::{ensure_none, ensure_root, pallet_prelude::HeaderFor}; use polkadot_parachain_primitives::primitives::RelayChainBlockNumber; use polkadot_runtime_parachains::FeeTracker; use scale_info::TypeInfo; +use sp_core::U256; use sp_runtime::{ - traits::{Block as BlockT, BlockNumberProvider, Hash}, + traits::{Block as BlockT, BlockNumberProvider, Hash, One}, BoundedSlice, FixedU128, RuntimeDebug, Saturating, }; use xcm::{latest::XcmHash, VersionedLocation, VersionedXcm}; @@ -186,6 +191,22 @@ pub mod ump_constants { pub const MESSAGE_SIZE_FEE_BASE: FixedU128 = FixedU128::from_rational(1, 1000); // 0.001 } +/// Trait for selecting the next core to build the candidate for. +pub trait SelectCore { + fn select_core_for_child() -> (CoreSelector, ClaimQueueOffset); +} + +/// The default core selection policy. +pub struct DefaultCoreSelector(PhantomData); + +impl SelectCore for DefaultCoreSelector { + fn select_core_for_child() -> (CoreSelector, ClaimQueueOffset) { + let core_selector: U256 = (frame_system::Pallet::::block_number() + One::one()).into(); + + (CoreSelector(core_selector.byte(0)), ClaimQueueOffset(DEFAULT_CLAIM_QUEUE_OFFSET)) + } +} + #[frame_support::pallet] pub mod pallet { use super::*; @@ -246,6 +267,9 @@ pub mod pallet { /// that collators aren't expected to have node versions that supply the included block /// in the relay-chain state proof. type ConsensusHook: ConsensusHook; + + /// Select core. + type SelectCore: SelectCore; } #[pallet::hooks] @@ -1372,6 +1396,11 @@ impl Pallet { } } + /// Returns the core selector. + pub fn core_selector() -> (CoreSelector, ClaimQueueOffset) { + T::SelectCore::select_core_for_child() + } + /// Set a custom head data that should be returned as result of `validate_block`. /// /// This will overwrite the head data that is returned as result of `validate_block` while diff --git a/cumulus/pallets/parachain-system/src/mock.rs b/cumulus/pallets/parachain-system/src/mock.rs index 247de3a29b6..1f5e4f4dbcf 100644 --- a/cumulus/pallets/parachain-system/src/mock.rs +++ b/cumulus/pallets/parachain-system/src/mock.rs @@ -94,6 +94,7 @@ impl Config for Test { type CheckAssociatedRelayNumber = AnyRelayNumber; type ConsensusHook = TestConsensusHook; type WeightInfo = (); + type SelectCore = DefaultCoreSelector; } std::thread_local! { diff --git a/cumulus/pallets/xcmp-queue/src/mock.rs b/cumulus/pallets/xcmp-queue/src/mock.rs index 348939de1f1..470e00fe94e 100644 --- a/cumulus/pallets/xcmp-queue/src/mock.rs +++ b/cumulus/pallets/xcmp-queue/src/mock.rs @@ -108,6 +108,7 @@ impl cumulus_pallet_parachain_system::Config for Test { type ReservedXcmpWeight = (); type CheckAssociatedRelayNumber = AnyRelayNumber; type ConsensusHook = cumulus_pallet_parachain_system::consensus_hook::ExpectParentIncluded; + type SelectCore = cumulus_pallet_parachain_system::DefaultCoreSelector; } parameter_types! { diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs index d495c78e5a2..e46734dfa97 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs @@ -38,7 +38,7 @@ use assets_common::{ AssetIdForTrustBackedAssetsConvert, }; use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; -use cumulus_primitives_core::AggregateMessageOrigin; +use cumulus_primitives_core::{AggregateMessageOrigin, ClaimQueueOffset, CoreSelector}; use sp_api::impl_runtime_apis; use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; use sp_runtime::{ @@ -666,6 +666,7 @@ impl cumulus_pallet_parachain_system::Config for Runtime { type ReservedXcmpWeight = ReservedXcmpWeight; type CheckAssociatedRelayNumber = RelayNumberMonotonicallyIncreases; type ConsensusHook = ConsensusHook; + type SelectCore = cumulus_pallet_parachain_system::DefaultCoreSelector; } type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< @@ -1389,6 +1390,12 @@ impl_runtime_apis! { } } + impl cumulus_primitives_core::GetCoreSelectorApi for Runtime { + fn core_selector() -> (CoreSelector, ClaimQueueOffset) { + ParachainSystem::core_selector() + } + } + #[cfg(feature = "try-runtime")] impl frame_try_runtime::TryRuntime for Runtime { fn on_runtime_upgrade(checks: frame_try_runtime::UpgradeCheckSelect) -> (Weight, Weight) { diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs index ca832a5e47c..946b72e966e 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -37,7 +37,7 @@ use assets_common::{ }; use codec::{Decode, Encode, MaxEncodedLen}; use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; -use cumulus_primitives_core::{AggregateMessageOrigin, ParaId}; +use cumulus_primitives_core::{AggregateMessageOrigin, ClaimQueueOffset, CoreSelector, ParaId}; use frame_support::{ construct_runtime, derive_impl, dispatch::DispatchClass, @@ -664,6 +664,7 @@ impl cumulus_pallet_parachain_system::Config for Runtime { type ReservedXcmpWeight = ReservedXcmpWeight; type CheckAssociatedRelayNumber = RelayNumberMonotonicallyIncreases; type ConsensusHook = ConsensusHook; + type SelectCore = cumulus_pallet_parachain_system::DefaultCoreSelector; } type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< @@ -1485,6 +1486,12 @@ impl_runtime_apis! { } } + impl cumulus_primitives_core::GetCoreSelectorApi for Runtime { + fn core_selector() -> (CoreSelector, ClaimQueueOffset) { + ParachainSystem::core_selector() + } + } + #[cfg(feature = "try-runtime")] impl frame_try_runtime::TryRuntime for Runtime { fn on_runtime_upgrade(checks: frame_try_runtime::UpgradeCheckSelect) -> (Weight, Weight) { diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/cumulus_pallet_parachain_system.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/cumulus_pallet_parachain_system.rs index fc63a0814d0..ef1a6a41cef 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/cumulus_pallet_parachain_system.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/cumulus_pallet_parachain_system.rs @@ -77,4 +77,4 @@ impl cumulus_pallet_parachain_system::WeightInfo for We .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(4)) } -} +} \ No newline at end of file diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs index 5158349cdb4..177978cee10 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs @@ -56,7 +56,7 @@ use sp_runtime::{ use sp_version::NativeVersion; use sp_version::RuntimeVersion; -use cumulus_primitives_core::ParaId; +use cumulus_primitives_core::{ClaimQueueOffset, CoreSelector, ParaId}; use frame_support::{ construct_runtime, derive_impl, dispatch::DispatchClass, @@ -373,6 +373,7 @@ impl cumulus_pallet_parachain_system::Config for Runtime { type ReservedXcmpWeight = ReservedXcmpWeight; type CheckAssociatedRelayNumber = RelayNumberMonotonicallyIncreases; type ConsensusHook = ConsensusHook; + type SelectCore = cumulus_pallet_parachain_system::DefaultCoreSelector; } type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< @@ -877,6 +878,12 @@ impl_runtime_apis! { } } + impl cumulus_primitives_core::GetCoreSelectorApi for Runtime { + fn core_selector() -> (CoreSelector, ClaimQueueOffset) { + ParachainSystem::core_selector() + } + } + impl bp_westend::WestendFinalityApi for Runtime { fn best_finalized() -> Option> { BridgeWestendGrandpa::best_finalized() diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs index 65397f038d5..5c40506442d 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs @@ -41,7 +41,7 @@ use bridge_runtime_common::extensions::{ CheckAndBoostBridgeGrandpaTransactions, CheckAndBoostBridgeParachainsTransactions, }; use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; -use cumulus_primitives_core::ParaId; +use cumulus_primitives_core::{ClaimQueueOffset, CoreSelector, ParaId}; use sp_api::impl_runtime_apis; use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; use sp_runtime::{ @@ -355,6 +355,7 @@ impl cumulus_pallet_parachain_system::Config for Runtime { type ReservedXcmpWeight = ReservedXcmpWeight; type CheckAssociatedRelayNumber = RelayNumberMonotonicallyIncreases; type ConsensusHook = ConsensusHook; + type SelectCore = cumulus_pallet_parachain_system::DefaultCoreSelector; } type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< @@ -815,6 +816,12 @@ impl_runtime_apis! { } } + impl cumulus_primitives_core::GetCoreSelectorApi for Runtime { + fn core_selector() -> (CoreSelector, ClaimQueueOffset) { + ParachainSystem::core_selector() + } + } + impl bp_rococo::RococoFinalityApi for Runtime { fn best_finalized() -> Option> { BridgeRococoGrandpa::best_finalized() diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs index ceedf4f86b2..7174595f12a 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs @@ -67,7 +67,7 @@ use sp_version::NativeVersion; use sp_version::RuntimeVersion; use codec::{Decode, Encode, MaxEncodedLen}; -use cumulus_primitives_core::{AggregateMessageOrigin, ParaId}; +use cumulus_primitives_core::{AggregateMessageOrigin, ClaimQueueOffset, CoreSelector, ParaId}; use frame_support::{ construct_runtime, derive_impl, dispatch::DispatchClass, @@ -398,6 +398,7 @@ impl cumulus_pallet_parachain_system::Config for Runtime { type ReservedXcmpWeight = ReservedXcmpWeight; type CheckAssociatedRelayNumber = RelayNumberMonotonicallyIncreases; type ConsensusHook = ConsensusHook; + type SelectCore = cumulus_pallet_parachain_system::DefaultCoreSelector; } type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< @@ -1008,6 +1009,12 @@ impl_runtime_apis! { } } + impl cumulus_primitives_core::GetCoreSelectorApi for Runtime { + fn core_selector() -> (CoreSelector, ClaimQueueOffset) { + ParachainSystem::core_selector() + } + } + #[cfg(feature = "try-runtime")] impl frame_try_runtime::TryRuntime for Runtime { fn on_runtime_upgrade(checks: frame_try_runtime::UpgradeCheckSelect) -> (Weight, Weight) { diff --git a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs index 55770515d73..5f03b6ab7e2 100644 --- a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs @@ -33,7 +33,7 @@ extern crate alloc; use alloc::{vec, vec::Vec}; use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; -use cumulus_primitives_core::AggregateMessageOrigin; +use cumulus_primitives_core::{AggregateMessageOrigin, ClaimQueueOffset, CoreSelector}; use sp_api::impl_runtime_apis; use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; use sp_runtime::{ @@ -293,6 +293,7 @@ impl cumulus_pallet_parachain_system::Config for Runtime { type ReservedXcmpWeight = ReservedXcmpWeight; type CheckAssociatedRelayNumber = RelayNumberMonotonicallyIncreases; type ConsensusHook = ConsensusHook; + type SelectCore = cumulus_pallet_parachain_system::DefaultCoreSelector; } type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< @@ -653,6 +654,12 @@ impl_runtime_apis! { } } + impl cumulus_primitives_core::GetCoreSelectorApi for Runtime { + fn core_selector() -> (CoreSelector, ClaimQueueOffset) { + ParachainSystem::core_selector() + } + } + impl pallet_contracts::ContractsApi for Runtime { fn call( origin: AccountId, diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs index 0c9f9461f7f..be332f67c9e 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs @@ -38,7 +38,7 @@ extern crate alloc; use alloc::{vec, vec::Vec}; use codec::{Decode, Encode, MaxEncodedLen}; use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; -use cumulus_primitives_core::{AggregateMessageOrigin, ParaId}; +use cumulus_primitives_core::{AggregateMessageOrigin, ClaimQueueOffset, CoreSelector, ParaId}; use frame_support::{ construct_runtime, derive_impl, dispatch::DispatchClass, @@ -284,6 +284,7 @@ impl cumulus_pallet_parachain_system::Config for Runtime { type ReservedXcmpWeight = ReservedXcmpWeight; type CheckAssociatedRelayNumber = RelayNumberMonotonicallyIncreases; type ConsensusHook = ConsensusHook; + type SelectCore = cumulus_pallet_parachain_system::DefaultCoreSelector; } type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< @@ -861,6 +862,12 @@ impl_runtime_apis! { } } + impl cumulus_primitives_core::GetCoreSelectorApi for Runtime { + fn core_selector() -> (CoreSelector, ClaimQueueOffset) { + ParachainSystem::core_selector() + } + } + #[cfg(feature = "try-runtime")] impl frame_try_runtime::TryRuntime for Runtime { fn on_runtime_upgrade(checks: frame_try_runtime::UpgradeCheckSelect) -> (Weight, Weight) { diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs index 614eae895a7..cc43c95f005 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs @@ -38,7 +38,7 @@ extern crate alloc; use alloc::{vec, vec::Vec}; use codec::{Decode, Encode, MaxEncodedLen}; use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; -use cumulus_primitives_core::{AggregateMessageOrigin, ParaId}; +use cumulus_primitives_core::{AggregateMessageOrigin, ClaimQueueOffset, CoreSelector, ParaId}; use frame_support::{ construct_runtime, derive_impl, dispatch::DispatchClass, @@ -284,6 +284,7 @@ impl cumulus_pallet_parachain_system::Config for Runtime { type ReservedXcmpWeight = ReservedXcmpWeight; type CheckAssociatedRelayNumber = RelayNumberMonotonicallyIncreases; type ConsensusHook = ConsensusHook; + type SelectCore = cumulus_pallet_parachain_system::DefaultCoreSelector; } type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< @@ -852,6 +853,12 @@ impl_runtime_apis! { } } + impl cumulus_primitives_core::GetCoreSelectorApi for Runtime { + fn core_selector() -> (CoreSelector, ClaimQueueOffset) { + ParachainSystem::core_selector() + } + } + #[cfg(feature = "try-runtime")] impl frame_try_runtime::TryRuntime for Runtime { fn on_runtime_upgrade(checks: frame_try_runtime::UpgradeCheckSelect) -> (Weight, Weight) { diff --git a/cumulus/parachains/runtimes/glutton/glutton-westend/src/lib.rs b/cumulus/parachains/runtimes/glutton/glutton-westend/src/lib.rs index abf13a596a7..1b213785cf9 100644 --- a/cumulus/parachains/runtimes/glutton/glutton-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/glutton/glutton-westend/src/lib.rs @@ -64,7 +64,7 @@ use sp_runtime::{ use sp_version::NativeVersion; use sp_version::RuntimeVersion; -use cumulus_primitives_core::AggregateMessageOrigin; +use cumulus_primitives_core::{AggregateMessageOrigin, ClaimQueueOffset, CoreSelector}; pub use frame_support::{ construct_runtime, derive_impl, dispatch::DispatchClass, @@ -172,8 +172,8 @@ parameter_types! { type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< Runtime, RELAY_CHAIN_SLOT_DURATION_MILLIS, - BLOCK_PROCESSING_VELOCITY, - UNINCLUDED_SEGMENT_CAPACITY, + 3, + 9, >; impl cumulus_pallet_parachain_system::Config for Runtime { @@ -188,6 +188,7 @@ impl cumulus_pallet_parachain_system::Config for Runtime { type CheckAssociatedRelayNumber = RelayNumberMonotonicallyIncreases; type ConsensusHook = ConsensusHook; type WeightInfo = weights::cumulus_pallet_parachain_system::WeightInfo; + type SelectCore = cumulus_pallet_parachain_system::DefaultCoreSelector; } parameter_types! { @@ -234,7 +235,7 @@ impl pallet_aura::Config for Runtime { type DisabledValidators = (); type MaxAuthorities = ConstU32<100_000>; type AllowMultipleBlocksPerSlot = ConstBool; - type SlotDuration = ConstU64; + type SlotDuration = ConstU64<2000>; } impl pallet_glutton::Config for Runtime { @@ -425,7 +426,13 @@ impl_runtime_apis! { } } - impl frame_system_rpc_runtime_api::AccountNonceApi for Runtime { + impl cumulus_primitives_core::GetCoreSelectorApi for Runtime { + fn core_selector() -> (CoreSelector, ClaimQueueOffset) { + ParachainSystem::core_selector() + } + } + + impl frame_system_rpc_runtime_api::AccountNonceApi for Runtime { fn account_nonce(account: AccountId) -> Nonce { System::account_nonce(account) } diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs b/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs index 9b251a90d67..150152964b9 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs @@ -27,7 +27,7 @@ extern crate alloc; use alloc::{vec, vec::Vec}; use codec::{Decode, Encode, MaxEncodedLen}; use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; -use cumulus_primitives_core::{AggregateMessageOrigin, ParaId}; +use cumulus_primitives_core::{AggregateMessageOrigin, ClaimQueueOffset, CoreSelector, ParaId}; use frame_support::{ construct_runtime, derive_impl, dispatch::DispatchClass, @@ -259,6 +259,7 @@ impl cumulus_pallet_parachain_system::Config for Runtime { type CheckAssociatedRelayNumber = RelayNumberMonotonicallyIncreases; type ConsensusHook = ConsensusHook; type WeightInfo = weights::cumulus_pallet_parachain_system::WeightInfo; + type SelectCore = cumulus_pallet_parachain_system::DefaultCoreSelector; } type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< @@ -804,6 +805,12 @@ impl_runtime_apis! { } } + impl cumulus_primitives_core::GetCoreSelectorApi for Runtime { + fn core_selector() -> (CoreSelector, ClaimQueueOffset) { + ParachainSystem::core_selector() + } + } + #[cfg(feature = "try-runtime")] impl frame_try_runtime::TryRuntime for Runtime { fn on_runtime_upgrade(checks: frame_try_runtime::UpgradeCheckSelect) -> (Weight, Weight) { diff --git a/cumulus/parachains/runtimes/people/people-westend/src/lib.rs b/cumulus/parachains/runtimes/people/people-westend/src/lib.rs index 07bfba92c93..60b861678c5 100644 --- a/cumulus/parachains/runtimes/people/people-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/people/people-westend/src/lib.rs @@ -27,7 +27,7 @@ extern crate alloc; use alloc::{vec, vec::Vec}; use codec::{Decode, Encode, MaxEncodedLen}; use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; -use cumulus_primitives_core::{AggregateMessageOrigin, ParaId}; +use cumulus_primitives_core::{AggregateMessageOrigin, ClaimQueueOffset, CoreSelector, ParaId}; use frame_support::{ construct_runtime, derive_impl, dispatch::DispatchClass, @@ -259,6 +259,7 @@ impl cumulus_pallet_parachain_system::Config for Runtime { type CheckAssociatedRelayNumber = RelayNumberMonotonicallyIncreases; type ConsensusHook = ConsensusHook; type WeightInfo = weights::cumulus_pallet_parachain_system::WeightInfo; + type SelectCore = cumulus_pallet_parachain_system::DefaultCoreSelector; } type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< @@ -804,6 +805,12 @@ impl_runtime_apis! { } } + impl cumulus_primitives_core::GetCoreSelectorApi for Runtime { + fn core_selector() -> (CoreSelector, ClaimQueueOffset) { + ParachainSystem::core_selector() + } + } + #[cfg(feature = "try-runtime")] impl frame_try_runtime::TryRuntime for Runtime { fn on_runtime_upgrade(checks: frame_try_runtime::UpgradeCheckSelect) -> (Weight, Weight) { diff --git a/cumulus/parachains/runtimes/starters/seedling/src/lib.rs b/cumulus/parachains/runtimes/starters/seedling/src/lib.rs index f126ee861fa..bffedd5bdf5 100644 --- a/cumulus/parachains/runtimes/starters/seedling/src/lib.rs +++ b/cumulus/parachains/runtimes/starters/seedling/src/lib.rs @@ -31,6 +31,7 @@ extern crate alloc; use alloc::{vec, vec::Vec}; use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; +use cumulus_primitives_core::{ClaimQueueOffset, CoreSelector}; use sp_api::impl_runtime_apis; pub use sp_consensus_aura::sr25519::AuthorityId as AuraId; use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; @@ -204,6 +205,7 @@ impl cumulus_pallet_parachain_system::Config for Runtime { BLOCK_PROCESSING_VELOCITY, UNINCLUDED_SEGMENT_CAPACITY, >; + type SelectCore = cumulus_pallet_parachain_system::DefaultCoreSelector; } impl parachain_info::Config for Runtime {} @@ -371,6 +373,12 @@ impl_runtime_apis! { } } + impl cumulus_primitives_core::GetCoreSelectorApi for Runtime { + fn core_selector() -> (CoreSelector, ClaimQueueOffset) { + ParachainSystem::core_selector() + } + } + impl sp_genesis_builder::GenesisBuilder for Runtime { fn build_state(config: Vec) -> sp_genesis_builder::Result { build_state::(config) diff --git a/cumulus/parachains/runtimes/starters/shell/src/lib.rs b/cumulus/parachains/runtimes/starters/shell/src/lib.rs index fac2d1312c0..a7c8bc23935 100644 --- a/cumulus/parachains/runtimes/starters/shell/src/lib.rs +++ b/cumulus/parachains/runtimes/starters/shell/src/lib.rs @@ -36,7 +36,7 @@ extern crate alloc; use alloc::{vec, vec::Vec}; use codec::{Decode, Encode}; use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; -use cumulus_primitives_core::AggregateMessageOrigin; +use cumulus_primitives_core::{AggregateMessageOrigin, ClaimQueueOffset, CoreSelector}; use frame_support::unsigned::TransactionValidityError; use scale_info::TypeInfo; use sp_api::impl_runtime_apis; @@ -206,6 +206,7 @@ impl cumulus_pallet_parachain_system::Config for Runtime { BLOCK_PROCESSING_VELOCITY, UNINCLUDED_SEGMENT_CAPACITY, >; + type SelectCore = cumulus_pallet_parachain_system::DefaultCoreSelector; } impl parachain_info::Config for Runtime {} @@ -429,6 +430,12 @@ impl_runtime_apis! { } } + impl cumulus_primitives_core::GetCoreSelectorApi for Runtime { + fn core_selector() -> (CoreSelector, ClaimQueueOffset) { + ParachainSystem::core_selector() + } + } + impl sp_genesis_builder::GenesisBuilder for Runtime { fn build_state(config: Vec) -> sp_genesis_builder::Result { build_state::(config) diff --git a/cumulus/parachains/runtimes/testing/penpal/src/lib.rs b/cumulus/parachains/runtimes/testing/penpal/src/lib.rs index 266894c3e4e..a98c7b4886f 100644 --- a/cumulus/parachains/runtimes/testing/penpal/src/lib.rs +++ b/cumulus/parachains/runtimes/testing/penpal/src/lib.rs @@ -41,7 +41,7 @@ use assets_common::{ }; use codec::Encode; use cumulus_pallet_parachain_system::RelayNumberStrictlyIncreases; -use cumulus_primitives_core::{AggregateMessageOrigin, ParaId}; +use cumulus_primitives_core::{AggregateMessageOrigin, ClaimQueueOffset, CoreSelector, ParaId}; use frame_support::{ construct_runtime, derive_impl, dispatch::DispatchClass, @@ -632,6 +632,7 @@ impl cumulus_pallet_parachain_system::Config for Runtime { BLOCK_PROCESSING_VELOCITY, UNINCLUDED_SEGMENT_CAPACITY, >; + type SelectCore = cumulus_pallet_parachain_system::DefaultCoreSelector; } impl parachain_info::Config for Runtime {} @@ -960,6 +961,12 @@ impl_runtime_apis! { } } + impl cumulus_primitives_core::GetCoreSelectorApi for Runtime { + fn core_selector() -> (CoreSelector, ClaimQueueOffset) { + ParachainSystem::core_selector() + } + } + impl xcm_runtime_apis::fees::XcmPaymentApi for Runtime { fn query_acceptable_payment_assets(xcm_version: xcm::Version) -> Result, XcmPaymentApiError> { let acceptable_assets = vec![AssetLocationId(xcm_config::RelayLocation::get())]; diff --git a/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs b/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs index 34646f84aed..4d39728df83 100644 --- a/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs +++ b/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs @@ -68,7 +68,7 @@ pub use sp_consensus_aura::sr25519::AuthorityId as AuraId; pub use sp_runtime::BuildStorage; pub use sp_runtime::{Perbill, Permill}; -use cumulus_primitives_core::{AggregateMessageOrigin, ParaId}; +use cumulus_primitives_core::{AggregateMessageOrigin, ClaimQueueOffset, CoreSelector, ParaId}; use frame_support::traits::TransformOrigin; use parachains_common::{ impls::{AssetsFrom, NonZeroIssuance}, @@ -299,6 +299,7 @@ impl cumulus_pallet_parachain_system::Config for Runtime { type ReservedXcmpWeight = ReservedXcmpWeight; type CheckAssociatedRelayNumber = RelayNumberMonotonicallyIncreases; type ConsensusHook = ConsensusHook; + type SelectCore = cumulus_pallet_parachain_system::DefaultCoreSelector; } impl parachain_info::Config for Runtime {} @@ -854,6 +855,12 @@ impl_runtime_apis! { ConsensusHook::can_build_upon(included_hash, slot) } } + + impl cumulus_primitives_core::GetCoreSelectorApi for Runtime { + fn core_selector() -> (CoreSelector, ClaimQueueOffset) { + ParachainSystem::core_selector() + } + } } cumulus_pallet_parachain_system::register_validate_block! { diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/mod.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/mod.rs index 907f09263fc..c6dce521e4a 100644 --- a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/mod.rs +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/mod.rs @@ -26,7 +26,7 @@ pub mod runtime; pub mod spec; pub mod types; -use cumulus_primitives_core::CollectCollationInfo; +use cumulus_primitives_core::{CollectCollationInfo, GetCoreSelectorApi}; use sc_client_db::DbHash; use sp_api::{ApiExt, CallApiAt, ConstructRuntimeApi, Metadata}; use sp_block_builder::BlockBuilder; @@ -66,6 +66,7 @@ pub trait NodeRuntimeApi: + BlockBuilder + TaggedTransactionQueue + CollectCollationInfo + + GetCoreSelectorApi + Sized { } @@ -76,6 +77,7 @@ impl NodeRuntimeApi for T where + SessionKeys + BlockBuilder + TaggedTransactionQueue + + GetCoreSelectorApi + CollectCollationInfo { } diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/fake_runtime_api/utils.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/fake_runtime_api/utils.rs index 442b87b5d77..0b1ed5d8288 100644 --- a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/fake_runtime_api/utils.rs +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/fake_runtime_api/utils.rs @@ -15,6 +15,7 @@ // along with Cumulus. If not, see . pub(crate) mod imports { + pub use cumulus_primitives_core::{ClaimQueueOffset, CoreSelector}; pub use parachains_common::{AccountId, Balance, Nonce}; pub use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; pub use sp_runtime::{ @@ -156,6 +157,12 @@ macro_rules! impl_node_runtime_apis { } } + impl cumulus_primitives_core::GetCoreSelectorApi<$block> for $runtime { + fn core_selector() -> (CoreSelector, ClaimQueueOffset) { + unimplemented!() + } + } + #[cfg(feature = "try-runtime")] impl frame_try_runtime::TryRuntime<$block> for $runtime { fn on_runtime_upgrade( diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/service.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/service.rs index 303ec1e3b29..b1c714784f4 100644 --- a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/service.rs +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/service.rs @@ -392,7 +392,7 @@ where relay_chain_interface: Arc, transaction_pool: Arc>>, keystore: KeystorePtr, - relay_chain_slot_duration: Duration, + _relay_chain_slot_duration: Duration, para_id: ParaId, collator_key: CollatorPair, _overseer_handle: OverseerHandle, @@ -429,7 +429,6 @@ where keystore, collator_key, para_id, - relay_chain_slot_duration, proposer, collator_service, authoring_duration: Duration::from_millis(2000), diff --git a/cumulus/primitives/core/src/lib.rs b/cumulus/primitives/core/src/lib.rs index 60b86af8e94..6bad65b3ff2 100644 --- a/cumulus/primitives/core/src/lib.rs +++ b/cumulus/primitives/core/src/lib.rs @@ -32,6 +32,7 @@ pub use polkadot_parachain_primitives::primitives::{ XcmpMessageHandler, }; pub use polkadot_primitives::{ + vstaging::{ClaimQueueOffset, CoreSelector}, AbridgedHostConfiguration, AbridgedHrmpChannel, PersistedValidationData, }; @@ -332,6 +333,10 @@ pub mod rpsr_digest { } } +/// The default claim queue offset to be used if it's not configured/accessible in the parachain +/// runtime +pub const DEFAULT_CLAIM_QUEUE_OFFSET: u8 = 1; + /// Information about a collation. /// /// This was used in version 1 of the [`CollectCollationInfo`] runtime api. @@ -395,4 +400,10 @@ sp_api::decl_runtime_apis! { /// we are collecting the collation info for. fn collect_collation_info(header: &Block::Header) -> CollationInfo; } + + /// Runtime api used to select the core for which the next block will be built. + pub trait GetCoreSelectorApi { + /// Retrieve core selector and claim queue offset for the next block. + fn core_selector() -> (CoreSelector, ClaimQueueOffset); + } } diff --git a/cumulus/test/runtime/src/lib.rs b/cumulus/test/runtime/src/lib.rs index ba0a3487011..861d55c77cd 100644 --- a/cumulus/test/runtime/src/lib.rs +++ b/cumulus/test/runtime/src/lib.rs @@ -42,6 +42,7 @@ use sp_api::{decl_runtime_apis, impl_runtime_apis}; pub use sp_consensus_aura::sr25519::AuthorityId as AuraId; use sp_core::{ConstBool, ConstU32, ConstU64, OpaqueMetadata}; +use cumulus_primitives_core::{ClaimQueueOffset, CoreSelector}; use sp_runtime::{ create_runtime_str, generic, impl_opaque_keys, traits::{BlakeTwo256, Block as BlockT, IdentifyAccount, IdentityLookup, Verify}, @@ -311,6 +312,7 @@ impl cumulus_pallet_parachain_system::Config for Runtime { type CheckAssociatedRelayNumber = cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; type ConsensusHook = ConsensusHook; + type SelectCore = cumulus_pallet_parachain_system::DefaultCoreSelector; } impl parachain_info::Config for Runtime {} @@ -528,6 +530,12 @@ impl_runtime_apis! { } } + impl cumulus_primitives_core::GetCoreSelectorApi for Runtime { + fn core_selector() -> (CoreSelector, ClaimQueueOffset) { + ParachainSystem::core_selector() + } + } + impl sp_genesis_builder::GenesisBuilder for Runtime { fn build_state(config: Vec) -> sp_genesis_builder::Result { build_state::(config) diff --git a/cumulus/test/service/src/lib.rs b/cumulus/test/service/src/lib.rs index db771f5fe53..9f93572e9ce 100644 --- a/cumulus/test/service/src/lib.rs +++ b/cumulus/test/service/src/lib.rs @@ -489,7 +489,6 @@ where keystore, collator_key, para_id, - relay_chain_slot_duration, proposer, collator_service, authoring_duration: Duration::from_millis(2000), diff --git a/docs/sdk/src/polkadot_sdk/cumulus.rs b/docs/sdk/src/polkadot_sdk/cumulus.rs index 9bd957c7c1c..c6abf9f7b4d 100644 --- a/docs/sdk/src/polkadot_sdk/cumulus.rs +++ b/docs/sdk/src/polkadot_sdk/cumulus.rs @@ -96,6 +96,7 @@ mod tests { >; type WeightInfo = (); type DmpQueue = frame::traits::EnqueueWithOrigin<(), sp_core::ConstU8<0>>; + type SelectCore = cumulus_pallet_parachain_system::DefaultCoreSelector; } impl parachain_info::Config for Runtime {} diff --git a/prdoc/pr_5372.prdoc b/prdoc/pr_5372.prdoc new file mode 100644 index 00000000000..fec856b3c0d --- /dev/null +++ b/prdoc/pr_5372.prdoc @@ -0,0 +1,71 @@ +title: "elastic scaling: add core selector to cumulus" + +doc: + - audience: [Node Dev, Runtime Dev] + description: | + Adds a runtime API for querying the core selector of a parachain. + Also use the core selector API and the claim queue relay chain runtime API in the slot based collator (if available) + to determine which cores to build on. + Part of implementing https://github.com/polkadot-fellows/RFCs/pull/103. + +crates: + - name: cumulus-client-consensus-aura + bump: major + - name: cumulus-relay-chain-inprocess-interface + bump: patch + - name: cumulus-relay-chain-interface + bump: major + validate: false + - name: cumulus-relay-chain-minimal-node + bump: none + - name: cumulus-relay-chain-rpc-interface + bump: patch + - name: cumulus-pallet-parachain-system + bump: major + validate: false + - name: asset-hub-rococo-runtime + bump: patch + - name: asset-hub-westend-runtime + bump: patch + - name: bridge-hub-rococo-runtime + bump: patch + - name: bridge-hub-westend-runtime + bump: patch + - name: collectives-westend-runtime + bump: patch + - name: contracts-rococo-runtime + bump: patch + - name: coretime-rococo-runtime + bump: patch + - name: coretime-westend-runtime + bump: patch + - name: glutton-westend-runtime + bump: patch + - name: people-rococo-runtime + bump: patch + - name: people-westend-runtime + bump: patch + - name: seedling-runtime + bump: patch + - name: shell-runtime + bump: patch + - name: penpal-runtime + bump: patch + - name: rococo-parachain-runtime + bump: patch + - name: polkadot-parachain-lib + bump: major + validate: false + - name: cumulus-primitives-core + bump: minor + validate: false + - name: cumulus-test-runtime + bump: minor + - name: cumulus-client-consensus-common + bump: none + - name: cumulus-client-pov-recovery + bump: none + - name: cumulus-client-network + bump: none + - name: cumulus-pallet-xcmp-queue + bump: none diff --git a/templates/parachain/runtime/src/configs/mod.rs b/templates/parachain/runtime/src/configs/mod.rs index 607797e690b..45618733712 100644 --- a/templates/parachain/runtime/src/configs/mod.rs +++ b/templates/parachain/runtime/src/configs/mod.rs @@ -198,6 +198,7 @@ impl cumulus_pallet_parachain_system::Config for Runtime { type ReservedXcmpWeight = ReservedXcmpWeight; type CheckAssociatedRelayNumber = RelayNumberMonotonicallyIncreases; type ConsensusHook = ConsensusHook; + type SelectCore = cumulus_pallet_parachain_system::DefaultCoreSelector; } impl parachain_info::Config for Runtime {} -- GitLab From e8dad101634748a164a26ed8f404f218af29789e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Sep 2024 13:45:41 +0000 Subject: [PATCH 093/125] Bump k256 from 0.13.3 to 0.13.4 (#5802) Bumps [k256](https://github.com/RustCrypto/elliptic-curves) from 0.13.3 to 0.13.4.
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=k256&package-manager=cargo&previous-version=0.13.3&new-version=0.13.4)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Oliver Tale-Yazdi --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 251b472352b..18517f94788 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8308,9 +8308,9 @@ dependencies = [ [[package]] name = "k256" -version = "0.13.3" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "956ff9b67e26e1a6a866cb758f12c6f8746208489e3e4a4b5580802f2f0a587b" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" dependencies = [ "cfg-if", "ecdsa", diff --git a/Cargo.toml b/Cargo.toml index a4be2cc0d0a..8e3330e4e61 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -817,7 +817,7 @@ jobserver = { version = "0.1.26" } jsonpath_lib = { version = "0.3" } jsonrpsee = { version = "0.24.3" } jsonrpsee-core = { version = "0.24.3" } -k256 = { version = "0.13.3", default-features = false } +k256 = { version = "0.13.4", default-features = false } kitchensink-runtime = { path = "substrate/bin/node/runtime" } kvdb = { version = "0.13.0" } kvdb-memorydb = { version = "0.13.0" } -- GitLab From 3962ec0845f9b1d72ceb82d66fe2128877d81b3b Mon Sep 17 00:00:00 2001 From: yjh Date: Tue, 24 Sep 2024 01:19:54 +0800 Subject: [PATCH 094/125] chore: export NodeHealthProxyLayer (#5768) Co-authored-by: command-bot <> --- prdoc/pr_5768.prdoc | 10 ++++++++++ substrate/client/rpc-servers/src/lib.rs | 3 +-- 2 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 prdoc/pr_5768.prdoc diff --git a/prdoc/pr_5768.prdoc b/prdoc/pr_5768.prdoc new file mode 100644 index 00000000000..5c606006561 --- /dev/null +++ b/prdoc/pr_5768.prdoc @@ -0,0 +1,10 @@ +title: "export NodeHealthProxyLayer" + +doc: + - audience: Node Dev + description: | + This PR export `NodeHealthProxyLayer` from sc-rpc-server. + +crates: + - name: sc-rpc-server + bump: patch diff --git a/substrate/client/rpc-servers/src/lib.rs b/substrate/client/rpc-servers/src/lib.rs index 0472a0a2f63..756e2a08c6d 100644 --- a/substrate/client/rpc-servers/src/lib.rs +++ b/substrate/client/rpc-servers/src/lib.rs @@ -32,7 +32,6 @@ use jsonrpsee::{ }, Methods, RpcModule, }; -use middleware::NodeHealthProxyLayer; use tower::Service; use utils::{ build_rpc_api, deny_unsafe, format_listen_addrs, get_proxy_ip, ListenAddrError, RpcSettings, @@ -43,7 +42,7 @@ pub use jsonrpsee::{ core::id_providers::{RandomIntegerIdProvider, RandomStringIdProvider}, server::{middleware::rpc::RpcServiceBuilder, BatchRequestConfig}, }; -pub use middleware::{Metrics, MiddlewareLayer, RpcMetrics}; +pub use middleware::{Metrics, MiddlewareLayer, NodeHealthProxyLayer, RpcMetrics}; pub use utils::{RpcEndpoint, RpcMethods}; const MEGABYTE: u32 = 1024 * 1024; -- GitLab From 71c768a9e1a467c629adc68423e47e37c855cd77 Mon Sep 17 00:00:00 2001 From: Cyrill Leutwiler Date: Mon, 23 Sep 2024 19:26:13 +0200 Subject: [PATCH 095/125] [pallet-revive] Add chain ID to config an runtime API (#5807) This PR adds the EVM chain ID to Config as well as a corresponding runtime API so contracts can query it. Related issue: https://github.com/paritytech/revive/issues/44 --------- Signed-off-by: xermicus Co-authored-by: command-bot <> --- prdoc/pr_5807.prdoc | 16 ++++++ substrate/bin/node/runtime/src/lib.rs | 5 +- .../revive/fixtures/contracts/chain_id.rs | 37 +++++++++++++ substrate/frame/revive/src/lib.rs | 12 +++- substrate/frame/revive/src/tests.rs | 15 +++++ substrate/frame/revive/src/wasm/runtime.rs | 55 +++++++++++++------ substrate/frame/revive/uapi/src/host.rs | 3 + .../frame/revive/uapi/src/host/riscv32.rs | 3 +- 8 files changed, 123 insertions(+), 23 deletions(-) create mode 100644 prdoc/pr_5807.prdoc create mode 100644 substrate/frame/revive/fixtures/contracts/chain_id.rs diff --git a/prdoc/pr_5807.prdoc b/prdoc/pr_5807.prdoc new file mode 100644 index 00000000000..3447ea64e43 --- /dev/null +++ b/prdoc/pr_5807.prdoc @@ -0,0 +1,16 @@ +title: "[pallet-revive] last call return data API" + +doc: + - audience: Runtime Dev + description: | + This PR adds the EVM chain ID to Config as well as a corresponding runtime API so contracts can query it. + + Related issue: https://github.com/paritytech/revive/issues/44 + +crates: + - name: pallet-revive + bump: major + - name: pallet-revive-fixtures + bump: patch + - name: pallet-revive-uapi + bump: minor diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index bfcaf5e9af8..784d74973c3 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -49,8 +49,8 @@ use frame_support::{ imbalance::ResolveAssetTo, nonfungibles_v2::Inspect, pay::PayAssetFromAccount, GetSalary, PayFromAccount, }, - AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU16, ConstU32, Contains, Currency, - EitherOfDiverse, EnsureOriginWithArg, EqualPrivilegeOnly, Imbalance, InsideBoth, + AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU16, ConstU32, ConstU64, Contains, + Currency, EitherOfDiverse, EnsureOriginWithArg, EqualPrivilegeOnly, Imbalance, InsideBoth, InstanceFilter, KeyOwnerProofSystem, LinearStoragePrice, LockIdentifier, Nothing, OnUnbalanced, VariantCountOf, WithdrawReasons, }, @@ -1419,6 +1419,7 @@ impl pallet_revive::Config for Runtime { type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent; type Debug = (); type Xcm = (); + type ChainId = ConstU64<420_420_420>; } impl pallet_sudo::Config for Runtime { diff --git a/substrate/frame/revive/fixtures/contracts/chain_id.rs b/substrate/frame/revive/fixtures/contracts/chain_id.rs new file mode 100644 index 00000000000..ce7a0cc671c --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/chain_id.rs @@ -0,0 +1,37 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_std] +#![no_main] + +extern crate common; + +use uapi::{HostFn, HostFnImpl as api, ReturnFlags}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() { + call() +} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + let mut buf = [0; 32]; + api::chain_id(&mut buf); + api::return_value(ReturnFlags::empty(), &buf); +} diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index bf980369056..114d51c8969 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -60,7 +60,7 @@ use frame_support::{ ensure, traits::{ fungible::{Inspect, Mutate, MutateHold}, - ConstU32, Contains, EnsureOrigin, Get, Time, + ConstU32, ConstU64, Contains, EnsureOrigin, Get, Time, }, weights::{Weight, WeightMeter}, BoundedVec, RuntimeDebugNoBound, @@ -293,6 +293,13 @@ pub mod pallet { /// This value is usually higher than [`Self::RuntimeMemory`] to account for the fact /// that validators have to hold all storage items in PvF memory. type PVFMemory: Get; + + /// The [EIP-155](https://eips.ethereum.org/EIPS/eip-155) chain ID. + /// + /// This is a unique identifier assigned to each blockchain network, + /// preventing replay attacks. + #[pallet::constant] + type ChainId: Get; } /// Container for different types that implement [`DefaultConfig`]` of this pallet. @@ -365,6 +372,7 @@ pub mod pallet { type Xcm = (); type RuntimeMemory = ConstU32<{ 128 * 1024 * 1024 }>; type PVFMemory = ConstU32<{ 512 * 1024 * 1024 }>; + type ChainId = ConstU64<{ 0 }>; } } @@ -919,7 +927,7 @@ pub mod pallet { let contract = if let Some(contract) = contract { contract } else { - return Err(>::ContractNotFound.into()) + return Err(>::ContractNotFound.into()); }; >>::increment_refcount(code_hash)?; >>::decrement_refcount(contract.code_hash); diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index 167e8da201b..d06cdcfd465 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -412,6 +412,7 @@ parameter_types! { pub static DepositPerByte: BalanceOf = 1; pub const DepositPerItem: BalanceOf = 2; pub static CodeHashLockupDepositPercent: Perbill = Perbill::from_percent(0); + pub static ChainId: u64 = 384; } impl Convert> for Test { @@ -496,6 +497,7 @@ impl Config for Test { type InstantiateOrigin = EnsureAccount; type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent; type Debug = TestDebug; + type ChainId = ChainId; } pub struct ExtBuilder { @@ -4310,4 +4312,17 @@ mod run_tests { assert_ok!(builder::call(addr).build()); }); } + + #[test] + fn chain_id_works() { + let (code, _) = compile_module("chain_id").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + let chain_id = U256::from(::ChainId::get()); + let received = builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_result(); + assert_eq!(received.result.data, chain_id.encode()); + }); + } } diff --git a/substrate/frame/revive/src/wasm/runtime.rs b/substrate/frame/revive/src/wasm/runtime.rs index 34cce533c14..ebc407adacd 100644 --- a/substrate/frame/revive/src/wasm/runtime.rs +++ b/substrate/frame/revive/src/wasm/runtime.rs @@ -468,22 +468,28 @@ impl Token for RuntimeCosts { Terminate(locked_dependencies) => T::WeightInfo::seal_terminate(locked_dependencies), DepositEvent { num_topic, len } => T::WeightInfo::seal_deposit_event(num_topic, len), DebugMessage(len) => T::WeightInfo::seal_debug_message(len), - SetStorage { new_bytes, old_bytes } => - cost_storage!(write, seal_set_storage, new_bytes, old_bytes), + SetStorage { new_bytes, old_bytes } => { + cost_storage!(write, seal_set_storage, new_bytes, old_bytes) + }, ClearStorage(len) => cost_storage!(write, seal_clear_storage, len), ContainsStorage(len) => cost_storage!(read, seal_contains_storage, len), GetStorage(len) => cost_storage!(read, seal_get_storage, len), TakeStorage(len) => cost_storage!(write, seal_take_storage, len), - SetTransientStorage { new_bytes, old_bytes } => - cost_storage!(write_transient, seal_set_transient_storage, new_bytes, old_bytes), - ClearTransientStorage(len) => - cost_storage!(write_transient, seal_clear_transient_storage, len), - ContainsTransientStorage(len) => - cost_storage!(read_transient, seal_contains_transient_storage, len), - GetTransientStorage(len) => - cost_storage!(read_transient, seal_get_transient_storage, len), - TakeTransientStorage(len) => - cost_storage!(write_transient, seal_take_transient_storage, len), + SetTransientStorage { new_bytes, old_bytes } => { + cost_storage!(write_transient, seal_set_transient_storage, new_bytes, old_bytes) + }, + ClearTransientStorage(len) => { + cost_storage!(write_transient, seal_clear_transient_storage, len) + }, + ContainsTransientStorage(len) => { + cost_storage!(read_transient, seal_contains_transient_storage, len) + }, + GetTransientStorage(len) => { + cost_storage!(read_transient, seal_get_transient_storage, len) + }, + TakeTransientStorage(len) => { + cost_storage!(write_transient, seal_take_transient_storage, len) + }, Transfer => T::WeightInfo::seal_transfer(), CallBase => T::WeightInfo::seal_call(0, 0), DelegateCallBase => T::WeightInfo::seal_delegate_call(), @@ -571,7 +577,7 @@ impl<'a, E: Ext, M: PolkaVmInstance> Runtime<'a, E, M> { Ok(Step) => None, Ok(Ecalli(idx)) => { let Some(syscall_symbol) = module.imports().get(idx) else { - return Some(Err(>::InvalidSyscall.into())) + return Some(Err(>::InvalidSyscall.into())); }; match self.handle_ecall(instance, syscall_symbol.as_bytes(), api_version) { Ok(None) => None, @@ -679,7 +685,7 @@ impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { create_token: impl FnOnce(u32) -> Option, ) -> Result<(), DispatchError> { if allow_skip && out_ptr == SENTINEL { - return Ok(()) + return Ok(()); } let len = memory.read_u32(out_len_ptr)?; @@ -703,7 +709,7 @@ impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { create_token: impl FnOnce(u32) -> Option, ) -> Result<(), DispatchError> { if allow_skip && out_ptr == SENTINEL { - return Ok(()) + return Ok(()); } let buf_len = buf.len() as u32; @@ -820,7 +826,7 @@ impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { let max_size = self.ext.max_value_size(); let charged = self.charge_gas(costs(value_len, self.ext.max_value_size()))?; if value_len > max_size { - return Err(Error::::ValueTooLarge.into()) + return Err(Error::::ValueTooLarge.into()); } let key = self.decode_key(memory, key_ptr, key_len)?; let value = Some(memory.read(value_ptr, value_len)?); @@ -1022,7 +1028,7 @@ impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { }, CallType::DelegateCall { code_hash_ptr } => { if flags.intersects(CallFlags::ALLOW_REENTRY | CallFlags::READ_ONLY) { - return Err(Error::::InvalidCallFlags.into()) + return Err(Error::::InvalidCallFlags.into()); } let code_hash = memory.read_h256(code_hash_ptr)?; @@ -1037,7 +1043,7 @@ impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { return Err(TrapReason::Return(ReturnData { flags: return_value.flags.bits(), data: return_value.data, - })) + })); } } @@ -1536,6 +1542,19 @@ pub mod env { )?) } + /// Returns the chain ID. + /// See [`pallet_revive_uapi::HostFn::chain_id`]. + #[api_version(0)] + fn chain_id(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { + Ok(self.write_fixed_sandbox_output( + memory, + out_ptr, + &as_bytes(U256::from(::ChainId::get())), + false, + |_| Some(RuntimeCosts::CopyToContract(32)), + )?) + } + /// Stores the value transferred along with this call/instantiate into the supplied buffer. /// See [`pallet_revive_uapi::HostFn::value_transferred`]. #[api_version(0)] diff --git a/substrate/frame/revive/uapi/src/host.rs b/substrate/frame/revive/uapi/src/host.rs index 538de7ea251..57a03332670 100644 --- a/substrate/frame/revive/uapi/src/host.rs +++ b/substrate/frame/revive/uapi/src/host.rs @@ -71,6 +71,9 @@ pub trait HostFn: private::Sealed { /// - `output`: A reference to the output data buffer to write the balance. fn balance_of(addr: &[u8; 20], output: &mut [u8; 32]); + /// Returns the [EIP-155](https://eips.ethereum.org/EIPS/eip-155) chain ID. + fn chain_id(output: &mut [u8; 32]); + /// Stores the current block number of the current contract into the supplied buffer. /// /// # Parameters diff --git a/substrate/frame/revive/uapi/src/host/riscv32.rs b/substrate/frame/revive/uapi/src/host/riscv32.rs index 0bb0ede4543..a60c338e8bd 100644 --- a/substrate/frame/revive/uapi/src/host/riscv32.rs +++ b/substrate/frame/revive/uapi/src/host/riscv32.rs @@ -83,6 +83,7 @@ mod sys { pub fn weight_left(out_ptr: *mut u8, out_len_ptr: *mut u32); pub fn balance(out_ptr: *mut u8); pub fn balance_of(addr_ptr: *const u8, out_ptr: *mut u8); + pub fn chain_id(out_ptr: *mut u8); pub fn value_transferred(out_ptr: *mut u8); pub fn now(out_ptr: *mut u8); pub fn minimum_balance(out_ptr: *mut u8); @@ -447,7 +448,7 @@ impl HostFn for HostFnImpl { } impl_wrapper_for! { - [u8; 32] => block_number, balance, value_transferred, now, minimum_balance; + [u8; 32] => block_number, balance, value_transferred, now, minimum_balance, chain_id; [u8; 20] => address, caller; } -- GitLab From e0766bb97dd33e2b877625737f78b0f41dce222a Mon Sep 17 00:00:00 2001 From: CJ13th <48095175+CJ13th@users.noreply.github.com> Date: Mon, 23 Sep 2024 22:42:17 +0200 Subject: [PATCH 096/125] Implement try_append for StorageNMap (#5745) # Description Closes #5722 Added an implementation of the `try_append` functionality which is present on the other storage map types but currently missing from StorageNMap. --------- Co-authored-by: Shawn Tabrizi --- prdoc/pr_5745.prdoc | 14 ++++ substrate/frame/support/src/storage/mod.rs | 76 +++++++++++++++++++ .../frame/support/src/storage/types/nmap.rs | 14 ++++ 3 files changed, 104 insertions(+) create mode 100644 prdoc/pr_5745.prdoc diff --git a/prdoc/pr_5745.prdoc b/prdoc/pr_5745.prdoc new file mode 100644 index 00000000000..7463589378a --- /dev/null +++ b/prdoc/pr_5745.prdoc @@ -0,0 +1,14 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Implement `try_append` for `StorageNMap` + +doc: + - audience: Runtime Dev + description: | + This PR introduces the `try_append` api which is available on other storage map types, + but missing on `StorageNMap`. + +crates: + - name: frame-support + bump: minor diff --git a/substrate/frame/support/src/storage/mod.rs b/substrate/frame/support/src/storage/mod.rs index 7fb991d3779..61939256303 100644 --- a/substrate/frame/support/src/storage/mod.rs +++ b/substrate/frame/support/src/storage/mod.rs @@ -1693,6 +1693,46 @@ where } } +/// Storage N map that is capable of [`StorageTryAppend`]. +pub trait TryAppendNMap, I: Encode> { + /// Try and append the `item` into the storage N map at the given `key`. + /// + /// This might fail if bounds are not respected. + fn try_append< + LikeK: EncodeLikeTuple + TupleToEncodedIter + Clone, + LikeI: EncodeLike, + >( + key: LikeK, + item: LikeI, + ) -> Result<(), ()>; +} + +impl TryAppendNMap for StorageNMapT +where + K: KeyGenerator, + T: FullCodec + StorageTryAppend, + I: Encode, + StorageNMapT: generator::StorageNMap, +{ + fn try_append< + LikeK: EncodeLikeTuple + TupleToEncodedIter + Clone, + LikeI: EncodeLike, + >( + key: LikeK, + item: LikeI, + ) -> Result<(), ()> { + let bound = T::bound(); + let current = Self::decode_len(key.clone()).unwrap_or_default(); + if current < bound { + let key = Self::storage_n_map_final_key::(key); + sp_io::storage::append(&key, item.encode()); + Ok(()) + } else { + Err(()) + } + } +} + /// Returns the storage prefix for a specific pallet name and storage name. /// /// The storage prefix is `concat(twox_128(pallet_name), twox_128(storage_name))`. @@ -2019,6 +2059,17 @@ mod test { (NMapKey, NMapKey, NMapKey), u64, >; + #[crate::storage_alias] + type FooQuadMap = StorageNMap< + Prefix, + ( + NMapKey, + NMapKey, + NMapKey, + NMapKey, + ), + BoundedVec>, + >; #[test] fn contains_prefix_works() { @@ -2109,6 +2160,31 @@ mod test { BoundedVec::>::try_from(vec![4, 5]).unwrap(), ); }); + + TestExternalities::default().execute_with(|| { + let bounded: BoundedVec> = vec![1, 2, 3].try_into().unwrap(); + FooQuadMap::insert((1, 1, 1, 1), bounded); + + assert_ok!(FooQuadMap::try_append((1, 1, 1, 1), 4)); + assert_ok!(FooQuadMap::try_append((1, 1, 1, 1), 5)); + assert_ok!(FooQuadMap::try_append((1, 1, 1, 1), 6)); + assert_ok!(FooQuadMap::try_append((1, 1, 1, 1), 7)); + assert_eq!(FooQuadMap::decode_len((1, 1, 1, 1)).unwrap(), 7); + assert!(FooQuadMap::try_append((1, 1, 1, 1), 8).is_err()); + + // append to a non-existing + assert!(FooQuadMap::get((2, 1, 1, 1)).is_none()); + assert_ok!(FooQuadMap::try_append((2, 1, 1, 1), 4)); + assert_eq!( + FooQuadMap::get((2, 1, 1, 1)).unwrap(), + BoundedVec::>::try_from(vec![4]).unwrap(), + ); + assert_ok!(FooQuadMap::try_append((2, 1, 1, 1), 5)); + assert_eq!( + FooQuadMap::get((2, 1, 1, 1)).unwrap(), + BoundedVec::>::try_from(vec![4, 5]).unwrap(), + ); + }); } #[crate::storage_alias] diff --git a/substrate/frame/support/src/storage/types/nmap.rs b/substrate/frame/support/src/storage/types/nmap.rs index 9ee012f8628..0fc22b35352 100755 --- a/substrate/frame/support/src/storage/types/nmap.rs +++ b/substrate/frame/support/src/storage/types/nmap.rs @@ -25,6 +25,7 @@ use crate::{ StorageEntryMetadataBuilder, TupleToEncodedIter, }, KeyGenerator, PrefixIterator, StorageAppend, StorageDecodeLength, StoragePrefixedMap, + StorageTryAppend, }, traits::{Get, GetDefault, StorageInfo, StorageInstance}, }; @@ -338,6 +339,19 @@ where >::append(key, item) } + /// Try and append the given item to the value in the storage. + /// + /// Is only available if `Value` of the storage implements [`StorageTryAppend`]. + pub fn try_append(key: KArg, item: EncodeLikeItem) -> Result<(), ()> + where + KArg: EncodeLikeTuple + TupleToEncodedIter + Clone, + Item: Encode, + EncodeLikeItem: EncodeLike, + Value: StorageTryAppend, + { + >::try_append(key, item) + } + /// Read the length of the storage value without decoding the entire value under the /// given `key1` and `key2`. /// -- GitLab From fbcda7e1ebff191e40fd23c71a5dae0a56524237 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Sep 2024 22:58:30 +0200 Subject: [PATCH 097/125] Bump the ci_dependencies group across 1 directory with 3 updates (#5803) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the ci_dependencies group with 3 updates in the / directory: [actions/setup-node](https://github.com/actions/setup-node), [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) and [actions-rust-lang/setup-rust-toolchain](https://github.com/actions-rust-lang/setup-rust-toolchain). Updates `actions/setup-node` from 4.0.3 to 4.0.4
Release notes

Sourced from actions/setup-node's releases.

v4.0.4

What's Changed

Documentation changes:

New Contributors

Full Changelog: https://github.com/actions/setup-node/compare/v4...v4.0.4

Commits

Updates `peter-evans/create-pull-request` from 7.0.1 to 7.0.5
Release notes

Sourced from peter-evans/create-pull-request's releases.

Create Pull Request v7.0.5

⚙️ Fixes an issue with commit signing to allow it to support symlinks

What's Changed

Full Changelog: https://github.com/peter-evans/create-pull-request/compare/v7.0.4...v7.0.5

Create Pull Request v7.0.4

⚙️ Fixes an issue with commit signing to allow it to support submodules

What's Changed

New Contributors

Full Changelog: https://github.com/peter-evans/create-pull-request/compare/v7.0.3...v7.0.4

Create Pull Request v7.0.3

⚙️ Fixes an issue with commit signing where commit SHAs have variable lengths when abbreviated.

What's Changed

Full Changelog: https://github.com/peter-evans/create-pull-request/compare/v7.0.2...v7.0.3

Create Pull Request v7.0.2

⚙️ Fixes an issue with commit signing when a change was detected as being a rename or copy.

What's Changed

Full Changelog: https://github.com/peter-evans/create-pull-request/compare/v7.0.1...v7.0.2

Commits
  • 5e91468 fix: support symlinks when commit signing (#3359)
  • 2f38cd2 fix: support submodules when commit signing (#3354)
  • 7a8aeac build(deps-dev): bump eslint from 8.57.0 to 8.57.1 (#3344)
  • d39d596 build(deps-dev): bump @​types/jest from 29.5.12 to 29.5.13 (#3343)
  • f6f978f docs: correct suggestion for bot setup (#3342)
  • 6cd32fd fix: disable abbreviated commit shas in diff (#3337)
  • d121e62 fix: disable diff detection for renames and copies (#3330)
  • f4d66f4 build(deps-dev): bump typescript from 5.5.4 to 5.6.2 (#3319)
  • 488c869 build(deps-dev): bump @​types/node from 18.19.48 to 18.19.50 (#3320)
  • 5354f85 docs: update readme
  • See full diff in compare view

Updates `actions-rust-lang/setup-rust-toolchain` from 1.9.0 to 1.10.0
Release notes

Sourced from actions-rust-lang/setup-rust-toolchain's releases.

v1.10.0

What's Changed

  • Add new parameter cache-directories that is propagated to Swatinem/rust-cache (#44 by @​pranc1ngpegasus)
  • Add new parameter cache-key that is propagated to Swatinem/rust-cache as key (#41 by @​iainlane)
  • Make rustup toolchain installation more robust in light of planned changes rust-lang/rustup#3635 and rust-lang/rustup#3985
  • Allow installing multiple Rust toolchains by specifying multiple versions in the toolchain input parameter.
  • Configure the rustup override behavior via the new override input. (#38)

New Contributors

Full Changelog: https://github.com/actions-rust-lang/setup-rust-toolchain/compare/v1...v1.10.0

Commits
  • 4d1965c Add override input parameter that controls the rustup override behavior
  • b31b131 Allow installing multiple Rust toolchains at once.
  • 9f99923 Add pre-commit for basic checks
  • 86a2ce6 Make toolchain install more robust
  • eb4a655 Document new keys and add changelog
  • a90048d Merge pull request #41 from iainlane/iainlane/propagate-cache-key
  • 597574a Update key name in README
  • 634cedf Merge pull request #44 from pranc1ngpegasus/feat/able-to-cache-additional-dir...
  • 5d6934e Able to cache additional directories
  • b01657d Add support for adding to cache key
  • See full diff in compare view

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/check-licenses.yml | 4 ++-- .github/workflows/checks-quick.yml | 2 +- .github/workflows/misc-sync-templates.yml | 4 ++-- .github/workflows/tests-misc.yml | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/check-licenses.yml b/.github/workflows/check-licenses.yml index 88bd06c6707..8bd87118201 100644 --- a/.github/workflows/check-licenses.yml +++ b/.github/workflows/check-licenses.yml @@ -21,7 +21,7 @@ jobs: steps: - name: Checkout sources uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.1.7 - - uses: actions/setup-node@v4.0.3 + - uses: actions/setup-node@v4.0.4 with: node-version: "18.x" registry-url: "https://npm.pkg.github.com" @@ -56,7 +56,7 @@ jobs: steps: - name: Checkout sources uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.1.7 - - uses: actions/setup-node@v4.0.3 + - uses: actions/setup-node@v4.0.4 with: node-version: "18.x" registry-url: "https://npm.pkg.github.com" diff --git a/.github/workflows/checks-quick.yml b/.github/workflows/checks-quick.yml index 9e36d2bcb2e..eefe36d9791 100644 --- a/.github/workflows/checks-quick.yml +++ b/.github/workflows/checks-quick.yml @@ -102,7 +102,7 @@ jobs: - name: Checkout sources uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.1.7 - name: Setup Node.js - uses: actions/setup-node@v4.0.3 + uses: actions/setup-node@v4.0.4 with: node-version: "18.x" registry-url: "https://npm.pkg.github.com" diff --git a/.github/workflows/misc-sync-templates.yml b/.github/workflows/misc-sync-templates.yml index 658da4451dc..b5db0538569 100644 --- a/.github/workflows/misc-sync-templates.yml +++ b/.github/workflows/misc-sync-templates.yml @@ -157,7 +157,7 @@ jobs: timeout-minutes: 90 - name: Create PR on failure if: failure() && steps.check-compilation.outcome == 'failure' - uses: peter-evans/create-pull-request@8867c4aba1b742c39f8d0ba35429c2dfa4b6cb20 # v5 + uses: peter-evans/create-pull-request@5e914681df9dc83aa4e4905692ca88beb2f9e91f # v5 with: path: "${{ env.template-path }}" token: ${{ steps.app_token.outputs.token }} @@ -167,7 +167,7 @@ jobs: body: "The template has NOT been successfully built and needs to be inspected." branch: "update-template/${{ github.event.inputs.stable_release_branch }}" - name: Create PR on success - uses: peter-evans/create-pull-request@8867c4aba1b742c39f8d0ba35429c2dfa4b6cb20 # v5 + uses: peter-evans/create-pull-request@5e914681df9dc83aa4e4905692ca88beb2f9e91f # v5 with: path: "${{ env.template-path }}" token: ${{ steps.app_token.outputs.token }} diff --git a/.github/workflows/tests-misc.yml b/.github/workflows/tests-misc.yml index dc6128a3f6e..843e09ef7a4 100644 --- a/.github/workflows/tests-misc.yml +++ b/.github/workflows/tests-misc.yml @@ -334,7 +334,7 @@ jobs: - name: Set up Homebrew uses: Homebrew/actions/setup-homebrew@1ccc07ccd54b6048295516a3eb89b192c35057dc # master from 12.09.2024 - name: Install rust ${{ env.RUST_VERSION }} - uses: actions-rust-lang/setup-rust-toolchain@1fbea72663f6d4c03efaab13560c8a24cfd2a7cc # v1.9.0 + uses: actions-rust-lang/setup-rust-toolchain@4d1965c9142484e48d40c19de54b5cba84953a06 # v1.10.0 with: cache: false toolchain: ${{ env.RUST_VERSION }} -- GitLab From 08498f5473351c3d2f8eacbe1bfd7bc6d3a2ef8d Mon Sep 17 00:00:00 2001 From: Jan-Jan Date: Mon, 23 Sep 2024 19:52:47 -0300 Subject: [PATCH 098/125] update solochain to use #[runtime] (#5772) # Description * This is part of [issue 5242](https://github.com/paritytech/polkadot-sdk/issues/5242), specifically getting solochain to use `#[frame::runtime]` * Furthermore, reinforced the convention of `Template` instead of `TemplateModule` ## Integration * Should be integrated into the `solochain` template and documentation ## Review Notes * Refactored `solochain` template from [construct_runtime!](https://paritytech.github.io/polkadot-sdk/master/frame_support/macro.construct_runtime.html) to [#[runtime]](https://paritytech.github.io/polkadot-sdk/master/frame_support/attr.runtime.html). * AFAIU `Template` is our new convention, and preferred over `TemplateModule`. # Out of scope * The [#[runtime]](https://paritytech.github.io/polkadot-sdk/master/frame_support/attr.runtime.html) documentation is still very rudimentary, and should ideally be expanded to explain the macro, both what it does and the input options. * Furthermore, suggest update [#[runtime]](https://paritytech.github.io/polkadot-sdk/master/frame_support/attr.runtime.html) documentation to replace `#[crate::runtime]` with `#[frame_support::runtime]` --------- Co-authored-by: Jan-Jan <111935+Jan-Jan@users.noreply.github.com> Co-authored-by: Oliver Tale-Yazdi Co-authored-by: Shawn Tabrizi Co-authored-by: command-bot <> --- prdoc/1.9.0/pr_1378.prdoc | 2 +- .../parachain/pallets/template/src/mock.rs | 2 +- .../parachain/pallets/template/src/tests.rs | 7 ++-- .../parachain/pallets/template/src/weights.rs | 16 +++++----- templates/solochain/README.md | 2 +- .../solochain/pallets/template/src/mock.rs | 32 ++++++++++++++----- .../solochain/pallets/template/src/tests.rs | 7 ++-- .../solochain/pallets/template/src/weights.rs | 16 +++++----- templates/solochain/runtime/src/benchmarks.rs | 2 +- templates/solochain/runtime/src/lib.rs | 2 +- 10 files changed, 49 insertions(+), 39 deletions(-) diff --git a/prdoc/1.9.0/pr_1378.prdoc b/prdoc/1.9.0/pr_1378.prdoc index 6533dcb6630..03427cdba99 100644 --- a/prdoc/1.9.0/pr_1378.prdoc +++ b/prdoc/1.9.0/pr_1378.prdoc @@ -10,7 +10,7 @@ doc: 3. `#[runtime::pallet_index]` must be attached to a pallet to define its index 4. `#[runtime::disable_call]` can be optionally attached to a pallet to disable its calls 5. `#[runtime::disable_unsigned]` can be optionally attached to a pallet to disable unsigned calls - 6. A pallet instance can be defined as `TemplateModule: pallet_template` + 6. A pallet instance can be defined as `Template: pallet_template` An optional attribute can be defined as `#[frame_support::runtime(legacy_ordering)]` to ensure that the order of hooks is same as the order of pallets (and not based on the pallet_index). This is to support legacy runtimes and should be avoided for new ones. diff --git a/templates/parachain/pallets/template/src/mock.rs b/templates/parachain/pallets/template/src/mock.rs index 46e3117596f..b8c730b6eb5 100644 --- a/templates/parachain/pallets/template/src/mock.rs +++ b/templates/parachain/pallets/template/src/mock.rs @@ -22,7 +22,7 @@ mod test_runtime { #[runtime::pallet_index(0)] pub type System = frame_system; #[runtime::pallet_index(1)] - pub type TemplateModule = crate; + pub type Template = crate; } #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] diff --git a/templates/parachain/pallets/template/src/tests.rs b/templates/parachain/pallets/template/src/tests.rs index a4a41af63c2..2f641ce08fb 100644 --- a/templates/parachain/pallets/template/src/tests.rs +++ b/templates/parachain/pallets/template/src/tests.rs @@ -5,7 +5,7 @@ use frame_support::{assert_noop, assert_ok}; fn it_works_for_default_value() { new_test_ext().execute_with(|| { // Dispatch a signed extrinsic. - assert_ok!(TemplateModule::do_something(RuntimeOrigin::signed(1), 42)); + assert_ok!(Template::do_something(RuntimeOrigin::signed(1), 42)); // Read pallet storage and assert an expected result. assert_eq!(Something::::get().map(|v| v.block_number), Some(42)); }); @@ -15,9 +15,6 @@ fn it_works_for_default_value() { fn correct_error_for_none_value() { new_test_ext().execute_with(|| { // Ensure the expected error is thrown when no value is present. - assert_noop!( - TemplateModule::cause_error(RuntimeOrigin::signed(1)), - Error::::NoneValue - ); + assert_noop!(Template::cause_error(RuntimeOrigin::signed(1)), Error::::NoneValue); }); } diff --git a/templates/parachain/pallets/template/src/weights.rs b/templates/parachain/pallets/template/src/weights.rs index 5bfe28e8b71..4b4522429c6 100644 --- a/templates/parachain/pallets/template/src/weights.rs +++ b/templates/parachain/pallets/template/src/weights.rs @@ -41,8 +41,8 @@ pub trait WeightInfo { /// Weights for pallet_template using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { - /// Storage: TemplateModule Something (r:0 w:1) - /// Proof: TemplateModule Something (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Template Something (r:0 w:1) + /// Proof: Template Something (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) fn do_something() -> Weight { // Proof Size summary in bytes: // Measured: `0` @@ -51,8 +51,8 @@ impl WeightInfo for SubstrateWeight { Weight::from_parts(9_000_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } - /// Storage: TemplateModule Something (r:1 w:1) - /// Proof: TemplateModule Something (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Template Something (r:1 w:1) + /// Proof: Template Something (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) fn cause_error() -> Weight { // Proof Size summary in bytes: // Measured: `32` @@ -66,8 +66,8 @@ impl WeightInfo for SubstrateWeight { // For backwards compatibility and tests impl WeightInfo for () { - /// Storage: TemplateModule Something (r:0 w:1) - /// Proof: TemplateModule Something (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Template Something (r:0 w:1) + /// Proof: Template Something (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) fn do_something() -> Weight { // Proof Size summary in bytes: // Measured: `0` @@ -76,8 +76,8 @@ impl WeightInfo for () { Weight::from_parts(9_000_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } - /// Storage: TemplateModule Something (r:1 w:1) - /// Proof: TemplateModule Something (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Template Something (r:1 w:1) + /// Proof: Template Something (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) fn cause_error() -> Weight { // Proof Size summary in bytes: // Measured: `32` diff --git a/templates/solochain/README.md b/templates/solochain/README.md index c4ce5c7f3fb..7f36a997985 100644 --- a/templates/solochain/README.md +++ b/templates/solochain/README.md @@ -185,7 +185,7 @@ template and note the following: configuration is defined by a code block that begins with `impl $PALLET_NAME::Config for Runtime`. - The pallets are composed into a single runtime by way of the - [`construct_runtime!`](https://paritytech.github.io/substrate/master/frame_support/macro.construct_runtime.html) + [#[runtime]](https://paritytech.github.io/polkadot-sdk/master/frame_support/attr.runtime.html) macro, which is part of the [core FRAME pallet library](https://docs.substrate.io/reference/frame-pallets/#system-pallets). diff --git a/templates/solochain/pallets/template/src/mock.rs b/templates/solochain/pallets/template/src/mock.rs index 0c2a247e802..1b86cd9b770 100644 --- a/templates/solochain/pallets/template/src/mock.rs +++ b/templates/solochain/pallets/template/src/mock.rs @@ -4,14 +4,30 @@ use sp_runtime::BuildStorage; type Block = frame_system::mocking::MockBlock; -// Configure a mock runtime to test the pallet. -frame_support::construct_runtime!( - pub enum Test - { - System: frame_system, - TemplateModule: pallet_template, - } -); +#[frame_support::runtime] +mod runtime { + // The main runtime + #[runtime::runtime] + // Runtime Types to be generated + #[runtime::derive( + RuntimeCall, + RuntimeEvent, + RuntimeError, + RuntimeOrigin, + RuntimeFreezeReason, + RuntimeHoldReason, + RuntimeSlashReason, + RuntimeLockId, + RuntimeTask + )] + pub struct Test; + + #[runtime::pallet_index(0)] + pub type System = frame_system::Pallet; + + #[runtime::pallet_index(1)] + pub type Template = pallet_template::Pallet; +} #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] impl frame_system::Config for Test { diff --git a/templates/solochain/pallets/template/src/tests.rs b/templates/solochain/pallets/template/src/tests.rs index 83e4bea7377..d05433c3add 100644 --- a/templates/solochain/pallets/template/src/tests.rs +++ b/templates/solochain/pallets/template/src/tests.rs @@ -7,7 +7,7 @@ fn it_works_for_default_value() { // Go past genesis block so events get deposited System::set_block_number(1); // Dispatch a signed extrinsic. - assert_ok!(TemplateModule::do_something(RuntimeOrigin::signed(1), 42)); + assert_ok!(Template::do_something(RuntimeOrigin::signed(1), 42)); // Read pallet storage and assert an expected result. assert_eq!(Something::::get(), Some(42)); // Assert that the correct event was deposited @@ -19,9 +19,6 @@ fn it_works_for_default_value() { fn correct_error_for_none_value() { new_test_ext().execute_with(|| { // Ensure the expected error is thrown when no value is present. - assert_noop!( - TemplateModule::cause_error(RuntimeOrigin::signed(1)), - Error::::NoneValue - ); + assert_noop!(Template::cause_error(RuntimeOrigin::signed(1)), Error::::NoneValue); }); } diff --git a/templates/solochain/pallets/template/src/weights.rs b/templates/solochain/pallets/template/src/weights.rs index 7c42936e09f..c2879fa503c 100644 --- a/templates/solochain/pallets/template/src/weights.rs +++ b/templates/solochain/pallets/template/src/weights.rs @@ -41,8 +41,8 @@ pub trait WeightInfo { /// Weights for pallet_template using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { - /// Storage: TemplateModule Something (r:0 w:1) - /// Proof: TemplateModule Something (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Template Something (r:0 w:1) + /// Proof: Template Something (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) fn do_something() -> Weight { // Proof Size summary in bytes: // Measured: `0` @@ -51,8 +51,8 @@ impl WeightInfo for SubstrateWeight { Weight::from_parts(9_000_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } - /// Storage: TemplateModule Something (r:1 w:1) - /// Proof: TemplateModule Something (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Template Something (r:1 w:1) + /// Proof: Template Something (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) fn cause_error() -> Weight { // Proof Size summary in bytes: // Measured: `32` @@ -66,8 +66,8 @@ impl WeightInfo for SubstrateWeight { // For backwards compatibility and tests impl WeightInfo for () { - /// Storage: TemplateModule Something (r:0 w:1) - /// Proof: TemplateModule Something (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Template Something (r:0 w:1) + /// Proof: Template Something (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) fn do_something() -> Weight { // Proof Size summary in bytes: // Measured: `0` @@ -76,8 +76,8 @@ impl WeightInfo for () { Weight::from_parts(9_000_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } - /// Storage: TemplateModule Something (r:1 w:1) - /// Proof: TemplateModule Something (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Template Something (r:1 w:1) + /// Proof: Template Something (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) fn cause_error() -> Weight { // Proof Size summary in bytes: // Measured: `32` diff --git a/templates/solochain/runtime/src/benchmarks.rs b/templates/solochain/runtime/src/benchmarks.rs index a42daf56b58..f39c2bd2959 100644 --- a/templates/solochain/runtime/src/benchmarks.rs +++ b/templates/solochain/runtime/src/benchmarks.rs @@ -29,5 +29,5 @@ frame_benchmarking::define_benchmarks!( [pallet_balances, Balances] [pallet_timestamp, Timestamp] [pallet_sudo, Sudo] - [pallet_template, TemplateModule] + [pallet_template, Template] ); diff --git a/templates/solochain/runtime/src/lib.rs b/templates/solochain/runtime/src/lib.rs index ce38c65479e..279518c8f47 100644 --- a/templates/solochain/runtime/src/lib.rs +++ b/templates/solochain/runtime/src/lib.rs @@ -220,5 +220,5 @@ mod runtime { // Include the custom logic from the pallet-template in the runtime. #[runtime::pallet_index(7)] - pub type TemplateModule = pallet_template; + pub type Template = pallet_template; } -- GitLab From 62534e53eaeabe021de6fbe9ad51fa0e160a56c5 Mon Sep 17 00:00:00 2001 From: Adrian Catangiu Date: Tue, 24 Sep 2024 14:14:43 +0300 Subject: [PATCH 099/125] snowbridge: improve destination fee handling to avoid trapping fees dust (#5563) On messages Ethereum -> Polkadot Asset Hub: whether they are a token transfer or a `Transact` for registering new token, make sure to handle unspent fees, rather than trapping them. This PR deposits them to Snowbridge's sovereign account on Asset Hub. --------- Co-authored-by: command-bot <> --- .../pallets/inbound-queue/src/test.rs | 4 +-- .../primitives/router/src/inbound/mod.rs | 34 +++++++++++++------ .../bridges/bridge-hub-rococo/src/lib.rs | 14 ++++---- .../bridge-hub-rococo/src/tests/snowbridge.rs | 18 +++++++--- prdoc/pr_5563.prdoc | 14 ++++++++ 5 files changed, 61 insertions(+), 23 deletions(-) create mode 100644 prdoc/pr_5563.prdoc diff --git a/bridges/snowbridge/pallets/inbound-queue/src/test.rs b/bridges/snowbridge/pallets/inbound-queue/src/test.rs index bd993c968df..41c38460aab 100644 --- a/bridges/snowbridge/pallets/inbound-queue/src/test.rs +++ b/bridges/snowbridge/pallets/inbound-queue/src/test.rs @@ -40,8 +40,8 @@ fn test_submit_happy_path() { .into(), nonce: 1, message_id: [ - 57, 61, 232, 3, 66, 61, 25, 190, 234, 188, 193, 174, 13, 186, 1, 64, 237, 94, 73, - 83, 14, 18, 209, 213, 78, 121, 43, 108, 251, 245, 107, 67, + 255, 125, 48, 71, 174, 185, 100, 26, 159, 43, 108, 6, 116, 218, 55, 155, 223, 143, + 141, 22, 124, 110, 241, 18, 122, 217, 130, 29, 139, 76, 97, 201, ], fee_burned: 110000000000, } diff --git a/bridges/snowbridge/primitives/router/src/inbound/mod.rs b/bridges/snowbridge/primitives/router/src/inbound/mod.rs index 5cff8413af6..a10884b4553 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/mod.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/mod.rs @@ -249,7 +249,7 @@ impl< let total_amount = fee + CreateAssetDeposit::get(); let total: Asset = (Location::parent(), total_amount).into(); - let bridge_location: Location = (Parent, Parent, GlobalConsensus(network)).into(); + let bridge_location = Location::new(2, GlobalConsensus(network)); let owner = GlobalConsensusEthereumConvertsFor::<[u8; 32]>::from_chain_id(&chain_id); let asset_id = Self::convert_token_address(network, token); @@ -262,8 +262,15 @@ impl< // Pay for execution. BuyExecution { fees: xcm_fee, weight_limit: Unlimited }, // Fund the snowbridge sovereign with the required deposit for creation. - DepositAsset { assets: Definite(deposit.into()), beneficiary: bridge_location }, - // Only our inbound-queue pallet is allowed to invoke `UniversalOrigin` + DepositAsset { assets: Definite(deposit.into()), beneficiary: bridge_location.clone() }, + // This `SetAppendix` ensures that `xcm_fee` not spent by `Transact` will be + // deposited to snowbridge sovereign, instead of being trapped, regardless of + // `Transact` success or not. + SetAppendix(Xcm(vec![ + RefundSurplus, + DepositAsset { assets: AllCounted(1).into(), beneficiary: bridge_location }, + ])), + // Only our inbound-queue pallet is allowed to invoke `UniversalOrigin`. DescendOrigin(PalletInstance(inbound_queue_pallet_index).into()), // Change origin to the bridge. UniversalOrigin(GlobalConsensus(network)), @@ -280,12 +287,10 @@ impl< .encode() .into(), }, - RefundSurplus, - // Clear the origin so that remaining assets in holding - // are claimable by the physical origin (BridgeHub) - ClearOrigin, // Forward message id to Asset Hub SetTopic(message_id.into()), + // Once the program ends here, appendix program will run, which will deposit any + // leftover fee to snowbridge sovereign. ] .into(); @@ -340,17 +345,24 @@ impl< match dest_para_id { Some(dest_para_id) => { let dest_para_fee_asset: Asset = (Location::parent(), dest_para_fee).into(); + let bridge_location = Location::new(2, GlobalConsensus(network)); instructions.extend(vec![ + // After program finishes deposit any leftover assets to the snowbridge + // sovereign. + SetAppendix(Xcm(vec![DepositAsset { + assets: Wild(AllCounted(2)), + beneficiary: bridge_location, + }])), // Perform a deposit reserve to send to destination chain. DepositReserveAsset { - assets: Definite(vec![dest_para_fee_asset.clone(), asset.clone()].into()), + assets: Definite(vec![dest_para_fee_asset.clone(), asset].into()), dest: Location::new(1, [Parachain(dest_para_id)]), xcm: vec![ // Buy execution on target. BuyExecution { fees: dest_para_fee_asset, weight_limit: Unlimited }, - // Deposit asset to beneficiary. - DepositAsset { assets: Definite(asset.into()), beneficiary }, + // Deposit assets to beneficiary. + DepositAsset { assets: Wild(AllCounted(2)), beneficiary }, // Forward message id to destination parachain. SetTopic(message_id.into()), ] @@ -371,6 +383,8 @@ impl< // Forward message id to Asset Hub. instructions.push(SetTopic(message_id.into())); + // The `instructions` to forward to AssetHub, and the `total_fees` to locally burn (since + // they are teleported within `instructions`). (instructions.into(), total_fees.into()) } diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/lib.rs index ac08e48ded6..9d54d431e83 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/lib.rs @@ -55,9 +55,12 @@ mod imports { BridgeHubRococoXcmConfig, EthereumBeaconClient, EthereumInboundQueue, }, penpal_emulated_chain::{ - penpal_runtime::xcm_config::{ - CustomizableAssetFromSystemAssetHub as PenpalCustomizableAssetFromSystemAssetHub, - UniversalLocation as PenpalUniversalLocation, + penpal_runtime::{ + self, + xcm_config::{ + CustomizableAssetFromSystemAssetHub as PenpalCustomizableAssetFromSystemAssetHub, + UniversalLocation as PenpalUniversalLocation, + }, }, PenpalAParaPallet as PenpalAPallet, PenpalAssetOwner, }, @@ -72,9 +75,8 @@ mod imports { BridgeHubRococoParaReceiver as BridgeHubRococoReceiver, BridgeHubRococoParaSender as BridgeHubRococoSender, BridgeHubWestendPara as BridgeHubWestend, PenpalAPara as PenpalA, - PenpalAParaReceiver as PenpalAReceiver, PenpalAParaSender as PenpalASender, - RococoRelay as Rococo, RococoRelayReceiver as RococoReceiver, - RococoRelaySender as RococoSender, + PenpalAParaSender as PenpalASender, RococoRelay as Rococo, + RococoRelayReceiver as RococoReceiver, RococoRelaySender as RococoSender, }; pub const ASSET_MIN_BALANCE: u128 = 1000; diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/snowbridge.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/snowbridge.rs index 84328fb7c6d..d91a0c6895f 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/snowbridge.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/snowbridge.rs @@ -286,11 +286,19 @@ fn send_token_from_ethereum_to_penpal() { // Fund AssetHub sovereign account so it can pay execution fees for the asset transfer BridgeHubRococo::fund_accounts(vec![(asset_hub_sovereign.clone(), INITIAL_FUND)]); - // Fund PenPal sender and receiver - PenpalA::fund_accounts(vec![ - (PenpalAReceiver::get(), INITIAL_FUND), - (PenpalASender::get(), INITIAL_FUND), - ]); + // Fund PenPal receiver (covering ED) + let native_id: Location = Parent.into(); + let receiver: AccountId = [ + 28, 189, 45, 67, 83, 10, 68, 112, 90, 208, 136, 175, 49, 62, 24, 248, 11, 83, 239, 22, 179, + 97, 119, 205, 75, 119, 184, 70, 242, 165, 240, 124, + ] + .into(); + PenpalA::mint_foreign_asset( + ::RuntimeOrigin::signed(PenpalAssetOwner::get()), + native_id, + receiver, + penpal_runtime::EXISTENTIAL_DEPOSIT, + ); PenpalA::execute_with(|| { assert_ok!(::System::set_storage( diff --git a/prdoc/pr_5563.prdoc b/prdoc/pr_5563.prdoc new file mode 100644 index 00000000000..cbf436125bb --- /dev/null +++ b/prdoc/pr_5563.prdoc @@ -0,0 +1,14 @@ +title: "snowbridge: improve destination fee handling to avoid trapping fees dust" + +doc: + - audience: Runtime User + description: | + On Ethereum -> Polkadot Asset Hub messages, whether they are a token transfer + or a `Transact` for registering a new token, any unspent fees are deposited to + Snowbridge's sovereign account on Asset Hub, rather than trapped in AH's asset trap. + +crates: + - name: snowbridge-router-primitives + bump: patch + - name: snowbridge-pallet-inbound-queue + bump: patch -- GitLab From 1790e4272b9fd8832f1ab9e3bf5aaaae9cf5ada7 Mon Sep 17 00:00:00 2001 From: Clara van Staden Date: Tue, 24 Sep 2024 13:31:25 +0200 Subject: [PATCH 100/125] Fix border condition in Snowbridge free consensus Updates (#5671) # Description A fix for a border condition introduced with new feature https://github.com/paritytech/polkadot-sdk/pull/5201. A malicious relayer could spam the Ethereum client with sync committee updates that have already been imported for the period. This PR adds a storage item to track the last imported sync committee period, so that subsequent irrelevant updates are not free. Original PR: https://github.com/Snowfork/polkadot-sdk/pull/172 ## Integration Downstream projects are not affected. Relayers will not be able to spam the Ethereum client with irrelevant sync committee updates for free. ## Review Notes Adds a storage item to track the last free sync committee update period, so that duplicate imports are not free. --------- Co-authored-by: Adrian Catangiu --- .../pallets/ethereum-client/src/lib.rs | 27 +- .../pallets/ethereum-client/src/mock.rs | 27 + .../pallets/ethereum-client/src/tests.rs | 63 +- .../tests/fixtures/initial-checkpoint.json | 30 +- .../sync-committee-update-period-0-newer.json | 565 ++++++++++++++++++ .../sync-committee-update-period-0-older.json | 565 ++++++++++++++++++ .../sync-committee-update-period-0.json | 565 ++++++++++++++++++ prdoc/pr_5671.prdoc | 16 + 8 files changed, 1828 insertions(+), 30 deletions(-) create mode 100755 bridges/snowbridge/pallets/ethereum-client/tests/fixtures/sync-committee-update-period-0-newer.json create mode 100755 bridges/snowbridge/pallets/ethereum-client/tests/fixtures/sync-committee-update-period-0-older.json create mode 100644 bridges/snowbridge/pallets/ethereum-client/tests/fixtures/sync-committee-update-period-0.json create mode 100644 prdoc/pr_5671.prdoc diff --git a/bridges/snowbridge/pallets/ethereum-client/src/lib.rs b/bridges/snowbridge/pallets/ethereum-client/src/lib.rs index 84b1476931c..311b54b97de 100644 --- a/bridges/snowbridge/pallets/ethereum-client/src/lib.rs +++ b/bridges/snowbridge/pallets/ethereum-client/src/lib.rs @@ -179,6 +179,10 @@ pub mod pallet { #[pallet::storage] pub type NextSyncCommittee = StorageValue<_, SyncCommitteePrepared, ValueQuery>; + /// The last period where the next sync committee was updated for free. + #[pallet::storage] + pub type LatestSyncCommitteeUpdatePeriod = StorageValue<_, u64, ValueQuery>; + /// The current operating mode of the pallet. #[pallet::storage] #[pallet::getter(fn operating_mode)] @@ -442,6 +446,13 @@ pub mod pallet { let latest_finalized_state = FinalizedBeaconState::::get(LatestFinalizedBlockRoot::::get()) .ok_or(Error::::NotBootstrapped)?; + + let pays_fee = Self::check_refundable(update, latest_finalized_state.slot); + let actual_weight = match update.next_sync_committee_update { + None => T::WeightInfo::submit(), + Some(_) => T::WeightInfo::submit_with_sync_committee(), + }; + if let Some(next_sync_committee_update) = &update.next_sync_committee_update { let store_period = compute_period(latest_finalized_state.slot); let update_finalized_period = compute_period(update.finalized_header.slot); @@ -465,17 +476,12 @@ pub mod pallet { "💫 SyncCommitteeUpdated at period {}.", update_finalized_period ); + >::set(update_finalized_period); Self::deposit_event(Event::SyncCommitteeUpdated { period: update_finalized_period, }); }; - let pays_fee = Self::check_refundable(update, latest_finalized_state.slot); - let actual_weight = match update.next_sync_committee_update { - None => T::WeightInfo::submit(), - Some(_) => T::WeightInfo::submit_with_sync_committee(), - }; - if update.finalized_header.slot > latest_finalized_state.slot { Self::store_finalized_header(update.finalized_header, update.block_roots_root)?; } @@ -657,7 +663,14 @@ pub mod pallet { /// successful sync committee updates are free. pub(super) fn check_refundable(update: &Update, latest_slot: u64) -> Pays { // If the sync committee was successfully updated, the update may be free. - if update.next_sync_committee_update.is_some() { + let update_period = compute_period(update.finalized_header.slot); + let latest_free_update_period = LatestSyncCommitteeUpdatePeriod::::get(); + // If the next sync committee is not known and this update sets it, the update is free. + // If the sync committee update is in a period that we have not received an update for, + // the update is free. + let refundable = + !>::exists() || update_period > latest_free_update_period; + if update.next_sync_committee_update.is_some() && refundable { return Pays::No; } diff --git a/bridges/snowbridge/pallets/ethereum-client/src/mock.rs b/bridges/snowbridge/pallets/ethereum-client/src/mock.rs index be456565d40..7dbabdee823 100644 --- a/bridges/snowbridge/pallets/ethereum-client/src/mock.rs +++ b/bridges/snowbridge/pallets/ethereum-client/src/mock.rs @@ -59,6 +59,33 @@ pub fn load_next_finalized_header_update_fixture() -> snowbridge_beacon_primitiv load_fixture("next-finalized-header-update.json".to_string()).unwrap() } +pub fn load_sync_committee_update_period_0() -> Box< + snowbridge_beacon_primitives::Update< + { config::SYNC_COMMITTEE_SIZE }, + { config::SYNC_COMMITTEE_BITS_SIZE }, + >, +> { + Box::new(load_fixture("sync-committee-update-period-0.json".to_string()).unwrap()) +} + +pub fn load_sync_committee_update_period_0_older_fixture() -> Box< + snowbridge_beacon_primitives::Update< + { config::SYNC_COMMITTEE_SIZE }, + { config::SYNC_COMMITTEE_BITS_SIZE }, + >, +> { + Box::new(load_fixture("sync-committee-update-period-0-older.json".to_string()).unwrap()) +} + +pub fn load_sync_committee_update_period_0_newer_fixture() -> Box< + snowbridge_beacon_primitives::Update< + { config::SYNC_COMMITTEE_SIZE }, + { config::SYNC_COMMITTEE_BITS_SIZE }, + >, +> { + Box::new(load_fixture("sync-committee-update-period-0-newer.json".to_string()).unwrap()) +} + pub fn get_message_verification_payload() -> (Log, Proof) { let inbound_fixture = snowbridge_pallet_ethereum_client_fixtures::make_inbound_fixture(); (inbound_fixture.message.event_log, inbound_fixture.message.proof) diff --git a/bridges/snowbridge/pallets/ethereum-client/src/tests.rs b/bridges/snowbridge/pallets/ethereum-client/src/tests.rs index 82a3b822447..de298ee711d 100644 --- a/bridges/snowbridge/pallets/ethereum-client/src/tests.rs +++ b/bridges/snowbridge/pallets/ethereum-client/src/tests.rs @@ -1,6 +1,8 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2023 Snowfork +pub use crate::mock::*; use crate::{ + config::{EPOCHS_PER_SYNC_COMMITTEE_PERIOD, SLOTS_PER_EPOCH, SLOTS_PER_HISTORICAL_ROOT}, functions::compute_period, mock::{ get_message_verification_payload, load_checkpoint_update_fixture, @@ -8,12 +10,9 @@ use crate::{ load_next_sync_committee_update_fixture, load_sync_committee_update_fixture, }, sync_committee_sum, verify_merkle_branch, BeaconHeader, CompactBeaconState, Error, - FinalizedBeaconState, LatestFinalizedBlockRoot, NextSyncCommittee, SyncCommitteePrepared, + FinalizedBeaconState, LatestFinalizedBlockRoot, LatestSyncCommitteeUpdatePeriod, + NextSyncCommittee, SyncCommitteePrepared, }; - -pub use crate::mock::*; - -use crate::config::{EPOCHS_PER_SYNC_COMMITTEE_PERIOD, SLOTS_PER_EPOCH, SLOTS_PER_HISTORICAL_ROOT}; use frame_support::{assert_err, assert_noop, assert_ok, pallet_prelude::Pays}; use hex_literal::hex; use snowbridge_beacon_primitives::{ @@ -374,7 +373,7 @@ fn submit_update_in_current_period() { assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint)); let result = EthereumBeaconClient::submit(RuntimeOrigin::signed(1), update.clone()); assert_ok!(result); - assert_eq!(result.unwrap().pays_fee, Pays::Yes); + assert_eq!(result.unwrap().pays_fee, Pays::No); let block_root: H256 = update.finalized_header.hash_tree_root().unwrap(); assert!(>::contains_key(block_root)); }); @@ -711,8 +710,56 @@ fn duplicate_sync_committee_updates_are_not_free() { // Check that if the same update is submitted, the update is not free. let second_result = EthereumBeaconClient::submit(RuntimeOrigin::signed(1), sync_committee_update); - assert_err!(second_result, Error::::IrrelevantUpdate); - assert_eq!(second_result.unwrap_err().post_info.pays_fee, Pays::Yes); + assert_ok!(second_result); + assert_eq!(second_result.unwrap().pays_fee, Pays::Yes); + }); +} + +#[test] +fn sync_committee_update_for_sync_committee_already_imported_are_not_free() { + let checkpoint = Box::new(load_checkpoint_update_fixture()); + let sync_committee_update = Box::new(load_sync_committee_update_fixture()); // slot 129 + let second_sync_committee_update = load_sync_committee_update_period_0(); // slot 128 + let third_sync_committee_update = load_sync_committee_update_period_0_newer_fixture(); // slot 224 + let fourth_sync_committee_update = load_sync_committee_update_period_0_older_fixture(); // slot 96 + let fith_sync_committee_update = Box::new(load_next_sync_committee_update_fixture()); // slot 8259 + + new_tester().execute_with(|| { + assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint)); + assert_eq!(>::get(), 0); + + // Check that setting the next sync committee for period 0 is free (it is not set yet). + let result = + EthereumBeaconClient::submit(RuntimeOrigin::signed(1), sync_committee_update.clone()); + assert_ok!(result); + assert_eq!(result.unwrap().pays_fee, Pays::No); + assert_eq!(>::get(), 0); + + // Check that setting the next sync committee for period 0 again is not free. + let second_result = + EthereumBeaconClient::submit(RuntimeOrigin::signed(1), second_sync_committee_update); + assert_eq!(second_result.unwrap().pays_fee, Pays::Yes); + assert_eq!(>::get(), 0); + + // Check that setting an update with a sync committee that has already been set, but with a + // newer finalized header, is free. + let third_result = + EthereumBeaconClient::submit(RuntimeOrigin::signed(1), third_sync_committee_update); + assert_eq!(third_result.unwrap().pays_fee, Pays::No); + assert_eq!(>::get(), 0); + + // Check that setting the next sync committee for period 0 again with an earlier slot is not + // free. + let fourth_result = + EthereumBeaconClient::submit(RuntimeOrigin::signed(1), fourth_sync_committee_update); + assert_err!(fourth_result, Error::::IrrelevantUpdate); + assert_eq!(fourth_result.unwrap_err().post_info.pays_fee, Pays::Yes); + + // Check that setting the next sync committee for period 1 is free. + let fith_result = + EthereumBeaconClient::submit(RuntimeOrigin::signed(1), fith_sync_committee_update); + assert_eq!(fith_result.unwrap().pays_fee, Pays::No); + assert_eq!(>::get(), 1); }); } diff --git a/bridges/snowbridge/pallets/ethereum-client/tests/fixtures/initial-checkpoint.json b/bridges/snowbridge/pallets/ethereum-client/tests/fixtures/initial-checkpoint.json index a62d646617e..34e65d20b88 100755 --- a/bridges/snowbridge/pallets/ethereum-client/tests/fixtures/initial-checkpoint.json +++ b/bridges/snowbridge/pallets/ethereum-client/tests/fixtures/initial-checkpoint.json @@ -1,10 +1,10 @@ { "header": { - "slot": 864, + "slot": 64, "proposer_index": 4, - "parent_root": "0x614e7672f991ac268cd841055973f55e1e42228831a211adef207bb7329be614", - "state_root": "0x5fa8dfca3d760e4242ab46d529144627aa85348a19173b6e081172c701197a4a", - "body_root": "0x0f34c083b1803666bb1ac5e73fa71582731a2cf37d279ff0a3b0cad5a2ff371e" + "parent_root": "0x88e5b7e0dd468b334caf9281e0665184d2d712d7ffe632123ea07631b714920c", + "state_root": "0x82771f834d4d896f4969abdaf45f28f49a7437ecfca7bf2f7db7bfac5ca7224f", + "body_root": "0x8b36f34ceba40a29c9c6fa6266564c7df30ea75fecf1a85e6ec1cb4aabf4dc68" }, "current_sync_committee": { "pubkeys": [ @@ -525,18 +525,18 @@ }, "current_sync_committee_branch": [ "0x3ade38d498a062b50880a9409e1ca3a7fd4315d91eeb3bb83e56ac6bfe8d6a59", - "0xa9e90f89e7f90fd5d79a6bbcaf40ba5cfc05ab1b561ac51c84867c32248d5b1e", - "0xbd1a76b03e02402bb24a627de1980a80ab17691980271f597b844b89b497ef75", - "0x07bbcd27c7cad089023db046eda17e8209842b7d97add8b873519e84fe6480e7", - "0x94c11eeee4cb6192bf40810f23486d8c75dfbc2b6f28d988d6f74435ede243b0" + "0x058baa5628d6156e55ab99da54244be4a071978528f2eb3b19a4f4d7ab36f870", + "0x5f89984c1068b616e99589e161d2bb73b92c68b3422ef309ace434894b4503ae", + "0x4f1c230cf2bbe39502171956421fbe4f1c0a71a9691944019047b84584b371d5", + "0xbf8d5f6021db16e9b50e639e5c489eb8dc06449bf4ed17045cb949cb89a58a04" ], "validators_root": "0x270d43e74ce340de4bca2b1936beca0f4f5408d9e78aec4850920baf659d5b69", - "block_roots_root": "0xb9aab9c388c4e4fcd899b71f62c498fc73406e38e8eb14aa440e9affa06f2a10", + "block_roots_root": "0x2c453665ba6fc024116daf5246126e36085c61257cfbcce69d0bdcf89c766dc0", "block_roots_branch": [ - "0x733422bd810895dab74cbbe07c69dd440cbb51f573181ad4dddac30fcdd0f41f", - "0x9b9eca73ab01d14549c325ba1b4610bb20bf1f8ec2dbd649f9d8cc7f3cea75fa", - "0xbcc666ad0ad9f9725cbd682bc95589d35b1b53b2a615f1e6e8dd5e086336becf", - "0x3069b547a08f703a1715016e926cbd64e71f93f64fb68d98d8c8f1ab745c46e5", - "0xc2de7e1097239404e17b263cfa0473533cc41e903cb03440d633bc5c27314cb4" + "0xbd04f51e43f63b0be48034920e8f5976111b7717225abccedbc6bcb327b95d00", + "0x758319a3bad11ee10fde1036551d982583c0392f284de5cc429b67fbd74c25d5", + "0xb42179d040c2bec20fa0a2750baf225b8097b5c9e4e22af9250cc773f4259427", + "0x5340ad5877c72dca689ca04bc8fedb78d67a4801d99887937edd8ccd29f87e82", + "0x9f03be8e70f74fc6b51e6ed03c96aabb544b5c50e5cdb8c0ab5001d1249d55f0" ] -} \ No newline at end of file +} diff --git a/bridges/snowbridge/pallets/ethereum-client/tests/fixtures/sync-committee-update-period-0-newer.json b/bridges/snowbridge/pallets/ethereum-client/tests/fixtures/sync-committee-update-period-0-newer.json new file mode 100755 index 00000000000..7139589acbc --- /dev/null +++ b/bridges/snowbridge/pallets/ethereum-client/tests/fixtures/sync-committee-update-period-0-newer.json @@ -0,0 +1,565 @@ +{ + "attested_header": { + "slot": 224, + "proposer_index": 0, + "parent_root": "0xecfba5f579f43f474039f6f9abce51eb5607f6295aa45e1c353fa20245ab4efb", + "state_root": "0x10b21ccac4df114a9c30eaaff57f064b692e957a52eb43a8264702da76ba81f7", + "body_root": "0x6bd1768f675673b4ae32a197f569f7d279568fd5f60d32bd6ea11ecff559fc35" + }, + "sync_aggregate": { + "sync_committee_bits": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000", + "sync_committee_signature": "0xb8f4800cb32edf6d05e9cace783d663719f7750f0438b8481c89895809c5430005df25b73393133c9df595e5998d6a540449d8840f8bd16474608bb0b9daa349b76429d8d7e314f2fb6e628c4f68c5469bc8c698bb232a767a4b080b8909aa53" + }, + "signature_slot": 225, + "next_sync_committee_update": { + "next_sync_committee": { + "pubkeys": [ + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b" + ], + "aggregate_pubkey": "0x8fbd66eeec2ff69ef0b836f04b1d67d88bcd4dfd495061964ad757c77abe822a39fa1cd8ed0d4d9bc9276cea73fd745c" + }, + "next_sync_committee_branch": [ + "0x3ade38d498a062b50880a9409e1ca3a7fd4315d91eeb3bb83e56ac6bfe8d6a59", + "0xaad994f17223061c45fb5ec4930b2da08512e221ca6857bde8929eda92dc115c", + "0x61145312b89c006c2d1406285a9f2f826679d20b00239f65f76d40e28abe3bca", + "0x37977cb0ebd513f5123ede3a57b228f31eb98ecaad7757cf8e405fee8224982e", + "0x8c24e3a8ddb0bad93d5dcd240f566c5d08bc381a58b94e337bed63f75104fe45" + ] + }, + "finalized_header": { + "slot": 160, + "proposer_index": 0, + "parent_root": "0x6b536af592b64a337ae033b9646c4a10fd3369be72fcdaf53ae37797df8ec581", + "state_root": "0x1ed5990e4a1188a49ee64cdeb0ee9e480f29ce4d8020a0c5407471771a76ef2d", + "body_root": "0x73fb27d7521c84855007a824231d3b2b1650cd9ee34d914625f692c36b8112ef" + }, + "finality_branch": [ + "0x0500000000000000000000000000000000000000000000000000000000000000", + "0x10c726fac935bf9657cc7476d3cfa7bedec5983dcfb59e8a7df6d0a619e108d7", + "0x98e9116c6bb7f20de18800dc63e73e689d06d6a47d35b5e2b32cf093d475840d", + "0x61145312b89c006c2d1406285a9f2f826679d20b00239f65f76d40e28abe3bca", + "0x37977cb0ebd513f5123ede3a57b228f31eb98ecaad7757cf8e405fee8224982e", + "0x8c24e3a8ddb0bad93d5dcd240f566c5d08bc381a58b94e337bed63f75104fe45" + ], + "block_roots_root": "0xa626dafac4b71585a5b18d18198d7e7c0a09c43b0fb3f2e68e04304d3be94b91", + "block_roots_branch": [ + "0x1a4ced7954adc2f360994137f07d1ae456b008d5ff81f40f252da770a0cd70c9", + "0xa6d595807cef4f868a03813aceb42f07fadf37f93d5b30a3603f55c1eab0081d", + "0x50f2310554199f26d4a326c940dd6e014db55bb8f18bf3642fed22e58ddb5dd6", + "0xd8a7fed47a6e1934c5a5750a44aa70de9898c42e877fc87f0acb0e1b9d236091", + "0xad421833151ec4b8fd8269f16b4b41f15e7e0b82d561553ed5a50e5d6c5f2190" + ], + "execution_header": null, + "execution_branch": null +} \ No newline at end of file diff --git a/bridges/snowbridge/pallets/ethereum-client/tests/fixtures/sync-committee-update-period-0-older.json b/bridges/snowbridge/pallets/ethereum-client/tests/fixtures/sync-committee-update-period-0-older.json new file mode 100755 index 00000000000..b0eff7cac1b --- /dev/null +++ b/bridges/snowbridge/pallets/ethereum-client/tests/fixtures/sync-committee-update-period-0-older.json @@ -0,0 +1,565 @@ +{ + "attested_header": { + "slot": 96, + "proposer_index": 5, + "parent_root": "0x711c0cbebb834c0cd47d74732d78bc9f4794be2d7805176a4613ebaa9546569e", + "state_root": "0xe5ee40ae4ce991c927de404f3aea3209a55f29b54ee96d146c1e9fb733e14018", + "body_root": "0x57953c9bb22c5231b07078e6a3d82bd85ccdf48f55b4bb410c20af4cf4c3b03e" + }, + "sync_aggregate": { + "sync_committee_bits": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "sync_committee_signature": "0xa8a01929a4018d7f5cf3d0511b68ae6af1e32320a263d282ff85bf56860154bd70cd9b0b0f4aa7a956d0375b9b4ba6700c723fcaaeb577acd9a0a88baf0bb418e39f97b17b1edcaeb95fa086d4c5d410addc9f29c0b6c6c14775216cdcb828db" + }, + "signature_slot": 97, + "next_sync_committee_update": { + "next_sync_committee": { + "pubkeys": [ + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b" + ], + "aggregate_pubkey": "0x8fbd66eeec2ff69ef0b836f04b1d67d88bcd4dfd495061964ad757c77abe822a39fa1cd8ed0d4d9bc9276cea73fd745c" + }, + "next_sync_committee_branch": [ + "0x3ade38d498a062b50880a9409e1ca3a7fd4315d91eeb3bb83e56ac6bfe8d6a59", + "0x48118ce24b62eda9ed2d37108f94efe223e6a385d84bcec6b2a53584271ea001", + "0xd72abb2443691ce25174da082c4c60880775d67f83802afd73cc2bf0edd06f73", + "0x0de609b4a50cd2729a8f9d9b6a505b008555dc121b18fb99c148be86ae08a53e", + "0xfb86aae7b54b08642d51132227e409e5247fa9ddb24287deab442ebf5dd9146c" + ] + }, + "finalized_header": { + "slot": 64, + "proposer_index": 4, + "parent_root": "0x60e496771388130ba1dc1d5d447bd43b4a5026a5d17d20f34d5352c0a97e5585", + "state_root": "0x7007a070c06dbd1c6de2f6fb1288f6569a13a00a1ed7505a8b1ede38827dd39c", + "body_root": "0xbccefd80ea680aa944837ec75d660651f369f72724f125e871b787c3dab18ea4" + }, + "finality_branch": [ + "0x0200000000000000000000000000000000000000000000000000000000000000", + "0x10c726fac935bf9657cc7476d3cfa7bedec5983dcfb59e8a7df6d0a619e108d7", + "0x98e9116c6bb7f20de18800dc63e73e689d06d6a47d35b5e2b32cf093d475840d", + "0xd72abb2443691ce25174da082c4c60880775d67f83802afd73cc2bf0edd06f73", + "0x0de609b4a50cd2729a8f9d9b6a505b008555dc121b18fb99c148be86ae08a53e", + "0xfb86aae7b54b08642d51132227e409e5247fa9ddb24287deab442ebf5dd9146c" + ], + "block_roots_root": "0xf70c00c84139e631f8d4a69120f5837e5d14db26aee6aa29f5a6a100b53f820b", + "block_roots_branch": [ + "0x3c2f0c8588c1501bcd371de7103ad74ae93fe72b4703a1bd00fd77acefd90c76", + "0x8ac33e1bd9a7fa543236bf6f385b6082bb6e68ec344d0bc03e620dd908df4b07", + "0x56e652a369b875c2f28e96d341ed76ca453e2f5a0ee2ca571a9ae19d92e842df", + "0x5340ad5877c72dca689ca04bc8fedb78d67a4801d99887937edd8ccd29f87e82", + "0x91eee53bd353a3e021e2c382d9502503b7f9f1198b042ff36e8abdc74fd920dc" + ], + "execution_header": null, + "execution_branch": null +} \ No newline at end of file diff --git a/bridges/snowbridge/pallets/ethereum-client/tests/fixtures/sync-committee-update-period-0.json b/bridges/snowbridge/pallets/ethereum-client/tests/fixtures/sync-committee-update-period-0.json new file mode 100644 index 00000000000..916deb7513c --- /dev/null +++ b/bridges/snowbridge/pallets/ethereum-client/tests/fixtures/sync-committee-update-period-0.json @@ -0,0 +1,565 @@ +{ + "attested_header": { + "slot": 128, + "proposer_index": 1, + "parent_root": "0x2161b169bc9dda1785a8c087e6455d9648d8df8c6d5f98f75d29c1c1c9e13ceb", + "state_root": "0x044bb5ec8eabc0ba7a74646cb92e4c6bd96f5d2974e0e191d3fd05de4eb1acea", + "body_root": "0x2b52b7dbe94cd1c024431064486880f2093480498f2b8a704fec9edc34f68eb8" + }, + "sync_aggregate": { + "sync_committee_bits": "0x00000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "sync_committee_signature": "0x95ceea859d98d209441120821af32fa7ceb6080cf62db7a00a0f578ac83a4a1c619104474e715d1688732e8fe5b19f2417a4f6ba957b3cd2b8c817c8d8c42fc822062385269858feb955cd010744d8357dffef00535cf2e7a1017e58b22c4423" + }, + "signature_slot": 129, + "next_sync_committee_update": { + "next_sync_committee": { + "pubkeys": [ + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b" + ], + "aggregate_pubkey": "0x8fbd66eeec2ff69ef0b836f04b1d67d88bcd4dfd495061964ad757c77abe822a39fa1cd8ed0d4d9bc9276cea73fd745c" + }, + "next_sync_committee_branch": [ + "0x3ade38d498a062b50880a9409e1ca3a7fd4315d91eeb3bb83e56ac6bfe8d6a59", + "0x028330a337168f77730425239a3abdfe336671cf5047fd03ea84eb668a0bad9e", + "0xe2b84cae247ad985d1d089df0f668f7f29ba1db750e5f32159e002dcda2d3f5f", + "0xecf54973b62af22f2620c37c14138021e5ea274f80815a52b3ed6c6234e039da", + "0x63a9c666a4d51dbfceda9b1c9dac57019fce464fd5733e6a6598dde49cc4ea23" + ] + }, + "finalized_header": { + "slot": 64, + "proposer_index": 4, + "parent_root": "0x88e5b7e0dd468b334caf9281e0665184d2d712d7ffe632123ea07631b714920c", + "state_root": "0x82771f834d4d896f4969abdaf45f28f49a7437ecfca7bf2f7db7bfac5ca7224f", + "body_root": "0x8b36f34ceba40a29c9c6fa6266564c7df30ea75fecf1a85e6ec1cb4aabf4dc68" + }, + "finality_branch": [ + "0x0200000000000000000000000000000000000000000000000000000000000000", + "0x10c726fac935bf9657cc7476d3cfa7bedec5983dcfb59e8a7df6d0a619e108d7", + "0x98e9116c6bb7f20de18800dc63e73e689d06d6a47d35b5e2b32cf093d475840d", + "0xe2b84cae247ad985d1d089df0f668f7f29ba1db750e5f32159e002dcda2d3f5f", + "0xecf54973b62af22f2620c37c14138021e5ea274f80815a52b3ed6c6234e039da", + "0x63a9c666a4d51dbfceda9b1c9dac57019fce464fd5733e6a6598dde49cc4ea23" + ], + "block_roots_root": "0x2c453665ba6fc024116daf5246126e36085c61257cfbcce69d0bdcf89c766dc0", + "block_roots_branch": [ + "0xbd04f51e43f63b0be48034920e8f5976111b7717225abccedbc6bcb327b95d00", + "0x758319a3bad11ee10fde1036551d982583c0392f284de5cc429b67fbd74c25d5", + "0xb42179d040c2bec20fa0a2750baf225b8097b5c9e4e22af9250cc773f4259427", + "0x5340ad5877c72dca689ca04bc8fedb78d67a4801d99887937edd8ccd29f87e82", + "0x9f03be8e70f74fc6b51e6ed03c96aabb544b5c50e5cdb8c0ab5001d1249d55f0" + ], + "execution_header": null, + "execution_branch": null +} \ No newline at end of file diff --git a/prdoc/pr_5671.prdoc b/prdoc/pr_5671.prdoc new file mode 100644 index 00000000000..364165ec820 --- /dev/null +++ b/prdoc/pr_5671.prdoc @@ -0,0 +1,16 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Snowbridge free consensus updates border condition fix + +doc: + - audience: Runtime Dev + description: | + A fix for a border condition introduced with the Ethereum client free consensus updates. A malicious relayer could + spam the Ethereum client with sync committee updates that have already been imported for the period. This PR adds + a storage item to track the last imported sync committee period, so that subsequent irrelevant updates are not free. + No impact for users or relayers, since the feature introducing the border condition has not been released. + +crates: + - name: snowbridge-pallet-ethereum-client + bump: patch -- GitLab From c987da33935898cd5b2f8605d548bc48727c1815 Mon Sep 17 00:00:00 2001 From: Evgeny Snitko Date: Tue, 24 Sep 2024 16:18:46 +0400 Subject: [PATCH 101/125] Preflight workflow update (#5771) - New and updated vars - New runner vars - Runners rename (see https://github.com/paritytech/ci_cd/issues/1039) --------- Co-authored-by: Oliver Tale-Yazdi --- .github/workflows/reusable-preflight.yml | 132 +++++++++++++++++---- .github/workflows/subsystem-benchmarks.yml | 2 +- .github/workflows/tests-linux-stable.yml | 2 +- .github/workflows/tests-misc.yml | 2 +- 4 files changed, 114 insertions(+), 24 deletions(-) diff --git a/.github/workflows/reusable-preflight.yml b/.github/workflows/reusable-preflight.yml index 71823a97ff2..e1799adddca 100644 --- a/.github/workflows/reusable-preflight.yml +++ b/.github/workflows/reusable-preflight.yml @@ -26,23 +26,55 @@ on: IMAGE: value: ${{ jobs.preflight.outputs.IMAGE }} description: "CI image" + + # Runners + # https://github.com/paritytech/ci_cd/wiki/GitHub#paritytech-self-hosted-runners RUNNER: value: ${{ jobs.preflight.outputs.RUNNER }} description: | - Runner name. + Main runner for resource-intensive tasks + By default we use spot machines that can be terminated at any time. + Merge queues use persistent runners to avoid kicking off from queue when the runner is terminated. + RUNNER_OLDLINUX: + value: ${{ jobs.preflight.outputs.RUNNER_OLDLINUX }} + description: | + parity-oldlinux By default we use spot machines that can be terminated at any time. Merge queues use persistent runners to avoid kicking off from queue when the runner is terminated. - OLDLINUXRUNNER: - value: ${{ jobs.preflight.outputs.OLDLINUXRUNNER }} + RUNNER_DEFAULT: + value: ${{ jobs.preflight.outputs.RUNNER_DEFAULT }} + description: "Relatively lightweight runner. When `ubuntu-latest` is not enough" + RUNNER_WEIGHTS: + value: ${{ jobs.preflight.outputs.RUNNER_WEIGHTS }} + RUNNER_BENCHMARK: + value: ${{ jobs.preflight.outputs.RUNNER_BENCHMARK }} + RUNNER_MACOS: + value: ${{ jobs.preflight.outputs.RUNNER_MACOS }} + + # Vars + SOURCE_REF_SLUG: + value: ${{ jobs.preflight.outputs.SOURCE_REF_SLUG }} + description: "Name of the current branch for `push` or source branch for `pull_request` with `/` replaced by `_`. Does not exists in merge_group" + REF_SLUG: + value: ${{ jobs.preflight.outputs.REF_SLUG }} + description: | + Name of the current revision (depending on the event) with `/` replaced by `_`, e.g: + push - master + pull_request - 49_merge + merge_group - gh-readonly-queue_master_pr-49-38d43798a986430231c828b2c762997f818ac012 - SOURCE_REF_NAME: - value: ${{ jobs.preflight.outputs.SOURCE_REF_NAME }} - description: "Name of the current branch for `push` or source branch for `pull_request`" COMMIT_SHA: value: ${{ jobs.preflight.outputs.COMMIT_SHA }} - description: "Sha of the current commit for `push` or head of the source branch for `pull_request`" + description: "Sha of the current revision" + COMMIT_SHA_SHORT: + value: ${{ jobs.preflight.outputs.COMMIT_SHA_SHORT }} + description: "Sha of the current revision, 8-symbols long" jobs: + + # + # + # preflight: runs-on: ubuntu-latest outputs: @@ -50,12 +82,21 @@ jobs: changes_currentWorkflow: ${{ steps.set_changes.outputs.currentWorkflow_any_changed }} IMAGE: ${{ steps.set_image.outputs.IMAGE }} + + # Runners + # https://github.com/paritytech/ci_cd/wiki/GitHub#paritytech-self-hosted-runners RUNNER: ${{ steps.set_runner.outputs.RUNNER }} - OLDLINUXRUNNER: ${{ steps.set_runner.outputs.OLDLINUXRUNNER }} + RUNNER_OLDLINUX: ${{ steps.set_runner.outputs.RUNNER_OLDLINUX }} + RUNNER_DEFAULT: ${{ steps.set_runner.outputs.RUNNER_DEFAULT }} + RUNNER_WEIGHTS: ${{ steps.set_runner.outputs.RUNNER_WEIGHTS }} + RUNNER_BENCHMARK: ${{ steps.set_runner.outputs.RUNNER_BENCHMARK }} + RUNNER_MACOS: ${{ steps.set_runner.outputs.RUNNER_MACOS }} - SOURCE_REF_NAME: ${{ steps.set_vars.outputs.SOURCE_REF_NAME }} - COMMIT_SHA: ${{ steps.set_vars.outputs.COMMIT_SHA }} + SOURCE_REF_SLUG: ${{ steps.set_vars.outputs.SOURCE_REF_SLUG }} + REF_SLUG: ${{ steps.set_vars.outputs.REF_SLUG }} + COMMIT_SHA: ${{ steps.set_vars.outputs.COMMIT_SHA }} + COMMIT_SHA_SHORT: ${{ steps.set_vars.outputs.COMMIT_SHA_SHORT }} steps: @@ -64,7 +105,8 @@ jobs: # # Set changes # - - id: current_file + - name: Current file + id: current_file shell: bash run: | echo "currentWorkflowFile=$(echo ${{ github.workflow_ref }} | sed -nE "s/.*(\.github\/workflows\/[a-zA-Z0-9_-]*\.y[a]?ml)@refs.*/\1/p")" >> $GITHUB_OUTPUT @@ -98,27 +140,40 @@ jobs: # By default we use spot machines that can be terminated at any time. # Merge queues use persistent runners to avoid kicking off from queue when the runner is terminated. # - - id: set_runner + - name: Set runner + id: set_runner shell: bash run: | + echo "RUNNER_DEFAULT=parity-default" >> $GITHUB_OUTPUT + echo "RUNNER_WEIGHTS=parity-weights" >> $GITHUB_OUTPUT + echo "RUNNER_BENCHMARK=parity-benchmark" >> $GITHUB_OUTPUT + echo "RUNNER_MACOS=parity-macos" >> $GITHUB_OUTPUT + # # Run merge queues on persistent runners if [[ $GITHUB_REF_NAME == *"gh-readonly-queue"* ]]; then - echo "RUNNER=arc-runners-polkadot-sdk-beefy-persistent" >> $GITHUB_OUTPUT - echo "OLDLINUXRUNNER=oldlinux-persistent" >> $GITHUB_OUTPUT + echo "RUNNER=parity-large-persistent" >> $GITHUB_OUTPUT + echo "RUNNER_OLDLINUX=parity-oldlinux-persistent" >> $GITHUB_OUTPUT else - echo "RUNNER=arc-runners-polkadot-sdk-beefy" >> $GITHUB_OUTPUT - echo "OLDLINUXRUNNER=oldlinux" >> $GITHUB_OUTPUT + echo "RUNNER=parity-large" >> $GITHUB_OUTPUT + echo "RUNNER_OLDLINUX=parity-oldlinux" >> $GITHUB_OUTPUT fi # # Set vars # - - id: set_vars + - name: Set vars + id: set_vars shell: bash run: | - export BRANCH_NAME=${{ github.head_ref || github.ref_name }} - echo "SOURCE_REF_NAME=${BRANCH_NAME//\//-}" >> $GITHUB_OUTPUT - echo "COMMIT_SHA=${{ github.event.pull_request.head.sha || github.sha }}" >> $GITHUB_OUTPUT + export SOURCE_REF_NAME=${{ github.head_ref || github.ref_name }} + echo "SOURCE_REF_SLUG=${SOURCE_REF_NAME//\//_}" >> $GITHUB_OUTPUT + # + export COMMIT_SHA=${{ github.sha }} + echo "COMMIT_SHA=$COMMIT_SHA" >> $GITHUB_OUTPUT + echo "COMMIT_SHA_SHORT=${COMMIT_SHA:0:8}" >> $GITHUB_OUTPUT + # + export REF_NAME=${{ github.ref_name }} + echo "REF_SLUG=${REF_NAME//\//_}" >> $GITHUB_OUTPUT - name: log @@ -126,6 +181,41 @@ jobs: run: | echo "workflow file: ${{ steps.current_file.outputs.currentWorkflowFile }}" echo "Modified: ${{ steps.set_changes.outputs.modified_keys }}" + + # + # + # + ci-versions: + needs: [preflight] + runs-on: ubuntu-latest + container: + image: ${{ needs.preflight.outputs.IMAGE }} + steps: + - uses: actions/checkout@v4 + + - name: Info rust + run: | + rustup show + cargo --version + cargo +nightly --version + cargo clippy --version + echo "yarn version: $(yarn --version)" + echo $( substrate-contracts-node --version | awk 'NF' ) + estuary --version + cargo-contract --version + + - name: Info forklift + run: forklift version + + - name: Info vars + run: | + echo "COMMIT_SHA: ${{ needs.preflight.outputs.COMMIT_SHA }}" + echo "COMMIT_SHA_SHORT: ${{ needs.preflight.outputs.COMMIT_SHA_SHORT }}" + echo "SOURCE_REF_SLUG: ${{ needs.preflight.outputs.SOURCE_REF_SLUG }}" + echo "REF_SLUG: ${{ needs.preflight.outputs.REF_SLUG }}" + echo "RUNNER: ${{ needs.preflight.outputs.RUNNER }}" + echo "IMAGE: ${{ needs.preflight.outputs.IMAGE }}" + # echo "github.ref: ${{ github.ref }}" echo "github.ref_name: ${{ github.ref_name }}" - echo "github.sha: ${{ github.sha }}" + echo "github.sha: ${{ github.sha }}" \ No newline at end of file diff --git a/.github/workflows/subsystem-benchmarks.yml b/.github/workflows/subsystem-benchmarks.yml index 210714d847f..3c0eaf30e45 100644 --- a/.github/workflows/subsystem-benchmarks.yml +++ b/.github/workflows/subsystem-benchmarks.yml @@ -22,7 +22,7 @@ jobs: build: timeout-minutes: 80 needs: [preflight] - runs-on: ${{ needs.preflight.outputs.RUNNER }} + runs-on: ${{ needs.preflight.outputs.RUNNER_BENCHMARK }} container: image: ${{ needs.preflight.outputs.IMAGE }} strategy: diff --git a/.github/workflows/tests-linux-stable.yml b/.github/workflows/tests-linux-stable.yml index 6cf71422511..994f1c9b78e 100644 --- a/.github/workflows/tests-linux-stable.yml +++ b/.github/workflows/tests-linux-stable.yml @@ -68,7 +68,7 @@ jobs: runners: [ "${{ needs.preflight.outputs.RUNNER }}", - "${{ needs.preflight.outputs.OLDLINUXRUNNER }}", + "${{ needs.preflight.outputs.RUNNER_OLDLINUX }}", ] container: image: ${{ needs.preflight.outputs.IMAGE }} diff --git a/.github/workflows/tests-misc.yml b/.github/workflows/tests-misc.yml index 843e09ef7a4..90162adc225 100644 --- a/.github/workflows/tests-misc.yml +++ b/.github/workflows/tests-misc.yml @@ -321,7 +321,7 @@ jobs: cargo-check-all-crate-macos: timeout-minutes: 30 needs: [preflight] - runs-on: parity-macos + runs-on: ${{ needs.preflight.outputs.RUNNER_MACOS }} env: SKIP_WASM_BUILD: 1 steps: -- GitLab From 9294572aa3a0cf3bde3b8b7ac5d48d2b1df113c2 Mon Sep 17 00:00:00 2001 From: Javier Viola <363911+pepoviola@users.noreply.github.com> Date: Tue, 24 Sep 2024 14:01:35 -0300 Subject: [PATCH 102/125] Fix parachain-template-test (#5821) Fix `parachain-template-test` (bump `zombienet` version). Thx! --- Cargo.lock | 26 +++++++++++++------------ Cargo.toml | 4 ++++ polkadot/zombienet-sdk-tests/Cargo.toml | 20 +++++++++---------- templates/zombienet/Cargo.toml | 10 +++++----- 4 files changed, 33 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 18517f94788..f97d29c56c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -27230,17 +27230,19 @@ dependencies = [ [[package]] name = "zombienet-configuration" -version = "0.2.10" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23322e411b8d19b41b5c20ab8e88c10822189a4fcfd069c7fcd1542b8d3035aa" +checksum = "ebbfc98adb25076777967f7aad078e74029e129b102eb0812c425432f8c2be7b" dependencies = [ "anyhow", "lazy_static", "multiaddr 0.18.1", "regex", + "reqwest 0.11.20", "serde", "serde_json", "thiserror", + "tokio", "toml 0.7.8", "url", "zombienet-support", @@ -27248,9 +27250,9 @@ dependencies = [ [[package]] name = "zombienet-orchestrator" -version = "0.2.10" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "381f701565b3918a909132743b3674569ce3da25b5c3a6493883abaf1046577a" +checksum = "5b17f4d1d05b3aedf02818eb0f4d5a76664da0e07bb2f7e7d02613e0ef0f316a" dependencies = [ "anyhow", "async-trait", @@ -27281,9 +27283,9 @@ dependencies = [ [[package]] name = "zombienet-prom-metrics-parser" -version = "0.2.10" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dab79fa58bcfecbcd41485c6f13052853ccde8b09f173b601f78747d7abc2b7f" +checksum = "7203390ab88919240da3a3eb06b625b6e300e94f98e04ba5141e9138dc663b7d" dependencies = [ "pest", "pest_derive", @@ -27292,9 +27294,9 @@ dependencies = [ [[package]] name = "zombienet-provider" -version = "0.2.10" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6af0264938da61b25da89f17ee0630393a4ba793582a4a8a1650eb15b47fc1ef" +checksum = "ee02ee957ec39b698798fa6dc2a0d5ba4524198471c37d57755e9685b67fb50c" dependencies = [ "anyhow", "async-trait", @@ -27323,9 +27325,9 @@ dependencies = [ [[package]] name = "zombienet-sdk" -version = "0.2.10" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bc5b7ebfba4ab62486c8cb5bcd7345c4376487487cfe3481476cb4d4accc75e" +checksum = "f594e67922182277a3da0926f21b693eb5a0c38b32ca7fd6ef16167809fe5064" dependencies = [ "async-trait", "futures", @@ -27340,9 +27342,9 @@ dependencies = [ [[package]] name = "zombienet-support" -version = "0.2.10" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f5b80d34a0eecca69dd84c2e13f84f1fae0cc378baf4f15f769027af068418b" +checksum = "93d3144537df7c8939bbb355cc5245a6dc0078446a6cdaf9272268bd1043c788" dependencies = [ "anyhow", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index 8e3330e4e61..b7c9c0cdcbf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -737,6 +737,7 @@ either = { version = "1.8.1", default-features = false } emulated-integration-tests-common = { path = "cumulus/parachains/integration-tests/emulated/common", default-features = false } enumflags2 = { version = "0.7.7" } enumn = { version = "0.1.13" } +env_logger = { version = "0.11.2" } environmental = { version = "1.1.4", default-features = false } equivocation-detector = { path = "bridges/relays/equivocation" } ethabi = { version = "1.0.0", default-features = false, package = "ethabi-decode" } @@ -1299,6 +1300,8 @@ substrate-test-runtime-client = { path = "substrate/test-utils/runtime/client" } substrate-test-runtime-transaction-pool = { path = "substrate/test-utils/runtime/transaction-pool" } substrate-test-utils = { path = "substrate/test-utils" } substrate-wasm-builder = { path = "substrate/utils/wasm-builder", default-features = false } +subxt = { version = "0.37", default-features = false } +subxt-signer = { version = "0.37" } syn = { version = "2.0.77" } sysinfo = { version = "0.30" } tar = { version = "0.4" } @@ -1368,6 +1371,7 @@ xcm-procedural = { path = "polkadot/xcm/procedural", default-features = false } xcm-runtime-apis = { path = "polkadot/xcm/xcm-runtime-apis", default-features = false } xcm-simulator = { path = "polkadot/xcm/xcm-simulator", default-features = false } zeroize = { version = "1.7.0", default-features = false } +zombienet-sdk = { version = "0.2.13" } zstd = { version = "0.12.4", default-features = false } [profile.release] diff --git a/polkadot/zombienet-sdk-tests/Cargo.toml b/polkadot/zombienet-sdk-tests/Cargo.toml index 3374ad572b9..4eac7af49f8 100644 --- a/polkadot/zombienet-sdk-tests/Cargo.toml +++ b/polkadot/zombienet-sdk-tests/Cargo.toml @@ -8,16 +8,16 @@ license.workspace = true publish = false [dependencies] -env_logger = "0.11.2" -log = "0.4" -subxt = { version = "0.37", features = ["substrate-compat"] } -subxt-signer = { version = "0.37" } -tokio = { version = "1.36.0", features = ["rt-multi-thread"] } -anyhow = "1.0.81" -zombienet-sdk = "0.2.6" -serde = "1.0.197" -serde_json = "1.0.114" -parity-scale-codec = { version = "3.6.9", features = ["derive"] } +env_logger = { workspace = true } +log = { workspace = true } +subxt = { workspace = true, features = ["substrate-compat"] } +subxt-signer = { workspace = true } +tokio = { workspace = true, features = ["rt-multi-thread"] } +anyhow = { workspace = true } +zombienet-sdk = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +codec = { workspace = true, features = ["derive"] } [features] zombie-metadata = [] diff --git a/templates/zombienet/Cargo.toml b/templates/zombienet/Cargo.toml index cb2adf70dbd..f29325dbe6a 100644 --- a/templates/zombienet/Cargo.toml +++ b/templates/zombienet/Cargo.toml @@ -10,11 +10,11 @@ edition.workspace = true publish = false [dependencies] -env_logger = "0.11.2" -log = "0.4" -tokio = { version = "1.36.0", features = ["rt-multi-thread"] } -anyhow = "1.0.81" -zombienet-sdk = "0.2.8" +env_logger = { workspace = true } +log = { workspace = true } +tokio = { workspace = true, features = ["rt-multi-thread"] } +anyhow = { workspace = true } +zombienet-sdk = { workspace = true } [features] zombienet = [] -- GitLab From 710e74ddefdff1e36b77ba65abe54feb0ac15040 Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Tue, 24 Sep 2024 23:21:25 +0200 Subject: [PATCH 103/125] Bridges lane id agnostic for backwards compatibility (#5649) This PR primarily fixes the issue with `zombienet-bridges-0001-asset-transfer-works` (see: https://gitlab.parity.io/parity/mirrors/polkadot-sdk/-/jobs/7404903). The PR looks large, but most of the changes involve splitting `LaneId` into `LegacyLaneId` and `HashedLaneId`. All pallets now use `LaneId` as a generic parameter. The actual bridging pallets are now backward compatible and work with actual **substrate-relay v1.6.10**, which does not even known anything about permissionless lanes or the new pallet changes. ## Important - [x] added migration for `pallet_bridge_relayers` and `RewardsAccountParams` change order of params, which generates different accounts ## Deployment follow ups - [ ] fix monitoring for `at_{}_relay_{}_reward_for_msgs_from_{}_on_lane_{}` - [ ] check sovereign reward accounts - because of changed `RewardsAccountParams` - [ ] deploy another messages instances for permissionless lanes - on BHs or AHs? - [ ] return back `open_and_close_bridge_works` for another `pallet-bridge-messages` instance --------- Signed-off-by: Oliver Tale-Yazdi Co-authored-by: Adrian Catangiu Co-authored-by: Oliver Tale-Yazdi --- Cargo.lock | 7 + bridges/bin/runtime-common/src/extensions.rs | 11 +- .../bin/runtime-common/src/messages_api.rs | 8 +- .../src/messages_benchmarking.rs | 78 ++--- bridges/bin/runtime-common/src/mock.rs | 29 +- .../chains/chain-bridge-hub-kusama/src/lib.rs | 2 +- .../chain-bridge-hub-polkadot/src/lib.rs | 2 +- .../chains/chain-bridge-hub-rococo/src/lib.rs | 2 +- .../chain-bridge-hub-westend/src/lib.rs | 2 +- .../chains/chain-polkadot-bulletin/src/lib.rs | 2 +- bridges/modules/messages/src/benchmarking.rs | 18 +- bridges/modules/messages/src/call_ext.rs | 30 +- bridges/modules/messages/src/inbound_lane.rs | 10 +- bridges/modules/messages/src/lanes_manager.rs | 32 +- bridges/modules/messages/src/lib.rs | 82 +++-- bridges/modules/messages/src/outbound_lane.rs | 6 +- bridges/modules/messages/src/proofs.rs | 43 +-- .../messages/src/tests/messages_generation.rs | 16 +- bridges/modules/messages/src/tests/mock.rs | 95 +++--- .../messages/src/tests/pallet_tests.rs | 25 +- bridges/modules/relayers/src/benchmarking.rs | 54 ++-- .../relayers/src/extension/grandpa_adapter.rs | 23 +- .../src/extension/messages_adapter.rs | 19 +- bridges/modules/relayers/src/extension/mod.rs | 150 +++++---- .../src/extension/parachain_adapter.rs | 24 +- bridges/modules/relayers/src/lib.rs | 216 +++++++------ bridges/modules/relayers/src/migration.rs | 243 ++++++++++++++ bridges/modules/relayers/src/mock.rs | 54 +++- .../modules/relayers/src/payment_adapter.rs | 11 +- bridges/modules/relayers/src/stake_adapter.rs | 8 +- .../modules/xcm-bridge-hub/src/dispatcher.rs | 32 +- .../modules/xcm-bridge-hub/src/exporter.rs | 157 +++++++-- bridges/modules/xcm-bridge-hub/src/lib.rs | 110 +++---- .../modules/xcm-bridge-hub/src/migration.rs | 3 +- bridges/modules/xcm-bridge-hub/src/mock.rs | 21 +- bridges/primitives/messages/src/call_info.rs | 32 +- bridges/primitives/messages/src/lane.rs | 297 +++++++++++------- bridges/primitives/messages/src/lib.rs | 36 +-- .../primitives/messages/src/source_chain.rs | 20 +- .../primitives/messages/src/storage_keys.rs | 38 ++- .../primitives/messages/src/target_chain.rs | 44 ++- bridges/primitives/relayers/src/extension.rs | 30 +- bridges/primitives/relayers/src/lib.rs | 175 ++++++++--- .../primitives/relayers/src/registration.rs | 18 +- bridges/primitives/runtime/src/chain.rs | 20 +- bridges/primitives/runtime/src/lib.rs | 4 +- bridges/primitives/xcm-bridge-hub/src/lib.rs | 32 +- bridges/relays/client-substrate/src/chain.rs | 3 - .../lib-substrate-relay/src/cli/bridge.rs | 23 +- .../relays/lib-substrate-relay/src/cli/mod.rs | 43 +-- .../src/cli/relay_headers_and_messages/mod.rs | 61 ++-- .../src/cli/relay_messages.rs | 16 +- .../src/messages/metrics.rs | 19 +- .../lib-substrate-relay/src/messages/mod.rs | 77 +++-- .../src/messages/source.rs | 27 +- .../src/messages/target.rs | 12 +- bridges/relays/messages/Cargo.toml | 3 + bridges/relays/messages/src/lib.rs | 1 + bridges/relays/messages/src/message_lane.rs | 4 + .../relays/messages/src/message_lane_loop.rs | 49 ++- bridges/relays/messages/src/metrics.rs | 31 +- bridges/testing/README.md | 2 +- .../rococo-westend/bridges_rococo_westend.sh | 116 +++---- .../bridges/bridge-hub-rococo/Cargo.toml | 6 + .../bridges/bridge-hub-rococo/src/genesis.rs | 12 + .../bridges/bridge-hub-westend/Cargo.toml | 6 + .../bridges/bridge-hub-westend/src/genesis.rs | 12 + .../emulated/common/src/impls.rs | 16 +- .../bridge-hub-rococo/src/tests/mod.rs | 22 -- .../bridge-hub-westend/src/tests/mod.rs | 22 -- .../src/bridge_common_config.rs | 33 +- .../src/bridge_to_bulletin_config.rs | 69 ++-- .../src/bridge_to_westend_config.rs | 140 +++++++-- .../src/genesis_config_presets.rs | 11 + .../bridge-hubs/bridge-hub-rococo/src/lib.rs | 125 +++++--- .../bridge-hub-rococo/tests/snowbridge.rs | 6 +- .../bridge-hub-rococo/tests/tests.rs | 119 ++++--- .../src/bridge_common_config.rs | 12 +- .../src/bridge_to_rococo_config.rs | 137 ++++++-- .../src/genesis_config_presets.rs | 11 + .../bridge-hubs/bridge-hub-westend/src/lib.rs | 54 +++- .../bridge-hub-westend/tests/tests.rs | 52 +-- .../bridge-hubs/test-utils/Cargo.toml | 2 + .../bridge-hubs/test-utils/src/lib.rs | 4 +- .../src/test_cases/from_grandpa_chain.rs | 135 ++++---- .../src/test_cases/from_parachain.rs | 148 +++++---- .../test-utils/src/test_cases/helpers.rs | 101 ++++-- .../test-utils/src/test_cases/mod.rs | 14 +- .../src/test_data/from_grandpa_chain.rs | 31 +- .../src/test_data/from_parachain.rs | 24 +- .../test-utils/src/test_data/mod.rs | 6 +- .../parachains/runtimes/test-utils/src/lib.rs | 2 +- cumulus/polkadot-parachain/Cargo.toml | 3 + ...ridges_zombienet_tests_injected.Dockerfile | 2 +- .../xcm/xcm-executor/src/traits/export.rs | 6 +- prdoc/pr_5649.prdoc | 49 +++ 96 files changed, 2759 insertions(+), 1491 deletions(-) create mode 100644 bridges/modules/relayers/src/migration.rs create mode 100644 prdoc/pr_5649.prdoc diff --git a/Cargo.lock b/Cargo.lock index f97d29c56c8..69eabeb04e2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2188,12 +2188,14 @@ dependencies = [ name = "bridge-hub-rococo-emulated-chain" version = "0.0.0" dependencies = [ + "bp-messages", "bridge-hub-common", "bridge-hub-rococo-runtime", "emulated-integration-tests-common", "frame-support", "parachains-common", "sp-core 28.0.0", + "staging-xcm", "testnet-parachains-constants", ] @@ -2357,6 +2359,7 @@ dependencies = [ "pallet-bridge-relayers", "pallet-timestamp", "pallet-utility", + "pallet-xcm", "pallet-xcm-bridge-hub", "parachains-common", "parachains-runtimes-test-utils", @@ -2375,12 +2378,14 @@ dependencies = [ name = "bridge-hub-westend-emulated-chain" version = "0.0.0" dependencies = [ + "bp-messages", "bridge-hub-common", "bridge-hub-westend-runtime", "emulated-integration-tests-common", "frame-support", "parachains-common", "sp-core 28.0.0", + "staging-xcm", "testnet-parachains-constants", ] @@ -9560,6 +9565,7 @@ dependencies = [ "parking_lot 0.12.3", "relay-utils", "sp-arithmetic 23.0.0", + "sp-core 28.0.0", ] [[package]] @@ -14876,6 +14882,7 @@ version = "4.0.0" dependencies = [ "asset-hub-rococo-runtime", "asset-hub-westend-runtime", + "bp-messages", "bridge-hub-rococo-runtime", "bridge-hub-westend-runtime", "collectives-westend-runtime", diff --git a/bridges/bin/runtime-common/src/extensions.rs b/bridges/bin/runtime-common/src/extensions.rs index dc7e14de28f..dced5023947 100644 --- a/bridges/bin/runtime-common/src/extensions.rs +++ b/bridges/bin/runtime-common/src/extensions.rs @@ -374,7 +374,7 @@ mod tests { use super::*; use crate::mock::*; use bp_header_chain::StoredHeaderDataBuilder; - use bp_messages::{InboundLaneData, LaneId, MessageNonce, OutboundLaneData}; + use bp_messages::{InboundLaneData, MessageNonce, OutboundLaneData}; use bp_parachains::{BestParaHeadHash, ParaInfo}; use bp_polkadot_core::parachains::{ParaHeadsProof, ParaId}; use bp_relayers::{RewardsAccountOwner, RewardsAccountParams}; @@ -390,17 +390,16 @@ mod tests { }; parameter_types! { - pub MsgProofsRewardsAccount: RewardsAccountParams = RewardsAccountParams::new( + pub MsgProofsRewardsAccount: RewardsAccountParams = RewardsAccountParams::new( test_lane_id(), TEST_BRIDGED_CHAIN_ID, RewardsAccountOwner::ThisChain, ); - pub MsgDeliveryProofsRewardsAccount: RewardsAccountParams = RewardsAccountParams::new( + pub MsgDeliveryProofsRewardsAccount: RewardsAccountParams = RewardsAccountParams::new( test_lane_id(), TEST_BRIDGED_CHAIN_ID, RewardsAccountOwner::BridgedChain, ); - pub TestLaneId: LaneId = test_lane_id(); } pub struct MockCall { @@ -476,10 +475,6 @@ mod tests { } } - fn test_lane_id() -> LaneId { - LaneId::new(1, 2) - } - fn initial_balance_of_relayer_account_at_this_chain() -> ThisChainBalance { let test_stake: ThisChainBalance = TestStake::get(); ExistentialDeposit::get().saturating_add(test_stake * 100) diff --git a/bridges/bin/runtime-common/src/messages_api.rs b/bridges/bin/runtime-common/src/messages_api.rs index 7fbdeb36612..c8522d4d1f2 100644 --- a/bridges/bin/runtime-common/src/messages_api.rs +++ b/bridges/bin/runtime-common/src/messages_api.rs @@ -16,14 +16,12 @@ //! Helpers for implementing various message-related runtime API methods. -use bp_messages::{ - InboundMessageDetails, LaneId, MessageNonce, MessagePayload, OutboundMessageDetails, -}; +use bp_messages::{InboundMessageDetails, MessageNonce, MessagePayload, OutboundMessageDetails}; use sp_std::vec::Vec; /// Implementation of the `To*OutboundLaneApi::message_details`. pub fn outbound_message_details( - lane: LaneId, + lane: Runtime::LaneId, begin: MessageNonce, end: MessageNonce, ) -> Vec @@ -48,7 +46,7 @@ where /// Implementation of the `To*InboundLaneApi::message_details`. pub fn inbound_message_details( - lane: LaneId, + lane: Runtime::LaneId, messages: Vec<(MessagePayload, OutboundMessageDetails)>, ) -> Vec where diff --git a/bridges/bin/runtime-common/src/messages_benchmarking.rs b/bridges/bin/runtime-common/src/messages_benchmarking.rs index 1880e65547f..acbdbcda8de 100644 --- a/bridges/bin/runtime-common/src/messages_benchmarking.rs +++ b/bridges/bin/runtime-common/src/messages_benchmarking.rs @@ -33,15 +33,15 @@ use pallet_bridge_messages::{ encode_all_messages, encode_lane_data, prepare_message_delivery_storage_proof, prepare_messages_storage_proof, }, - BridgedChainOf, ThisChainOf, + BridgedChainOf, LaneIdOf, ThisChainOf, }; use sp_runtime::traits::{Header, Zero}; use sp_std::prelude::*; use xcm::latest::prelude::*; /// Prepare inbound bridge message according to given message proof parameters. -fn prepare_inbound_message( - params: &MessageProofParams, +fn prepare_inbound_message( + params: &MessageProofParams, successful_dispatch_message_generator: impl Fn(usize) -> MessagePayload, ) -> MessagePayload { let expected_size = params.proof_params.db_size.unwrap_or(0) as usize; @@ -71,9 +71,9 @@ fn prepare_inbound_message( /// uses GRANDPA finality. For parachains, please use the `prepare_message_proof_from_parachain` /// function. pub fn prepare_message_proof_from_grandpa_chain( - params: MessageProofParams, + params: MessageProofParams>, message_generator: impl Fn(usize) -> MessagePayload, -) -> (FromBridgedChainMessagesProof>>, Weight) +) -> (FromBridgedChainMessagesProof>, LaneIdOf>, Weight) where R: pallet_bridge_grandpa::Config> + pallet_bridge_messages::Config< @@ -84,18 +84,21 @@ where MI: 'static, { // prepare storage proof - let (state_root, storage_proof) = - prepare_messages_storage_proof::, ThisChainOf>( - params.lane, - params.message_nonces.clone(), - params.outbound_lane_data.clone(), - params.proof_params, - |_| prepare_inbound_message(¶ms, &message_generator), - encode_all_messages, - encode_lane_data, - false, - false, - ); + let (state_root, storage_proof) = prepare_messages_storage_proof::< + BridgedChainOf, + ThisChainOf, + LaneIdOf, + >( + params.lane, + params.message_nonces.clone(), + params.outbound_lane_data.clone(), + params.proof_params, + |_| prepare_inbound_message(¶ms, &message_generator), + encode_all_messages, + encode_lane_data, + false, + false, + ); // update runtime storage let (_, bridged_header_hash) = insert_header_to_grandpa_pallet::(state_root); @@ -121,9 +124,9 @@ where /// uses parachain finality. For GRANDPA chains, please use the /// `prepare_message_proof_from_grandpa_chain` function. pub fn prepare_message_proof_from_parachain( - params: MessageProofParams, + params: MessageProofParams>, message_generator: impl Fn(usize) -> MessagePayload, -) -> (FromBridgedChainMessagesProof>>, Weight) +) -> (FromBridgedChainMessagesProof>, LaneIdOf>, Weight) where R: pallet_bridge_parachains::Config + pallet_bridge_messages::Config, PI: 'static, @@ -131,18 +134,21 @@ where BridgedChainOf: Chain + Parachain, { // prepare storage proof - let (state_root, storage_proof) = - prepare_messages_storage_proof::, ThisChainOf>( - params.lane, - params.message_nonces.clone(), - params.outbound_lane_data.clone(), - params.proof_params, - |_| prepare_inbound_message(¶ms, &message_generator), - encode_all_messages, - encode_lane_data, - false, - false, - ); + let (state_root, storage_proof) = prepare_messages_storage_proof::< + BridgedChainOf, + ThisChainOf, + LaneIdOf, + >( + params.lane, + params.message_nonces.clone(), + params.outbound_lane_data.clone(), + params.proof_params, + |_| prepare_inbound_message(¶ms, &message_generator), + encode_all_messages, + encode_lane_data, + false, + false, + ); // update runtime storage let (_, bridged_header_hash) = @@ -166,8 +172,8 @@ where /// uses GRANDPA finality. For parachains, please use the /// `prepare_message_delivery_proof_from_parachain` function. pub fn prepare_message_delivery_proof_from_grandpa_chain( - params: MessageDeliveryProofParams>>, -) -> FromBridgedChainMessagesDeliveryProof>> + params: MessageDeliveryProofParams>, LaneIdOf>, +) -> FromBridgedChainMessagesDeliveryProof>, LaneIdOf> where R: pallet_bridge_grandpa::Config> + pallet_bridge_messages::Config< @@ -182,6 +188,7 @@ where let (state_root, storage_proof) = prepare_message_delivery_storage_proof::< BridgedChainOf, ThisChainOf, + LaneIdOf, >(params.lane, params.inbound_lane_data, params.proof_params); // update runtime storage @@ -200,8 +207,8 @@ where /// uses parachain finality. For GRANDPA chains, please use the /// `prepare_message_delivery_proof_from_grandpa_chain` function. pub fn prepare_message_delivery_proof_from_parachain( - params: MessageDeliveryProofParams>>, -) -> FromBridgedChainMessagesDeliveryProof>> + params: MessageDeliveryProofParams>, LaneIdOf>, +) -> FromBridgedChainMessagesDeliveryProof>, LaneIdOf> where R: pallet_bridge_parachains::Config + pallet_bridge_messages::Config, PI: 'static, @@ -213,6 +220,7 @@ where let (state_root, storage_proof) = prepare_message_delivery_storage_proof::< BridgedChainOf, ThisChainOf, + LaneIdOf, >(params.lane, params.inbound_lane_data, params.proof_params); // update runtime storage diff --git a/bridges/bin/runtime-common/src/mock.rs b/bridges/bin/runtime-common/src/mock.rs index fed0d15cc08..1d4043fc4b6 100644 --- a/bridges/bin/runtime-common/src/mock.rs +++ b/bridges/bin/runtime-common/src/mock.rs @@ -21,7 +21,7 @@ use bp_header_chain::ChainWithGrandpa; use bp_messages::{ target_chain::{DispatchMessage, MessageDispatch}, - ChainWithMessages, LaneId, MessageNonce, + ChainWithMessages, HashedLaneId, LaneIdType, MessageNonce, }; use bp_parachains::SingleParaStoredHeaderDataBuilder; use bp_relayers::PayRewardFromAccount; @@ -70,7 +70,7 @@ pub type BridgedChainHeader = sp_runtime::generic::Header; /// Rewards payment procedure. -pub type TestPaymentProcedure = PayRewardFromAccount; +pub type TestPaymentProcedure = PayRewardFromAccount; /// Stake that we are using in tests. pub type TestStake = ConstU64<5_000>; /// Stake and slash mechanism to use in tests. @@ -83,10 +83,11 @@ pub type TestStakeAndSlash = pallet_bridge_relayers::StakeAndSlashNamed< ConstU32<8>, >; -/// Message lane used in tests. -#[allow(unused)] -pub fn test_lane_id() -> LaneId { - LaneId::new(1, 2) +/// Lane identifier type used for tests. +pub type TestLaneIdType = HashedLaneId; +/// Lane that we're using in tests. +pub fn test_lane_id() -> TestLaneIdType { + TestLaneIdType::try_new(1, 2).unwrap() } /// Bridged chain id used in tests. @@ -189,10 +190,10 @@ impl pallet_bridge_messages::Config for TestRuntime { type WeightInfo = pallet_bridge_messages::weights::BridgeWeight; type OutboundPayload = Vec; - type InboundPayload = Vec; - type DeliveryPayments = (); + type LaneId = TestLaneIdType; + type DeliveryPayments = (); type DeliveryConfirmationPayments = pallet_bridge_relayers::DeliveryConfirmationPaymentsAdapter< TestRuntime, (), @@ -213,13 +214,14 @@ impl pallet_bridge_relayers::Config for TestRuntime { type PaymentProcedure = TestPaymentProcedure; type StakeAndSlash = TestStakeAndSlash; type WeightInfo = (); + type LaneId = TestLaneIdType; } /// Dummy message dispatcher. pub struct DummyMessageDispatch; impl DummyMessageDispatch { - pub fn deactivate(lane: LaneId) { + pub fn deactivate(lane: TestLaneIdType) { frame_support::storage::unhashed::put(&(b"inactive", lane).encode()[..], &false); } } @@ -227,18 +229,21 @@ impl DummyMessageDispatch { impl MessageDispatch for DummyMessageDispatch { type DispatchPayload = Vec; type DispatchLevelResult = (); + type LaneId = TestLaneIdType; - fn is_active(lane: LaneId) -> bool { + fn is_active(lane: Self::LaneId) -> bool { frame_support::storage::unhashed::take::(&(b"inactive", lane).encode()[..]) != Some(false) } - fn dispatch_weight(_message: &mut DispatchMessage) -> Weight { + fn dispatch_weight( + _message: &mut DispatchMessage, + ) -> Weight { Weight::zero() } fn dispatch( - _: DispatchMessage, + _: DispatchMessage, ) -> MessageDispatchResult { MessageDispatchResult { unspent_weight: Weight::zero(), dispatch_level_result: () } } diff --git a/bridges/chains/chain-bridge-hub-kusama/src/lib.rs b/bridges/chains/chain-bridge-hub-kusama/src/lib.rs index c990e8a12f3..485fb3d31f2 100644 --- a/bridges/chains/chain-bridge-hub-kusama/src/lib.rs +++ b/bridges/chains/chain-bridge-hub-kusama/src/lib.rs @@ -93,4 +93,4 @@ pub const WITH_BRIDGE_HUB_KUSAMA_MESSAGES_PALLET_NAME: &str = "BridgeKusamaMessa pub const WITH_BRIDGE_HUB_KUSAMA_RELAYERS_PALLET_NAME: &str = "BridgeRelayers"; decl_bridge_finality_runtime_apis!(bridge_hub_kusama); -decl_bridge_messages_runtime_apis!(bridge_hub_kusama); +decl_bridge_messages_runtime_apis!(bridge_hub_kusama, LegacyLaneId); diff --git a/bridges/chains/chain-bridge-hub-polkadot/src/lib.rs b/bridges/chains/chain-bridge-hub-polkadot/src/lib.rs index 7379b8863b1..7a1793b4da4 100644 --- a/bridges/chains/chain-bridge-hub-polkadot/src/lib.rs +++ b/bridges/chains/chain-bridge-hub-polkadot/src/lib.rs @@ -85,4 +85,4 @@ pub const WITH_BRIDGE_HUB_POLKADOT_MESSAGES_PALLET_NAME: &str = "BridgePolkadotM pub const WITH_BRIDGE_HUB_POLKADOT_RELAYERS_PALLET_NAME: &str = "BridgeRelayers"; decl_bridge_finality_runtime_apis!(bridge_hub_polkadot); -decl_bridge_messages_runtime_apis!(bridge_hub_polkadot); +decl_bridge_messages_runtime_apis!(bridge_hub_polkadot, LegacyLaneId); diff --git a/bridges/chains/chain-bridge-hub-rococo/src/lib.rs b/bridges/chains/chain-bridge-hub-rococo/src/lib.rs index 7920eb93403..538bc44019f 100644 --- a/bridges/chains/chain-bridge-hub-rococo/src/lib.rs +++ b/bridges/chains/chain-bridge-hub-rococo/src/lib.rs @@ -99,7 +99,7 @@ pub const WITH_BRIDGE_ROCOCO_TO_WESTEND_MESSAGES_PALLET_INDEX: u8 = 51; pub const WITH_BRIDGE_ROCOCO_TO_BULLETIN_MESSAGES_PALLET_INDEX: u8 = 61; decl_bridge_finality_runtime_apis!(bridge_hub_rococo); -decl_bridge_messages_runtime_apis!(bridge_hub_rococo); +decl_bridge_messages_runtime_apis!(bridge_hub_rococo, LegacyLaneId); frame_support::parameter_types! { /// The XCM fee that is paid for executing XCM program (with `ExportMessage` instruction) at the Rococo diff --git a/bridges/chains/chain-bridge-hub-westend/src/lib.rs b/bridges/chains/chain-bridge-hub-westend/src/lib.rs index 644fa64c687..7a213fdb28c 100644 --- a/bridges/chains/chain-bridge-hub-westend/src/lib.rs +++ b/bridges/chains/chain-bridge-hub-westend/src/lib.rs @@ -88,7 +88,7 @@ pub const WITH_BRIDGE_HUB_WESTEND_RELAYERS_PALLET_NAME: &str = "BridgeRelayers"; pub const WITH_BRIDGE_WESTEND_TO_ROCOCO_MESSAGES_PALLET_INDEX: u8 = 44; decl_bridge_finality_runtime_apis!(bridge_hub_westend); -decl_bridge_messages_runtime_apis!(bridge_hub_westend); +decl_bridge_messages_runtime_apis!(bridge_hub_westend, LegacyLaneId); frame_support::parameter_types! { /// The XCM fee that is paid for executing XCM program (with `ExportMessage` instruction) at the Westend diff --git a/bridges/chains/chain-polkadot-bulletin/src/lib.rs b/bridges/chains/chain-polkadot-bulletin/src/lib.rs index 88980a95750..d0093691972 100644 --- a/bridges/chains/chain-polkadot-bulletin/src/lib.rs +++ b/bridges/chains/chain-polkadot-bulletin/src/lib.rs @@ -228,4 +228,4 @@ impl ChainWithMessages for PolkadotBulletin { } decl_bridge_finality_runtime_apis!(polkadot_bulletin, grandpa); -decl_bridge_messages_runtime_apis!(polkadot_bulletin); +decl_bridge_messages_runtime_apis!(polkadot_bulletin, bp_messages::HashedLaneId); diff --git a/bridges/modules/messages/src/benchmarking.rs b/bridges/modules/messages/src/benchmarking.rs index b3a4447fb02..355fb08ab28 100644 --- a/bridges/modules/messages/src/benchmarking.rs +++ b/bridges/modules/messages/src/benchmarking.rs @@ -26,7 +26,7 @@ use crate::{ use bp_messages::{ source_chain::FromBridgedChainMessagesDeliveryProof, target_chain::FromBridgedChainMessagesProof, ChainWithMessages, DeliveredMessages, - InboundLaneData, LaneId, LaneState, MessageNonce, OutboundLaneData, UnrewardedRelayer, + InboundLaneData, LaneState, MessageNonce, OutboundLaneData, UnrewardedRelayer, UnrewardedRelayersState, }; use bp_runtime::{AccountIdOf, HashOf, UnverifiedStorageProofParams}; @@ -44,7 +44,7 @@ pub struct Pallet, I: 'static = ()>(crate::Pallet); /// Benchmark-specific message proof parameters. #[derive(Debug)] -pub struct MessageProofParams { +pub struct MessageProofParams { /// Id of the lane. pub lane: LaneId, /// Range of messages to include in the proof. @@ -62,7 +62,7 @@ pub struct MessageProofParams { /// Benchmark-specific message delivery proof parameters. #[derive(Debug)] -pub struct MessageDeliveryProofParams { +pub struct MessageDeliveryProofParams { /// Id of the lane. pub lane: LaneId, /// The proof needs to include this inbound lane data. @@ -74,8 +74,8 @@ pub struct MessageDeliveryProofParams { /// Trait that must be implemented by runtime. pub trait Config: crate::Config { /// Lane id to use in benchmarks. - fn bench_lane_id() -> LaneId { - LaneId::new(1, 2) + fn bench_lane_id() -> Self::LaneId { + Self::LaneId::default() } /// Return id of relayer account at the bridged chain. @@ -94,12 +94,12 @@ pub trait Config: crate::Config { /// Prepare messages proof to receive by the module. fn prepare_message_proof( - params: MessageProofParams, - ) -> (FromBridgedChainMessagesProof>>, Weight); + params: MessageProofParams, + ) -> (FromBridgedChainMessagesProof>, Self::LaneId>, Weight); /// Prepare messages delivery proof to receive by the module. fn prepare_message_delivery_proof( - params: MessageDeliveryProofParams, - ) -> FromBridgedChainMessagesDeliveryProof>>; + params: MessageDeliveryProofParams, + ) -> FromBridgedChainMessagesDeliveryProof>, Self::LaneId>; /// Returns true if message has been successfully dispatched or not. fn is_message_successfully_dispatched(_nonce: MessageNonce) -> bool { diff --git a/bridges/modules/messages/src/call_ext.rs b/bridges/modules/messages/src/call_ext.rs index 8e021c8e5e2..9e5f5f8d112 100644 --- a/bridges/modules/messages/src/call_ext.rs +++ b/bridges/modules/messages/src/call_ext.rs @@ -20,8 +20,8 @@ use crate::{BridgedChainOf, Config, InboundLanes, OutboundLanes, Pallet, LOG_TAR use bp_messages::{ target_chain::MessageDispatch, BaseMessagesProofInfo, ChainWithMessages, InboundLaneData, - LaneId, MessageNonce, MessagesCallInfo, ReceiveMessagesDeliveryProofInfo, - ReceiveMessagesProofInfo, UnrewardedRelayerOccupation, + MessageNonce, MessagesCallInfo, ReceiveMessagesDeliveryProofInfo, ReceiveMessagesProofInfo, + UnrewardedRelayerOccupation, }; use bp_runtime::{AccountIdOf, OwnedBridgeModule}; use frame_support::{dispatch::CallableCallFor, traits::IsSubType}; @@ -39,7 +39,7 @@ impl, I: 'static> CallHelper { /// /// - call is `receive_messages_delivery_proof` and all messages confirmations have been /// received. - pub fn was_successful(info: &MessagesCallInfo) -> bool { + pub fn was_successful(info: &MessagesCallInfo) -> bool { match info { MessagesCallInfo::ReceiveMessagesProof(info) => { let inbound_lane_data = match InboundLanes::::get(info.base.lane_id) { @@ -75,19 +75,21 @@ pub trait CallSubType, I: 'static>: IsSubType, T>> { /// Create a new instance of `ReceiveMessagesProofInfo` from a `ReceiveMessagesProof` call. - fn receive_messages_proof_info(&self) -> Option; + fn receive_messages_proof_info(&self) -> Option>; /// Create a new instance of `ReceiveMessagesDeliveryProofInfo` from /// a `ReceiveMessagesDeliveryProof` call. - fn receive_messages_delivery_proof_info(&self) -> Option; + fn receive_messages_delivery_proof_info( + &self, + ) -> Option>; /// Create a new instance of `MessagesCallInfo` from a `ReceiveMessagesProof` /// or a `ReceiveMessagesDeliveryProof` call. - fn call_info(&self) -> Option; + fn call_info(&self) -> Option>; /// Create a new instance of `MessagesCallInfo` from a `ReceiveMessagesProof` /// or a `ReceiveMessagesDeliveryProof` call, if the call is for the provided lane. - fn call_info_for(&self, lane_id: LaneId) -> Option; + fn call_info_for(&self, lane_id: T::LaneId) -> Option>; /// Ensures that a `ReceiveMessagesProof` or a `ReceiveMessagesDeliveryProof` call: /// @@ -114,7 +116,7 @@ impl< I: 'static, > CallSubType for T::RuntimeCall { - fn receive_messages_proof_info(&self) -> Option { + fn receive_messages_proof_info(&self) -> Option> { if let Some(crate::Call::::receive_messages_proof { ref proof, .. }) = self.is_sub_type() { @@ -135,7 +137,9 @@ impl< None } - fn receive_messages_delivery_proof_info(&self) -> Option { + fn receive_messages_delivery_proof_info( + &self, + ) -> Option> { if let Some(crate::Call::::receive_messages_delivery_proof { ref proof, ref relayers_state, @@ -159,7 +163,7 @@ impl< None } - fn call_info(&self) -> Option { + fn call_info(&self) -> Option> { if let Some(info) = self.receive_messages_proof_info() { return Some(MessagesCallInfo::ReceiveMessagesProof(info)) } @@ -171,7 +175,7 @@ impl< None } - fn call_info_for(&self, lane_id: LaneId) -> Option { + fn call_info_for(&self, lane_id: T::LaneId) -> Option> { self.call_info().filter(|info| { let actual_lane_id = match info { MessagesCallInfo::ReceiveMessagesProof(info) => info.base.lane_id, @@ -251,10 +255,6 @@ mod tests { }; use sp_std::ops::RangeInclusive; - fn test_lane_id() -> LaneId { - LaneId::new(1, 2) - } - fn fill_unrewarded_relayers() { let mut inbound_lane_state = InboundLanes::::get(test_lane_id()).unwrap(); for n in 0..BridgedChain::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX { diff --git a/bridges/modules/messages/src/inbound_lane.rs b/bridges/modules/messages/src/inbound_lane.rs index 65240feb719..91f1159f8f9 100644 --- a/bridges/modules/messages/src/inbound_lane.rs +++ b/bridges/modules/messages/src/inbound_lane.rs @@ -20,8 +20,8 @@ use crate::{BridgedChainOf, Config}; use bp_messages::{ target_chain::{DispatchMessage, DispatchMessageData, MessageDispatch}, - ChainWithMessages, DeliveredMessages, InboundLaneData, LaneId, LaneState, MessageKey, - MessageNonce, OutboundLaneData, ReceptionResult, UnrewardedRelayer, + ChainWithMessages, DeliveredMessages, InboundLaneData, LaneState, MessageKey, MessageNonce, + OutboundLaneData, ReceptionResult, UnrewardedRelayer, }; use bp_runtime::AccountIdOf; use codec::{Decode, Encode, EncodeLike, MaxEncodedLen}; @@ -33,9 +33,11 @@ use sp_std::prelude::PartialEq; pub trait InboundLaneStorage { /// Id of relayer on source chain. type Relayer: Clone + PartialEq; + /// Lane identifier type. + type LaneId: Encode; /// Lane id. - fn id(&self) -> LaneId; + fn id(&self) -> Self::LaneId; /// Return maximal number of unrewarded relayer entries in inbound lane. fn max_unrewarded_relayer_entries(&self) -> MessageNonce; /// Return maximal number of unconfirmed messages in inbound lane. @@ -181,7 +183,7 @@ impl InboundLane { } /// Receive new message. - pub fn receive_message( + pub fn receive_message>( &mut self, relayer_at_bridged_chain: &S::Relayer, nonce: MessageNonce, diff --git a/bridges/modules/messages/src/lanes_manager.rs b/bridges/modules/messages/src/lanes_manager.rs index 4f5ac1c0a40..27cab48535d 100644 --- a/bridges/modules/messages/src/lanes_manager.rs +++ b/bridges/modules/messages/src/lanes_manager.rs @@ -21,8 +21,8 @@ use crate::{ }; use bp_messages::{ - target_chain::MessageDispatch, ChainWithMessages, InboundLaneData, LaneId, LaneState, - MessageKey, MessageNonce, OutboundLaneData, + target_chain::MessageDispatch, ChainWithMessages, InboundLaneData, LaneState, MessageKey, + MessageNonce, OutboundLaneData, }; use bp_runtime::AccountIdOf; use codec::{Decode, Encode, MaxEncodedLen}; @@ -68,7 +68,7 @@ impl, I: 'static> LanesManager { /// Create new inbound lane in `Opened` state. pub fn create_inbound_lane( &self, - lane_id: LaneId, + lane_id: T::LaneId, ) -> Result>, LanesManagerError> { InboundLanes::::try_mutate(lane_id, |lane| match lane { Some(_) => Err(LanesManagerError::InboundLaneAlreadyExists), @@ -87,7 +87,7 @@ impl, I: 'static> LanesManager { /// Create new outbound lane in `Opened` state. pub fn create_outbound_lane( &self, - lane_id: LaneId, + lane_id: T::LaneId, ) -> Result>, LanesManagerError> { OutboundLanes::::try_mutate(lane_id, |lane| match lane { Some(_) => Err(LanesManagerError::OutboundLaneAlreadyExists), @@ -103,7 +103,7 @@ impl, I: 'static> LanesManager { /// Get existing inbound lane, checking that it is in usable state. pub fn active_inbound_lane( &self, - lane_id: LaneId, + lane_id: T::LaneId, ) -> Result>, LanesManagerError> { Ok(InboundLane::new(RuntimeInboundLaneStorage::from_lane_id(lane_id, true)?)) } @@ -111,7 +111,7 @@ impl, I: 'static> LanesManager { /// Get existing outbound lane, checking that it is in usable state. pub fn active_outbound_lane( &self, - lane_id: LaneId, + lane_id: T::LaneId, ) -> Result>, LanesManagerError> { Ok(OutboundLane::new(RuntimeOutboundLaneStorage::from_lane_id(lane_id, true)?)) } @@ -119,7 +119,7 @@ impl, I: 'static> LanesManager { /// Get existing inbound lane without any additional state checks. pub fn any_state_inbound_lane( &self, - lane_id: LaneId, + lane_id: T::LaneId, ) -> Result>, LanesManagerError> { Ok(InboundLane::new(RuntimeInboundLaneStorage::from_lane_id(lane_id, false)?)) } @@ -127,7 +127,7 @@ impl, I: 'static> LanesManager { /// Get existing outbound lane without any additional state checks. pub fn any_state_outbound_lane( &self, - lane_id: LaneId, + lane_id: T::LaneId, ) -> Result>, LanesManagerError> { Ok(OutboundLane::new(RuntimeOutboundLaneStorage::from_lane_id(lane_id, false)?)) } @@ -135,14 +135,14 @@ impl, I: 'static> LanesManager { /// Runtime inbound lane storage. pub struct RuntimeInboundLaneStorage, I: 'static = ()> { - pub(crate) lane_id: LaneId, + pub(crate) lane_id: T::LaneId, pub(crate) cached_data: InboundLaneData>>, } impl, I: 'static> RuntimeInboundLaneStorage { /// Creates new runtime inbound lane storage for given **existing** lane. fn from_lane_id( - lane_id: LaneId, + lane_id: T::LaneId, check_active: bool, ) -> Result, LanesManagerError> { let cached_data = @@ -196,8 +196,9 @@ impl, I: 'static> RuntimeInboundLaneStorage { impl, I: 'static> InboundLaneStorage for RuntimeInboundLaneStorage { type Relayer = AccountIdOf>; + type LaneId = T::LaneId; - fn id(&self) -> LaneId { + fn id(&self) -> Self::LaneId { self.lane_id } @@ -225,15 +226,15 @@ impl, I: 'static> InboundLaneStorage for RuntimeInboundLaneStorage< /// Runtime outbound lane storage. #[derive(Debug, PartialEq, Eq)] -pub struct RuntimeOutboundLaneStorage { - pub(crate) lane_id: LaneId, +pub struct RuntimeOutboundLaneStorage, I: 'static> { + pub(crate) lane_id: T::LaneId, pub(crate) cached_data: OutboundLaneData, pub(crate) _phantom: PhantomData<(T, I)>, } impl, I: 'static> RuntimeOutboundLaneStorage { /// Creates new runtime outbound lane storage for given **existing** lane. - fn from_lane_id(lane_id: LaneId, check_active: bool) -> Result { + fn from_lane_id(lane_id: T::LaneId, check_active: bool) -> Result { let cached_data = OutboundLanes::::get(lane_id).ok_or(LanesManagerError::UnknownOutboundLane)?; ensure!( @@ -246,8 +247,9 @@ impl, I: 'static> RuntimeOutboundLaneStorage { impl, I: 'static> OutboundLaneStorage for RuntimeOutboundLaneStorage { type StoredMessagePayload = StoredMessagePayload; + type LaneId = T::LaneId; - fn id(&self) -> LaneId { + fn id(&self) -> Self::LaneId { self.lane_id } diff --git a/bridges/modules/messages/src/lib.rs b/bridges/modules/messages/src/lib.rs index b7fe1c7dbb1..af14257db99 100644 --- a/bridges/modules/messages/src/lib.rs +++ b/bridges/modules/messages/src/lib.rs @@ -60,9 +60,9 @@ use bp_messages::{ DeliveryPayments, DispatchMessage, FromBridgedChainMessagesProof, MessageDispatch, ProvedLaneMessages, ProvedMessages, }, - ChainWithMessages, DeliveredMessages, InboundLaneData, InboundMessageDetails, LaneId, - MessageKey, MessageNonce, MessagePayload, MessagesOperatingMode, OutboundLaneData, - OutboundMessageDetails, UnrewardedRelayersState, VerificationError, + ChainWithMessages, DeliveredMessages, InboundLaneData, InboundMessageDetails, MessageKey, + MessageNonce, MessagePayload, MessagesOperatingMode, OutboundLaneData, OutboundMessageDetails, + UnrewardedRelayersState, VerificationError, }; use bp_runtime::{ AccountIdOf, BasicOperatingMode, HashOf, OwnedBridgeModule, PreComputedSize, RangeInclusiveExt, @@ -97,7 +97,7 @@ pub const LOG_TARGET: &str = "runtime::bridge-messages"; #[frame_support::pallet] pub mod pallet { use super::*; - use bp_messages::{ReceivedMessages, ReceptionResult}; + use bp_messages::{LaneIdType, ReceivedMessages, ReceptionResult}; use bp_runtime::RangeInclusiveExt; use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; @@ -123,17 +123,25 @@ pub mod pallet { type OutboundPayload: Parameter + Size; /// Payload type of inbound messages. This payload is dispatched on this chain. type InboundPayload: Decode; + /// Lane identifier type. + type LaneId: LaneIdType; /// Handler for relayer payments that happen during message delivery transaction. type DeliveryPayments: DeliveryPayments; /// Handler for relayer payments that happen during message delivery confirmation /// transaction. - type DeliveryConfirmationPayments: DeliveryConfirmationPayments; + type DeliveryConfirmationPayments: DeliveryConfirmationPayments< + Self::AccountId, + Self::LaneId, + >; /// Delivery confirmation callback. - type OnMessagesDelivered: OnMessagesDelivered; + type OnMessagesDelivered: OnMessagesDelivered; /// Message dispatch handler. - type MessageDispatch: MessageDispatch; + type MessageDispatch: MessageDispatch< + DispatchPayload = Self::InboundPayload, + LaneId = Self::LaneId, + >; } /// Shortcut to this chain type for Config. @@ -142,6 +150,8 @@ pub mod pallet { pub type BridgedChainOf = >::BridgedChain; /// Shortcut to bridged header chain type for Config. pub type BridgedHeaderChainOf = >::BridgedHeaderChain; + /// Shortcut to lane identifier type for Config. + pub type LaneIdOf = >::LaneId; #[pallet::pallet] #[pallet::storage_version(migration::STORAGE_VERSION)] @@ -203,7 +213,7 @@ pub mod pallet { pub fn receive_messages_proof( origin: OriginFor, relayer_id_at_bridged_chain: AccountIdOf>, - proof: Box>>>, + proof: Box>, T::LaneId>>, messages_count: u32, dispatch_weight: Weight, ) -> DispatchResultWithPostInfo { @@ -350,7 +360,7 @@ pub mod pallet { ))] pub fn receive_messages_delivery_proof( origin: OriginFor, - proof: FromBridgedChainMessagesDeliveryProof>>, + proof: FromBridgedChainMessagesDeliveryProof>, T::LaneId>, mut relayers_state: UnrewardedRelayersState, ) -> DispatchResultWithPostInfo { Self::ensure_not_halted().map_err(Error::::BridgeModule)?; @@ -387,7 +397,7 @@ pub mod pallet { // emit 'delivered' event let received_range = confirmed_messages.begin..=confirmed_messages.end; Self::deposit_event(Event::MessagesDelivered { - lane_id, + lane_id: lane_id.into(), messages: confirmed_messages, }); @@ -441,19 +451,22 @@ pub mod pallet { /// Message has been accepted and is waiting to be delivered. MessageAccepted { /// Lane, which has accepted the message. - lane_id: LaneId, + lane_id: T::LaneId, /// Nonce of accepted message. nonce: MessageNonce, }, /// Messages have been received from the bridged chain. MessagesReceived( /// Result of received messages dispatch. - ReceivedMessages<::DispatchLevelResult>, + ReceivedMessages< + ::DispatchLevelResult, + T::LaneId, + >, ), /// Messages in the inclusive range have been delivered to the bridged chain. MessagesDelivered { /// Lane for which the delivery has been confirmed. - lane_id: LaneId, + lane_id: T::LaneId, /// Delivered messages. messages: DeliveredMessages, }, @@ -510,13 +523,13 @@ pub mod pallet { /// Map of lane id => inbound lane data. #[pallet::storage] pub type InboundLanes, I: 'static = ()> = - StorageMap<_, Blake2_128Concat, LaneId, StoredInboundLaneData, OptionQuery>; + StorageMap<_, Blake2_128Concat, T::LaneId, StoredInboundLaneData, OptionQuery>; /// Map of lane id => outbound lane data. #[pallet::storage] pub type OutboundLanes, I: 'static = ()> = StorageMap< Hasher = Blake2_128Concat, - Key = LaneId, + Key = T::LaneId, Value = OutboundLaneData, QueryKind = OptionQuery, >; @@ -524,7 +537,7 @@ pub mod pallet { /// All queued outbound messages. #[pallet::storage] pub type OutboundMessages, I: 'static = ()> = - StorageMap<_, Blake2_128Concat, MessageKey, StoredMessagePayload>; + StorageMap<_, Blake2_128Concat, MessageKey, StoredMessagePayload>; #[pallet::genesis_config] #[derive(DefaultNoBound)] @@ -534,7 +547,7 @@ pub mod pallet { /// Initial pallet owner. pub owner: Option, /// Opened lanes. - pub opened_lanes: Vec, + pub opened_lanes: Vec, /// Dummy marker. #[serde(skip)] pub _phantom: sp_std::marker::PhantomData, @@ -565,13 +578,16 @@ pub mod pallet { impl, I: 'static> Pallet { /// Get stored data of the outbound message with given nonce. - pub fn outbound_message_data(lane: LaneId, nonce: MessageNonce) -> Option { + pub fn outbound_message_data( + lane: T::LaneId, + nonce: MessageNonce, + ) -> Option { OutboundMessages::::get(MessageKey { lane_id: lane, nonce }).map(Into::into) } /// Prepare data, related to given inbound message. pub fn inbound_message_data( - lane: LaneId, + lane: T::LaneId, payload: MessagePayload, outbound_details: OutboundMessageDetails, ) -> InboundMessageDetails { @@ -585,13 +601,13 @@ pub mod pallet { } /// Return outbound lane data. - pub fn outbound_lane_data(lane: LaneId) -> Option { + pub fn outbound_lane_data(lane: T::LaneId) -> Option { OutboundLanes::::get(lane) } /// Return inbound lane data. pub fn inbound_lane_data( - lane: LaneId, + lane: T::LaneId, ) -> Option>>> { InboundLanes::::get(lane).map(|lane| lane.0) } @@ -654,12 +670,12 @@ pub mod pallet { /// to send it on the bridge. #[derive(Debug, PartialEq, Eq)] pub struct SendMessageArgs, I: 'static> { - lane_id: LaneId, + lane_id: T::LaneId, lane: OutboundLane>, payload: StoredMessagePayload, } -impl bp_messages::source_chain::MessagesBridge for Pallet +impl bp_messages::source_chain::MessagesBridge for Pallet where T: Config, I: 'static, @@ -668,7 +684,7 @@ where type SendMessageArgs = SendMessageArgs; fn validate_message( - lane_id: LaneId, + lane_id: T::LaneId, message: &T::OutboundPayload, ) -> Result, Self::Error> { // we can't accept any messages if the pallet is halted @@ -703,7 +719,10 @@ where message_len, ); - Pallet::::deposit_event(Event::MessageAccepted { lane_id: args.lane_id, nonce }); + Pallet::::deposit_event(Event::MessageAccepted { + lane_id: args.lane_id.into(), + nonce, + }); SendMessageArtifacts { nonce, enqueued_messages } } @@ -722,7 +741,7 @@ fn ensure_normal_operating_mode, I: 'static>() -> Result<(), Error< /// Creates new inbound lane object, backed by runtime storage. Lane must be active. fn active_inbound_lane, I: 'static>( - lane_id: LaneId, + lane_id: T::LaneId, ) -> Result>, Error> { LanesManager::::new() .active_inbound_lane(lane_id) @@ -731,7 +750,7 @@ fn active_inbound_lane, I: 'static>( /// Creates new outbound lane object, backed by runtime storage. Lane must be active. fn active_outbound_lane, I: 'static>( - lane_id: LaneId, + lane_id: T::LaneId, ) -> Result>, Error> { LanesManager::::new() .active_outbound_lane(lane_id) @@ -740,7 +759,7 @@ fn active_outbound_lane, I: 'static>( /// Creates new outbound lane object, backed by runtime storage. fn any_state_outbound_lane, I: 'static>( - lane_id: LaneId, + lane_id: T::LaneId, ) -> Result>, Error> { LanesManager::::new() .any_state_outbound_lane(lane_id) @@ -749,9 +768,12 @@ fn any_state_outbound_lane, I: 'static>( /// Verify messages proof and return proved messages with decoded payload. fn verify_and_decode_messages_proof, I: 'static>( - proof: FromBridgedChainMessagesProof>>, + proof: FromBridgedChainMessagesProof>, T::LaneId>, messages_count: u32, -) -> Result>, VerificationError> { +) -> Result< + ProvedMessages>, + VerificationError, +> { // `receive_messages_proof` weight formula and `MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX` // check guarantees that the `message_count` is sane and Vec may be allocated. // (tx with too many messages will either be rejected from the pool, or will fail earlier) diff --git a/bridges/modules/messages/src/outbound_lane.rs b/bridges/modules/messages/src/outbound_lane.rs index f71240ab7c7..c72713e7455 100644 --- a/bridges/modules/messages/src/outbound_lane.rs +++ b/bridges/modules/messages/src/outbound_lane.rs @@ -19,7 +19,7 @@ use crate::{Config, LOG_TARGET}; use bp_messages::{ - ChainWithMessages, DeliveredMessages, LaneId, LaneState, MessageNonce, OutboundLaneData, + ChainWithMessages, DeliveredMessages, LaneState, MessageNonce, OutboundLaneData, UnrewardedRelayer, }; use codec::{Decode, Encode}; @@ -32,9 +32,11 @@ use sp_std::{collections::vec_deque::VecDeque, marker::PhantomData, ops::RangeIn pub trait OutboundLaneStorage { /// Stored message payload type. type StoredMessagePayload; + /// Lane identifier type. + type LaneId: Encode; /// Lane id. - fn id(&self) -> LaneId; + fn id(&self) -> Self::LaneId; /// Get lane data from the storage. fn data(&self) -> OutboundLaneData; /// Update lane data in the storage. diff --git a/bridges/modules/messages/src/proofs.rs b/bridges/modules/messages/src/proofs.rs index f35eb24d98c..dcd642341d7 100644 --- a/bridges/modules/messages/src/proofs.rs +++ b/bridges/modules/messages/src/proofs.rs @@ -22,7 +22,7 @@ use bp_header_chain::{HeaderChain, HeaderChainError}; use bp_messages::{ source_chain::FromBridgedChainMessagesDeliveryProof, target_chain::{FromBridgedChainMessagesProof, ProvedLaneMessages, ProvedMessages}, - ChainWithMessages, InboundLaneData, LaneId, Message, MessageKey, MessageNonce, MessagePayload, + ChainWithMessages, InboundLaneData, Message, MessageKey, MessageNonce, MessagePayload, OutboundLaneData, VerificationError, }; use bp_runtime::{ @@ -32,8 +32,8 @@ use codec::Decode; use sp_std::vec::Vec; /// 'Parsed' message delivery proof - inbound lane id and its state. -pub(crate) type ParsedMessagesDeliveryProofFromBridgedChain = - (LaneId, InboundLaneData<::AccountId>); +pub(crate) type ParsedMessagesDeliveryProofFromBridgedChain = + (>::LaneId, InboundLaneData<::AccountId>); /// Verify proof of Bridged -> This chain messages. /// @@ -44,9 +44,9 @@ pub(crate) type ParsedMessagesDeliveryProofFromBridgedChain = /// outside of this function. This function only verifies that the proof declares exactly /// `messages_count` messages. pub fn verify_messages_proof, I: 'static>( - proof: FromBridgedChainMessagesProof>>, + proof: FromBridgedChainMessagesProof>, T::LaneId>, messages_count: u32, -) -> Result, VerificationError> { +) -> Result>, VerificationError> { let FromBridgedChainMessagesProof { bridged_header_hash, storage_proof, @@ -103,8 +103,8 @@ pub fn verify_messages_proof, I: 'static>( /// Verify proof of This -> Bridged chain messages delivery. pub fn verify_messages_delivery_proof, I: 'static>( - proof: FromBridgedChainMessagesDeliveryProof>>, -) -> Result, VerificationError> { + proof: FromBridgedChainMessagesDeliveryProof>, T::LaneId>, +) -> Result, VerificationError> { let FromBridgedChainMessagesDeliveryProof { bridged_header_hash, storage_proof, lane } = proof; let mut parser: MessagesStorageProofAdapter = MessagesStorageProofAdapter::try_new_with_verified_storage_proof( @@ -143,7 +143,7 @@ trait StorageProofAdapter, I: 'static> { fn read_and_decode_outbound_lane_data( &mut self, - lane_id: &LaneId, + lane_id: &T::LaneId, ) -> Result, StorageProofError> { let storage_outbound_lane_data_key = bp_messages::storage_keys::outbound_lane_data_key( T::ThisChain::WITH_CHAIN_MESSAGES_PALLET_NAME, @@ -154,7 +154,7 @@ trait StorageProofAdapter, I: 'static> { fn read_and_decode_message_payload( &mut self, - message_key: &MessageKey, + message_key: &MessageKey, ) -> Result { let storage_message_key = bp_messages::storage_keys::message_key( T::ThisChain::WITH_CHAIN_MESSAGES_PALLET_NAME, @@ -229,19 +229,20 @@ mod tests { encode_outbound_lane_data: impl Fn(&OutboundLaneData) -> Vec, add_duplicate_key: bool, add_unused_key: bool, - test: impl Fn(FromBridgedChainMessagesProof) -> R, + test: impl Fn(FromBridgedChainMessagesProof) -> R, ) -> R { - let (state_root, storage_proof) = prepare_messages_storage_proof::( - test_lane_id(), - 1..=nonces_end, - outbound_lane_data, - bp_runtime::UnverifiedStorageProofParams::default(), - generate_dummy_message, - encode_message, - encode_outbound_lane_data, - add_duplicate_key, - add_unused_key, - ); + let (state_root, storage_proof) = + prepare_messages_storage_proof::( + test_lane_id(), + 1..=nonces_end, + outbound_lane_data, + bp_runtime::UnverifiedStorageProofParams::default(), + generate_dummy_message, + encode_message, + encode_outbound_lane_data, + add_duplicate_key, + add_unused_key, + ); sp_io::TestExternalities::new(Default::default()).execute_with(move || { let bridged_header = BridgedChainHeader::new( diff --git a/bridges/modules/messages/src/tests/messages_generation.rs b/bridges/modules/messages/src/tests/messages_generation.rs index 6c4867fa6de..00b1d3eefe4 100644 --- a/bridges/modules/messages/src/tests/messages_generation.rs +++ b/bridges/modules/messages/src/tests/messages_generation.rs @@ -17,8 +17,8 @@ //! Helpers for generating message storage proofs, that are used by tests and by benchmarks. use bp_messages::{ - storage_keys, ChainWithMessages, InboundLaneData, LaneId, MessageKey, MessageNonce, - MessagePayload, OutboundLaneData, + storage_keys, ChainWithMessages, InboundLaneData, MessageKey, MessageNonce, MessagePayload, + OutboundLaneData, }; use bp_runtime::{ grow_storage_value, record_all_trie_keys, AccountIdOf, Chain, HashOf, HasherOf, @@ -47,7 +47,11 @@ pub fn encode_lane_data(d: &OutboundLaneData) -> Vec { /// /// Returns state trie root and nodes with prepared messages. #[allow(clippy::too_many_arguments)] -pub fn prepare_messages_storage_proof( +pub fn prepare_messages_storage_proof< + BridgedChain: Chain, + ThisChain: ChainWithMessages, + LaneId: Encode + Copy, +>( lane: LaneId, message_nonces: RangeInclusive, outbound_lane_data: Option, @@ -132,7 +136,11 @@ where /// Prepare storage proof of given messages delivery. /// /// Returns state trie root and nodes with prepared messages. -pub fn prepare_message_delivery_storage_proof( +pub fn prepare_message_delivery_storage_proof< + BridgedChain: Chain, + ThisChain: ChainWithMessages, + LaneId: Encode, +>( lane: LaneId, inbound_lane_data: InboundLaneData>, proof_params: UnverifiedStorageProofParams, diff --git a/bridges/modules/messages/src/tests/mock.rs b/bridges/modules/messages/src/tests/mock.rs index 2caea9813e8..2935ebd6961 100644 --- a/bridges/modules/messages/src/tests/mock.rs +++ b/bridges/modules/messages/src/tests/mock.rs @@ -35,8 +35,9 @@ use bp_messages::{ DeliveryPayments, DispatchMessage, DispatchMessageData, FromBridgedChainMessagesProof, MessageDispatch, }, - ChainWithMessages, DeliveredMessages, InboundLaneData, LaneId, LaneState, Message, MessageKey, - MessageNonce, OutboundLaneData, UnrewardedRelayer, UnrewardedRelayersState, + ChainWithMessages, DeliveredMessages, HashedLaneId, InboundLaneData, LaneIdType, LaneState, + Message, MessageKey, MessageNonce, OutboundLaneData, UnrewardedRelayer, + UnrewardedRelayersState, }; use bp_runtime::{ messages::MessageDispatchResult, Chain, ChainId, Size, UnverifiedStorageProofParams, @@ -195,10 +196,10 @@ impl Config for TestRuntime { type BridgedHeaderChain = BridgedChainGrandpa; type OutboundPayload = TestPayload; - type InboundPayload = TestPayload; - type DeliveryPayments = TestDeliveryPayments; + type LaneId = TestLaneIdType; + type DeliveryPayments = TestDeliveryPayments; type DeliveryConfirmationPayments = TestDeliveryConfirmationPayments; type OnMessagesDelivered = TestOnMessagesDelivered; @@ -207,13 +208,13 @@ impl Config for TestRuntime { #[cfg(feature = "runtime-benchmarks")] impl crate::benchmarking::Config<()> for TestRuntime { - fn bench_lane_id() -> LaneId { + fn bench_lane_id() -> Self::LaneId { test_lane_id() } fn prepare_message_proof( - params: crate::benchmarking::MessageProofParams, - ) -> (FromBridgedChainMessagesProof, Weight) { + params: crate::benchmarking::MessageProofParams, + ) -> (FromBridgedChainMessagesProof, Weight) { use bp_runtime::RangeInclusiveExt; let dispatch_weight = @@ -228,8 +229,8 @@ impl crate::benchmarking::Config<()> for TestRuntime { } fn prepare_message_delivery_proof( - params: crate::benchmarking::MessageDeliveryProofParams, - ) -> FromBridgedChainMessagesDeliveryProof { + params: crate::benchmarking::MessageDeliveryProofParams, + ) -> FromBridgedChainMessagesDeliveryProof { // in mock run we only care about benchmarks correctness, not the benchmark results // => ignore size related arguments prepare_messages_delivery_proof(params.lane, params.inbound_lane_data) @@ -258,19 +259,21 @@ pub const TEST_RELAYER_B: AccountId = 101; /// Account id of additional test relayer - C. pub const TEST_RELAYER_C: AccountId = 102; +/// Lane identifier type used for tests. +pub type TestLaneIdType = HashedLaneId; /// Lane that we're using in tests. -pub fn test_lane_id() -> LaneId { - LaneId::new(1, 2) +pub fn test_lane_id() -> TestLaneIdType { + TestLaneIdType::try_new(1, 2).unwrap() } /// Lane that is completely unknown to our runtime. -pub fn unknown_lane_id() -> LaneId { - LaneId::new(1, 3) +pub fn unknown_lane_id() -> TestLaneIdType { + TestLaneIdType::try_new(1, 3).unwrap() } /// Lane that is registered, but it is closed. -pub fn closed_lane_id() -> LaneId { - LaneId::new(1, 4) +pub fn closed_lane_id() -> TestLaneIdType { + TestLaneIdType::try_new(1, 4).unwrap() } /// Regular message payload. @@ -316,11 +319,11 @@ impl TestDeliveryConfirmationPayments { } } -impl DeliveryConfirmationPayments for TestDeliveryConfirmationPayments { +impl DeliveryConfirmationPayments for TestDeliveryConfirmationPayments { type Error = &'static str; fn pay_reward( - _lane_id: LaneId, + _lane_id: TestLaneIdType, messages_relayers: VecDeque>, _confirmation_relayer: &AccountId, received_range: &RangeInclusive, @@ -341,7 +344,7 @@ impl DeliveryConfirmationPayments for TestDeliveryConfirmationPayment pub struct TestMessageDispatch; impl TestMessageDispatch { - pub fn deactivate(lane: LaneId) { + pub fn deactivate(lane: TestLaneIdType) { // "enqueue" enough (to deactivate dispatcher) messages at dispatcher let latest_received_nonce = BridgedChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX + 1; for _ in 1..=latest_received_nonce { @@ -349,7 +352,7 @@ impl TestMessageDispatch { } } - pub fn emulate_enqueued_message(lane: LaneId) { + pub fn emulate_enqueued_message(lane: TestLaneIdType) { let key = (b"dispatched", lane).encode(); let dispatched = frame_support::storage::unhashed::get_or_default::(&key[..]); frame_support::storage::unhashed::put(&key[..], &(dispatched + 1)); @@ -359,14 +362,15 @@ impl TestMessageDispatch { impl MessageDispatch for TestMessageDispatch { type DispatchPayload = TestPayload; type DispatchLevelResult = TestDispatchLevelResult; + type LaneId = TestLaneIdType; - fn is_active(lane: LaneId) -> bool { + fn is_active(lane: Self::LaneId) -> bool { frame_support::storage::unhashed::get_or_default::( &(b"dispatched", lane).encode()[..], ) <= BridgedChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX } - fn dispatch_weight(message: &mut DispatchMessage) -> Weight { + fn dispatch_weight(message: &mut DispatchMessage) -> Weight { match message.data.payload.as_ref() { Ok(payload) => payload.declared_weight, Err(_) => Weight::zero(), @@ -374,7 +378,7 @@ impl MessageDispatch for TestMessageDispatch { } fn dispatch( - message: DispatchMessage, + message: DispatchMessage, ) -> MessageDispatchResult { match message.data.payload.as_ref() { Ok(payload) => { @@ -390,13 +394,13 @@ impl MessageDispatch for TestMessageDispatch { pub struct TestOnMessagesDelivered; impl TestOnMessagesDelivered { - pub fn call_arguments() -> Option<(LaneId, MessageNonce)> { + pub fn call_arguments() -> Option<(TestLaneIdType, MessageNonce)> { frame_support::storage::unhashed::get(b"TestOnMessagesDelivered.OnMessagesDelivered") } } -impl OnMessagesDelivered for TestOnMessagesDelivered { - fn on_messages_delivered(lane: LaneId, enqueued_messages: MessageNonce) { +impl OnMessagesDelivered for TestOnMessagesDelivered { + fn on_messages_delivered(lane: TestLaneIdType, enqueued_messages: MessageNonce) { frame_support::storage::unhashed::put( b"TestOnMessagesDelivered.OnMessagesDelivered", &(lane, enqueued_messages), @@ -405,7 +409,7 @@ impl OnMessagesDelivered for TestOnMessagesDelivered { } /// Return test lane message with given nonce and payload. -pub fn message(nonce: MessageNonce, payload: TestPayload) -> Message { +pub fn message(nonce: MessageNonce, payload: TestPayload) -> Message { Message { key: MessageKey { lane_id: test_lane_id(), nonce }, payload: payload.encode() } } @@ -449,7 +453,7 @@ pub fn unrewarded_relayer( } /// Returns unrewarded relayers state at given lane. -pub fn inbound_unrewarded_relayers_state(lane: bp_messages::LaneId) -> UnrewardedRelayersState { +pub fn inbound_unrewarded_relayers_state(lane: TestLaneIdType) -> UnrewardedRelayersState { let inbound_lane_data = crate::InboundLanes::::get(lane).unwrap().0; UnrewardedRelayersState::from(&inbound_lane_data) } @@ -486,24 +490,25 @@ pub fn run_test(test: impl FnOnce() -> T) -> T { /// Since this function changes the runtime storage, you can't "inline" it in the /// `asset_noop` macro calls. pub fn prepare_messages_proof( - messages: Vec, + messages: Vec>, outbound_lane_data: Option, -) -> Box> { +) -> Box> { // first - let's generate storage proof let lane = messages.first().unwrap().key.lane_id; let nonces_start = messages.first().unwrap().key.nonce; let nonces_end = messages.last().unwrap().key.nonce; - let (storage_root, storage_proof) = prepare_messages_storage_proof::( - lane, - nonces_start..=nonces_end, - outbound_lane_data, - UnverifiedStorageProofParams::default(), - |nonce| messages[(nonce - nonces_start) as usize].payload.clone(), - encode_all_messages, - encode_lane_data, - false, - false, - ); + let (storage_root, storage_proof) = + prepare_messages_storage_proof::( + lane, + nonces_start..=nonces_end, + outbound_lane_data, + UnverifiedStorageProofParams::default(), + |nonce| messages[(nonce - nonces_start) as usize].payload.clone(), + encode_all_messages, + encode_lane_data, + false, + false, + ); // let's now insert bridged chain header into the storage let bridged_header_hash = Default::default(); @@ -512,7 +517,7 @@ pub fn prepare_messages_proof( StoredHeaderData { number: 0, state_root: storage_root }, ); - Box::new(FromBridgedChainMessagesProof:: { + Box::new(FromBridgedChainMessagesProof:: { bridged_header_hash, storage_proof, lane, @@ -527,12 +532,12 @@ pub fn prepare_messages_proof( /// Since this function changes the runtime storage, you can't "inline" it in the /// `asset_noop` macro calls. pub fn prepare_messages_delivery_proof( - lane: LaneId, + lane: TestLaneIdType, inbound_lane_data: InboundLaneData, -) -> FromBridgedChainMessagesDeliveryProof { +) -> FromBridgedChainMessagesDeliveryProof { // first - let's generate storage proof let (storage_root, storage_proof) = - prepare_message_delivery_storage_proof::( + prepare_message_delivery_storage_proof::( lane, inbound_lane_data, UnverifiedStorageProofParams::default(), @@ -545,7 +550,7 @@ pub fn prepare_messages_delivery_proof( StoredHeaderData { number: 0, state_root: storage_root }, ); - FromBridgedChainMessagesDeliveryProof:: { + FromBridgedChainMessagesDeliveryProof:: { bridged_header_hash, storage_proof, lane, diff --git a/bridges/modules/messages/src/tests/pallet_tests.rs b/bridges/modules/messages/src/tests/pallet_tests.rs index ceb1744c066..9df103a7cf6 100644 --- a/bridges/modules/messages/src/tests/pallet_tests.rs +++ b/bridges/modules/messages/src/tests/pallet_tests.rs @@ -30,7 +30,7 @@ use bp_messages::{ source_chain::{FromBridgedChainMessagesDeliveryProof, MessagesBridge}, target_chain::{FromBridgedChainMessagesProof, MessageDispatch}, BridgeMessagesCall, ChainWithMessages, DeliveredMessages, InboundLaneData, - InboundMessageDetails, LaneId, LaneState, MessageKey, MessageNonce, MessagesOperatingMode, + InboundMessageDetails, LaneIdType, LaneState, MessageKey, MessageNonce, MessagesOperatingMode, OutboundLaneData, OutboundMessageDetails, UnrewardedRelayer, UnrewardedRelayersState, VerificationError, }; @@ -51,7 +51,7 @@ fn get_ready_for_events() { System::::reset_events(); } -fn send_regular_message(lane_id: LaneId) { +fn send_regular_message(lane_id: TestLaneIdType) { get_ready_for_events(); let outbound_lane = active_outbound_lane::(lane_id).unwrap(); @@ -67,7 +67,10 @@ fn send_regular_message(lane_id: LaneId) { System::::events(), vec![EventRecord { phase: Phase::Initialization, - event: TestEvent::Messages(Event::MessageAccepted { lane_id, nonce: message_nonce }), + event: TestEvent::Messages(Event::MessageAccepted { + lane_id: lane_id.into(), + nonce: message_nonce + }), topics: vec![], }], ); @@ -105,7 +108,7 @@ fn receive_messages_delivery_proof() { vec![EventRecord { phase: Phase::Initialization, event: TestEvent::Messages(Event::MessagesDelivered { - lane_id: test_lane_id(), + lane_id: test_lane_id().into(), messages: DeliveredMessages::new(1), }), topics: vec![], @@ -629,7 +632,7 @@ fn receive_messages_delivery_proof_rewards_relayers() { fn receive_messages_delivery_proof_rejects_invalid_proof() { run_test(|| { let mut proof = prepare_messages_delivery_proof(test_lane_id(), Default::default()); - proof.lane = bp_messages::LaneId::new(42, 84); + proof.lane = TestLaneIdType::try_new(42, 84).unwrap(); assert_noop!( Pallet::::receive_messages_delivery_proof( @@ -1038,8 +1041,8 @@ fn test_bridge_messages_call_is_correctly_defined() { }; let indirect_receive_messages_proof_call = BridgeMessagesCall::< AccountId, - FromBridgedChainMessagesProof, - FromBridgedChainMessagesDeliveryProof, + FromBridgedChainMessagesProof, + FromBridgedChainMessagesDeliveryProof, >::receive_messages_proof { relayer_id_at_bridged_chain: account_id, proof: *message_proof, @@ -1058,8 +1061,8 @@ fn test_bridge_messages_call_is_correctly_defined() { }; let indirect_receive_messages_delivery_proof_call = BridgeMessagesCall::< AccountId, - FromBridgedChainMessagesProof, - FromBridgedChainMessagesDeliveryProof, + FromBridgedChainMessagesProof, + FromBridgedChainMessagesDeliveryProof, >::receive_messages_delivery_proof { proof: message_delivery_proof, relayers_state: unrewarded_relayer_state, @@ -1084,7 +1087,7 @@ fn inbound_storage_extra_proof_size_bytes_works() { fn storage(relayer_entries: usize) -> RuntimeInboundLaneStorage { RuntimeInboundLaneStorage { - lane_id: LaneId::new(1, 2), + lane_id: TestLaneIdType::try_new(1, 2).unwrap(), cached_data: InboundLaneData { state: LaneState::Opened, relayers: vec![relayer_entry(); relayer_entries].into(), @@ -1165,7 +1168,7 @@ fn receive_messages_proof_fails_if_inbound_lane_is_not_opened() { #[test] fn receive_messages_delivery_proof_fails_if_outbound_lane_is_unknown() { run_test(|| { - let make_proof = |lane: LaneId| { + let make_proof = |lane: TestLaneIdType| { prepare_messages_delivery_proof( lane, InboundLaneData { diff --git a/bridges/modules/relayers/src/benchmarking.rs b/bridges/modules/relayers/src/benchmarking.rs index 8a3f905a8f2..8fe3fc11d6a 100644 --- a/bridges/modules/relayers/src/benchmarking.rs +++ b/bridges/modules/relayers/src/benchmarking.rs @@ -20,9 +20,8 @@ use crate::*; -use bp_messages::LaneId; use bp_relayers::RewardsAccountOwner; -use frame_benchmarking::{benchmarks, whitelisted_caller}; +use frame_benchmarking::{benchmarks_instance_pallet, whitelisted_caller}; use frame_system::RawOrigin; use sp_runtime::traits::One; @@ -30,27 +29,34 @@ use sp_runtime::traits::One; const REWARD_AMOUNT: u32 = u32::MAX; /// Pallet we're benchmarking here. -pub struct Pallet(crate::Pallet); +pub struct Pallet, I: 'static = ()>(crate::Pallet); /// Trait that must be implemented by runtime. -pub trait Config: crate::Config { +pub trait Config: crate::Config { + /// Lane id to use in benchmarks. + fn bench_lane_id() -> Self::LaneId { + Self::LaneId::default() + } /// Prepare environment for paying given reward for serving given lane. - fn prepare_rewards_account(account_params: RewardsAccountParams, reward: Self::Reward); + fn prepare_rewards_account( + account_params: RewardsAccountParams, + reward: Self::Reward, + ); /// Give enough balance to given account. fn deposit_account(account: Self::AccountId, balance: Self::Reward); } -benchmarks! { +benchmarks_instance_pallet! { // Benchmark `claim_rewards` call. claim_rewards { - let lane = LaneId::new(1, 2); + let lane = T::bench_lane_id(); let account_params = RewardsAccountParams::new(lane, *b"test", RewardsAccountOwner::ThisChain); let relayer: T::AccountId = whitelisted_caller(); let reward = T::Reward::from(REWARD_AMOUNT); T::prepare_rewards_account(account_params, reward); - RelayerRewards::::insert(&relayer, account_params, reward); + RelayerRewards::::insert(&relayer, account_params, reward); }: _(RawOrigin::Signed(relayer), account_params) verify { // we can't check anything here, because `PaymentProcedure` is responsible for @@ -62,30 +68,30 @@ benchmarks! { register { let relayer: T::AccountId = whitelisted_caller(); let valid_till = frame_system::Pallet::::block_number() - .saturating_add(crate::Pallet::::required_registration_lease()) + .saturating_add(crate::Pallet::::required_registration_lease()) .saturating_add(One::one()) .saturating_add(One::one()); - T::deposit_account(relayer.clone(), crate::Pallet::::required_stake()); + T::deposit_account(relayer.clone(), crate::Pallet::::required_stake()); }: _(RawOrigin::Signed(relayer.clone()), valid_till) verify { - assert!(crate::Pallet::::is_registration_active(&relayer)); + assert!(crate::Pallet::::is_registration_active(&relayer)); } // Benchmark `deregister` call. deregister { let relayer: T::AccountId = whitelisted_caller(); let valid_till = frame_system::Pallet::::block_number() - .saturating_add(crate::Pallet::::required_registration_lease()) + .saturating_add(crate::Pallet::::required_registration_lease()) .saturating_add(One::one()) .saturating_add(One::one()); - T::deposit_account(relayer.clone(), crate::Pallet::::required_stake()); - crate::Pallet::::register(RawOrigin::Signed(relayer.clone()).into(), valid_till).unwrap(); + T::deposit_account(relayer.clone(), crate::Pallet::::required_stake()); + crate::Pallet::::register(RawOrigin::Signed(relayer.clone()).into(), valid_till).unwrap(); frame_system::Pallet::::set_block_number(valid_till.saturating_add(One::one())); }: _(RawOrigin::Signed(relayer.clone())) verify { - assert!(!crate::Pallet::::is_registration_active(&relayer)); + assert!(!crate::Pallet::::is_registration_active(&relayer)); } // Benchmark `slash_and_deregister` method of the pallet. We are adding this weight to @@ -95,36 +101,36 @@ benchmarks! { // prepare and register relayer account let relayer: T::AccountId = whitelisted_caller(); let valid_till = frame_system::Pallet::::block_number() - .saturating_add(crate::Pallet::::required_registration_lease()) + .saturating_add(crate::Pallet::::required_registration_lease()) .saturating_add(One::one()) .saturating_add(One::one()); - T::deposit_account(relayer.clone(), crate::Pallet::::required_stake()); - crate::Pallet::::register(RawOrigin::Signed(relayer.clone()).into(), valid_till).unwrap(); + T::deposit_account(relayer.clone(), crate::Pallet::::required_stake()); + crate::Pallet::::register(RawOrigin::Signed(relayer.clone()).into(), valid_till).unwrap(); // create slash destination account - let lane = LaneId::new(1, 2); + let lane = T::bench_lane_id(); let slash_destination = RewardsAccountParams::new(lane, *b"test", RewardsAccountOwner::ThisChain); T::prepare_rewards_account(slash_destination, Zero::zero()); }: { - crate::Pallet::::slash_and_deregister(&relayer, slash_destination.into()) + crate::Pallet::::slash_and_deregister(&relayer, slash_destination.into()) } verify { - assert!(!crate::Pallet::::is_registration_active(&relayer)); + assert!(!crate::Pallet::::is_registration_active(&relayer)); } // Benchmark `register_relayer_reward` method of the pallet. We are adding this weight to // the weight of message delivery call if `RefundBridgedParachainMessages` signed extension // is deployed at runtime level. register_relayer_reward { - let lane = LaneId::new(1, 2); + let lane = T::bench_lane_id(); let relayer: T::AccountId = whitelisted_caller(); let account_params = RewardsAccountParams::new(lane, *b"test", RewardsAccountOwner::ThisChain); }: { - crate::Pallet::::register_relayer_reward(account_params, &relayer, One::one()); + crate::Pallet::::register_relayer_reward(account_params, &relayer, One::one()); } verify { - assert_eq!(RelayerRewards::::get(relayer, &account_params), Some(One::one())); + assert_eq!(RelayerRewards::::get(relayer, &account_params), Some(One::one())); } impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::TestRuntime) diff --git a/bridges/modules/relayers/src/extension/grandpa_adapter.rs b/bridges/modules/relayers/src/extension/grandpa_adapter.rs index 6c9ae1c2968..2a8a6e78ef9 100644 --- a/bridges/modules/relayers/src/extension/grandpa_adapter.rs +++ b/bridges/modules/relayers/src/extension/grandpa_adapter.rs @@ -30,7 +30,7 @@ use pallet_bridge_grandpa::{ SubmitFinalityProofHelper, }; use pallet_bridge_messages::{ - CallSubType as BridgeMessagesCallSubType, Config as BridgeMessagesConfig, + CallSubType as BridgeMessagesCallSubType, Config as BridgeMessagesConfig, LaneIdOf, }; use sp_runtime::{ traits::{Dispatchable, Get}, @@ -54,6 +54,8 @@ pub struct WithGrandpaChainExtensionConfig< BridgeGrandpaPalletInstance, // instance of BridgedChain `pallet-bridge-messages`, tracked by this extension BridgeMessagesPalletInstance, + // instance of `pallet-bridge-relayers`, tracked by this extension + BridgeRelayersPalletInstance, // message delivery transaction priority boost for every additional message PriorityBoostPerMessage, >( @@ -63,20 +65,22 @@ pub struct WithGrandpaChainExtensionConfig< BatchCallUnpacker, BridgeGrandpaPalletInstance, BridgeMessagesPalletInstance, + BridgeRelayersPalletInstance, PriorityBoostPerMessage, )>, ); -impl ExtensionConfig - for WithGrandpaChainExtensionConfig +impl ExtensionConfig + for WithGrandpaChainExtensionConfig where ID: StaticStrProvider, - R: BridgeRelayersConfig + R: BridgeRelayersConfig + BridgeMessagesConfig> + BridgeGrandpaConfig, BCU: BatchCallUnpacker, GI: 'static, MI: 'static, + RI: 'static, P: Get, R::RuntimeCall: Dispatchable + BridgeGrandpaCallSubtype @@ -85,14 +89,15 @@ where type IdProvider = ID; type Runtime = R; type BridgeMessagesPalletInstance = MI; + type BridgeRelayersPalletInstance = RI; type PriorityBoostPerMessage = P; - type Reward = R::Reward; type RemoteGrandpaChainBlockNumber = pallet_bridge_grandpa::BridgedBlockNumber; + type LaneId = LaneIdOf; fn parse_and_check_for_obsolete_call( call: &R::RuntimeCall, ) -> Result< - Option>, + Option>, TransactionValidityError, > { let calls = BCU::unpack(call, 2); @@ -120,12 +125,12 @@ where } fn check_call_result( - call_info: &ExtensionCallInfo, + call_info: &ExtensionCallInfo, call_data: &mut ExtensionCallData, relayer: &R::AccountId, ) -> bool { verify_submit_finality_proof_succeeded::(call_info, call_data, relayer) && - verify_messages_call_succeeded::(call_info, call_data, relayer) + verify_messages_call_succeeded::(call_info, call_data, relayer) } } @@ -134,7 +139,7 @@ where /// /// Only returns false when GRANDPA chain state update call has failed. pub(crate) fn verify_submit_finality_proof_succeeded( - call_info: &ExtensionCallInfo, + call_info: &ExtensionCallInfo, call_data: &mut ExtensionCallData, relayer: &::AccountId, ) -> bool diff --git a/bridges/modules/relayers/src/extension/messages_adapter.rs b/bridges/modules/relayers/src/extension/messages_adapter.rs index ecb575524bb..e8c2088b7f2 100644 --- a/bridges/modules/relayers/src/extension/messages_adapter.rs +++ b/bridges/modules/relayers/src/extension/messages_adapter.rs @@ -23,7 +23,7 @@ use bp_relayers::{ExtensionCallData, ExtensionCallInfo, ExtensionConfig}; use bp_runtime::StaticStrProvider; use frame_support::dispatch::{DispatchInfo, PostDispatchInfo}; use pallet_bridge_messages::{ - CallSubType as BridgeMessagesCallSubType, Config as BridgeMessagesConfig, + CallSubType as BridgeMessagesCallSubType, Config as BridgeMessagesConfig, LaneIdOf, }; use sp_runtime::{ traits::{Dispatchable, Get}, @@ -37,6 +37,7 @@ pub struct WithMessagesExtensionConfig< IdProvider, Runtime, BridgeMessagesPalletInstance, + BridgeRelayersPalletInstance, PriorityBoostPerMessage, >( PhantomData<( @@ -46,16 +47,19 @@ pub struct WithMessagesExtensionConfig< Runtime, // instance of BridgedChain `pallet-bridge-messages`, tracked by this extension BridgeMessagesPalletInstance, + // instance of `pallet-bridge-relayers`, tracked by this extension + BridgeRelayersPalletInstance, // message delivery transaction priority boost for every additional message PriorityBoostPerMessage, )>, ); -impl ExtensionConfig for WithMessagesExtensionConfig +impl ExtensionConfig for WithMessagesExtensionConfig where ID: StaticStrProvider, - R: BridgeRelayersConfig + BridgeMessagesConfig, + R: BridgeRelayersConfig + BridgeMessagesConfig, MI: 'static, + RI: 'static, P: Get, R::RuntimeCall: Dispatchable + BridgeMessagesCallSubType, @@ -63,14 +67,15 @@ where type IdProvider = ID; type Runtime = R; type BridgeMessagesPalletInstance = MI; + type BridgeRelayersPalletInstance = RI; type PriorityBoostPerMessage = P; - type Reward = R::Reward; type RemoteGrandpaChainBlockNumber = (); + type LaneId = LaneIdOf; fn parse_and_check_for_obsolete_call( call: &R::RuntimeCall, ) -> Result< - Option>, + Option>, TransactionValidityError, > { let call = Self::check_obsolete_parsed_call(call)?; @@ -85,10 +90,10 @@ where } fn check_call_result( - call_info: &ExtensionCallInfo, + call_info: &ExtensionCallInfo, call_data: &mut ExtensionCallData, relayer: &R::AccountId, ) -> bool { - verify_messages_call_succeeded::(call_info, call_data, relayer) + verify_messages_call_succeeded::(call_info, call_data, relayer) } } diff --git a/bridges/modules/relayers/src/extension/mod.rs b/bridges/modules/relayers/src/extension/mod.rs index e1a7abd0ad1..9a248eb8e79 100644 --- a/bridges/modules/relayers/src/extension/mod.rs +++ b/bridges/modules/relayers/src/extension/mod.rs @@ -36,7 +36,9 @@ use frame_support::{ CloneNoBound, DefaultNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound, }; use frame_system::Config as SystemConfig; -use pallet_bridge_messages::{CallHelper as MessagesCallHelper, Config as BridgeMessagesConfig}; +use pallet_bridge_messages::{ + CallHelper as MessagesCallHelper, Config as BridgeMessagesConfig, LaneIdOf, +}; use pallet_transaction_payment::{ Config as TransactionPaymentConfig, OnChargeTransaction, Pallet as TransactionPaymentPallet, }; @@ -62,15 +64,19 @@ mod priority; /// Data that is crafted in `pre_dispatch` method and used at `post_dispatch`. #[cfg_attr(test, derive(Debug, PartialEq))] -pub struct PreDispatchData { +pub struct PreDispatchData< + AccountId, + RemoteGrandpaChainBlockNumber: Debug, + LaneId: Clone + Copy + Debug, +> { /// Transaction submitter (relayer) account. relayer: AccountId, /// Type of the call. - call_info: ExtensionCallInfo, + call_info: ExtensionCallInfo, } -impl - PreDispatchData +impl + PreDispatchData { /// Returns mutable reference to pre-dispatch `finality_target` sent to the /// `SubmitFinalityProof` call. @@ -88,13 +94,13 @@ impl /// The actions on relayer account that need to be performed because of his actions. #[derive(RuntimeDebug, PartialEq)] -pub enum RelayerAccountAction { +pub enum RelayerAccountAction { /// Do nothing with relayer account. None, /// Reward the relayer. - Reward(AccountId, RewardsAccountParams, Reward), + Reward(AccountId, RewardsAccountParams, Reward), /// Slash the relayer. - Slash(AccountId, RewardsAccountParams), + Slash(AccountId, RewardsAccountParams), } /// A signed extension, built around `pallet-bridge-relayers`. @@ -112,19 +118,22 @@ pub enum RelayerAccountAction { RuntimeDebugNoBound, TypeInfo, )] -#[scale_info(skip_type_params(Runtime, Config))] -pub struct BridgeRelayersSignedExtension(PhantomData<(Runtime, Config)>); +#[scale_info(skip_type_params(Runtime, Config, LaneId))] +pub struct BridgeRelayersSignedExtension( + PhantomData<(Runtime, Config, LaneId)>, +); -impl BridgeRelayersSignedExtension +impl BridgeRelayersSignedExtension where Self: 'static + Send + Sync, - R: RelayersConfig - + BridgeMessagesConfig + R: RelayersConfig + + BridgeMessagesConfig + TransactionPaymentConfig, - C: ExtensionConfig, + C: ExtensionConfig, R::RuntimeCall: Dispatchable, ::OnChargeTransaction: OnChargeTransaction, + LaneId: Clone + Copy + Decode + Encode + Debug + TypeInfo, { /// Returns number of bundled messages `Some(_)`, if the given call info is a: /// @@ -136,7 +145,7 @@ where /// virtually boosted. The relayer registration (we only boost priority for registered /// relayer transactions) must be checked outside. fn bundled_messages_for_priority_boost( - call_info: Option<&ExtensionCallInfo>, + call_info: Option<&ExtensionCallInfo>, ) -> Option { // we only boost priority of message delivery transactions let parsed_call = match call_info { @@ -160,12 +169,14 @@ where /// Given post-dispatch information, analyze the outcome of relayer call and return /// actions that need to be performed on relayer account. fn analyze_call_result( - pre: Option>>, + pre: Option< + Option>, + >, info: &DispatchInfo, post_info: &PostDispatchInfo, len: usize, result: &DispatchResult, - ) -> RelayerAccountAction { + ) -> RelayerAccountAction { // We don't refund anything for transactions that we don't support. let (relayer, call_info) = match pre { Some(Some(pre)) => (pre.relayer, pre.call_info), @@ -263,22 +274,23 @@ where } } -impl SignedExtension for BridgeRelayersSignedExtension +impl SignedExtension for BridgeRelayersSignedExtension where Self: 'static + Send + Sync, - R: RelayersConfig - + BridgeMessagesConfig + R: RelayersConfig + + BridgeMessagesConfig + TransactionPaymentConfig, - C: ExtensionConfig, + C: ExtensionConfig, R::RuntimeCall: Dispatchable, ::OnChargeTransaction: OnChargeTransaction, + LaneId: Clone + Copy + Decode + Encode + Debug + TypeInfo, { const IDENTIFIER: &'static str = C::IdProvider::STR; type AccountId = R::AccountId; type Call = R::RuntimeCall; type AdditionalSigned = (); - type Pre = Option>; + type Pre = Option>; fn additional_signed(&self) -> Result<(), TransactionValidityError> { Ok(()) @@ -392,19 +404,23 @@ where } /// Verify that the messages pallet call, supported by extension has succeeded. -pub(crate) fn verify_messages_call_succeeded( - call_info: &ExtensionCallInfo, +pub(crate) fn verify_messages_call_succeeded( + call_info: &ExtensionCallInfo< + C::RemoteGrandpaChainBlockNumber, + LaneIdOf, + >, _call_data: &mut ExtensionCallData, relayer: &::AccountId, ) -> bool where C: ExtensionConfig, - MI: 'static, - C::Runtime: BridgeMessagesConfig, + C::Runtime: BridgeMessagesConfig, { let messages_call = call_info.messages_call_info(); - if !MessagesCallHelper::::was_successful(messages_call) { + if !MessagesCallHelper::::was_successful( + messages_call, + ) { log::trace!( target: LOG_TARGET, "{}.{:?}: relayer {:?} has submitted invalid messages call", @@ -427,9 +443,9 @@ mod tests { use bp_messages::{ source_chain::FromBridgedChainMessagesDeliveryProof, target_chain::FromBridgedChainMessagesProof, BaseMessagesProofInfo, DeliveredMessages, - InboundLaneData, LaneId, MessageNonce, MessagesCallInfo, MessagesOperatingMode, - OutboundLaneData, ReceiveMessagesDeliveryProofInfo, ReceiveMessagesProofInfo, - UnrewardedRelayer, UnrewardedRelayerOccupation, UnrewardedRelayersState, + InboundLaneData, MessageNonce, MessagesCallInfo, MessagesOperatingMode, OutboundLaneData, + ReceiveMessagesDeliveryProofInfo, ReceiveMessagesProofInfo, UnrewardedRelayer, + UnrewardedRelayerOccupation, UnrewardedRelayersState, }; use bp_parachains::{BestParaHeadHash, ParaInfo, SubmitParachainHeadsInfo}; use bp_polkadot_core::parachains::{ParaHeadsProof, ParaId}; @@ -454,17 +470,16 @@ mod tests { parameter_types! { TestParachain: u32 = BridgedUnderlyingParachain::PARACHAIN_ID; - pub MsgProofsRewardsAccount: RewardsAccountParams = RewardsAccountParams::new( + pub MsgProofsRewardsAccount: RewardsAccountParams = RewardsAccountParams::new( test_lane_id(), TEST_BRIDGED_CHAIN_ID, RewardsAccountOwner::ThisChain, ); - pub MsgDeliveryProofsRewardsAccount: RewardsAccountParams = RewardsAccountParams::new( + pub MsgDeliveryProofsRewardsAccount: RewardsAccountParams = RewardsAccountParams::new( test_lane_id(), TEST_BRIDGED_CHAIN_ID, RewardsAccountOwner::BridgedChain, ); - pub TestLaneId: LaneId = test_lane_id(); } bp_runtime::generate_static_str_provider!(TestGrandpaExtension); @@ -477,31 +492,31 @@ mod tests { RuntimeWithUtilityPallet, (), (), + (), ConstU64<1>, >; type TestGrandpaExtension = - BridgeRelayersSignedExtension; + BridgeRelayersSignedExtension; type TestExtensionConfig = parachain_adapter::WithParachainExtensionConfig< StrTestExtension, TestRuntime, RuntimeWithUtilityPallet, (), (), + (), ConstU64<1>, >; - type TestExtension = BridgeRelayersSignedExtension; + type TestExtension = + BridgeRelayersSignedExtension; type TestMessagesExtensionConfig = messages_adapter::WithMessagesExtensionConfig< StrTestMessagesExtension, TestRuntime, (), + (), ConstU64<1>, >; type TestMessagesExtension = - BridgeRelayersSignedExtension; - - fn test_lane_id() -> LaneId { - LaneId::new(1, 2) - } + BridgeRelayersSignedExtension; fn initial_balance_of_relayer_account_at_this_chain() -> ThisChainBalance { let test_stake: ThisChainBalance = Stake::get(); @@ -795,7 +810,7 @@ mod tests { } fn all_finality_pre_dispatch_data( - ) -> PreDispatchData { + ) -> PreDispatchData { PreDispatchData { relayer: relayer_account_at_this_chain(), call_info: ExtensionCallInfo::AllFinalityAndMsgs( @@ -832,14 +847,14 @@ mod tests { #[cfg(test)] fn all_finality_pre_dispatch_data_ex( - ) -> PreDispatchData { + ) -> PreDispatchData { let mut data = all_finality_pre_dispatch_data(); data.submit_finality_proof_info_mut().unwrap().current_set_id = Some(TEST_GRANDPA_SET_ID); data } fn all_finality_confirmation_pre_dispatch_data( - ) -> PreDispatchData { + ) -> PreDispatchData { PreDispatchData { relayer: relayer_account_at_this_chain(), call_info: ExtensionCallInfo::AllFinalityAndMsgs( @@ -869,14 +884,14 @@ mod tests { } fn all_finality_confirmation_pre_dispatch_data_ex( - ) -> PreDispatchData { + ) -> PreDispatchData { let mut data = all_finality_confirmation_pre_dispatch_data(); data.submit_finality_proof_info_mut().unwrap().current_set_id = Some(TEST_GRANDPA_SET_ID); data } fn relay_finality_pre_dispatch_data( - ) -> PreDispatchData { + ) -> PreDispatchData { PreDispatchData { relayer: relayer_account_at_this_chain(), call_info: ExtensionCallInfo::RelayFinalityAndMsgs( @@ -906,14 +921,14 @@ mod tests { } fn relay_finality_pre_dispatch_data_ex( - ) -> PreDispatchData { + ) -> PreDispatchData { let mut data = relay_finality_pre_dispatch_data(); data.submit_finality_proof_info_mut().unwrap().current_set_id = Some(TEST_GRANDPA_SET_ID); data } fn relay_finality_confirmation_pre_dispatch_data( - ) -> PreDispatchData { + ) -> PreDispatchData { PreDispatchData { relayer: relayer_account_at_this_chain(), call_info: ExtensionCallInfo::RelayFinalityAndMsgs( @@ -937,14 +952,14 @@ mod tests { } fn relay_finality_confirmation_pre_dispatch_data_ex( - ) -> PreDispatchData { + ) -> PreDispatchData { let mut data = relay_finality_confirmation_pre_dispatch_data(); data.submit_finality_proof_info_mut().unwrap().current_set_id = Some(TEST_GRANDPA_SET_ID); data } fn parachain_finality_pre_dispatch_data( - ) -> PreDispatchData { + ) -> PreDispatchData { PreDispatchData { relayer: relayer_account_at_this_chain(), call_info: ExtensionCallInfo::ParachainFinalityAndMsgs( @@ -972,7 +987,7 @@ mod tests { } fn parachain_finality_confirmation_pre_dispatch_data( - ) -> PreDispatchData { + ) -> PreDispatchData { PreDispatchData { relayer: relayer_account_at_this_chain(), call_info: ExtensionCallInfo::ParachainFinalityAndMsgs( @@ -994,7 +1009,7 @@ mod tests { } fn delivery_pre_dispatch_data( - ) -> PreDispatchData { + ) -> PreDispatchData { PreDispatchData { relayer: relayer_account_at_this_chain(), call_info: ExtensionCallInfo::Msgs(MessagesCallInfo::ReceiveMessagesProof( @@ -1016,7 +1031,7 @@ mod tests { } fn confirmation_pre_dispatch_data( - ) -> PreDispatchData { + ) -> PreDispatchData { PreDispatchData { relayer: relayer_account_at_this_chain(), call_info: ExtensionCallInfo::Msgs(MessagesCallInfo::ReceiveMessagesDeliveryProof( @@ -1030,9 +1045,13 @@ mod tests { } fn set_bundled_range_end( - mut pre_dispatch_data: PreDispatchData, + mut pre_dispatch_data: PreDispatchData< + ThisChainAccountId, + BridgedChainBlockNumber, + TestLaneIdType, + >, end: MessageNonce, - ) -> PreDispatchData { + ) -> PreDispatchData { let msg_info = match pre_dispatch_data.call_info { ExtensionCallInfo::AllFinalityAndMsgs(_, _, ref mut info) => info, ExtensionCallInfo::RelayFinalityAndMsgs(_, ref mut info) => info, @@ -1072,7 +1091,7 @@ mod tests { fn run_pre_dispatch( call: RuntimeCall, ) -> Result< - Option>, + Option>, TransactionValidityError, > { sp_tracing::try_init_simple(); @@ -1083,7 +1102,7 @@ mod tests { fn run_grandpa_pre_dispatch( call: RuntimeCall, ) -> Result< - Option>, + Option>, TransactionValidityError, > { let extension: TestGrandpaExtension = BridgeRelayersSignedExtension(PhantomData); @@ -1092,7 +1111,10 @@ mod tests { fn run_messages_pre_dispatch( call: RuntimeCall, - ) -> Result>, TransactionValidityError> { + ) -> Result< + Option>, + TransactionValidityError, + > { let extension: TestMessagesExtension = BridgeRelayersSignedExtension(PhantomData); extension.pre_dispatch(&relayer_account_at_this_chain(), &call, &DispatchInfo::default(), 0) } @@ -1113,7 +1135,9 @@ mod tests { } fn run_post_dispatch( - pre_dispatch_data: Option>, + pre_dispatch_data: Option< + PreDispatchData, + >, dispatch_result: DispatchResult, ) { let post_dispatch_result = TestExtension::post_dispatch( @@ -1886,9 +1910,13 @@ mod tests { } fn run_analyze_call_result( - pre_dispatch_data: PreDispatchData, + pre_dispatch_data: PreDispatchData< + ThisChainAccountId, + BridgedChainBlockNumber, + TestLaneIdType, + >, dispatch_result: DispatchResult, - ) -> RelayerAccountAction { + ) -> RelayerAccountAction { TestExtension::analyze_call_result( Some(Some(pre_dispatch_data)), &dispatch_info(), @@ -2318,7 +2346,7 @@ mod tests { .unwrap(); // allow empty message delivery transactions - let lane_id = TestLaneId::get(); + let lane_id = test_lane_id(); let in_lane_data = InboundLaneData { last_confirmed_nonce: 0, relayers: vec![UnrewardedRelayer { diff --git a/bridges/modules/relayers/src/extension/parachain_adapter.rs b/bridges/modules/relayers/src/extension/parachain_adapter.rs index b6f57cebc30..69cf766dd67 100644 --- a/bridges/modules/relayers/src/extension/parachain_adapter.rs +++ b/bridges/modules/relayers/src/extension/parachain_adapter.rs @@ -32,7 +32,7 @@ use pallet_bridge_grandpa::{ CallSubType as BridgeGrandpaCallSubtype, Config as BridgeGrandpaConfig, }; use pallet_bridge_messages::{ - CallSubType as BridgeMessagesCallSubType, Config as BridgeMessagesConfig, + CallSubType as BridgeMessagesCallSubType, Config as BridgeMessagesConfig, LaneIdOf, }; use pallet_bridge_parachains::{ CallSubType as BridgeParachainsCallSubtype, Config as BridgeParachainsConfig, @@ -58,6 +58,8 @@ pub struct WithParachainExtensionConfig< BridgeParachainsPalletInstance, // instance of BridgedChain `pallet-bridge-messages`, tracked by this extension BridgeMessagesPalletInstance, + // instance of `pallet-bridge-relayers`, tracked by this extension + BridgeRelayersPalletInstance, // message delivery transaction priority boost for every additional message PriorityBoostPerMessage, >( @@ -67,20 +69,23 @@ pub struct WithParachainExtensionConfig< BatchCallUnpacker, BridgeParachainsPalletInstance, BridgeMessagesPalletInstance, + BridgeRelayersPalletInstance, PriorityBoostPerMessage, )>, ); -impl ExtensionConfig for WithParachainExtensionConfig +impl ExtensionConfig + for WithParachainExtensionConfig where ID: StaticStrProvider, - R: BridgeRelayersConfig + R: BridgeRelayersConfig + BridgeMessagesConfig + BridgeParachainsConfig + BridgeGrandpaConfig, BCU: BatchCallUnpacker, PI: 'static, MI: 'static, + RI: 'static, P: Get, R::RuntimeCall: Dispatchable + BridgeGrandpaCallSubtype @@ -91,15 +96,16 @@ where type IdProvider = ID; type Runtime = R; type BridgeMessagesPalletInstance = MI; + type BridgeRelayersPalletInstance = RI; type PriorityBoostPerMessage = P; - type Reward = R::Reward; type RemoteGrandpaChainBlockNumber = pallet_bridge_grandpa::BridgedBlockNumber; + type LaneId = LaneIdOf; fn parse_and_check_for_obsolete_call( call: &R::RuntimeCall, ) -> Result< - Option>, + Option>, TransactionValidityError, > { let calls = BCU::unpack(call, 3); @@ -109,7 +115,7 @@ where let msgs_call = calls.next().transpose()?.and_then(|c| c.call_info()); let para_finality_call = calls.next().transpose()?.and_then(|c| { let r = c.submit_parachain_heads_info_for( - >::BridgedChain::PARACHAIN_ID, + >::BridgedChain::PARACHAIN_ID, ); r }); @@ -139,14 +145,14 @@ where } fn check_call_result( - call_info: &ExtensionCallInfo, + call_info: &ExtensionCallInfo, call_data: &mut ExtensionCallData, relayer: &R::AccountId, ) -> bool { verify_submit_finality_proof_succeeded::( call_info, call_data, relayer, ) && verify_submit_parachain_head_succeeded::(call_info, call_data, relayer) && - verify_messages_call_succeeded::(call_info, call_data, relayer) + verify_messages_call_succeeded::(call_info, call_data, relayer) } } @@ -155,7 +161,7 @@ where /// /// Only returns false when parachain state update call has failed. pub(crate) fn verify_submit_parachain_head_succeeded( - call_info: &ExtensionCallInfo, + call_info: &ExtensionCallInfo, _call_data: &mut ExtensionCallData, relayer: &::AccountId, ) -> bool diff --git a/bridges/modules/relayers/src/lib.rs b/bridges/modules/relayers/src/lib.rs index b9627774db1..f06c2e16ac2 100644 --- a/bridges/modules/relayers/src/lib.rs +++ b/bridges/modules/relayers/src/lib.rs @@ -43,6 +43,7 @@ mod weights_ext; pub mod benchmarking; pub mod extension; +pub mod migration; pub mod weights; /// The target that will be used when publishing logs related to this pallet. @@ -51,46 +52,58 @@ pub const LOG_TARGET: &str = "runtime::bridge-relayers"; #[frame_support::pallet] pub mod pallet { use super::*; + use bp_messages::LaneIdType; use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; /// `RelayerRewardsKeyProvider` for given configuration. - type RelayerRewardsKeyProviderOf = - RelayerRewardsKeyProvider<::AccountId, ::Reward>; + type RelayerRewardsKeyProviderOf = RelayerRewardsKeyProvider< + ::AccountId, + >::Reward, + >::LaneId, + >; #[pallet::config] - pub trait Config: frame_system::Config { + pub trait Config: frame_system::Config { /// The overarching event type. - type RuntimeEvent: From> + IsType<::RuntimeEvent>; + type RuntimeEvent: From> + + IsType<::RuntimeEvent>; /// Type of relayer reward. type Reward: AtLeast32BitUnsigned + Copy + Member + Parameter + MaxEncodedLen; /// Pay rewards scheme. - type PaymentProcedure: PaymentProcedure; + type PaymentProcedure: PaymentProcedure< + Self::AccountId, + Self::Reward, + LaneId = Self::LaneId, + >; /// Stake and slash scheme. type StakeAndSlash: StakeAndSlash, Self::Reward>; /// Pallet call weights. type WeightInfo: WeightInfoExt; + /// Lane identifier type. + type LaneId: LaneIdType + Send + Sync; } #[pallet::pallet] - pub struct Pallet(PhantomData); + #[pallet::storage_version(migration::STORAGE_VERSION)] + pub struct Pallet(PhantomData<(T, I)>); #[pallet::call] - impl Pallet { + impl, I: 'static> Pallet { /// Claim accumulated rewards. #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::claim_rewards())] pub fn claim_rewards( origin: OriginFor, - rewards_account_params: RewardsAccountParams, + rewards_account_params: RewardsAccountParams, ) -> DispatchResult { let relayer = ensure_signed(origin)?; - RelayerRewards::::try_mutate_exists( + RelayerRewards::::try_mutate_exists( &relayer, rewards_account_params, |maybe_reward| -> DispatchResult { - let reward = maybe_reward.take().ok_or(Error::::NoRewardForRelayer)?; + let reward = maybe_reward.take().ok_or(Error::::NoRewardForRelayer)?; T::PaymentProcedure::pay_reward(&relayer, rewards_account_params, reward) .map_err(|e| { log::trace!( @@ -100,10 +113,10 @@ pub mod pallet { relayer, e, ); - Error::::FailedToPayReward + Error::::FailedToPayReward })?; - Self::deposit_event(Event::::RewardPaid { + Self::deposit_event(Event::::RewardPaid { relayer: relayer.clone(), rewards_account_params, reward, @@ -125,53 +138,57 @@ pub mod pallet { // than the `RequiredRegistrationLease` let lease = valid_till.saturating_sub(frame_system::Pallet::::block_number()); ensure!( - lease > Pallet::::required_registration_lease(), - Error::::InvalidRegistrationLease + lease > Self::required_registration_lease(), + Error::::InvalidRegistrationLease ); - RegisteredRelayers::::try_mutate(&relayer, |maybe_registration| -> DispatchResult { - let mut registration = maybe_registration - .unwrap_or_else(|| Registration { valid_till, stake: Zero::zero() }); + RegisteredRelayers::::try_mutate( + &relayer, + |maybe_registration| -> DispatchResult { + let mut registration = maybe_registration + .unwrap_or_else(|| Registration { valid_till, stake: Zero::zero() }); + + // new `valid_till` must be larger (or equal) than the old one + ensure!( + valid_till >= registration.valid_till, + Error::::CannotReduceRegistrationLease, + ); + registration.valid_till = valid_till; + + // regarding stake, there are three options: + // - if relayer stake is larger than required stake, we may do unreserve + // - if relayer stake equals to required stake, we do nothing + // - if relayer stake is smaller than required stake, we do additional reserve + let required_stake = Self::required_stake(); + if let Some(to_unreserve) = registration.stake.checked_sub(&required_stake) { + Self::do_unreserve(&relayer, to_unreserve)?; + } else if let Some(to_reserve) = required_stake.checked_sub(®istration.stake) + { + T::StakeAndSlash::reserve(&relayer, to_reserve).map_err(|e| { + log::trace!( + target: LOG_TARGET, + "Failed to reserve {:?} on relayer {:?} account: {:?}", + to_reserve, + relayer, + e, + ); - // new `valid_till` must be larger (or equal) than the old one - ensure!( - valid_till >= registration.valid_till, - Error::::CannotReduceRegistrationLease, - ); - registration.valid_till = valid_till; - - // regarding stake, there are three options: - // - if relayer stake is larger than required stake, we may do unreserve - // - if relayer stake equals to required stake, we do nothing - // - if relayer stake is smaller than required stake, we do additional reserve - let required_stake = Pallet::::required_stake(); - if let Some(to_unreserve) = registration.stake.checked_sub(&required_stake) { - Self::do_unreserve(&relayer, to_unreserve)?; - } else if let Some(to_reserve) = required_stake.checked_sub(®istration.stake) { - T::StakeAndSlash::reserve(&relayer, to_reserve).map_err(|e| { - log::trace!( - target: LOG_TARGET, - "Failed to reserve {:?} on relayer {:?} account: {:?}", - to_reserve, - relayer, - e, - ); - - Error::::FailedToReserve - })?; - } - registration.stake = required_stake; - - log::trace!(target: LOG_TARGET, "Successfully registered relayer: {:?}", relayer); - Self::deposit_event(Event::::RegistrationUpdated { - relayer: relayer.clone(), - registration, - }); - - *maybe_registration = Some(registration); - - Ok(()) - }) + Error::::FailedToReserve + })?; + } + registration.stake = required_stake; + + log::trace!(target: LOG_TARGET, "Successfully registered relayer: {:?}", relayer); + Self::deposit_event(Event::::RegistrationUpdated { + relayer: relayer.clone(), + registration, + }); + + *maybe_registration = Some(registration); + + Ok(()) + }, + ) } /// `Deregister` relayer. @@ -183,34 +200,37 @@ pub mod pallet { pub fn deregister(origin: OriginFor) -> DispatchResult { let relayer = ensure_signed(origin)?; - RegisteredRelayers::::try_mutate(&relayer, |maybe_registration| -> DispatchResult { - let registration = match maybe_registration.take() { - Some(registration) => registration, - None => fail!(Error::::NotRegistered), - }; - - // we can't deregister until `valid_till + 1` - ensure!( - registration.valid_till < frame_system::Pallet::::block_number(), - Error::::RegistrationIsStillActive, - ); + RegisteredRelayers::::try_mutate( + &relayer, + |maybe_registration| -> DispatchResult { + let registration = match maybe_registration.take() { + Some(registration) => registration, + None => fail!(Error::::NotRegistered), + }; + + // we can't deregister until `valid_till + 1` + ensure!( + registration.valid_till < frame_system::Pallet::::block_number(), + Error::::RegistrationIsStillActive, + ); - // if stake is non-zero, we should do unreserve - if !registration.stake.is_zero() { - Self::do_unreserve(&relayer, registration.stake)?; - } + // if stake is non-zero, we should do unreserve + if !registration.stake.is_zero() { + Self::do_unreserve(&relayer, registration.stake)?; + } - log::trace!(target: LOG_TARGET, "Successfully deregistered relayer: {:?}", relayer); - Self::deposit_event(Event::::Deregistered { relayer: relayer.clone() }); + log::trace!(target: LOG_TARGET, "Successfully deregistered relayer: {:?}", relayer); + Self::deposit_event(Event::::Deregistered { relayer: relayer.clone() }); - *maybe_registration = None; + *maybe_registration = None; - Ok(()) - }) + Ok(()) + }, + ) } } - impl Pallet { + impl, I: 'static> Pallet { /// Returns true if given relayer registration is active at current block. /// /// This call respects both `RequiredStake` and `RequiredRegistrationLease`, meaning that @@ -243,9 +263,9 @@ pub mod pallet { /// It may fail inside, but error is swallowed and we only log it. pub fn slash_and_deregister( relayer: &T::AccountId, - slash_destination: ExplicitOrAccountParams, + slash_destination: ExplicitOrAccountParams, ) { - let registration = match RegisteredRelayers::::take(relayer) { + let registration = match RegisteredRelayers::::take(relayer) { Some(registration) => registration, None => { log::trace!( @@ -304,7 +324,7 @@ pub mod pallet { /// Register reward for given relayer. pub fn register_relayer_reward( - rewards_account_params: RewardsAccountParams, + rewards_account_params: RewardsAccountParams, relayer: &T::AccountId, reward: T::Reward, ) { @@ -312,7 +332,7 @@ pub mod pallet { return } - RelayerRewards::::mutate( + RelayerRewards::::mutate( relayer, rewards_account_params, |old_reward: &mut Option| { @@ -327,7 +347,7 @@ pub mod pallet { new_reward, ); - Self::deposit_event(Event::::RewardRegistered { + Self::deposit_event(Event::::RewardRegistered { relayer: relayer.clone(), rewards_account_params, reward, @@ -366,7 +386,7 @@ pub mod pallet { relayer, ); - fail!(Error::::FailedToUnreserve) + fail!(Error::::FailedToUnreserve) } Ok(()) @@ -375,13 +395,13 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] - pub enum Event { + pub enum Event, I: 'static = ()> { /// Relayer reward has been registered and may be claimed later. RewardRegistered { /// Relayer account that can claim reward. relayer: T::AccountId, /// Relayer can claim reward from this account. - rewards_account_params: RewardsAccountParams, + rewards_account_params: RewardsAccountParams, /// Reward amount. reward: T::Reward, }, @@ -390,7 +410,7 @@ pub mod pallet { /// Relayer account that has been rewarded. relayer: T::AccountId, /// Relayer has received reward from this account. - rewards_account_params: RewardsAccountParams, + rewards_account_params: RewardsAccountParams, /// Reward amount. reward: T::Reward, }, @@ -416,7 +436,7 @@ pub mod pallet { } #[pallet::error] - pub enum Error { + pub enum Error { /// No reward can be claimed by given relayer. NoRewardForRelayer, /// Reward payment procedure has failed. @@ -439,13 +459,13 @@ pub mod pallet { /// Map of the relayer => accumulated reward. #[pallet::storage] #[pallet::getter(fn relayer_reward)] - pub type RelayerRewards = StorageDoubleMap< + pub type RelayerRewards, I: 'static = ()> = StorageDoubleMap< _, - as StorageDoubleMapKeyProvider>::Hasher1, - as StorageDoubleMapKeyProvider>::Key1, - as StorageDoubleMapKeyProvider>::Hasher2, - as StorageDoubleMapKeyProvider>::Key2, - as StorageDoubleMapKeyProvider>::Value, + as StorageDoubleMapKeyProvider>::Hasher1, + as StorageDoubleMapKeyProvider>::Key1, + as StorageDoubleMapKeyProvider>::Hasher2, + as StorageDoubleMapKeyProvider>::Key2, + as StorageDoubleMapKeyProvider>::Value, OptionQuery, >; @@ -457,7 +477,7 @@ pub mod pallet { /// relayer is present. #[pallet::storage] #[pallet::getter(fn registered_relayer)] - pub type RegisteredRelayers = StorageMap< + pub type RegisteredRelayers, I: 'static = ()> = StorageMap< _, Blake2_128Concat, T::AccountId, @@ -469,10 +489,10 @@ pub mod pallet { #[cfg(test)] mod tests { use super::*; + use bp_messages::LaneIdType; use mock::{RuntimeEvent as TestEvent, *}; use crate::Event::{RewardPaid, RewardRegistered}; - use bp_messages::LaneId; use bp_relayers::RewardsAccountOwner; use frame_support::{ assert_noop, assert_ok, @@ -596,16 +616,16 @@ mod tests { fn pay_reward_from_account_actually_pays_reward() { type Balances = pallet_balances::Pallet; type PayLaneRewardFromAccount = - bp_relayers::PayRewardFromAccount; + bp_relayers::PayRewardFromAccount; run_test(|| { let in_lane_0 = RewardsAccountParams::new( - LaneId::new(1, 2), + TestLaneIdType::try_new(1, 2).unwrap(), *b"test", RewardsAccountOwner::ThisChain, ); let out_lane_1 = RewardsAccountParams::new( - LaneId::new(1, 3), + TestLaneIdType::try_new(1, 3).unwrap(), *b"test", RewardsAccountOwner::BridgedChain, ); diff --git a/bridges/modules/relayers/src/migration.rs b/bridges/modules/relayers/src/migration.rs new file mode 100644 index 00000000000..8bf473b300c --- /dev/null +++ b/bridges/modules/relayers/src/migration.rs @@ -0,0 +1,243 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! A module that is responsible for migration of storage. + +use frame_support::{ + traits::{Get, StorageVersion}, + weights::Weight, +}; + +/// The in-code storage version. +pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); + +/// This module contains data structures that are valid for the initial state of `0`. +/// (used with v1 migration). +pub mod v0 { + use crate::{Config, Pallet}; + use bp_relayers::RewardsAccountOwner; + use bp_runtime::{ChainId, StorageDoubleMapKeyProvider}; + use codec::{Codec, Decode, Encode, EncodeLike, MaxEncodedLen}; + use frame_support::{pallet_prelude::OptionQuery, Blake2_128Concat, Identity}; + use scale_info::TypeInfo; + use sp_runtime::traits::AccountIdConversion; + use sp_std::marker::PhantomData; + + /// Structure used to identify the account that pays a reward to the relayer. + #[derive(Copy, Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo, MaxEncodedLen)] + pub struct RewardsAccountParams { + /// lane_id + pub lane_id: LaneId, + /// bridged_chain_id + pub bridged_chain_id: ChainId, + /// owner + pub owner: RewardsAccountOwner, + } + + impl RewardsAccountParams { + /// Create a new instance of `RewardsAccountParams`. + pub const fn new( + lane_id: LaneId, + bridged_chain_id: ChainId, + owner: RewardsAccountOwner, + ) -> Self { + Self { lane_id, bridged_chain_id, owner } + } + } + + impl sp_runtime::TypeId for RewardsAccountParams { + const TYPE_ID: [u8; 4] = *b"brap"; + } + + pub(crate) struct RelayerRewardsKeyProvider( + PhantomData<(AccountId, Reward, LaneId)>, + ); + + impl StorageDoubleMapKeyProvider + for RelayerRewardsKeyProvider + where + AccountId: 'static + Codec + EncodeLike + Send + Sync, + Reward: 'static + Codec + EncodeLike + Send + Sync, + LaneId: Codec + EncodeLike + Send + Sync, + { + const MAP_NAME: &'static str = "RelayerRewards"; + + type Hasher1 = Blake2_128Concat; + type Key1 = AccountId; + type Hasher2 = Identity; + type Key2 = RewardsAccountParams; + type Value = Reward; + } + + pub(crate) type RelayerRewardsKeyProviderOf = RelayerRewardsKeyProvider< + ::AccountId, + >::Reward, + >::LaneId, + >; + + #[frame_support::storage_alias] + pub(crate) type RelayerRewards, I: 'static> = StorageDoubleMap< + Pallet, + as StorageDoubleMapKeyProvider>::Hasher1, + as StorageDoubleMapKeyProvider>::Key1, + as StorageDoubleMapKeyProvider>::Hasher2, + as StorageDoubleMapKeyProvider>::Key2, + as StorageDoubleMapKeyProvider>::Value, + OptionQuery, + >; + + /// Reward account generator for `v0`. + pub struct PayRewardFromAccount(PhantomData<(Account, LaneId)>); + impl PayRewardFromAccount + where + Account: Decode + Encode, + LaneId: Decode + Encode, + { + /// Return account that pays rewards based on the provided parameters. + pub fn rewards_account(params: RewardsAccountParams) -> Account { + params.into_sub_account_truncating(b"rewards-account") + } + } +} + +/// This migration updates `RelayerRewards` where `RewardsAccountParams` was used as the key with +/// `lane_id` as the first attribute, which affects `into_sub_account_truncating`. We are migrating +/// this key to use the new `RewardsAccountParams` where `lane_id` is the last attribute. +pub mod v1 { + use super::*; + use crate::{Config, Pallet}; + use bp_relayers::RewardsAccountParams; + use frame_support::traits::UncheckedOnRuntimeUpgrade; + use sp_std::marker::PhantomData; + + #[cfg(feature = "try-runtime")] + use crate::RelayerRewards; + + /// Migrates the pallet storage to v1. + pub struct UncheckedMigrationV0ToV1(PhantomData<(T, I)>); + + #[cfg(feature = "try-runtime")] + const LOG_TARGET: &str = "runtime::bridge-relayers-migration"; + + impl, I: 'static> UncheckedOnRuntimeUpgrade for UncheckedMigrationV0ToV1 { + fn on_runtime_upgrade() -> Weight { + let mut weight = T::DbWeight::get().reads(1); + + // list all rewards (we cannot do this as one step because of `drain` limitation) + let mut rewards_to_migrate = + sp_std::vec::Vec::with_capacity(v0::RelayerRewards::::iter().count()); + for (key1, key2, reward) in v0::RelayerRewards::::drain() { + rewards_to_migrate.push((key1, key2, reward)); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); + } + + // re-register rewards with new format of `RewardsAccountParams`. + for (key1, key2, reward) in rewards_to_migrate { + // expand old key + let v0::RewardsAccountParams { owner, lane_id, bridged_chain_id } = key2; + + // re-register reward + Pallet::::register_relayer_reward( + v1::RewardsAccountParams::new(lane_id, bridged_chain_id, owner), + &key1, + reward, + ); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); + } + + weight + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, sp_runtime::DispatchError> { + use codec::Encode; + use frame_support::BoundedBTreeMap; + use sp_runtime::traits::ConstU32; + + // collect actual rewards + let mut rewards: BoundedBTreeMap< + (T::AccountId, T::LaneId), + T::Reward, + ConstU32<{ u32::MAX }>, + > = BoundedBTreeMap::new(); + for (key1, key2, reward) in v0::RelayerRewards::::iter() { + log::info!(target: LOG_TARGET, "Reward to migrate: {key1:?}::{key2:?} - {reward:?}"); + rewards = rewards + .try_mutate(|inner| { + inner + .entry((key1.clone(), key2.lane_id)) + .and_modify(|value| *value += reward) + .or_insert(reward); + }) + .unwrap(); + } + log::info!(target: LOG_TARGET, "Found total rewards to migrate: {rewards:?}"); + + Ok(rewards.encode()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(state: sp_std::vec::Vec) -> Result<(), sp_runtime::DispatchError> { + use codec::Decode; + use frame_support::BoundedBTreeMap; + use sp_runtime::traits::ConstU32; + + let rewards_before: BoundedBTreeMap< + (T::AccountId, T::LaneId), + T::Reward, + ConstU32<{ u32::MAX }>, + > = Decode::decode(&mut &state[..]).unwrap(); + + // collect migrated rewards + let mut rewards_after: BoundedBTreeMap< + (T::AccountId, T::LaneId), + T::Reward, + ConstU32<{ u32::MAX }>, + > = BoundedBTreeMap::new(); + for (key1, key2, reward) in v1::RelayerRewards::::iter() { + log::info!(target: LOG_TARGET, "Migrated rewards: {key1:?}::{key2:?} - {reward:?}"); + rewards_after = rewards_after + .try_mutate(|inner| { + inner + .entry((key1.clone(), *key2.lane_id())) + .and_modify(|value| *value += reward) + .or_insert(reward); + }) + .unwrap(); + } + log::info!(target: LOG_TARGET, "Found total migrated rewards: {rewards_after:?}"); + + frame_support::ensure!( + rewards_before == rewards_after, + "The rewards were not migrated correctly!." + ); + + log::info!(target: LOG_TARGET, "migrated all."); + Ok(()) + } + } + + /// [`UncheckedMigrationV0ToV1`] wrapped in a + /// [`VersionedMigration`](frame_support::migrations::VersionedMigration), ensuring the + /// migration is only performed when on-chain version is 0. + pub type MigrationToV1 = frame_support::migrations::VersionedMigration< + 0, + 1, + UncheckedMigrationV0ToV1, + Pallet, + ::DbWeight, + >; +} diff --git a/bridges/modules/relayers/src/mock.rs b/bridges/modules/relayers/src/mock.rs index de1d292b7c0..d186e968e64 100644 --- a/bridges/modules/relayers/src/mock.rs +++ b/bridges/modules/relayers/src/mock.rs @@ -21,7 +21,7 @@ use crate as pallet_bridge_relayers; use bp_header_chain::ChainWithGrandpa; use bp_messages::{ target_chain::{DispatchMessage, MessageDispatch}, - ChainWithMessages, LaneId, MessageNonce, + ChainWithMessages, HashedLaneId, LaneIdType, MessageNonce, }; use bp_parachains::SingleParaStoredHeaderDataBuilder; use bp_relayers::{ @@ -75,6 +75,13 @@ pub const TEST_BRIDGED_CHAIN_ID: ChainId = *b"brdg"; /// Maximal extrinsic size at the `BridgedChain`. pub const BRIDGED_CHAIN_MAX_EXTRINSIC_SIZE: u32 = 1024; +/// Lane identifier type used for tests. +pub type TestLaneIdType = HashedLaneId; +/// Lane that we're using in tests. +pub fn test_lane_id() -> TestLaneIdType { + TestLaneIdType::try_new(1, 2).unwrap() +} + /// Underlying chain of `ThisChain`. pub struct ThisUnderlyingChain; @@ -253,10 +260,10 @@ impl pallet_bridge_messages::Config for TestRuntime { type WeightInfo = pallet_bridge_messages::weights::BridgeWeight; type OutboundPayload = Vec; - type InboundPayload = Vec; - type DeliveryPayments = (); + type LaneId = TestLaneIdType; + type DeliveryPayments = (); type DeliveryConfirmationPayments = pallet_bridge_relayers::DeliveryConfirmationPaymentsAdapter< TestRuntime, (), @@ -276,15 +283,20 @@ impl pallet_bridge_relayers::Config for TestRuntime { type PaymentProcedure = TestPaymentProcedure; type StakeAndSlash = TestStakeAndSlash; type WeightInfo = (); + type LaneId = TestLaneIdType; } #[cfg(feature = "runtime-benchmarks")] impl pallet_bridge_relayers::benchmarking::Config for TestRuntime { - fn prepare_rewards_account(account_params: RewardsAccountParams, reward: ThisChainBalance) { - let rewards_account = - bp_relayers::PayRewardFromAccount::::rewards_account( - account_params, - ); + fn prepare_rewards_account( + account_params: RewardsAccountParams, + reward: Self::Reward, + ) { + let rewards_account = bp_relayers::PayRewardFromAccount::< + Balances, + ThisChainAccountId, + Self::LaneId, + >::rewards_account(account_params); Self::deposit_account(rewards_account, reward); } @@ -306,17 +318,18 @@ pub const REGISTER_RELAYER: ThisChainAccountId = 42; pub struct TestPaymentProcedure; impl TestPaymentProcedure { - pub fn rewards_account(params: RewardsAccountParams) -> ThisChainAccountId { - PayRewardFromAccount::<(), ThisChainAccountId>::rewards_account(params) + pub fn rewards_account(params: RewardsAccountParams) -> ThisChainAccountId { + PayRewardFromAccount::<(), ThisChainAccountId, TestLaneIdType>::rewards_account(params) } } impl PaymentProcedure for TestPaymentProcedure { type Error = (); + type LaneId = TestLaneIdType; fn pay_reward( relayer: &ThisChainAccountId, - _lane_id: RewardsAccountParams, + _lane_id: RewardsAccountParams, _reward: ThisChainBalance, ) -> Result<(), Self::Error> { match *relayer { @@ -330,7 +343,7 @@ impl PaymentProcedure for TestPaymentProce pub struct DummyMessageDispatch; impl DummyMessageDispatch { - pub fn deactivate(lane: LaneId) { + pub fn deactivate(lane: TestLaneIdType) { frame_support::storage::unhashed::put(&(b"inactive", lane).encode()[..], &false); } } @@ -338,26 +351,33 @@ impl DummyMessageDispatch { impl MessageDispatch for DummyMessageDispatch { type DispatchPayload = Vec; type DispatchLevelResult = (); + type LaneId = TestLaneIdType; - fn is_active(lane: LaneId) -> bool { + fn is_active(lane: Self::LaneId) -> bool { frame_support::storage::unhashed::take::(&(b"inactive", lane).encode()[..]) != Some(false) } - fn dispatch_weight(_message: &mut DispatchMessage) -> Weight { + fn dispatch_weight( + _message: &mut DispatchMessage, + ) -> Weight { Weight::zero() } fn dispatch( - _: DispatchMessage, + _: DispatchMessage, ) -> MessageDispatchResult { MessageDispatchResult { unspent_weight: Weight::zero(), dispatch_level_result: () } } } /// Reward account params that we are using in tests. -pub fn test_reward_account_param() -> RewardsAccountParams { - RewardsAccountParams::new(LaneId::new(1, 2), *b"test", RewardsAccountOwner::ThisChain) +pub fn test_reward_account_param() -> RewardsAccountParams { + RewardsAccountParams::new( + TestLaneIdType::try_new(1, 2).unwrap(), + *b"test", + RewardsAccountOwner::ThisChain, + ) } /// Return test externalities to use in tests. diff --git a/bridges/modules/relayers/src/payment_adapter.rs b/bridges/modules/relayers/src/payment_adapter.rs index 3693793a3e5..5383cba5ecb 100644 --- a/bridges/modules/relayers/src/payment_adapter.rs +++ b/bridges/modules/relayers/src/payment_adapter.rs @@ -20,11 +20,12 @@ use crate::{Config, Pallet}; use bp_messages::{ source_chain::{DeliveryConfirmationPayments, RelayersRewards}, - LaneId, MessageNonce, + MessageNonce, }; use bp_relayers::{RewardsAccountOwner, RewardsAccountParams}; use bp_runtime::Chain; use frame_support::{sp_runtime::SaturatedConversion, traits::Get}; +use pallet_bridge_messages::LaneIdOf; use sp_arithmetic::traits::{Saturating, Zero}; use sp_std::{collections::vec_deque::VecDeque, marker::PhantomData, ops::RangeInclusive}; @@ -34,17 +35,17 @@ pub struct DeliveryConfirmationPaymentsAdapter( PhantomData<(T, MI, DeliveryReward)>, ); -impl DeliveryConfirmationPayments +impl DeliveryConfirmationPayments> for DeliveryConfirmationPaymentsAdapter where - T: Config + pallet_bridge_messages::Config, + T: Config + pallet_bridge_messages::Config::LaneId>, MI: 'static, DeliveryReward: Get, { type Error = &'static str; fn pay_reward( - lane_id: LaneId, + lane_id: LaneIdOf, messages_relayers: VecDeque>, confirmation_relayer: &T::AccountId, received_range: &RangeInclusive, @@ -72,7 +73,7 @@ where fn register_relayers_rewards( confirmation_relayer: &T::AccountId, relayers_rewards: RelayersRewards, - lane_id: RewardsAccountParams, + lane_id: RewardsAccountParams, delivery_fee: T::Reward, ) { // reward every relayer except `confirmation_relayer` diff --git a/bridges/modules/relayers/src/stake_adapter.rs b/bridges/modules/relayers/src/stake_adapter.rs index 0c965e9e6bf..1792f0be831 100644 --- a/bridges/modules/relayers/src/stake_adapter.rs +++ b/bridges/modules/relayers/src/stake_adapter.rs @@ -18,7 +18,7 @@ //! mechanism of the relayers pallet. use bp_relayers::{ExplicitOrAccountParams, PayRewardFromAccount, StakeAndSlash}; -use codec::Codec; +use codec::{Codec, Decode, Encode}; use frame_support::traits::{tokens::BalanceStatus, NamedReservableCurrency}; use sp_runtime::{traits::Get, DispatchError, DispatchResult}; use sp_std::{fmt::Debug, marker::PhantomData}; @@ -53,15 +53,15 @@ where Currency::unreserve_named(&ReserveId::get(), relayer, amount) } - fn repatriate_reserved( + fn repatriate_reserved( relayer: &AccountId, - beneficiary: ExplicitOrAccountParams, + beneficiary: ExplicitOrAccountParams, amount: Currency::Balance, ) -> Result { let beneficiary_account = match beneficiary { ExplicitOrAccountParams::Explicit(account) => account, ExplicitOrAccountParams::Params(params) => - PayRewardFromAccount::<(), AccountId>::rewards_account(params), + PayRewardFromAccount::<(), AccountId, LaneId>::rewards_account(params), }; Currency::repatriate_reserved_named( &ReserveId::get(), diff --git a/bridges/modules/xcm-bridge-hub/src/dispatcher.rs b/bridges/modules/xcm-bridge-hub/src/dispatcher.rs index 2412bb0f3bb..dd855c7069a 100644 --- a/bridges/modules/xcm-bridge-hub/src/dispatcher.rs +++ b/bridges/modules/xcm-bridge-hub/src/dispatcher.rs @@ -23,10 +23,7 @@ use crate::{Config, Pallet, LOG_TARGET}; -use bp_messages::{ - target_chain::{DispatchMessage, MessageDispatch}, - LaneId, -}; +use bp_messages::target_chain::{DispatchMessage, MessageDispatch}; use bp_runtime::messages::MessageDispatchResult; use bp_xcm_bridge_hub::{LocalXcmChannelManager, XcmAsPlainPayload}; use codec::{Decode, Encode}; @@ -58,15 +55,18 @@ where { type DispatchPayload = XcmAsPlainPayload; type DispatchLevelResult = XcmBlobMessageDispatchResult; + type LaneId = T::LaneId; - fn is_active(lane: LaneId) -> bool { + fn is_active(lane: Self::LaneId) -> bool { Pallet::::bridge_by_lane_id(&lane) .and_then(|(_, bridge)| bridge.bridge_origin_relative_location.try_as().cloned().ok()) .map(|recipient: Location| !T::LocalXcmChannelManager::is_congested(&recipient)) .unwrap_or(false) } - fn dispatch_weight(message: &mut DispatchMessage) -> Weight { + fn dispatch_weight( + message: &mut DispatchMessage, + ) -> Weight { match message.data.payload { Ok(ref payload) => { let payload_size = payload.encoded_size().saturated_into(); @@ -77,14 +77,14 @@ where } fn dispatch( - message: DispatchMessage, + message: DispatchMessage, ) -> MessageDispatchResult { let payload = match message.data.payload { Ok(payload) => payload, Err(e) => { log::error!( target: LOG_TARGET, - "dispatch - payload error: {e:?} for lane_id: {} and message_nonce: {:?}", + "dispatch - payload error: {e:?} for lane_id: {:?} and message_nonce: {:?}", message.key.lane_id, message.key.nonce ); @@ -98,7 +98,7 @@ where Ok(_) => { log::debug!( target: LOG_TARGET, - "dispatch - `DispatchBlob::dispatch_blob` was ok for lane_id: {} and message_nonce: {:?}", + "dispatch - `DispatchBlob::dispatch_blob` was ok for lane_id: {:?} and message_nonce: {:?}", message.key.lane_id, message.key.nonce ); @@ -107,7 +107,7 @@ where Err(e) => { log::error!( target: LOG_TARGET, - "dispatch - `DispatchBlob::dispatch_blob` failed with error: {e:?} for lane_id: {} and message_nonce: {:?}", + "dispatch - `DispatchBlob::dispatch_blob` failed with error: {e:?} for lane_id: {:?} and message_nonce: {:?}", message.key.lane_id, message.key.nonce ); @@ -123,13 +123,13 @@ mod tests { use super::*; use crate::{mock::*, Bridges, LaneToBridge, LanesManagerOf}; - use bp_messages::{target_chain::DispatchMessageData, MessageKey}; + use bp_messages::{target_chain::DispatchMessageData, LaneIdType, MessageKey}; use bp_xcm_bridge_hub::{Bridge, BridgeLocations, BridgeState}; use frame_support::assert_ok; use pallet_bridge_messages::InboundLaneStorage; use xcm_executor::traits::ConvertLocation; - fn bridge() -> (Box, LaneId) { + fn bridge() -> (Box, TestLaneIdType) { let origin = OpenBridgeOrigin::sibling_parachain_origin(); let with = bridged_asset_hub_universal_location(); let locations = @@ -194,16 +194,16 @@ mod tests { }); } - fn invalid_message() -> DispatchMessage> { + fn invalid_message() -> DispatchMessage, TestLaneIdType> { DispatchMessage { - key: MessageKey { lane_id: LaneId::new(1, 2), nonce: 1 }, + key: MessageKey { lane_id: TestLaneIdType::try_new(1, 2).unwrap(), nonce: 1 }, data: DispatchMessageData { payload: Err(codec::Error::from("test")) }, } } - fn valid_message() -> DispatchMessage> { + fn valid_message() -> DispatchMessage, TestLaneIdType> { DispatchMessage { - key: MessageKey { lane_id: LaneId::new(1, 2), nonce: 1 }, + key: MessageKey { lane_id: TestLaneIdType::try_new(1, 2).unwrap(), nonce: 1 }, data: DispatchMessageData { payload: Ok(vec![42]) }, } } diff --git a/bridges/modules/xcm-bridge-hub/src/exporter.rs b/bridges/modules/xcm-bridge-hub/src/exporter.rs index b42ae1e267f..5afb9f36bc9 100644 --- a/bridges/modules/xcm-bridge-hub/src/exporter.rs +++ b/bridges/modules/xcm-bridge-hub/src/exporter.rs @@ -26,7 +26,7 @@ use crate::{BridgeOf, Bridges}; use bp_messages::{ source_chain::{MessagesBridge, OnMessagesDelivered}, - LaneId, MessageNonce, + MessageNonce, }; use bp_xcm_bridge_hub::{BridgeId, BridgeState, LocalXcmChannelManager, XcmAsPlainPayload}; use frame_support::{ensure, traits::Get}; @@ -62,7 +62,7 @@ where type Ticket = ( BridgeId, BridgeOf, - as MessagesBridge>::SendMessageArgs, + as MessagesBridge>::SendMessageArgs, XcmHash, ); @@ -94,7 +94,7 @@ where "Destination: {dest:?} is already universal, checking dest_network: {dest_network:?} and network: {network:?} if matches: {:?}", dest_network == network ); - ensure!(dest_network == network, SendError::Unroutable); + ensure!(dest_network == network, SendError::NotApplicable); // ok, `dest` looks like a universal location, so let's use it dest }, @@ -108,23 +108,12 @@ where error_data.0, error_data.1, ); - SendError::Unroutable + SendError::NotApplicable })? }, } }; - // check if we are able to route the message. We use existing `HaulBlobExporter` for that. - // It will make all required changes and will encode message properly, so that the - // `DispatchBlob` at the bridged bridge hub will be able to decode it - let ((blob, id), price) = PalletAsHaulBlobExporter::::validate( - network, - channel, - universal_source, - destination, - message, - )?; - // prepare the origin relative location let bridge_origin_relative_location = bridge_origin_universal_location.relative_to(&T::UniversalLocation::get()); @@ -139,9 +128,28 @@ where target: LOG_TARGET, "Validate `bridge_locations` with error: {e:?}", ); - SendError::Unroutable + SendError::NotApplicable + })?; + let bridge = Self::bridge(locations.bridge_id()).ok_or_else(|| { + log::error!( + target: LOG_TARGET, + "No opened bridge for requested bridge_origin_relative_location: {:?} and bridge_destination_universal_location: {:?}", + locations.bridge_origin_relative_location(), + locations.bridge_destination_universal_location(), + ); + SendError::NotApplicable })?; - let bridge = Self::bridge(locations.bridge_id()).ok_or(SendError::Unroutable)?; + + // check if we are able to route the message. We use existing `HaulBlobExporter` for that. + // It will make all required changes and will encode message properly, so that the + // `DispatchBlob` at the bridged bridge hub will be able to decode it + let ((blob, id), price) = PalletAsHaulBlobExporter::::validate( + network, + channel, + universal_source, + destination, + message, + )?; let bridge_message = MessagesPallet::::validate_message(bridge.lane_id, &blob) .map_err(|e| { @@ -190,8 +198,8 @@ where } } -impl, I: 'static> OnMessagesDelivered for Pallet { - fn on_messages_delivered(lane_id: LaneId, enqueued_messages: MessageNonce) { +impl, I: 'static> OnMessagesDelivered for Pallet { + fn on_messages_delivered(lane_id: T::LaneId, enqueued_messages: MessageNonce) { Self::on_bridge_messages_delivered(lane_id, enqueued_messages); } } @@ -265,7 +273,7 @@ impl, I: 'static> Pallet { } /// Must be called whenever we receive a message delivery confirmation. - fn on_bridge_messages_delivered(lane_id: LaneId, enqueued_messages: MessageNonce) { + fn on_bridge_messages_delivered(lane_id: T::LaneId, enqueued_messages: MessageNonce) { // if the bridge queue is still congested, we don't want to do anything let is_congested = enqueued_messages > OUTBOUND_LANE_UNCONGESTED_THRESHOLD; if is_congested { @@ -373,7 +381,7 @@ mod tests { BridgedUniversalDestination::get() } - fn open_lane() -> (BridgeLocations, LaneId) { + fn open_lane() -> (BridgeLocations, TestLaneIdType) { // open expected outbound lane let origin = OpenBridgeOrigin::sibling_parachain_origin(); let with = bridged_asset_hub_universal_location(); @@ -430,7 +438,7 @@ mod tests { (*locations, lane_id) } - fn open_lane_and_send_regular_message() -> (BridgeId, LaneId) { + fn open_lane_and_send_regular_message() -> (BridgeId, TestLaneIdType) { let (locations, lane_id) = open_lane(); // now let's try to enqueue message using our `ExportXcm` implementation @@ -466,7 +474,7 @@ mod tests { run_test(|| { let (bridge_id, _) = open_lane_and_send_regular_message(); assert!(!TestLocalXcmChannelManager::is_bridge_suspened()); - assert_eq!(XcmOverBridge::bridge(bridge_id).unwrap().state, BridgeState::Opened); + assert_eq!(XcmOverBridge::bridge(&bridge_id).unwrap().state, BridgeState::Opened); }); } @@ -495,11 +503,11 @@ mod tests { } assert!(!TestLocalXcmChannelManager::is_bridge_suspened()); - assert_eq!(XcmOverBridge::bridge(bridge_id).unwrap().state, BridgeState::Opened); + assert_eq!(XcmOverBridge::bridge(&bridge_id).unwrap().state, BridgeState::Opened); open_lane_and_send_regular_message(); assert!(TestLocalXcmChannelManager::is_bridge_suspened()); - assert_eq!(XcmOverBridge::bridge(bridge_id).unwrap().state, BridgeState::Suspended); + assert_eq!(XcmOverBridge::bridge(&bridge_id).unwrap().state, BridgeState::Suspended); }); } @@ -516,7 +524,7 @@ mod tests { ); assert!(!TestLocalXcmChannelManager::is_bridge_resumed()); - assert_eq!(XcmOverBridge::bridge(bridge_id).unwrap().state, BridgeState::Suspended); + assert_eq!(XcmOverBridge::bridge(&bridge_id).unwrap().state, BridgeState::Suspended); }); } @@ -530,7 +538,7 @@ mod tests { ); assert!(!TestLocalXcmChannelManager::is_bridge_resumed()); - assert_eq!(XcmOverBridge::bridge(bridge_id).unwrap().state, BridgeState::Opened); + assert_eq!(XcmOverBridge::bridge(&bridge_id).unwrap().state, BridgeState::Opened); }); } @@ -547,7 +555,7 @@ mod tests { ); assert!(TestLocalXcmChannelManager::is_bridge_resumed()); - assert_eq!(XcmOverBridge::bridge(bridge_id).unwrap().state, BridgeState::Opened); + assert_eq!(XcmOverBridge::bridge(&bridge_id).unwrap().state, BridgeState::Opened); }); } @@ -679,4 +687,97 @@ mod tests { ); }) } + + #[test] + fn validate_works() { + run_test(|| { + let xcm: Xcm<()> = vec![ClearOrigin].into(); + + // check that router does not consume when `NotApplicable` + let mut xcm_wrapper = Some(xcm.clone()); + let mut universal_source_wrapper = Some(universal_source()); + + // wrong `NetworkId` + let mut dest_wrapper = Some(bridged_relative_destination()); + assert_eq!( + XcmOverBridge::validate( + NetworkId::ByGenesis([0; 32]), + 0, + &mut universal_source_wrapper, + &mut dest_wrapper, + &mut xcm_wrapper, + ), + Err(SendError::NotApplicable), + ); + // dest and xcm is NOT consumed and untouched + assert_eq!(&Some(xcm.clone()), &xcm_wrapper); + assert_eq!(&Some(universal_source()), &universal_source_wrapper); + assert_eq!(&Some(bridged_relative_destination()), &dest_wrapper); + + // dest starts with wrong `NetworkId` + let mut invalid_dest_wrapper = Some( + [GlobalConsensus(NetworkId::ByGenesis([0; 32])), Parachain(BRIDGED_ASSET_HUB_ID)] + .into(), + ); + assert_eq!( + XcmOverBridge::validate( + BridgedRelayNetwork::get(), + 0, + &mut Some(universal_source()), + &mut invalid_dest_wrapper, + &mut xcm_wrapper, + ), + Err(SendError::NotApplicable), + ); + // dest and xcm is NOT consumed and untouched + assert_eq!(&Some(xcm.clone()), &xcm_wrapper); + assert_eq!(&Some(universal_source()), &universal_source_wrapper); + assert_eq!( + &Some( + [ + GlobalConsensus(NetworkId::ByGenesis([0; 32]),), + Parachain(BRIDGED_ASSET_HUB_ID) + ] + .into() + ), + &invalid_dest_wrapper + ); + + // no opened lane for dest + let mut dest_without_lane_wrapper = + Some([GlobalConsensus(BridgedRelayNetwork::get()), Parachain(5679)].into()); + assert_eq!( + XcmOverBridge::validate( + BridgedRelayNetwork::get(), + 0, + &mut Some(universal_source()), + &mut dest_without_lane_wrapper, + &mut xcm_wrapper, + ), + Err(SendError::NotApplicable), + ); + // dest and xcm is NOT consumed and untouched + assert_eq!(&Some(xcm.clone()), &xcm_wrapper); + assert_eq!(&Some(universal_source()), &universal_source_wrapper); + assert_eq!( + &Some([GlobalConsensus(BridgedRelayNetwork::get(),), Parachain(5679)].into()), + &dest_without_lane_wrapper + ); + + // ok + let _ = open_lane(); + let mut dest_wrapper = Some(bridged_relative_destination()); + assert_ok!(XcmOverBridge::validate( + BridgedRelayNetwork::get(), + 0, + &mut Some(universal_source()), + &mut dest_wrapper, + &mut xcm_wrapper, + )); + // dest and xcm IS consumed + assert_eq!(None, xcm_wrapper); + assert_eq!(&Some(universal_source()), &universal_source_wrapper); + assert_eq!(None, dest_wrapper); + }); + } } diff --git a/bridges/modules/xcm-bridge-hub/src/lib.rs b/bridges/modules/xcm-bridge-hub/src/lib.rs index 02d578386a7..22c60fb4ad6 100644 --- a/bridges/modules/xcm-bridge-hub/src/lib.rs +++ b/bridges/modules/xcm-bridge-hub/src/lib.rs @@ -143,7 +143,7 @@ #![warn(missing_docs)] #![cfg_attr(not(feature = "std"), no_std)] -use bp_messages::{LaneId, LaneState, MessageNonce}; +use bp_messages::{LaneState, MessageNonce}; use bp_runtime::{AccountIdOf, BalanceOf, RangeInclusiveExt}; pub use bp_xcm_bridge_hub::{Bridge, BridgeId, BridgeState}; use bp_xcm_bridge_hub::{BridgeLocations, BridgeLocationsError, LocalXcmChannelManager}; @@ -213,9 +213,8 @@ pub mod pallet { type DestinationVersion: GetVersion; /// The origin that is allowed to call privileged operations on the pallet, e.g. open/close - /// bridge for location that coresponds to `Self::BridgeOriginAccountIdConverter` and - /// `Self::BridgedNetwork`. - type AdminOrigin: EnsureOrigin<::RuntimeOrigin>; + /// bridge for locations. + type ForceOrigin: EnsureOrigin<::RuntimeOrigin>; /// A set of XCM locations within local consensus system that are allowed to open /// bridges with remote destinations. type OpenBridgeOrigin: EnsureOrigin< @@ -248,10 +247,13 @@ pub mod pallet { } /// An alias for the bridge metadata. - pub type BridgeOf = Bridge>; + pub type BridgeOf = Bridge, LaneIdOf>; /// An alias for this chain. pub type ThisChainOf = pallet_bridge_messages::ThisChainOf>::BridgeMessagesPalletInstance>; + /// An alias for lane identifier type. + pub type LaneIdOf = + >::BridgeMessagesPalletInstance>>::LaneId; /// An alias for the associated lanes manager. pub type LanesManagerOf = pallet_bridge_messages::LanesManager>::BridgeMessagesPalletInstance>; @@ -392,7 +394,7 @@ pub mod pallet { // deposit the `ClosingBridge` event Self::deposit_event(Event::::ClosingBridge { bridge_id: *locations.bridge_id(), - lane_id: bridge.lane_id, + lane_id: bridge.lane_id.into(), pruned_messages, enqueued_messages, }); @@ -439,7 +441,7 @@ pub mod pallet { // deposit the `BridgePruned` event Self::deposit_event(Event::::BridgePruned { bridge_id: *locations.bridge_id(), - lane_id: bridge.lane_id, + lane_id: bridge.lane_id.into(), bridge_deposit: released_deposit, pruned_messages, }); @@ -449,9 +451,10 @@ pub mod pallet { } impl, I: 'static> Pallet { - pub(crate) fn do_open_bridge( + /// Open bridge for lane. + pub fn do_open_bridge( locations: Box, - lane_id: LaneId, + lane_id: T::LaneId, create_lanes: bool, ) -> Result<(), DispatchError> { // reserve balance on the origin's sovereign account (if needed) @@ -542,7 +545,7 @@ pub mod pallet { remote_endpoint: Box::new( locations.bridge_destination_universal_location().clone(), ), - lane_id, + lane_id: lane_id.into(), }); Ok(()) @@ -585,10 +588,15 @@ pub mod pallet { }) } + /// Return bridge metadata by bridge_id + pub fn bridge(bridge_id: &BridgeId) -> Option> { + Bridges::::get(bridge_id) + } + /// Return bridge metadata by lane_id - pub fn bridge_by_lane_id(lane_id: &LaneId) -> Option<(BridgeId, BridgeOf)> { + pub fn bridge_by_lane_id(lane_id: &T::LaneId) -> Option<(BridgeId, BridgeOf)> { LaneToBridge::::get(lane_id) - .and_then(|bridge_id| Self::bridge(bridge_id).map(|bridge| (bridge_id, bridge))) + .and_then(|bridge_id| Self::bridge(&bridge_id).map(|bridge| (bridge_id, bridge))) } } @@ -634,7 +642,7 @@ pub mod pallet { pub fn do_try_state_for_bridge( bridge_id: BridgeId, bridge: BridgeOf, - ) -> Result { + ) -> Result { log::info!(target: LOG_TARGET, "Checking `do_try_state_for_bridge` for bridge_id: {bridge_id:?} and bridge: {bridge:?}"); // check `BridgeId` points to the same `LaneId` and vice versa. @@ -707,13 +715,12 @@ pub mod pallet { /// All registered bridges. #[pallet::storage] - #[pallet::getter(fn bridge)] pub type Bridges, I: 'static = ()> = StorageMap<_, Identity, BridgeId, BridgeOf>; /// All registered `lane_id` and `bridge_id` mappings. #[pallet::storage] pub type LaneToBridge, I: 'static = ()> = - StorageMap<_, Identity, LaneId, BridgeId>; + StorageMap<_, Identity, T::LaneId, BridgeId>; #[pallet::genesis_config] #[derive(DefaultNoBound)] @@ -723,7 +730,7 @@ pub mod pallet { /// Keep in mind that we are **NOT** reserving any amount for the bridges opened at /// genesis. We are **NOT** opening lanes, used by this bridge. It all must be done using /// other pallets genesis configuration or some other means. - pub opened_bridges: Vec<(Location, InteriorLocation)>, + pub opened_bridges: Vec<(Location, InteriorLocation, Option)>, /// Dummy marker. #[serde(skip)] pub _phantom: sp_std::marker::PhantomData<(T, I)>, @@ -735,48 +742,26 @@ pub mod pallet { T: frame_system::Config>>, { fn build(&self) { - for (bridge_origin_relative_location, bridge_destination_universal_location) in - &self.opened_bridges + for ( + bridge_origin_relative_location, + bridge_destination_universal_location, + maybe_lane_id, + ) in &self.opened_bridges { let locations = Pallet::::bridge_locations( bridge_origin_relative_location.clone(), bridge_destination_universal_location.clone().into(), ) .expect("Invalid genesis configuration"); - let lane_id = - locations.calculate_lane_id(xcm::latest::VERSION).expect("Valid locations"); - let bridge_owner_account = T::BridgeOriginAccountIdConverter::convert_location( - locations.bridge_origin_relative_location(), - ) - .expect("Invalid genesis configuration"); - Bridges::::insert( - locations.bridge_id(), - Bridge { - bridge_origin_relative_location: Box::new( - locations.bridge_origin_relative_location().clone().into(), - ), - bridge_origin_universal_location: Box::new( - locations.bridge_origin_universal_location().clone().into(), - ), - bridge_destination_universal_location: Box::new( - locations.bridge_destination_universal_location().clone().into(), - ), - state: BridgeState::Opened, - bridge_owner_account, - deposit: Zero::zero(), - lane_id, - }, - ); - LaneToBridge::::insert(lane_id, locations.bridge_id()); + let lane_id = match maybe_lane_id { + Some(lane_id) => *lane_id, + None => + locations.calculate_lane_id(xcm::latest::VERSION).expect("Valid locations"), + }; - let lanes_manager = LanesManagerOf::::new(); - lanes_manager - .create_inbound_lane(lane_id) - .expect("Invalid genesis configuration"); - lanes_manager - .create_outbound_lane(lane_id) - .expect("Invalid genesis configuration"); + Pallet::::do_open_bridge(locations, lane_id, true) + .expect("Valid opened bridge!"); } } } @@ -796,14 +781,14 @@ pub mod pallet { /// Universal location of remote bridge endpoint. remote_endpoint: Box, /// Lane identifier. - lane_id: LaneId, + lane_id: T::LaneId, }, /// Bridge is going to be closed, but not yet fully pruned from the runtime storage. ClosingBridge { /// Bridge identifier. bridge_id: BridgeId, /// Lane identifier. - lane_id: LaneId, + lane_id: T::LaneId, /// Number of pruned messages during the close call. pruned_messages: MessageNonce, /// Number of enqueued messages that need to be pruned in follow up calls. @@ -815,7 +800,7 @@ pub mod pallet { /// Bridge identifier. bridge_id: BridgeId, /// Lane identifier. - lane_id: LaneId, + lane_id: T::LaneId, /// Amount of deposit released. bridge_deposit: BalanceOf>, /// Number of pruned messages during the close call. @@ -849,12 +834,11 @@ pub mod pallet { #[cfg(test)] mod tests { use super::*; + use bp_messages::LaneIdType; use mock::*; - use bp_messages::LaneId; use frame_support::{assert_err, assert_noop, assert_ok, traits::fungible::Mutate, BoundedVec}; use frame_system::{EventRecord, Phase}; - use sp_core::H256; use sp_runtime::TryRuntimeError; fn fund_origin_sovereign_account(locations: &BridgeLocations, balance: Balance) -> AccountId { @@ -911,7 +895,7 @@ mod tests { mock_open_bridge_from_with(origin, deposit, bridged_asset_hub_universal_location()) } - fn enqueue_message(lane: LaneId) { + fn enqueue_message(lane: TestLaneIdType) { let lanes_manager = LanesManagerOf::::new(); lanes_manager .active_outbound_lane(lane) @@ -1212,7 +1196,7 @@ mod tests { remote_endpoint: Box::new( locations.bridge_destination_universal_location().clone() ), - lane_id + lane_id: lane_id.into() }), topics: vec![], }), @@ -1355,7 +1339,7 @@ mod tests { phase: Phase::Initialization, event: RuntimeEvent::XcmOverBridge(Event::ClosingBridge { bridge_id: *locations.bridge_id(), - lane_id: bridge.lane_id, + lane_id: bridge.lane_id.into(), pruned_messages: 16, enqueued_messages: 16, }), @@ -1403,7 +1387,7 @@ mod tests { phase: Phase::Initialization, event: RuntimeEvent::XcmOverBridge(Event::ClosingBridge { bridge_id: *locations.bridge_id(), - lane_id: bridge.lane_id, + lane_id: bridge.lane_id.into(), pruned_messages: 8, enqueued_messages: 8, }), @@ -1444,7 +1428,7 @@ mod tests { phase: Phase::Initialization, event: RuntimeEvent::XcmOverBridge(Event::BridgePruned { bridge_id: *locations.bridge_id(), - lane_id: bridge.lane_id, + lane_id: bridge.lane_id.into(), bridge_deposit: expected_deposit, pruned_messages: 8, }), @@ -1456,8 +1440,6 @@ mod tests { #[test] fn do_try_state_works() { - use sp_runtime::Either; - let bridge_origin_relative_location = SiblingLocation::get(); let bridge_origin_universal_location = SiblingUniversalLocation::get(); let bridge_destination_universal_location = BridgedUniversalDestination::get(); @@ -1471,8 +1453,8 @@ mod tests { &bridge_destination_universal_location, ); let bridge_id_mismatch = BridgeId::new(&InteriorLocation::Here, &InteriorLocation::Here); - let lane_id = LaneId::from_inner(Either::Left(H256::default())); - let lane_id_mismatch = LaneId::from_inner(Either::Left(H256::from([1u8; 32]))); + let lane_id = TestLaneIdType::try_new(1, 2).unwrap(); + let lane_id_mismatch = TestLaneIdType::try_new(3, 4).unwrap(); let test_bridge_state = |id, bridge, diff --git a/bridges/modules/xcm-bridge-hub/src/migration.rs b/bridges/modules/xcm-bridge-hub/src/migration.rs index c9d8b67176a..ffd5233a917 100644 --- a/bridges/modules/xcm-bridge-hub/src/migration.rs +++ b/bridges/modules/xcm-bridge-hub/src/migration.rs @@ -17,7 +17,6 @@ //! A module that is responsible for migration of storage. use crate::{Config, Pallet, LOG_TARGET}; -use bp_messages::LaneId; use frame_support::{ traits::{Get, OnRuntimeUpgrade, StorageVersion}, weights::Weight, @@ -52,7 +51,7 @@ pub struct OpenBridgeForLane< impl< T: Config, I: 'static, - Lane: Get, + Lane: Get, CreateLane: Get, SourceRelativeLocation: Get, BridgedUniversalLocation: Get, diff --git a/bridges/modules/xcm-bridge-hub/src/mock.rs b/bridges/modules/xcm-bridge-hub/src/mock.rs index aff3526b558..6511b9fc5b0 100644 --- a/bridges/modules/xcm-bridge-hub/src/mock.rs +++ b/bridges/modules/xcm-bridge-hub/src/mock.rs @@ -20,7 +20,7 @@ use crate as pallet_xcm_bridge_hub; use bp_messages::{ target_chain::{DispatchMessage, MessageDispatch}, - ChainWithMessages, LaneId, MessageNonce, + ChainWithMessages, HashedLaneId, MessageNonce, }; use bp_runtime::{messages::MessageDispatchResult, Chain, ChainId, HashOf}; use bp_xcm_bridge_hub::{BridgeId, LocalXcmChannelManager}; @@ -50,6 +50,9 @@ pub type AccountId = AccountId32; pub type Balance = u64; type Block = frame_system::mocking::MockBlock; +/// Lane identifier type used for tests. +pub type TestLaneIdType = HashedLaneId; + pub const SIBLING_ASSET_HUB_ID: u32 = 2001; pub const THIS_BRIDGE_HUB_ID: u32 = 2002; pub const BRIDGED_ASSET_HUB_ID: u32 = 1001; @@ -92,6 +95,7 @@ impl pallet_bridge_messages::Config for TestRuntime { type OutboundPayload = Vec; type InboundPayload = Vec; + type LaneId = TestLaneIdType; type DeliveryPayments = (); type DeliveryConfirmationPayments = (); @@ -152,7 +156,7 @@ parameter_types! { pub SiblingLocation: Location = Location::new(1, [Parachain(SIBLING_ASSET_HUB_ID)]); pub SiblingUniversalLocation: InteriorLocation = [GlobalConsensus(RelayNetwork::get()), Parachain(SIBLING_ASSET_HUB_ID)].into(); - pub const BridgedRelayNetwork: NetworkId = NetworkId::Polkadot; + pub const BridgedRelayNetwork: NetworkId = NetworkId::ByGenesis([1; 32]); pub BridgedRelayNetworkLocation: Location = (Parent, GlobalConsensus(BridgedRelayNetwork::get())).into(); pub BridgedRelativeDestination: InteriorLocation = [Parachain(BRIDGED_ASSET_HUB_ID)].into(); pub BridgedUniversalDestination: InteriorLocation = [GlobalConsensus(BridgedRelayNetwork::get()), Parachain(BRIDGED_ASSET_HUB_ID)].into(); @@ -190,7 +194,7 @@ impl pallet_xcm_bridge_hub::Config for TestRuntime { type MessageExportPrice = (); type DestinationVersion = AlwaysLatest; - type AdminOrigin = frame_system::EnsureNever<()>; + type ForceOrigin = frame_system::EnsureNever<()>; type OpenBridgeOrigin = OpenBridgeOrigin; type BridgeOriginAccountIdConverter = LocationToAccountId; @@ -523,7 +527,7 @@ impl bp_header_chain::HeaderChain for BridgedHeaderChain pub struct TestMessageDispatch; impl TestMessageDispatch { - pub fn deactivate(lane: LaneId) { + pub fn deactivate(lane: TestLaneIdType) { frame_support::storage::unhashed::put(&(b"inactive", lane).encode()[..], &false); } } @@ -531,18 +535,21 @@ impl TestMessageDispatch { impl MessageDispatch for TestMessageDispatch { type DispatchPayload = Vec; type DispatchLevelResult = (); + type LaneId = TestLaneIdType; - fn is_active(lane: LaneId) -> bool { + fn is_active(lane: Self::LaneId) -> bool { frame_support::storage::unhashed::take::(&(b"inactive", lane).encode()[..]) != Some(false) } - fn dispatch_weight(_message: &mut DispatchMessage) -> Weight { + fn dispatch_weight( + _message: &mut DispatchMessage, + ) -> Weight { Weight::zero() } fn dispatch( - _: DispatchMessage, + _: DispatchMessage, ) -> MessageDispatchResult { MessageDispatchResult { unspent_weight: Weight::zero(), dispatch_level_result: () } } diff --git a/bridges/primitives/messages/src/call_info.rs b/bridges/primitives/messages/src/call_info.rs index c8f06ed8cb7..dfd076f029b 100644 --- a/bridges/primitives/messages/src/call_info.rs +++ b/bridges/primitives/messages/src/call_info.rs @@ -16,22 +16,14 @@ //! Defines structures related to calls of the `pallet-bridge-messages` pallet. -use crate::{source_chain, target_chain, LaneId, MessageNonce, UnrewardedRelayersState}; +use crate::{MessageNonce, UnrewardedRelayersState}; -use bp_runtime::{AccountIdOf, HashOf}; use codec::{Decode, Encode}; use frame_support::weights::Weight; use scale_info::TypeInfo; use sp_core::RuntimeDebug; use sp_std::ops::RangeInclusive; -/// The `BridgeMessagesCall` used to bridge with a given chain. -pub type BridgeMessagesCallOf = BridgeMessagesCall< - AccountIdOf, - target_chain::FromBridgedChainMessagesProof>, - source_chain::FromBridgedChainMessagesDeliveryProof>, ->; - /// A minimized version of `pallet-bridge-messages::Call` that can be used without a runtime. #[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)] #[allow(non_camel_case_types)] @@ -60,7 +52,7 @@ pub enum BridgeMessagesCall { /// Generic info about a messages delivery/confirmation proof. #[derive(PartialEq, RuntimeDebug)] -pub struct BaseMessagesProofInfo { +pub struct BaseMessagesProofInfo { /// Message lane, used by the call. pub lane_id: LaneId, /// Nonces of messages, included in the call. @@ -75,7 +67,7 @@ pub struct BaseMessagesProofInfo { pub best_stored_nonce: MessageNonce, } -impl BaseMessagesProofInfo { +impl BaseMessagesProofInfo { /// Returns true if `bundled_range` continues the `0..=best_stored_nonce` range. pub fn appends_to_stored_nonce(&self) -> bool { Some(*self.bundled_range.start()) == self.best_stored_nonce.checked_add(1) @@ -94,14 +86,14 @@ pub struct UnrewardedRelayerOccupation { /// Info about a `ReceiveMessagesProof` call which tries to update a single lane. #[derive(PartialEq, RuntimeDebug)] -pub struct ReceiveMessagesProofInfo { +pub struct ReceiveMessagesProofInfo { /// Base messages proof info - pub base: BaseMessagesProofInfo, + pub base: BaseMessagesProofInfo, /// State of unrewarded relayers vector. pub unrewarded_relayers: UnrewardedRelayerOccupation, } -impl ReceiveMessagesProofInfo { +impl ReceiveMessagesProofInfo { /// Returns true if: /// /// - either inbound lane is ready to accept bundled messages; @@ -134,9 +126,9 @@ impl ReceiveMessagesProofInfo { /// Info about a `ReceiveMessagesDeliveryProof` call which tries to update a single lane. #[derive(PartialEq, RuntimeDebug)] -pub struct ReceiveMessagesDeliveryProofInfo(pub BaseMessagesProofInfo); +pub struct ReceiveMessagesDeliveryProofInfo(pub BaseMessagesProofInfo); -impl ReceiveMessagesDeliveryProofInfo { +impl ReceiveMessagesDeliveryProofInfo { /// Returns true if outbound lane is ready to accept confirmations of bundled messages. pub fn is_obsolete(&self) -> bool { self.0.bundled_range.is_empty() || !self.0.appends_to_stored_nonce() @@ -146,14 +138,14 @@ impl ReceiveMessagesDeliveryProofInfo { /// Info about a `ReceiveMessagesProof` or a `ReceiveMessagesDeliveryProof` call /// which tries to update a single lane. #[derive(PartialEq, RuntimeDebug)] -pub enum MessagesCallInfo { +pub enum MessagesCallInfo { /// Messages delivery call info. - ReceiveMessagesProof(ReceiveMessagesProofInfo), + ReceiveMessagesProof(ReceiveMessagesProofInfo), /// Messages delivery confirmation call info. - ReceiveMessagesDeliveryProof(ReceiveMessagesDeliveryProofInfo), + ReceiveMessagesDeliveryProof(ReceiveMessagesDeliveryProofInfo), } -impl MessagesCallInfo { +impl MessagesCallInfo { /// Returns lane, used by the call. pub fn lane_id(&self) -> LaneId { match *self { diff --git a/bridges/primitives/messages/src/lane.rs b/bridges/primitives/messages/src/lane.rs index 6d4ca402eb3..c835449db27 100644 --- a/bridges/primitives/messages/src/lane.rs +++ b/bridges/primitives/messages/src/lane.rs @@ -16,12 +16,88 @@ //! Primitives of messages module, that represents lane id. -use codec::{Decode, Encode, Error as CodecError, Input, MaxEncodedLen}; -use frame_support::sp_runtime::Either; +use codec::{Codec, Decode, Encode, EncodeLike, MaxEncodedLen}; use scale_info::TypeInfo; -use serde::{Deserialize, Serialize}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use sp_core::{RuntimeDebug, TypeId, H256}; use sp_io::hashing::blake2_256; +use sp_std::fmt::Debug; + +/// Trait representing a generic `LaneId` type. +pub trait LaneIdType: + Clone + + Copy + + Codec + + EncodeLike + + Debug + + Default + + PartialEq + + Eq + + Ord + + TypeInfo + + MaxEncodedLen + + Serialize + + DeserializeOwned +{ + /// Creates a new `LaneId` type (if supported). + fn try_new(endpoint1: E, endpoint2: E) -> Result; +} + +/// Bridge lane identifier (legacy). +/// +/// Note: For backwards compatibility reasons, we also handle the older format `[u8; 4]`. +#[derive( + Clone, + Copy, + Decode, + Default, + Encode, + Eq, + Ord, + PartialOrd, + PartialEq, + TypeInfo, + MaxEncodedLen, + Serialize, + Deserialize, +)] +pub struct LegacyLaneId(pub [u8; 4]); + +impl LaneIdType for LegacyLaneId { + /// Create lane identifier from two locations. + fn try_new(_endpoint1: T, _endpoint2: T) -> Result { + // we don't support this for `LegacyLaneId`, because it was hard-coded before + Err(()) + } +} + +#[cfg(any(feature = "std", feature = "runtime-benchmarks", test))] +impl TryFrom> for LegacyLaneId { + type Error = (); + + fn try_from(value: Vec) -> Result { + if value.len() == 4 { + return <[u8; 4]>::try_from(value).map(Self).map_err(|_| ()); + } + Err(()) + } +} + +impl core::fmt::Debug for LegacyLaneId { + fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result { + self.0.fmt(fmt) + } +} + +impl AsRef<[u8]> for LegacyLaneId { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +impl TypeId for LegacyLaneId { + const TYPE_ID: [u8; 4] = *b"blan"; +} /// Bridge lane identifier. /// @@ -41,12 +117,11 @@ use sp_io::hashing::blake2_256; /// (endpoint2, VALUES_SEPARATOR, endpoint1) /// }.using_encoded(blake2_256); /// ``` -/// -/// Note: For backwards compatibility reasons, we also handle the older format `[u8; 4]`. #[derive( Clone, Copy, Decode, + Default, Encode, Eq, Ord, @@ -57,115 +132,67 @@ use sp_io::hashing::blake2_256; Serialize, Deserialize, )] -pub struct LaneId(InnerLaneId); - -impl LaneId { - /// Create lane identifier from two locations. - pub fn new(endpoint1: T, endpoint2: T) -> Self { - const VALUES_SEPARATOR: [u8; 31] = *b"bridges-lane-id-value-separator"; - - LaneId(InnerLaneId::Hash( - if endpoint1 < endpoint2 { - (endpoint1, VALUES_SEPARATOR, endpoint2) - } else { - (endpoint2, VALUES_SEPARATOR, endpoint1) - } - .using_encoded(blake2_256) - .into(), - )) - } +pub struct HashedLaneId(H256); +impl HashedLaneId { /// Create lane identifier from given hash. /// /// There's no `From` implementation for the `LaneId`, because using this conversion /// in a wrong way (i.e. computing hash of endpoints manually) may lead to issues. So we /// want the call to be explicit. - pub const fn from_inner(inner: Either) -> Self { - LaneId(match inner { - Either::Left(hash) => InnerLaneId::Hash(hash), - Either::Right(array) => InnerLaneId::Array(array), - }) + #[cfg(any(feature = "std", feature = "runtime-benchmarks", test))] + pub const fn from_inner(inner: H256) -> Self { + Self(inner) + } + + /// Access the inner lane representation. + pub fn inner(&self) -> &H256 { + &self.0 } } -impl core::fmt::Display for LaneId { +impl core::fmt::Display for HashedLaneId { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { core::fmt::Display::fmt(&self.0, f) } } -impl core::fmt::Debug for LaneId { +impl core::fmt::Debug for HashedLaneId { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { core::fmt::Debug::fmt(&self.0, f) } } -impl AsRef<[u8]> for LaneId { - fn as_ref(&self) -> &[u8] { - self.0.as_ref() - } +impl TypeId for HashedLaneId { + const TYPE_ID: [u8; 4] = *b"hlan"; } -impl TypeId for LaneId { - const TYPE_ID: [u8; 4] = *b"blan"; -} - -#[derive( - Clone, Copy, Eq, Ord, PartialOrd, PartialEq, TypeInfo, MaxEncodedLen, Serialize, Deserialize, -)] -enum InnerLaneId { - /// Old format (for backwards compatibility). - Array([u8; 4]), - /// New format 32-byte hash generated by `blake2_256`. - Hash(H256), -} - -impl Encode for InnerLaneId { - fn encode(&self) -> sp_std::vec::Vec { - match self { - InnerLaneId::Array(array) => array.encode(), - InnerLaneId::Hash(hash) => hash.encode(), - } - } -} - -impl Decode for InnerLaneId { - fn decode(input: &mut I) -> Result { - // check backwards compatibly first - if input.remaining_len() == Ok(Some(4)) { - let array: [u8; 4] = Decode::decode(input)?; - return Ok(InnerLaneId::Array(array)) - } - - // else check new format - H256::decode(input).map(InnerLaneId::Hash) - } -} +impl LaneIdType for HashedLaneId { + /// Create lane identifier from two locations. + fn try_new(endpoint1: T, endpoint2: T) -> Result { + const VALUES_SEPARATOR: [u8; 31] = *b"bridges-lane-id-value-separator"; -impl core::fmt::Display for InnerLaneId { - fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { - match self { - InnerLaneId::Array(array) => write!(f, "Array({:?})", array), - InnerLaneId::Hash(hash) => write!(f, "Hash({:?})", hash), - } + Ok(Self( + if endpoint1 < endpoint2 { + (endpoint1, VALUES_SEPARATOR, endpoint2) + } else { + (endpoint2, VALUES_SEPARATOR, endpoint1) + } + .using_encoded(blake2_256) + .into(), + )) } } -impl core::fmt::Debug for InnerLaneId { - fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result { - match self { - InnerLaneId::Array(array) => array.fmt(fmt), - InnerLaneId::Hash(hash) => hash.fmt(fmt), - } - } -} +#[cfg(any(feature = "std", feature = "runtime-benchmarks", test))] +impl TryFrom> for HashedLaneId { + type Error = (); -impl AsRef<[u8]> for InnerLaneId { - fn as_ref(&self) -> &[u8] { - match self { - InnerLaneId::Array(array) => array.as_ref(), - InnerLaneId::Hash(hash) => hash.as_ref(), + fn try_from(value: Vec) -> Result { + if value.len() == 32 { + return <[u8; 32]>::try_from(value).map(|v| Self(H256::from(v))).map_err(|_| ()); } + Err(()) } } @@ -194,63 +221,89 @@ impl LaneState { #[cfg(test)] mod tests { use super::*; + use crate::MessageNonce; #[test] fn lane_id_debug_format_matches_inner_hash_format() { assert_eq!( - format!("{:?}", LaneId(InnerLaneId::Hash(H256::from([1u8; 32])))), + format!("{:?}", HashedLaneId(H256::from([1u8; 32]))), format!("{:?}", H256::from([1u8; 32])), ); - assert_eq!( - format!("{:?}", LaneId(InnerLaneId::Array([0, 0, 0, 1]))), - format!("{:?}", [0, 0, 0, 1]), - ); + assert_eq!(format!("{:?}", LegacyLaneId([0, 0, 0, 1])), format!("{:?}", [0, 0, 0, 1]),); } #[test] - fn lane_id_as_ref_works() { + fn hashed_encode_decode_works() { + // simple encode/decode - new format + let lane_id = HashedLaneId(H256::from([1u8; 32])); + let encoded_lane_id = lane_id.encode(); + let decoded_lane_id = HashedLaneId::decode(&mut &encoded_lane_id[..]).expect("decodable"); + assert_eq!(lane_id, decoded_lane_id); assert_eq!( "0101010101010101010101010101010101010101010101010101010101010101", - hex::encode(LaneId(InnerLaneId::Hash(H256::from([1u8; 32]))).as_ref()), + hex::encode(encoded_lane_id) ); - assert_eq!("00000001", hex::encode(LaneId(InnerLaneId::Array([0, 0, 0, 1])).as_ref()),); } #[test] - fn lane_id_encode_decode_works() { - let test_encode_decode = |expected_hex, lane_id: LaneId| { - let enc = lane_id.encode(); - let decoded_lane_id = LaneId::decode(&mut &enc[..]).expect("decodable"); - assert_eq!(lane_id, decoded_lane_id); - - assert_eq!(expected_hex, hex::encode(lane_id.as_ref()),); - assert_eq!(expected_hex, hex::encode(decoded_lane_id.as_ref()),); - - let hex_bytes = hex::decode(expected_hex).expect("valid hex"); - let hex_decoded_lane_id = LaneId::decode(&mut &hex_bytes[..]).expect("decodable"); - assert_eq!(hex_decoded_lane_id, lane_id); - assert_eq!(hex_decoded_lane_id, decoded_lane_id); - }; - - test_encode_decode( - "0101010101010101010101010101010101010101010101010101010101010101", - LaneId(InnerLaneId::Hash(H256::from([1u8; 32]))), - ); - test_encode_decode("00000001", LaneId(InnerLaneId::Array([0, 0, 0, 1]))); + fn legacy_encode_decode_works() { + // simple encode/decode - old format + let lane_id = LegacyLaneId([0, 0, 0, 1]); + let encoded_lane_id = lane_id.encode(); + let decoded_lane_id = LegacyLaneId::decode(&mut &encoded_lane_id[..]).expect("decodable"); + assert_eq!(lane_id, decoded_lane_id); + assert_eq!("00000001", hex::encode(encoded_lane_id)); + + // decode sample + let bytes = vec![0, 0, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0]; + let (lane, nonce_start, nonce_end): (LegacyLaneId, MessageNonce, MessageNonce) = + Decode::decode(&mut &bytes[..]).unwrap(); + assert_eq!(lane, LegacyLaneId([0, 0, 0, 2])); + assert_eq!(nonce_start, 1); + assert_eq!(nonce_end, 1); + + // run encode/decode for `LaneId` with different positions + let expected_lane = LegacyLaneId([0, 0, 0, 1]); + let expected_nonce_start = 1088_u64; + let expected_nonce_end = 9185_u64; + + // decode: LaneId,Nonce,Nonce + let bytes = (expected_lane, expected_nonce_start, expected_nonce_end).encode(); + let (lane, nonce_start, nonce_end): (LegacyLaneId, MessageNonce, MessageNonce) = + Decode::decode(&mut &bytes[..]).unwrap(); + assert_eq!(lane, expected_lane); + assert_eq!(nonce_start, expected_nonce_start); + assert_eq!(nonce_end, expected_nonce_end); + + // decode: Nonce,LaneId,Nonce + let bytes = (expected_nonce_start, expected_lane, expected_nonce_end).encode(); + let (nonce_start, lane, nonce_end): (MessageNonce, LegacyLaneId, MessageNonce) = + Decode::decode(&mut &bytes[..]).unwrap(); + assert_eq!(lane, expected_lane); + assert_eq!(nonce_start, expected_nonce_start); + assert_eq!(nonce_end, expected_nonce_end); + + // decode: Nonce,Nonce,LaneId + let bytes = (expected_nonce_start, expected_nonce_end, expected_lane).encode(); + let (nonce_start, nonce_end, lane): (MessageNonce, MessageNonce, LegacyLaneId) = + Decode::decode(&mut &bytes[..]).unwrap(); + assert_eq!(lane, expected_lane); + assert_eq!(nonce_start, expected_nonce_start); + assert_eq!(nonce_end, expected_nonce_end); } #[test] - fn lane_id_is_generated_using_ordered_endpoints() { - assert_eq!(LaneId::new(1, 2), LaneId::new(2, 1)); + fn hashed_lane_id_is_generated_using_ordered_endpoints() { + assert_eq!(HashedLaneId::try_new(1, 2).unwrap(), HashedLaneId::try_new(2, 1).unwrap()); } #[test] - fn lane_id_is_different_for_different_endpoints() { - assert_ne!(LaneId::new(1, 2), LaneId::new(1, 3)); + fn hashed_lane_id_is_different_for_different_endpoints() { + assert_ne!(HashedLaneId::try_new(1, 2).unwrap(), HashedLaneId::try_new(1, 3).unwrap()); } #[test] - fn lane_id_is_different_even_if_arguments_has_partial_matching_encoding() { + fn hashed_lane_id_is_different_even_if_arguments_has_partial_matching_encoding() { /// Some artificial type that generates the same encoding for different values /// concatenations. I.e. the encoding for `(Either::Two(1, 2), Either::Two(3, 4))` /// is the same as encoding of `(Either::Three(1, 2, 3), Either::One(4))`. @@ -274,8 +327,8 @@ mod tests { } assert_ne!( - LaneId::new(Either::Two(1, 2), Either::Two(3, 4)), - LaneId::new(Either::Three(1, 2, 3), Either::One(4)), + HashedLaneId::try_new(Either::Two(1, 2), Either::Two(3, 4)).unwrap(), + HashedLaneId::try_new(Either::Three(1, 2, 3), Either::One(4)).unwrap(), ); } } diff --git a/bridges/primitives/messages/src/lib.rs b/bridges/primitives/messages/src/lib.rs index 7eb0c562939..2776b806cc1 100644 --- a/bridges/primitives/messages/src/lib.rs +++ b/bridges/primitives/messages/src/lib.rs @@ -35,10 +35,10 @@ use sp_core::RuntimeDebug; use sp_std::{collections::vec_deque::VecDeque, ops::RangeInclusive, prelude::*}; pub use call_info::{ - BaseMessagesProofInfo, BridgeMessagesCall, BridgeMessagesCallOf, MessagesCallInfo, - ReceiveMessagesDeliveryProofInfo, ReceiveMessagesProofInfo, UnrewardedRelayerOccupation, + BaseMessagesProofInfo, BridgeMessagesCall, MessagesCallInfo, ReceiveMessagesDeliveryProofInfo, + ReceiveMessagesProofInfo, UnrewardedRelayerOccupation, }; -pub use lane::{LaneId, LaneState}; +pub use lane::{HashedLaneId, LaneIdType, LaneState, LegacyLaneId}; mod call_info; mod lane; @@ -181,7 +181,7 @@ pub type MessagePayload = Vec; /// Message key (unique message identifier) as it is stored in the storage. #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] -pub struct MessageKey { +pub struct MessageKey { /// ID of the message lane. pub lane_id: LaneId, /// Message nonce. @@ -190,9 +190,9 @@ pub struct MessageKey { /// Message as it is stored in the storage. #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] -pub struct Message { +pub struct Message { /// Message key. - pub key: MessageKey, + pub key: MessageKey, /// Message payload. pub payload: MessagePayload, } @@ -200,11 +200,6 @@ pub struct Message { /// Inbound lane data. #[derive(Encode, Decode, Clone, RuntimeDebug, PartialEq, Eq, TypeInfo)] pub struct InboundLaneData { - /// Inbound lane state. - /// - /// If state is `Closed`, then all attempts to deliver messages to this end will fail. - pub state: LaneState, - /// Identifiers of relayers and messages that they have delivered to this lane (ordered by /// message nonce). /// @@ -233,6 +228,11 @@ pub struct InboundLaneData { /// This value is updated indirectly when an `OutboundLane` state of the source /// chain is received alongside with new messages delivery. pub last_confirmed_nonce: MessageNonce, + + /// Inbound lane state. + /// + /// If state is `Closed`, then all attempts to deliver messages to this end will fail. + pub state: LaneState, } impl Default for InboundLaneData { @@ -337,20 +337,20 @@ pub struct UnrewardedRelayer { /// Received messages with their dispatch result. #[derive(Clone, Encode, Decode, RuntimeDebug, PartialEq, Eq, TypeInfo)] -pub struct ReceivedMessages { +pub struct ReceivedMessages { /// Id of the lane which is receiving messages. pub lane: LaneId, /// Result of messages which we tried to dispatch pub receive_results: Vec<(MessageNonce, ReceptionResult)>, } -impl ReceivedMessages { +impl ReceivedMessages { /// Creates new `ReceivedMessages` structure from given results. pub fn new( lane: LaneId, receive_results: Vec<(MessageNonce, ReceptionResult)>, ) -> Self { - ReceivedMessages { lane, receive_results } + ReceivedMessages { lane: lane.into(), receive_results } } /// Push `result` of the `message` delivery onto `receive_results` vector. @@ -449,10 +449,6 @@ impl From<&InboundLaneData> for UnrewardedRelayersState { /// Outbound lane data. #[derive(Encode, Decode, Clone, RuntimeDebug, PartialEq, Eq, TypeInfo, MaxEncodedLen)] pub struct OutboundLaneData { - /// Lane state. - /// - /// If state is `Closed`, then all attempts to send messages messages at this end will fail. - pub state: LaneState, /// Nonce of the oldest message that we haven't yet pruned. May point to not-yet-generated /// message if all sent messages are already pruned. pub oldest_unpruned_nonce: MessageNonce, @@ -460,6 +456,10 @@ pub struct OutboundLaneData { pub latest_received_nonce: MessageNonce, /// Nonce of the latest message, generated by us. pub latest_generated_nonce: MessageNonce, + /// Lane state. + /// + /// If state is `Closed`, then all attempts to send messages at this end will fail. + pub state: LaneState, } impl OutboundLaneData { diff --git a/bridges/primitives/messages/src/source_chain.rs b/bridges/primitives/messages/src/source_chain.rs index 64f015bdb82..1d4a513035c 100644 --- a/bridges/primitives/messages/src/source_chain.rs +++ b/bridges/primitives/messages/src/source_chain.rs @@ -16,7 +16,7 @@ //! Primitives of messages module, that are used on the source chain. -use crate::{LaneId, MessageNonce, UnrewardedRelayer}; +use crate::{MessageNonce, UnrewardedRelayer}; use bp_runtime::{raw_storage_proof_size, RawStorageProof, Size}; use codec::{Decode, Encode}; @@ -39,7 +39,7 @@ use sp_std::{ /// /// - lane id. #[derive(Clone, Decode, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo)] -pub struct FromBridgedChainMessagesDeliveryProof { +pub struct FromBridgedChainMessagesDeliveryProof { /// Hash of the bridge header the proof is for. pub bridged_header_hash: BridgedHeaderHash, /// Storage trie proof generated for [`Self::bridged_header_hash`]. @@ -48,7 +48,9 @@ pub struct FromBridgedChainMessagesDeliveryProof { pub lane: LaneId, } -impl Size for FromBridgedChainMessagesDeliveryProof { +impl Size + for FromBridgedChainMessagesDeliveryProof +{ fn size(&self) -> u32 { use frame_support::sp_runtime::SaturatedConversion; raw_storage_proof_size(&self.storage_proof).saturated_into() @@ -60,7 +62,7 @@ pub type RelayersRewards = BTreeMap; /// Manages payments that are happening at the source chain during delivery confirmation /// transaction. -pub trait DeliveryConfirmationPayments { +pub trait DeliveryConfirmationPayments { /// Error type. type Error: Debug + Into<&'static str>; @@ -78,7 +80,7 @@ pub trait DeliveryConfirmationPayments { ) -> MessageNonce; } -impl DeliveryConfirmationPayments for () { +impl DeliveryConfirmationPayments for () { type Error = &'static str; fn pay_reward( @@ -94,14 +96,14 @@ impl DeliveryConfirmationPayments for () { /// Callback that is called at the source chain (bridge hub) when we get delivery confirmation /// for new messages. -pub trait OnMessagesDelivered { +pub trait OnMessagesDelivered { /// New messages delivery has been confirmed. /// /// The only argument of the function is the number of yet undelivered messages fn on_messages_delivered(lane: LaneId, enqueued_messages: MessageNonce); } -impl OnMessagesDelivered for () { +impl OnMessagesDelivered for () { fn on_messages_delivered(_lane: LaneId, _enqueued_messages: MessageNonce) {} } @@ -115,7 +117,7 @@ pub struct SendMessageArtifacts { } /// Messages bridge API to be used from other pallets. -pub trait MessagesBridge { +pub trait MessagesBridge { /// Error type. type Error: Debug; @@ -141,7 +143,7 @@ pub trait MessagesBridge { /// where outbound messages are forbidden. pub struct ForbidOutboundMessages; -impl DeliveryConfirmationPayments for ForbidOutboundMessages { +impl DeliveryConfirmationPayments for ForbidOutboundMessages { type Error = &'static str; fn pay_reward( diff --git a/bridges/primitives/messages/src/storage_keys.rs b/bridges/primitives/messages/src/storage_keys.rs index ff62dab078e..fb3371cb830 100644 --- a/bridges/primitives/messages/src/storage_keys.rs +++ b/bridges/primitives/messages/src/storage_keys.rs @@ -25,7 +25,7 @@ pub const OUTBOUND_LANES_MAP_NAME: &str = "OutboundLanes"; /// Name of the `InboundLanes` storage map. pub const INBOUND_LANES_MAP_NAME: &str = "InboundLanes"; -use crate::{LaneId, MessageKey, MessageNonce}; +use crate::{MessageKey, MessageNonce}; use codec::Encode; use frame_support::Blake2_128Concat; @@ -43,16 +43,20 @@ pub fn operating_mode_key(pallet_prefix: &str) -> StorageKey { } /// Storage key of the outbound message in the runtime storage. -pub fn message_key(pallet_prefix: &str, lane: &LaneId, nonce: MessageNonce) -> StorageKey { +pub fn message_key( + pallet_prefix: &str, + lane: LaneId, + nonce: MessageNonce, +) -> StorageKey { bp_runtime::storage_map_final_key::( pallet_prefix, OUTBOUND_MESSAGES_MAP_NAME, - &MessageKey { lane_id: *lane, nonce }.encode(), + &MessageKey { lane_id: lane, nonce }.encode(), ) } /// Storage key of the outbound message lane state in the runtime storage. -pub fn outbound_lane_data_key(pallet_prefix: &str, lane: &LaneId) -> StorageKey { +pub fn outbound_lane_data_key(pallet_prefix: &str, lane: &LaneId) -> StorageKey { bp_runtime::storage_map_final_key::( pallet_prefix, OUTBOUND_LANES_MAP_NAME, @@ -61,7 +65,7 @@ pub fn outbound_lane_data_key(pallet_prefix: &str, lane: &LaneId) -> StorageKey } /// Storage key of the inbound message lane state in the runtime storage. -pub fn inbound_lane_data_key(pallet_prefix: &str, lane: &LaneId) -> StorageKey { +pub fn inbound_lane_data_key(pallet_prefix: &str, lane: &LaneId) -> StorageKey { bp_runtime::storage_map_final_key::( pallet_prefix, INBOUND_LANES_MAP_NAME, @@ -72,7 +76,10 @@ pub fn inbound_lane_data_key(pallet_prefix: &str, lane: &LaneId) -> StorageKey { #[cfg(test)] mod tests { use super::*; - use frame_support::sp_runtime::Either; + use crate::{ + lane::{HashedLaneId, LegacyLaneId}, + LaneIdType, + }; use hex_literal::hex; #[test] @@ -92,7 +99,8 @@ mod tests { fn storage_message_key_computed_properly() { // If this test fails, then something has been changed in module storage that is breaking // all previously crafted messages proofs. - let storage_key = message_key("BridgeMessages", &LaneId::new(1, 2), 42).0; + let storage_key = + message_key("BridgeMessages", &HashedLaneId::try_new(1, 2).unwrap(), 42).0; assert_eq!( storage_key, hex!("dd16c784ebd3390a9bc0357c7511ed018a395e6242c6813b196ca31ed0547ea70e9bdb8f50c68d12f06eabb57759ee5eb1d3dccd8b3c3a012afe265f3e3c4432129b8aee50c9dcf87f9793be208e5ea02a00000000000000").to_vec(), @@ -101,8 +109,7 @@ mod tests { ); // check backwards compatibility - let storage_key = - message_key("BridgeMessages", &LaneId::from_inner(Either::Right(*b"test")), 42).0; + let storage_key = message_key("BridgeMessages", &LegacyLaneId(*b"test"), 42).0; assert_eq!( storage_key, hex!("dd16c784ebd3390a9bc0357c7511ed018a395e6242c6813b196ca31ed0547ea79446af0e09063bd4a7874aef8a997cec746573742a00000000000000").to_vec(), @@ -115,7 +122,8 @@ mod tests { fn outbound_lane_data_key_computed_properly() { // If this test fails, then something has been changed in module storage that is breaking // all previously crafted outbound lane state proofs. - let storage_key = outbound_lane_data_key("BridgeMessages", &LaneId::new(1, 2)).0; + let storage_key = + outbound_lane_data_key("BridgeMessages", &HashedLaneId::try_new(1, 2).unwrap()).0; assert_eq!( storage_key, hex!("dd16c784ebd3390a9bc0357c7511ed0196c246acb9b55077390e3ca723a0ca1fd3bef8b00df8ca7b01813b5e2741950db1d3dccd8b3c3a012afe265f3e3c4432129b8aee50c9dcf87f9793be208e5ea0").to_vec(), @@ -124,9 +132,7 @@ mod tests { ); // check backwards compatibility - let storage_key = - outbound_lane_data_key("BridgeMessages", &LaneId::from_inner(Either::Right(*b"test"))) - .0; + let storage_key = outbound_lane_data_key("BridgeMessages", &LegacyLaneId(*b"test")).0; assert_eq!( storage_key, hex!("dd16c784ebd3390a9bc0357c7511ed0196c246acb9b55077390e3ca723a0ca1f44a8995dd50b6657a037a7839304535b74657374").to_vec(), @@ -139,7 +145,8 @@ mod tests { fn inbound_lane_data_key_computed_properly() { // If this test fails, then something has been changed in module storage that is breaking // all previously crafted inbound lane state proofs. - let storage_key = inbound_lane_data_key("BridgeMessages", &LaneId::new(1, 2)).0; + let storage_key = + inbound_lane_data_key("BridgeMessages", &HashedLaneId::try_new(1, 2).unwrap()).0; assert_eq!( storage_key, hex!("dd16c784ebd3390a9bc0357c7511ed01e5f83cf83f2127eb47afdc35d6e43fabd3bef8b00df8ca7b01813b5e2741950db1d3dccd8b3c3a012afe265f3e3c4432129b8aee50c9dcf87f9793be208e5ea0").to_vec(), @@ -148,8 +155,7 @@ mod tests { ); // check backwards compatibility - let storage_key = - inbound_lane_data_key("BridgeMessages", &LaneId::from_inner(Either::Right(*b"test"))).0; + let storage_key = inbound_lane_data_key("BridgeMessages", &LegacyLaneId(*b"test")).0; assert_eq!( storage_key, hex!("dd16c784ebd3390a9bc0357c7511ed01e5f83cf83f2127eb47afdc35d6e43fab44a8995dd50b6657a037a7839304535b74657374").to_vec(), diff --git a/bridges/primitives/messages/src/target_chain.rs b/bridges/primitives/messages/src/target_chain.rs index 67868ff7c7c..cf07a400933 100644 --- a/bridges/primitives/messages/src/target_chain.rs +++ b/bridges/primitives/messages/src/target_chain.rs @@ -16,7 +16,7 @@ //! Primitives of messages module, that are used on the target chain. -use crate::{LaneId, Message, MessageKey, MessageNonce, MessagePayload, OutboundLaneData}; +use crate::{Message, MessageKey, MessageNonce, MessagePayload, OutboundLaneData}; use bp_runtime::{messages::MessageDispatchResult, raw_storage_proof_size, RawStorageProof, Size}; use codec::{Decode, Encode, Error as CodecError}; @@ -38,20 +38,20 @@ use sp_std::{fmt::Debug, marker::PhantomData, prelude::*}; /// /// - nonces (inclusive range) of messages which are included in this proof. #[derive(Clone, Decode, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo)] -pub struct FromBridgedChainMessagesProof { +pub struct FromBridgedChainMessagesProof { /// Hash of the finalized bridged header the proof is for. pub bridged_header_hash: BridgedHeaderHash, /// A storage trie proof of messages being delivered. pub storage_proof: RawStorageProof, /// Messages in this proof are sent over this lane. - pub lane: LaneId, + pub lane: Lane, /// Nonce of the first message being delivered. pub nonces_start: MessageNonce, /// Nonce of the last message being delivered. pub nonces_end: MessageNonce, } -impl Size for FromBridgedChainMessagesProof { +impl Size for FromBridgedChainMessagesProof { fn size(&self) -> u32 { use frame_support::sp_runtime::SaturatedConversion; raw_storage_proof_size(&self.storage_proof).saturated_into() @@ -59,7 +59,7 @@ impl Size for FromBridgedChainMessagesProof = (LaneId, ProvedLaneMessages); +pub type ProvedMessages = (LaneId, ProvedLaneMessages); /// Proved messages from single lane of the source chain. #[derive(RuntimeDebug, Encode, Decode, Clone, PartialEq, Eq, TypeInfo)] @@ -79,9 +79,9 @@ pub struct DispatchMessageData { /// Message with decoded dispatch payload. #[derive(RuntimeDebug)] -pub struct DispatchMessage { +pub struct DispatchMessage { /// Message key. - pub key: MessageKey, + pub key: MessageKey, /// Message data with decoded dispatch payload. pub data: DispatchMessageData, } @@ -96,6 +96,9 @@ pub trait MessageDispatch { /// Fine-grained result of single message dispatch (for better diagnostic purposes) type DispatchLevelResult: Clone + sp_std::fmt::Debug + Eq; + /// Lane identifier type. + type LaneId: Encode; + /// Returns `true` if dispatcher is ready to accept additional messages. The `false` should /// be treated as a hint by both dispatcher and its consumers - i.e. dispatcher shall not /// simply drop messages if it returns `false`. The consumer may still call the `dispatch` @@ -103,21 +106,23 @@ pub trait MessageDispatch { /// /// We check it in the messages delivery transaction prologue. So if it becomes `false` /// after some portion of messages is already dispatched, it doesn't fail the whole transaction. - fn is_active(lane: LaneId) -> bool; + fn is_active(lane: Self::LaneId) -> bool; /// Estimate dispatch weight. /// /// This function must return correct upper bound of dispatch weight. The return value /// of this function is expected to match return value of the corresponding /// `FromInboundLaneApi::message_details().dispatch_weight` call. - fn dispatch_weight(message: &mut DispatchMessage) -> Weight; + fn dispatch_weight( + message: &mut DispatchMessage, + ) -> Weight; /// Called when inbound message is received. /// /// It is up to the implementers of this trait to determine whether the message /// is invalid (i.e. improperly encoded, has too large weight, ...) or not. fn dispatch( - message: DispatchMessage, + message: DispatchMessage, ) -> MessageDispatchResult; } @@ -146,8 +151,10 @@ impl Default for ProvedLaneMessages { } } -impl From for DispatchMessage { - fn from(message: Message) -> Self { +impl From> + for DispatchMessage +{ + fn from(message: Message) -> Self { DispatchMessage { key: message.key, data: message.payload.into() } } } @@ -173,22 +180,27 @@ impl DeliveryPayments for () { /// Structure that may be used in place of `MessageDispatch` on chains, /// where inbound messages are forbidden. -pub struct ForbidInboundMessages(PhantomData); +pub struct ForbidInboundMessages(PhantomData<(DispatchPayload, LaneId)>); -impl MessageDispatch for ForbidInboundMessages { +impl MessageDispatch + for ForbidInboundMessages +{ type DispatchPayload = DispatchPayload; type DispatchLevelResult = (); + type LaneId = LaneId; fn is_active(_: LaneId) -> bool { false } - fn dispatch_weight(_message: &mut DispatchMessage) -> Weight { + fn dispatch_weight( + _message: &mut DispatchMessage, + ) -> Weight { Weight::MAX } fn dispatch( - _: DispatchMessage, + _: DispatchMessage, ) -> MessageDispatchResult { MessageDispatchResult { unspent_weight: Weight::zero(), dispatch_level_result: () } } diff --git a/bridges/primitives/relayers/src/extension.rs b/bridges/primitives/relayers/src/extension.rs index 5ab8e6cde96..a9fa4df27ea 100644 --- a/bridges/primitives/relayers/src/extension.rs +++ b/bridges/primitives/relayers/src/extension.rs @@ -21,6 +21,7 @@ use bp_header_chain::SubmitFinalityProofInfo; use bp_messages::MessagesCallInfo; use bp_parachains::SubmitParachainHeadsInfo; use bp_runtime::StaticStrProvider; +use codec::{Decode, Encode}; use frame_support::{ dispatch::CallableCallFor, traits::IsSubType, weights::Weight, RuntimeDebugNoBound, }; @@ -35,25 +36,28 @@ use sp_std::{fmt::Debug, marker::PhantomData, vec, vec::Vec}; /// Type of the call that the signed extension recognizes. #[derive(PartialEq, RuntimeDebugNoBound)] -pub enum ExtensionCallInfo { +pub enum ExtensionCallInfo { /// Relay chain finality + parachain finality + message delivery/confirmation calls. AllFinalityAndMsgs( SubmitFinalityProofInfo, SubmitParachainHeadsInfo, - MessagesCallInfo, + MessagesCallInfo, ), /// Relay chain finality + message delivery/confirmation calls. - RelayFinalityAndMsgs(SubmitFinalityProofInfo, MessagesCallInfo), + RelayFinalityAndMsgs( + SubmitFinalityProofInfo, + MessagesCallInfo, + ), /// Parachain finality + message delivery/confirmation calls. /// /// This variant is used only when bridging with parachain. - ParachainFinalityAndMsgs(SubmitParachainHeadsInfo, MessagesCallInfo), + ParachainFinalityAndMsgs(SubmitParachainHeadsInfo, MessagesCallInfo), /// Standalone message delivery/confirmation call. - Msgs(MessagesCallInfo), + Msgs(MessagesCallInfo), } -impl - ExtensionCallInfo +impl + ExtensionCallInfo { /// Returns true if call is a message delivery call (with optional finality calls). pub fn is_receive_messages_proof_call(&self) -> bool { @@ -84,7 +88,7 @@ impl } /// Returns the pre-dispatch `ReceiveMessagesProofInfo`. - pub fn messages_call_info(&self) -> &MessagesCallInfo { + pub fn messages_call_info(&self) -> &MessagesCallInfo { match self { Self::AllFinalityAndMsgs(_, _, info) => info, Self::RelayFinalityAndMsgs(_, info) => info, @@ -119,25 +123,27 @@ pub trait ExtensionConfig { /// Runtime that optionally supports batched calls. We assume that batched call /// succeeds if and only if all of its nested calls succeed. type Runtime: frame_system::Config; + /// Relayers pallet instance. + type BridgeRelayersPalletInstance: 'static; /// Messages pallet instance. type BridgeMessagesPalletInstance: 'static; /// Additional priority that is added to base message delivery transaction priority /// for every additional bundled message. type PriorityBoostPerMessage: Get; - /// Type of reward, that the `pallet-bridge-relayers` is using. - type Reward; /// Block number for the remote **GRANDPA chain**. Mind that this chain is not /// necessarily the chain that we are bridging with. If we are bridging with /// parachain, it must be its parent relay chain. If we are bridging with the /// GRANDPA chain, it must be it. type RemoteGrandpaChainBlockNumber: Clone + Copy + Debug; + /// Lane identifier type. + type LaneId: Clone + Copy + Decode + Encode + Debug; /// Given runtime call, check if it is supported by the signed extension. Additionally, /// check if call (or any of batched calls) are obsolete. fn parse_and_check_for_obsolete_call( call: &::RuntimeCall, ) -> Result< - Option>, + Option>, TransactionValidityError, >; @@ -149,7 +155,7 @@ pub trait ExtensionConfig { /// Given runtime call info, check that this call has been successful and has updated /// runtime storage accordingly. fn check_call_result( - call_info: &ExtensionCallInfo, + call_info: &ExtensionCallInfo, call_data: &mut ExtensionCallData, relayer: &::AccountId, ) -> bool; diff --git a/bridges/primitives/relayers/src/lib.rs b/bridges/primitives/relayers/src/lib.rs index 1e63c89ecd7..faa4cb17762 100644 --- a/bridges/primitives/relayers/src/lib.rs +++ b/bridges/primitives/relayers/src/lib.rs @@ -25,7 +25,6 @@ pub use extension::{ }; pub use registration::{ExplicitOrAccountParams, Registration, StakeAndSlash}; -use bp_messages::LaneId; use bp_runtime::{ChainId, StorageDoubleMapKeyProvider}; use frame_support::{traits::tokens::Preservation, Blake2_128Concat, Identity}; use scale_info::TypeInfo; @@ -61,7 +60,7 @@ pub enum RewardsAccountOwner { /// of the sovereign accounts will pay rewards for different operations. So we need multiple /// parameters to identify the account that pays a reward to the relayer. #[derive(Copy, Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo, MaxEncodedLen)] -pub struct RewardsAccountParams { +pub struct RewardsAccountParams { // **IMPORTANT NOTE**: the order of fields here matters - we are using // `into_account_truncating` and lane id is already `32` byte, so if other fields are encoded // after it, they're simply dropped. So lane id shall be the last field. @@ -70,7 +69,7 @@ pub struct RewardsAccountParams { lane_id: LaneId, } -impl RewardsAccountParams { +impl RewardsAccountParams { /// Create a new instance of `RewardsAccountParams`. pub const fn new( lane_id: LaneId, @@ -79,9 +78,14 @@ impl RewardsAccountParams { ) -> Self { Self { lane_id, bridged_chain_id, owner } } + + /// Getter for `lane_id`. + pub const fn lane_id(&self) -> &LaneId { + &self.lane_id + } } -impl TypeId for RewardsAccountParams { +impl TypeId for RewardsAccountParams { const TYPE_ID: [u8; 4] = *b"brap"; } @@ -89,47 +93,58 @@ impl TypeId for RewardsAccountParams { pub trait PaymentProcedure { /// Error that may be returned by the procedure. type Error: Debug; + /// Lane identifier type. + type LaneId: Decode + Encode; /// Pay reward to the relayer from the account with provided params. fn pay_reward( relayer: &Relayer, - rewards_account_params: RewardsAccountParams, + rewards_account_params: RewardsAccountParams, reward: Reward, ) -> Result<(), Self::Error>; } impl PaymentProcedure for () { type Error = &'static str; + type LaneId = (); - fn pay_reward(_: &Relayer, _: RewardsAccountParams, _: Reward) -> Result<(), Self::Error> { + fn pay_reward( + _: &Relayer, + _: RewardsAccountParams, + _: Reward, + ) -> Result<(), Self::Error> { Ok(()) } } /// Reward payment procedure that does `balances::transfer` call from the account, derived from /// given params. -pub struct PayRewardFromAccount(PhantomData<(T, Relayer)>); +pub struct PayRewardFromAccount(PhantomData<(T, Relayer, LaneId)>); -impl PayRewardFromAccount +impl PayRewardFromAccount where Relayer: Decode + Encode, + LaneId: Decode + Encode, { /// Return account that pays rewards based on the provided parameters. - pub fn rewards_account(params: RewardsAccountParams) -> Relayer { + pub fn rewards_account(params: RewardsAccountParams) -> Relayer { params.into_sub_account_truncating(b"rewards-account") } } -impl PaymentProcedure for PayRewardFromAccount +impl PaymentProcedure + for PayRewardFromAccount where T: frame_support::traits::fungible::Mutate, Relayer: Decode + Encode + Eq, + LaneId: Decode + Encode, { type Error = sp_runtime::DispatchError; + type LaneId = LaneId; fn pay_reward( relayer: &Relayer, - rewards_account_params: RewardsAccountParams, + rewards_account_params: RewardsAccountParams, reward: T::Balance, ) -> Result<(), Self::Error> { T::transfer( @@ -142,48 +157,56 @@ where } } -/// Can be use to access the runtime storage key within the `RelayerRewards` map of the relayers +/// Can be used to access the runtime storage key within the `RelayerRewards` map of the relayers /// pallet. -pub struct RelayerRewardsKeyProvider(PhantomData<(AccountId, Reward)>); +pub struct RelayerRewardsKeyProvider( + PhantomData<(AccountId, Reward, LaneId)>, +); -impl StorageDoubleMapKeyProvider for RelayerRewardsKeyProvider +impl StorageDoubleMapKeyProvider + for RelayerRewardsKeyProvider where AccountId: 'static + Codec + EncodeLike + Send + Sync, Reward: 'static + Codec + EncodeLike + Send + Sync, + LaneId: Codec + EncodeLike + Send + Sync, { const MAP_NAME: &'static str = "RelayerRewards"; type Hasher1 = Blake2_128Concat; type Key1 = AccountId; type Hasher2 = Identity; - type Key2 = RewardsAccountParams; + type Key2 = RewardsAccountParams; type Value = Reward; } #[cfg(test)] mod tests { use super::*; - use bp_messages::LaneId; - use sp_runtime::testing::H256; + use bp_messages::{HashedLaneId, LaneIdType, LegacyLaneId}; + use sp_runtime::{app_crypto::Ss58Codec, testing::H256}; #[test] fn different_lanes_are_using_different_accounts() { assert_eq!( - PayRewardFromAccount::<(), H256>::rewards_account(RewardsAccountParams::new( - LaneId::new(1, 2), - *b"test", - RewardsAccountOwner::ThisChain - )), + PayRewardFromAccount::<(), H256, HashedLaneId>::rewards_account( + RewardsAccountParams::new( + HashedLaneId::try_new(1, 2).unwrap(), + *b"test", + RewardsAccountOwner::ThisChain + ) + ), hex_literal::hex!("627261700074657374b1d3dccd8b3c3a012afe265f3e3c4432129b8aee50c9dc") .into(), ); assert_eq!( - PayRewardFromAccount::<(), H256>::rewards_account(RewardsAccountParams::new( - LaneId::new(1, 3), - *b"test", - RewardsAccountOwner::ThisChain - )), + PayRewardFromAccount::<(), H256, HashedLaneId>::rewards_account( + RewardsAccountParams::new( + HashedLaneId::try_new(1, 3).unwrap(), + *b"test", + RewardsAccountOwner::ThisChain + ) + ), hex_literal::hex!("627261700074657374a43e8951aa302c133beb5f85821a21645f07b487270ef3") .into(), ); @@ -192,23 +215,101 @@ mod tests { #[test] fn different_directions_are_using_different_accounts() { assert_eq!( - PayRewardFromAccount::<(), H256>::rewards_account(RewardsAccountParams::new( - LaneId::new(1, 2), - *b"test", - RewardsAccountOwner::ThisChain - )), + PayRewardFromAccount::<(), H256, HashedLaneId>::rewards_account( + RewardsAccountParams::new( + HashedLaneId::try_new(1, 2).unwrap(), + *b"test", + RewardsAccountOwner::ThisChain + ) + ), hex_literal::hex!("627261700074657374b1d3dccd8b3c3a012afe265f3e3c4432129b8aee50c9dc") .into(), ); assert_eq!( - PayRewardFromAccount::<(), H256>::rewards_account(RewardsAccountParams::new( - LaneId::new(1, 2), - *b"test", - RewardsAccountOwner::BridgedChain - )), + PayRewardFromAccount::<(), H256, HashedLaneId>::rewards_account( + RewardsAccountParams::new( + HashedLaneId::try_new(1, 2).unwrap(), + *b"test", + RewardsAccountOwner::BridgedChain + ) + ), hex_literal::hex!("627261700174657374b1d3dccd8b3c3a012afe265f3e3c4432129b8aee50c9dc") .into(), ); } + + #[test] + fn pay_reward_from_account_for_legacy_lane_id_works() { + let test_data = vec![ + // Note: these accounts are used for integration tests within + // `bridges_rococo_westend.sh` + ( + LegacyLaneId([0, 0, 0, 1]), + b"bhks", + RewardsAccountOwner::ThisChain, + (0_u16, "13E5fui97x6KTwNnSjaEKZ8s7kJNot5F3aUsy3jUtuoMyUec"), + ), + ( + LegacyLaneId([0, 0, 0, 1]), + b"bhks", + RewardsAccountOwner::BridgedChain, + (0_u16, "13E5fui9Ka9Vz4JbGN3xWjmwDNxnxF1N9Hhhbeu3VCqLChuj"), + ), + ( + LegacyLaneId([0, 0, 0, 1]), + b"bhpd", + RewardsAccountOwner::ThisChain, + (2_u16, "EoQBtnwtXqnSnr9cgBEJpKU7NjeC9EnR4D1VjgcvHz9ZYmS"), + ), + ( + LegacyLaneId([0, 0, 0, 1]), + b"bhpd", + RewardsAccountOwner::BridgedChain, + (2_u16, "EoQBtnx69txxumxSJexVzxYD1Q4LWAuWmRq8LrBWb27nhYN"), + ), + // Note: these accounts are used for integration tests within + // `bridges_polkadot_kusama.sh` from fellows. + ( + LegacyLaneId([0, 0, 0, 2]), + b"bhwd", + RewardsAccountOwner::ThisChain, + (4_u16, "SNihsskf7bFhnHH9HJFMjWD3FJ96ESdAQTFZUAtXudRQbaH"), + ), + ( + LegacyLaneId([0, 0, 0, 2]), + b"bhwd", + RewardsAccountOwner::BridgedChain, + (4_u16, "SNihsskrjeSDuD5xumyYv9H8sxZEbNkG7g5C5LT8CfPdaSE"), + ), + ( + LegacyLaneId([0, 0, 0, 2]), + b"bhro", + RewardsAccountOwner::ThisChain, + (4_u16, "SNihsskf7bF2vWogkC6uFoiqPhd3dUX6TGzYZ1ocJdo3xHp"), + ), + ( + LegacyLaneId([0, 0, 0, 2]), + b"bhro", + RewardsAccountOwner::BridgedChain, + (4_u16, "SNihsskrjeRZ3ScWNfq6SSnw2N3BzQeCAVpBABNCbfmHENB"), + ), + ]; + + for (lane_id, bridged_chain_id, owner, (expected_ss58, expected_account)) in test_data { + assert_eq!( + expected_account, + sp_runtime::AccountId32::new(PayRewardFromAccount::< + [u8; 32], + [u8; 32], + LegacyLaneId, + >::rewards_account(RewardsAccountParams::new( + lane_id, + *bridged_chain_id, + owner + ))) + .to_ss58check_with_version(expected_ss58.into()) + ); + } + } } diff --git a/bridges/primitives/relayers/src/registration.rs b/bridges/primitives/relayers/src/registration.rs index 9d9b7e48122..d74ef18cf70 100644 --- a/bridges/primitives/relayers/src/registration.rs +++ b/bridges/primitives/relayers/src/registration.rs @@ -48,15 +48,17 @@ use sp_runtime::{ /// Either explicit account reference or `RewardsAccountParams`. #[derive(Clone, Debug)] -pub enum ExplicitOrAccountParams { +pub enum ExplicitOrAccountParams { /// Explicit account reference. Explicit(AccountId), /// Account, referenced using `RewardsAccountParams`. - Params(RewardsAccountParams), + Params(RewardsAccountParams), } -impl From for ExplicitOrAccountParams { - fn from(params: RewardsAccountParams) -> Self { +impl From> + for ExplicitOrAccountParams +{ + fn from(params: RewardsAccountParams) -> Self { ExplicitOrAccountParams::Params(params) } } @@ -103,9 +105,9 @@ pub trait StakeAndSlash { /// `beneficiary`. /// /// Returns `Ok(_)` with non-zero balance if we have failed to repatriate some portion of stake. - fn repatriate_reserved( + fn repatriate_reserved( relayer: &AccountId, - beneficiary: ExplicitOrAccountParams, + beneficiary: ExplicitOrAccountParams, amount: Balance, ) -> Result; } @@ -126,9 +128,9 @@ where Zero::zero() } - fn repatriate_reserved( + fn repatriate_reserved( _relayer: &AccountId, - _beneficiary: ExplicitOrAccountParams, + _beneficiary: ExplicitOrAccountParams, _amount: Balance, ) -> Result { Ok(Zero::zero()) diff --git a/bridges/primitives/runtime/src/chain.rs b/bridges/primitives/runtime/src/chain.rs index 0db4eac79a7..eba3bcadfea 100644 --- a/bridges/primitives/runtime/src/chain.rs +++ b/bridges/primitives/runtime/src/chain.rs @@ -365,17 +365,23 @@ macro_rules! decl_bridge_finality_runtime_apis { }; } +// Re-export to avoid include tuplex dependency everywhere. +#[doc(hidden)] +pub mod __private { + pub use codec; +} + /// Convenience macro that declares bridge messages runtime apis and related constants for a chain. /// This includes: /// - chain-specific bridge runtime APIs: -/// - `ToOutboundLaneApi` -/// - `FromInboundLaneApi` +/// - `ToOutboundLaneApi` +/// - `FromInboundLaneApi` /// - constants that are stringified names of runtime API methods: /// - `FROM__MESSAGE_DETAILS_METHOD`, /// The name of the chain has to be specified in snake case (e.g. `bridge_hub_polkadot`). #[macro_export] macro_rules! decl_bridge_messages_runtime_apis { - ($chain: ident) => { + ($chain: ident, $lane_id_type:ty) => { bp_runtime::paste::item! { mod [<$chain _messages_api>] { use super::*; @@ -400,7 +406,7 @@ macro_rules! decl_bridge_messages_runtime_apis { /// If some (or all) messages are missing from the storage, they'll also will /// be missing from the resulting vector. The vector is ordered by the nonce. fn message_details( - lane: bp_messages::LaneId, + lane: $lane_id_type, begin: bp_messages::MessageNonce, end: bp_messages::MessageNonce, ) -> sp_std::vec::Vec; @@ -416,7 +422,7 @@ macro_rules! decl_bridge_messages_runtime_apis { pub trait [] { /// Return details of given inbound messages. fn message_details( - lane: bp_messages::LaneId, + lane: $lane_id_type, messages: sp_std::vec::Vec<(bp_messages::MessagePayload, bp_messages::OutboundMessageDetails)>, ) -> sp_std::vec::Vec; } @@ -433,8 +439,8 @@ macro_rules! decl_bridge_messages_runtime_apis { /// The name of the chain has to be specified in snake case (e.g. `bridge_hub_polkadot`). #[macro_export] macro_rules! decl_bridge_runtime_apis { - ($chain: ident $(, $consensus: ident)?) => { + ($chain: ident $(, $consensus: ident, $lane_id_type:ident)?) => { bp_runtime::decl_bridge_finality_runtime_apis!($chain $(, $consensus)?); - bp_runtime::decl_bridge_messages_runtime_apis!($chain); + bp_runtime::decl_bridge_messages_runtime_apis!($chain, $lane_id_type); }; } diff --git a/bridges/primitives/runtime/src/lib.rs b/bridges/primitives/runtime/src/lib.rs index 8f5040ad9a1..90eb72922be 100644 --- a/bridges/primitives/runtime/src/lib.rs +++ b/bridges/primitives/runtime/src/lib.rs @@ -36,7 +36,7 @@ use sp_std::{fmt::Debug, ops::RangeInclusive, vec, vec::Vec}; pub use chain::{ AccountIdOf, AccountPublicOf, BalanceOf, BlockNumberOf, Chain, EncodedOrDecodedCall, HashOf, HasherOf, HeaderOf, NonceOf, Parachain, ParachainIdOf, SignatureOf, TransactionEraOf, - UnderlyingChainOf, UnderlyingChainProvider, + UnderlyingChainOf, UnderlyingChainProvider, __private, }; pub use frame_support::storage::storage_prefix as storage_value_final_key; use num_traits::{CheckedAdd, CheckedSub, One, SaturatingAdd, Zero}; @@ -272,7 +272,7 @@ pub trait StorageMapKeyProvider { } } -/// Can be use to access the runtime storage key of a `StorageDoubleMap`. +/// Can be used to access the runtime storage key of a `StorageDoubleMap`. pub trait StorageDoubleMapKeyProvider { /// The name of the variable that holds the `StorageDoubleMap`. const MAP_NAME: &'static str; diff --git a/bridges/primitives/xcm-bridge-hub/src/lib.rs b/bridges/primitives/xcm-bridge-hub/src/lib.rs index 44a90a57d4f..061e7a27506 100644 --- a/bridges/primitives/xcm-bridge-hub/src/lib.rs +++ b/bridges/primitives/xcm-bridge-hub/src/lib.rs @@ -19,7 +19,7 @@ #![warn(missing_docs)] #![cfg_attr(not(feature = "std"), no_std)] -use bp_messages::LaneId; +use bp_messages::LaneIdType; use bp_runtime::{AccountIdOf, BalanceOf, Chain}; pub use call_info::XcmBridgeHubCall; use codec::{Decode, Encode, MaxEncodedLen}; @@ -63,7 +63,6 @@ pub type XcmAsPlainPayload = sp_std::vec::Vec; Ord, PartialOrd, PartialEq, - RuntimeDebug, TypeInfo, MaxEncodedLen, Serialize, @@ -90,6 +89,12 @@ impl BridgeId { } } +impl core::fmt::Debug for BridgeId { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + core::fmt::Debug::fmt(&self.0, f) + } +} + /// Local XCM channel manager. pub trait LocalXcmChannelManager { /// Error that may be returned when suspending/resuming the bridge. @@ -149,8 +154,8 @@ pub enum BridgeState { #[derive( CloneNoBound, Decode, Encode, Eq, PartialEqNoBound, TypeInfo, MaxEncodedLen, RuntimeDebugNoBound, )] -#[scale_info(skip_type_params(ThisChain))] -pub struct Bridge { +#[scale_info(skip_type_params(ThisChain, LaneId))] +pub struct Bridge { /// Relative location of the bridge origin chain. This is expected to be **convertible** to the /// `latest` XCM, so the check and migration needs to be ensured. pub bridge_origin_relative_location: Box, @@ -204,6 +209,8 @@ pub enum BridgeLocationsError { UnsupportedDestinationLocation, /// The version of XCM location argument is unsupported. UnsupportedXcmVersion, + /// The `LaneIdType` generator is not supported. + UnsupportedLaneIdType, } impl BridgeLocations { @@ -318,7 +325,7 @@ impl BridgeLocations { /// Generates the exact same `LaneId` on the both bridge hubs. /// /// Note: Use this **only** when opening a new bridge. - pub fn calculate_lane_id( + pub fn calculate_lane_id( &self, xcm_version: XcmVersion, ) -> Result { @@ -341,10 +348,11 @@ impl BridgeLocations { .into_version(xcm_version) .map_err(|_| BridgeLocationsError::UnsupportedXcmVersion); - Ok(LaneId::new( + LaneId::try_new( EncodedVersionedInteriorLocation(universal_location1.encode()), EncodedVersionedInteriorLocation(universal_location2.encode()), - )) + ) + .map_err(|_| BridgeLocationsError::UnsupportedLaneIdType) } } @@ -590,6 +598,8 @@ mod tests { #[test] fn calculate_lane_id_works() { + type TestLaneId = bp_messages::HashedLaneId; + let from_local_to_remote = run_successful_test(SuccessfulTest { here_universal_location: [GlobalConsensus(LOCAL_NETWORK), Parachain(LOCAL_BRIDGE_HUB)] .into(), @@ -631,12 +641,12 @@ mod tests { }); assert_ne!( - from_local_to_remote.calculate_lane_id(xcm::latest::VERSION), - from_remote_to_local.calculate_lane_id(xcm::latest::VERSION - 1), + from_local_to_remote.calculate_lane_id::(xcm::latest::VERSION), + from_remote_to_local.calculate_lane_id::(xcm::latest::VERSION - 1), ); assert_eq!( - from_local_to_remote.calculate_lane_id(xcm::latest::VERSION), - from_remote_to_local.calculate_lane_id(xcm::latest::VERSION), + from_local_to_remote.calculate_lane_id::(xcm::latest::VERSION), + from_remote_to_local.calculate_lane_id::(xcm::latest::VERSION), ); } diff --git a/bridges/relays/client-substrate/src/chain.rs b/bridges/relays/client-substrate/src/chain.rs index 227e9c31c5b..9856f0d0237 100644 --- a/bridges/relays/client-substrate/src/chain.rs +++ b/bridges/relays/client-substrate/src/chain.rs @@ -113,9 +113,6 @@ impl Parachain for T where T: UnderlyingChainProvider + Chain + ParachainBase /// Substrate-based chain with messaging support from minimal relay-client point of view. pub trait ChainWithMessages: Chain + ChainWithMessagesBase { - // TODO (https://github.com/paritytech/parity-bridges-common/issues/1692): check all the names - // after the issue is fixed - all names must be changed - /// Name of the bridge relayers pallet (used in `construct_runtime` macro call) that is deployed /// at some other chain to bridge with this `ChainWithMessages`. /// diff --git a/bridges/relays/lib-substrate-relay/src/cli/bridge.rs b/bridges/relays/lib-substrate-relay/src/cli/bridge.rs index 28b0eb0ad52..2e15562f6c2 100644 --- a/bridges/relays/lib-substrate-relay/src/cli/bridge.rs +++ b/bridges/relays/lib-substrate-relay/src/cli/bridge.rs @@ -23,9 +23,12 @@ use crate::{ parachains::SubstrateParachainsPipeline, }; use bp_parachains::{RelayBlockHash, RelayBlockHasher, RelayBlockNumber}; +use codec::{Codec, EncodeLike}; +use messages_relay::Labeled; use relay_substrate_client::{ Chain, ChainWithRuntimeVersion, ChainWithTransactions, Parachain, RelayChain, }; +use std::fmt::Debug; /// Minimal bridge representation that can be used from the CLI. /// It connects a source chain to a target chain. @@ -99,7 +102,22 @@ where /// Bridge representation that can be used from the CLI for relaying messages. pub trait MessagesCliBridge: CliBridgeBase { /// The Source -> Destination messages synchronization pipeline. - type MessagesLane: SubstrateMessageLane; + type MessagesLane: SubstrateMessageLane< + SourceChain = Self::Source, + TargetChain = Self::Target, + LaneId = Self::LaneId, + >; + /// Lane identifier type. + type LaneId: Clone + + Copy + + Debug + + Codec + + EncodeLike + + Send + + Sync + + Labeled + + TryFrom> + + Default; /// Optional messages delivery transaction limits that the messages relay is going /// to use. If it returns `None`, limits are estimated using `TransactionPayment` API @@ -108,3 +126,6 @@ pub trait MessagesCliBridge: CliBridgeBase { None } } + +/// An alias for lane identifier type. +pub type MessagesLaneIdOf = ::LaneId; diff --git a/bridges/relays/lib-substrate-relay/src/cli/mod.rs b/bridges/relays/lib-substrate-relay/src/cli/mod.rs index ef8403ff68e..be64866fc14 100644 --- a/bridges/relays/lib-substrate-relay/src/cli/mod.rs +++ b/bridges/relays/lib-substrate-relay/src/cli/mod.rs @@ -16,10 +16,8 @@ //! Deal with CLI args of substrate-to-substrate relay. -use bp_messages::LaneId; use rbtag::BuildInfo; -use sp_core::H256; -use sp_runtime::Either; +use sp_runtime::traits::TryConvert; use std::str::FromStr; use structopt::StructOpt; use strum::{EnumString, VariantNames}; @@ -43,36 +41,19 @@ pub type DefaultClient = relay_substrate_client::RpcWithCachingClient; /// Lane id. #[derive(Debug, Clone, PartialEq, Eq)] -pub struct HexLaneId(Either); +pub struct HexLaneId(Vec); -impl From for LaneId { - fn from(lane_id: HexLaneId) -> LaneId { - LaneId::from_inner(lane_id.0) +impl>> TryConvert for HexLaneId { + fn try_convert(lane_id: HexLaneId) -> Result { + T::try_from(lane_id.0.clone()).map_err(|_| lane_id) } } impl FromStr for HexLaneId { - type Err = rustc_hex::FromHexError; + type Err = hex::FromHexError; fn from_str(s: &str) -> Result { - // check `H256` variant at first - match H256::from_str(s) { - Ok(hash) => Ok(HexLaneId(Either::Left(hash))), - Err(hash_error) => { - // check backwards compatible - let mut lane_id = [0u8; 4]; - match hex::decode_to_slice(s, &mut lane_id) { - Ok(_) => Ok(HexLaneId(Either::Right(lane_id))), - Err(array_error) => { - log::error!( - target: "bridge", - "Failed to parse `HexLaneId` as hex string: {s:?} - hash_error: {hash_error:?}, array_error: {array_error:?}", - ); - Err(hash_error) - }, - } - }, - } + hex::decode(s).map(Self) } } @@ -172,6 +153,8 @@ pub enum RuntimeVersionType { #[cfg(test)] mod tests { use super::*; + use bp_messages::{HashedLaneId, LegacyLaneId}; + use sp_core::H256; #[test] fn hex_lane_id_from_str_works() { @@ -185,21 +168,21 @@ mod tests { ) .is_err()); assert_eq!( - LaneId::from( + HexLaneId::try_convert( HexLaneId::from_str( "0101010101010101010101010101010101010101010101010101010101010101" ) .unwrap() ), - LaneId::from_inner(Either::Left(H256::from([1u8; 32]))) + Ok(HashedLaneId::from_inner(H256::from([1u8; 32]))) ); // array variant assert!(HexLaneId::from_str("0000001").is_err()); assert!(HexLaneId::from_str("000000001").is_err()); assert_eq!( - LaneId::from(HexLaneId::from_str("00000001").unwrap()), - LaneId::from_inner(Either::Right([0, 0, 0, 1])) + HexLaneId::try_convert(HexLaneId::from_str("00000001").unwrap()), + Ok(LegacyLaneId([0, 0, 0, 1])) ); } } diff --git a/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/mod.rs b/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/mod.rs index 3786976bed9..9261dc43753 100644 --- a/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/mod.rs +++ b/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/mod.rs @@ -31,25 +31,30 @@ pub mod relay_to_relay; pub mod relay_to_parachain; use async_trait::async_trait; -use std::{marker::PhantomData, sync::Arc}; +use codec::{Codec, EncodeLike}; +use std::{fmt::Debug, marker::PhantomData, sync::Arc}; use structopt::StructOpt; use futures::{FutureExt, TryFutureExt}; use crate::{ - cli::{bridge::MessagesCliBridge, DefaultClient, HexLaneId, PrometheusParams}, + cli::{ + bridge::{MessagesCliBridge, MessagesLaneIdOf}, + DefaultClient, HexLaneId, PrometheusParams, + }, messages::{MessagesRelayLimits, MessagesRelayParams}, on_demand::OnDemandRelay, HeadersToRelay, TaggedAccount, TransactionParams, }; -use bp_messages::LaneId; use bp_runtime::BalanceOf; +use messages_relay::Labeled; use relay_substrate_client::{ AccountIdOf, AccountKeyPairOf, Chain, ChainWithBalances, ChainWithMessages, ChainWithRuntimeVersion, ChainWithTransactions, }; use relay_utils::metrics::MetricsParams; use sp_core::Pair; +use sp_runtime::traits::TryConvert; /// Parameters that have the same names across all bridges. #[derive(Debug, PartialEq, StructOpt)] @@ -163,7 +168,7 @@ where &self, source_to_target_headers_relay: Arc>, target_to_source_headers_relay: Arc>, - lane_id: LaneId, + lane_id: MessagesLaneIdOf, maybe_limits: Option, ) -> MessagesRelayParams, DefaultClient> { MessagesRelayParams { @@ -234,9 +239,20 @@ where + ChainWithRuntimeVersion; /// Left to Right bridge. - type L2R: MessagesCliBridge; + type L2R: MessagesCliBridge; /// Right to Left bridge - type R2L: MessagesCliBridge; + type R2L: MessagesCliBridge; + /// Lane identifier type. + type LaneId: Clone + + Copy + + Debug + + Codec + + EncodeLike + + Send + + Sync + + Labeled + + TryFrom> + + Default; /// Construct new bridge. fn new(params: ::Params) -> anyhow::Result; @@ -287,30 +303,29 @@ where self.mut_base().start_on_demand_headers_relayers().await?; // add balance-related metrics - let lanes = self + let lanes: Vec = self .base() .common() .shared .lane .iter() .cloned() - .map(Into::into) - .collect::>(); + .map(HexLaneId::try_convert) + .collect::, HexLaneId>>() + .expect(""); { let common = self.mut_base().mut_common(); - crate::messages::metrics::add_relay_balances_metrics::<_, Self::Right>( - common.left.client.clone(), - &common.metrics_params, - &common.left.accounts, - &lanes, - ) + crate::messages::metrics::add_relay_balances_metrics::< + _, + Self::Right, + MessagesLaneIdOf, + >(common.left.client.clone(), &common.metrics_params, &common.left.accounts, &lanes) .await?; - crate::messages::metrics::add_relay_balances_metrics::<_, Self::Left>( - common.right.client.clone(), - &common.metrics_params, - &common.right.accounts, - &lanes, - ) + crate::messages::metrics::add_relay_balances_metrics::< + _, + Self::Left, + MessagesLaneIdOf, + >(common.right.client.clone(), &common.metrics_params, &common.right.accounts, &lanes) .await?; } @@ -359,8 +374,6 @@ mod tests { use crate::{cli::chain_schema::RuntimeVersionType, declare_chain_cli_schema}; use relay_substrate_client::{ChainRuntimeVersion, Parachain, SimpleRuntimeVersion}; - use sp_core::H256; - use sp_runtime::Either; #[test] // We need `#[allow(dead_code)]` because some of the methods generated by the macros @@ -434,7 +447,7 @@ mod tests { res, BridgeHubKusamaBridgeHubPolkadotHeadersAndMessages { shared: HeadersAndMessagesSharedParams { - lane: vec![HexLaneId(Either::Left(H256::from([0x00u8; 32])))], + lane: vec![HexLaneId(vec![0x00u8; 32])], only_mandatory_headers: false, only_free_headers: false, prometheus_params: PrometheusParams { diff --git a/bridges/relays/lib-substrate-relay/src/cli/relay_messages.rs b/bridges/relays/lib-substrate-relay/src/cli/relay_messages.rs index 34d5226e90c..3878b081d6c 100644 --- a/bridges/relays/lib-substrate-relay/src/cli/relay_messages.rs +++ b/bridges/relays/lib-substrate-relay/src/cli/relay_messages.rs @@ -33,6 +33,7 @@ use relay_substrate_client::{ ChainWithTransactions, Client, }; use relay_utils::UniqueSaturatedInto; +use sp_runtime::traits::TryConvert; /// Messages relaying params. #[derive(StructOpt)] @@ -116,6 +117,9 @@ where let target_client = data.target.into_client::().await?; let target_sign = data.target_sign.to_keypair::()?; let target_transactions_mortality = data.target_sign.transactions_mortality()?; + let lane_id = HexLaneId::try_convert(data.lane).map_err(|invalid_lane_id| { + anyhow::format_err!("Invalid laneId: {:?}!", invalid_lane_id) + })?; crate::messages::run::(MessagesRelayParams { source_client, @@ -130,7 +134,7 @@ where }, source_to_target_headers_relay: None, target_to_source_headers_relay: None, - lane_id: data.lane.into(), + lane_id, limits: Self::maybe_messages_limits(), metrics_params: data.prometheus_params.into_metrics_params()?, }) @@ -146,6 +150,9 @@ where let source_transactions_mortality = data.source_sign.transactions_mortality()?; let target_sign = data.target_sign.to_keypair::()?; let target_transactions_mortality = data.target_sign.transactions_mortality()?; + let lane_id = HexLaneId::try_convert(data.lane).map_err(|invalid_lane_id| { + anyhow::format_err!("Invalid laneId: {:?}!", invalid_lane_id) + })?; let at_source_block = source_client .header_by_number(data.at_source_block.unique_saturated_into()) @@ -167,7 +174,7 @@ where TransactionParams { signer: source_sign, mortality: source_transactions_mortality }, TransactionParams { signer: target_sign, mortality: target_transactions_mortality }, at_source_block, - data.lane.into(), + lane_id, data.messages_start..=data.messages_end, data.outbound_state_proof_required, ) @@ -182,6 +189,9 @@ where let target_client = data.target.into_client::().await?; let source_sign = data.source_sign.to_keypair::()?; let source_transactions_mortality = data.source_sign.transactions_mortality()?; + let lane_id = HexLaneId::try_convert(data.lane).map_err(|invalid_lane_id| { + anyhow::format_err!("Invalid laneId: {:?}!", invalid_lane_id) + })?; let at_target_block = target_client .header_by_number(data.at_target_block.unique_saturated_into()) @@ -202,7 +212,7 @@ where target_client, TransactionParams { signer: source_sign, mortality: source_transactions_mortality }, at_target_block, - data.lane.into(), + lane_id, ) .await } diff --git a/bridges/relays/lib-substrate-relay/src/messages/metrics.rs b/bridges/relays/lib-substrate-relay/src/messages/metrics.rs index 8845f43dcb6..9d45a4b3d66 100644 --- a/bridges/relays/lib-substrate-relay/src/messages/metrics.rs +++ b/bridges/relays/lib-substrate-relay/src/messages/metrics.rs @@ -18,11 +18,11 @@ use crate::TaggedAccount; -use bp_messages::LaneId; use bp_relayers::{RewardsAccountOwner, RewardsAccountParams}; use bp_runtime::StorageDoubleMapKeyProvider; -use codec::Decode; +use codec::{Decode, EncodeLike}; use frame_system::AccountInfo; +use messages_relay::Labeled; use pallet_balances::AccountData; use relay_substrate_client::{ metrics::{FloatStorageValue, FloatStorageValueMetric}, @@ -35,7 +35,7 @@ use sp_runtime::{FixedPointNumber, FixedU128}; use std::{fmt::Debug, marker::PhantomData}; /// Add relay accounts balance metrics. -pub async fn add_relay_balances_metrics( +pub async fn add_relay_balances_metrics( client: impl Client, metrics: &MetricsParams, relay_accounts: &Vec>>, @@ -43,6 +43,7 @@ pub async fn add_relay_balances_metrics anyhow::Result<()> where BalanceOf: Into + std::fmt::Debug, + LaneId: Clone + Copy + Decode + EncodeLike + Send + Sync + Labeled, { if relay_accounts.is_empty() { return Ok(()) @@ -86,25 +87,25 @@ where FloatStorageValueMetric::new( AccountBalance:: { token_decimals, _phantom: Default::default() }, client.clone(), - bp_relayers::RelayerRewardsKeyProvider::, BalanceOf>::final_key( + bp_relayers::RelayerRewardsKeyProvider::, BalanceOf, LaneId>::final_key( relayers_pallet_name, account.id(), &RewardsAccountParams::new(*lane, BC::ID, RewardsAccountOwner::ThisChain), ), - format!("at_{}_relay_{}_reward_for_msgs_from_{}_on_lane_{}", C::NAME, account.tag(), BC::NAME, hex::encode(lane.as_ref())), - format!("Reward of the {} relay account at {} for delivering messages from {} on lane {:?}", account.tag(), C::NAME, BC::NAME, lane), + format!("at_{}_relay_{}_reward_for_msgs_from_{}_on_lane_{}", C::NAME, account.tag(), BC::NAME, lane.label()), + format!("Reward of the {} relay account at {} for delivering messages from {} on lane {:?}", account.tag(), C::NAME, BC::NAME, lane.label()), )?.register_and_spawn(&metrics.registry)?; FloatStorageValueMetric::new( AccountBalance:: { token_decimals, _phantom: Default::default() }, client.clone(), - bp_relayers::RelayerRewardsKeyProvider::, BalanceOf>::final_key( + bp_relayers::RelayerRewardsKeyProvider::, BalanceOf, LaneId>::final_key( relayers_pallet_name, account.id(), &RewardsAccountParams::new(*lane, BC::ID, RewardsAccountOwner::BridgedChain), ), - format!("at_{}_relay_{}_reward_for_msgs_to_{}_on_lane_{}", C::NAME, account.tag(), BC::NAME, hex::encode(lane.as_ref())), - format!("Reward of the {} relay account at {} for delivering messages confirmations from {} on lane {:?}", account.tag(), C::NAME, BC::NAME, lane), + format!("at_{}_relay_{}_reward_for_msgs_to_{}_on_lane_{}", C::NAME, account.tag(), BC::NAME, lane.label()), + format!("Reward of the {} relay account at {} for delivering messages confirmations from {} on lane {:?}", account.tag(), C::NAME, BC::NAME, lane.label()), )?.register_and_spawn(&metrics.registry)?; } } diff --git a/bridges/relays/lib-substrate-relay/src/messages/mod.rs b/bridges/relays/lib-substrate-relay/src/messages/mod.rs index 28bc5c7f5e8..f7031648bc3 100644 --- a/bridges/relays/lib-substrate-relay/src/messages/mod.rs +++ b/bridges/relays/lib-substrate-relay/src/messages/mod.rs @@ -27,19 +27,17 @@ use crate::{ use async_std::sync::Arc; use bp_messages::{ - target_chain::FromBridgedChainMessagesProof, ChainWithMessages as _, LaneId, MessageNonce, + target_chain::FromBridgedChainMessagesProof, ChainWithMessages as _, MessageNonce, }; -use bp_runtime::{ - AccountIdOf, Chain as _, EncodedOrDecodedCall, HeaderIdOf, TransactionEra, WeightExtraOps, -}; -use codec::Encode; +use bp_runtime::{AccountIdOf, EncodedOrDecodedCall, HeaderIdOf, TransactionEra, WeightExtraOps}; +use codec::{Codec, Encode, EncodeLike}; use frame_support::{dispatch::GetDispatchInfo, weights::Weight}; -use messages_relay::{message_lane::MessageLane, message_lane_loop::BatchTransaction}; +use messages_relay::{message_lane::MessageLane, message_lane_loop::BatchTransaction, Labeled}; use pallet_bridge_messages::{Call as BridgeMessagesCall, Config as BridgeMessagesConfig}; use relay_substrate_client::{ transaction_stall_timeout, AccountKeyPairOf, BalanceOf, BlockNumberOf, CallOf, Chain, - ChainWithMessages, ChainWithTransactions, Client, Error as SubstrateError, HashOf, SignParam, - UnsignedTransaction, + ChainBase, ChainWithMessages, ChainWithTransactions, Client, Error as SubstrateError, HashOf, + SignParam, UnsignedTransaction, }; use relay_utils::{ metrics::{GlobalMetrics, MetricsParams, StandaloneMetric}, @@ -60,6 +58,18 @@ pub trait SubstrateMessageLane: 'static + Clone + Debug + Send + Sync { /// Messages from the `SourceChain` are dispatched on this chain. type TargetChain: ChainWithMessages + ChainWithTransactions; + /// Lane identifier type. + type LaneId: Clone + + Copy + + Debug + + Codec + + EncodeLike + + Send + + Sync + + Labeled + + TryFrom> + + Default; + /// How receive messages proof call is built? type ReceiveMessagesProofCallBuilder: ReceiveMessagesProofCallBuilder; /// How receive messages delivery proof call is built? @@ -81,8 +91,10 @@ impl MessageLane for MessageLaneAdapter

{ const SOURCE_NAME: &'static str = P::SourceChain::NAME; const TARGET_NAME: &'static str = P::TargetChain::NAME; - type MessagesProof = SubstrateMessagesProof; - type MessagesReceivingProof = SubstrateMessagesDeliveryProof; + type LaneId = P::LaneId; + + type MessagesProof = SubstrateMessagesProof; + type MessagesReceivingProof = SubstrateMessagesDeliveryProof; type SourceChainBalance = BalanceOf; type SourceHeaderNumber = BlockNumberOf; @@ -109,7 +121,7 @@ pub struct MessagesRelayParams pub target_to_source_headers_relay: Option>>, /// Identifier of lane that needs to be served. - pub lane_id: LaneId, + pub lane_id: P::LaneId, /// Messages relay limits. If not provided, the relay tries to determine it automatically, /// using `TransactionPayment` pallet runtime API. pub limits: Option, @@ -293,7 +305,7 @@ pub async fn relay_messages_range( source_transaction_params: TransactionParams>, target_transaction_params: TransactionParams>, at_source_block: HeaderIdOf, - lane_id: LaneId, + lane_id: P::LaneId, range: RangeInclusive, outbound_state_proof_required: bool, ) -> anyhow::Result<()> @@ -335,7 +347,7 @@ pub async fn relay_messages_delivery_confirmation( target_client: impl Client, source_transaction_params: TransactionParams>, at_target_block: HeaderIdOf, - lane_id: LaneId, + lane_id: P::LaneId, ) -> anyhow::Result<()> where AccountIdOf: From< as Pair>::Public>, @@ -372,7 +384,7 @@ pub trait ReceiveMessagesProofCallBuilder { /// messages module at the target chain. fn build_receive_messages_proof_call( relayer_id_at_source: AccountIdOf, - proof: SubstrateMessagesProof, + proof: SubstrateMessagesProof, messages_count: u32, dispatch_weight: Weight, trace_call: bool, @@ -388,7 +400,7 @@ pub struct DirectReceiveMessagesProofCallBuilder { impl ReceiveMessagesProofCallBuilder

for DirectReceiveMessagesProofCallBuilder where P: SubstrateMessageLane, - R: BridgeMessagesConfig, + R: BridgeMessagesConfig, I: 'static, R::BridgedChain: bp_runtime::Chain, Hash = HashOf>, @@ -396,7 +408,7 @@ where { fn build_receive_messages_proof_call( relayer_id_at_source: AccountIdOf, - proof: SubstrateMessagesProof, + proof: SubstrateMessagesProof, messages_count: u32, dispatch_weight: Weight, trace_call: bool, @@ -444,7 +456,8 @@ macro_rules! generate_receive_message_proof_call_builder { <$pipeline as $crate::messages::SubstrateMessageLane>::SourceChain >, proof: $crate::messages::source::SubstrateMessagesProof< - <$pipeline as $crate::messages::SubstrateMessageLane>::SourceChain + <$pipeline as $crate::messages::SubstrateMessageLane>::SourceChain, + <$pipeline as $crate::messages::SubstrateMessageLane>::LaneId >, messages_count: u32, dispatch_weight: bp_messages::Weight, @@ -470,7 +483,7 @@ pub trait ReceiveMessagesDeliveryProofCallBuilder { /// Given messages delivery proof, build call of `receive_messages_delivery_proof` function of /// bridge messages module at the source chain. fn build_receive_messages_delivery_proof_call( - proof: SubstrateMessagesDeliveryProof, + proof: SubstrateMessagesDeliveryProof, trace_call: bool, ) -> CallOf; } @@ -485,13 +498,13 @@ impl ReceiveMessagesDeliveryProofCallBuilder

for DirectReceiveMessagesDeliveryProofCallBuilder where P: SubstrateMessageLane, - R: BridgeMessagesConfig, + R: BridgeMessagesConfig, I: 'static, R::BridgedChain: bp_runtime::Chain>, CallOf: From> + GetDispatchInfo, { fn build_receive_messages_delivery_proof_call( - proof: SubstrateMessagesDeliveryProof, + proof: SubstrateMessagesDeliveryProof, trace_call: bool, ) -> CallOf { let call: CallOf = @@ -533,7 +546,8 @@ macro_rules! generate_receive_message_delivery_proof_call_builder { { fn build_receive_messages_delivery_proof_call( proof: $crate::messages::target::SubstrateMessagesDeliveryProof< - <$pipeline as $crate::messages::SubstrateMessageLane>::TargetChain + <$pipeline as $crate::messages::SubstrateMessageLane>::TargetChain, + <$pipeline as $crate::messages::SubstrateMessageLane>::LaneId >, _trace_call: bool, ) -> relay_substrate_client::CallOf< @@ -644,7 +658,7 @@ where FromBridgedChainMessagesProof { bridged_header_hash: Default::default(), storage_proof: Default::default(), - lane: LaneId::new(1, 2), + lane: P::LaneId::default(), nonces_start: 1, nonces_end: messages as u64, }, @@ -674,7 +688,7 @@ where mod tests { use super::*; use bp_messages::{ - source_chain::FromBridgedChainMessagesDeliveryProof, UnrewardedRelayersState, + source_chain::FromBridgedChainMessagesDeliveryProof, LaneIdType, UnrewardedRelayersState, }; use relay_substrate_client::calls::{UtilityCall as MockUtilityCall, UtilityCall}; @@ -687,8 +701,8 @@ mod tests { } pub type CodegenBridgeMessagesCall = bp_messages::BridgeMessagesCall< u64, - Box>, - FromBridgedChainMessagesDeliveryProof, + Box>, + FromBridgedChainMessagesDeliveryProof, >; impl From> for RuntimeCall { @@ -706,7 +720,7 @@ mod tests { let receive_messages_proof = FromBridgedChainMessagesProof { bridged_header_hash: Default::default(), storage_proof: Default::default(), - lane: LaneId::new(1, 2), + lane: mock::TestLaneIdType::try_new(1, 2).unwrap(), nonces_start: 0, nonces_end: 0, }; @@ -761,7 +775,7 @@ mod tests { let receive_messages_delivery_proof = FromBridgedChainMessagesDeliveryProof { bridged_header_hash: Default::default(), storage_proof: Default::default(), - lane: LaneId::new(1, 2), + lane: mock::TestLaneIdType::try_new(1, 2).unwrap(), }; let relayers_state = UnrewardedRelayersState { unrewarded_relayer_entries: 0, @@ -808,7 +822,7 @@ mod tests { // mock runtime with `pallet_bridge_messages` mod mock { use super::super::*; - use bp_messages::target_chain::ForbidInboundMessages; + use bp_messages::{target_chain::ForbidInboundMessages, HashedLaneId}; use bp_runtime::ChainId; use frame_support::derive_impl; use sp_core::H256; @@ -819,6 +833,9 @@ mod tests { type Block = frame_system::mocking::MockBlock; pub type SignedBlock = generic::SignedBlock; + /// Lane identifier type used for tests. + pub type TestLaneIdType = HashedLaneId; + frame_support::construct_runtime! { pub enum TestRuntime { @@ -840,10 +857,11 @@ mod tests { type BridgedHeaderChain = BridgedHeaderChain; type OutboundPayload = Vec; type InboundPayload = Vec; + type LaneId = TestLaneIdType; type DeliveryPayments = (); type DeliveryConfirmationPayments = (); type OnMessagesDelivered = (); - type MessageDispatch = ForbidInboundMessages>; + type MessageDispatch = ForbidInboundMessages, Self::LaneId>; } pub struct ThisUnderlyingChain; @@ -1005,6 +1023,7 @@ mod tests { impl SubstrateMessageLane for ThisChainToBridgedChainMessageLane { type SourceChain = ThisChain; type TargetChain = BridgedChain; + type LaneId = mock::TestLaneIdType; type ReceiveMessagesProofCallBuilder = ThisChainToBridgedChainMessageLaneReceiveMessagesProofCallBuilder; type ReceiveMessagesDeliveryProofCallBuilder = diff --git a/bridges/relays/lib-substrate-relay/src/messages/source.rs b/bridges/relays/lib-substrate-relay/src/messages/source.rs index 2c49df3452a..b560867a235 100644 --- a/bridges/relays/lib-substrate-relay/src/messages/source.rs +++ b/bridges/relays/lib-substrate-relay/src/messages/source.rs @@ -34,7 +34,7 @@ use async_trait::async_trait; use bp_messages::{ storage_keys::{operating_mode_key, outbound_lane_data_key}, target_chain::FromBridgedChainMessagesProof, - ChainWithMessages as _, InboundMessageDetails, LaneId, MessageNonce, MessagePayload, + ChainWithMessages as _, InboundMessageDetails, MessageNonce, MessagePayload, MessagesOperatingMode, OutboundLaneData, OutboundMessageDetails, }; use bp_runtime::{BasicOperatingMode, HeaderIdProvider, RangeInclusiveExt}; @@ -60,14 +60,14 @@ use std::ops::RangeInclusive; /// Intermediate message proof returned by the source Substrate node. Includes everything /// required to submit to the target node: cumulative dispatch weight of bundled messages and /// the proof itself. -pub type SubstrateMessagesProof = (Weight, FromBridgedChainMessagesProof>); +pub type SubstrateMessagesProof = (Weight, FromBridgedChainMessagesProof, L>); type MessagesToRefine<'a> = Vec<(MessagePayload, &'a mut OutboundMessageDetails)>; /// Substrate client as Substrate messages source. pub struct SubstrateMessagesSource { source_client: SourceClnt, target_client: TargetClnt, - lane_id: LaneId, + lane_id: P::LaneId, transaction_params: TransactionParams>, target_to_source_headers_relay: Option>>, } @@ -79,7 +79,7 @@ impl, TargetClnt> pub fn new( source_client: SourceClnt, target_client: TargetClnt, - lane_id: LaneId, + lane_id: P::LaneId, transaction_params: TransactionParams>, target_to_source_headers_relay: Option< Arc>, @@ -256,8 +256,11 @@ where } let best_target_header_hash = self.target_client.best_header_hash().await?; - for mut msgs_to_refine_batch in - split_msgs_to_refine::(self.lane_id, msgs_to_refine)? + for mut msgs_to_refine_batch in split_msgs_to_refine::< + P::SourceChain, + P::TargetChain, + P::LaneId, + >(self.lane_id, msgs_to_refine)? { let in_msgs_details = self .target_client @@ -542,7 +545,7 @@ fn validate_out_msgs_details( Ok(()) } -fn split_msgs_to_refine( +fn split_msgs_to_refine( lane_id: LaneId, msgs_to_refine: MessagesToRefine, ) -> Result, SubstrateError> { @@ -578,8 +581,12 @@ fn split_msgs_to_refine( #[cfg(test)] mod tests { use super::*; + use bp_messages::{HashedLaneId, LaneIdType}; use relay_substrate_client::test_chain::TestChain; + /// Lane identifier type used for tests. + type TestLaneIdType = HashedLaneId; + fn message_details_from_rpc( nonces: RangeInclusive, ) -> Vec { @@ -660,8 +667,10 @@ mod tests { msgs_to_refine.push((payload, out_msg_details)); } - let maybe_batches = - split_msgs_to_refine::(LaneId::new(1, 2), msgs_to_refine); + let maybe_batches = split_msgs_to_refine::( + TestLaneIdType::try_new(1, 2).unwrap(), + msgs_to_refine, + ); match expected_batches { Ok(expected_batches) => { let batches = maybe_batches.unwrap(); diff --git a/bridges/relays/lib-substrate-relay/src/messages/target.rs b/bridges/relays/lib-substrate-relay/src/messages/target.rs index a6bf169cffb..0d1aac88a32 100644 --- a/bridges/relays/lib-substrate-relay/src/messages/target.rs +++ b/bridges/relays/lib-substrate-relay/src/messages/target.rs @@ -36,7 +36,7 @@ use async_std::sync::Arc; use async_trait::async_trait; use bp_messages::{ source_chain::FromBridgedChainMessagesDeliveryProof, storage_keys::inbound_lane_data_key, - ChainWithMessages as _, InboundLaneData, LaneId, MessageNonce, UnrewardedRelayersState, + ChainWithMessages as _, InboundLaneData, MessageNonce, UnrewardedRelayersState, }; use messages_relay::{ message_lane::{MessageLane, SourceHeaderIdOf, TargetHeaderIdOf}, @@ -51,14 +51,14 @@ use sp_core::Pair; use std::{convert::TryFrom, ops::RangeInclusive}; /// Message receiving proof returned by the target Substrate node. -pub type SubstrateMessagesDeliveryProof = - (UnrewardedRelayersState, FromBridgedChainMessagesDeliveryProof>); +pub type SubstrateMessagesDeliveryProof = + (UnrewardedRelayersState, FromBridgedChainMessagesDeliveryProof, L>); /// Substrate client as Substrate messages target. pub struct SubstrateMessagesTarget { target_client: TargetClnt, source_client: SourceClnt, - lane_id: LaneId, + lane_id: P::LaneId, relayer_id_at_source: AccountIdOf, transaction_params: Option>>, source_to_target_headers_relay: Option>>, @@ -73,7 +73,7 @@ where pub fn new( target_client: TargetClnt, source_client: SourceClnt, - lane_id: LaneId, + lane_id: P::LaneId, relayer_id_at_source: AccountIdOf, transaction_params: Option>>, source_to_target_headers_relay: Option< @@ -308,7 +308,7 @@ where fn make_messages_delivery_call( relayer_id_at_source: AccountIdOf, nonces: RangeInclusive, - proof: SubstrateMessagesProof, + proof: SubstrateMessagesProof, trace_call: bool, ) -> CallOf { let messages_count = nonces.end() - nonces.start() + 1; diff --git a/bridges/relays/messages/Cargo.toml b/bridges/relays/messages/Cargo.toml index c7a132bb3ba..f9df73507c7 100644 --- a/bridges/relays/messages/Cargo.toml +++ b/bridges/relays/messages/Cargo.toml @@ -26,3 +26,6 @@ finality-relay = { workspace = true } relay-utils = { workspace = true } sp-arithmetic = { workspace = true, default-features = true } + +[dev-dependencies] +sp-core = { workspace = true } diff --git a/bridges/relays/messages/src/lib.rs b/bridges/relays/messages/src/lib.rs index 78a3237ba4f..f5e09f4d468 100644 --- a/bridges/relays/messages/src/lib.rs +++ b/bridges/relays/messages/src/lib.rs @@ -38,3 +38,4 @@ mod message_race_strategy; pub use message_race_delivery::relay_messages_range; pub use message_race_receiving::relay_messages_delivery_confirmation; +pub use metrics::Labeled; diff --git a/bridges/relays/messages/src/message_lane.rs b/bridges/relays/messages/src/message_lane.rs index 5c9728ad93a..84c1e57ba4e 100644 --- a/bridges/relays/messages/src/message_lane.rs +++ b/bridges/relays/messages/src/message_lane.rs @@ -19,6 +19,7 @@ //! 1) relay new messages from source to target node; //! 2) relay proof-of-delivery from target to source node. +use crate::metrics::Labeled; use num_traits::{SaturatingAdd, Zero}; use relay_utils::{BlockNumberBase, HeaderId}; use sp_arithmetic::traits::AtLeast32BitUnsigned; @@ -31,6 +32,9 @@ pub trait MessageLane: 'static + Clone + Send + Sync { /// Name of the messages target. const TARGET_NAME: &'static str; + /// Lane identifier type. + type LaneId: Clone + Send + Sync + Labeled; + /// Messages proof. type MessagesProof: Clone + Debug + Send + Sync; /// Messages receiving proof. diff --git a/bridges/relays/messages/src/message_lane_loop.rs b/bridges/relays/messages/src/message_lane_loop.rs index 995499092c3..36de637f04c 100644 --- a/bridges/relays/messages/src/message_lane_loop.rs +++ b/bridges/relays/messages/src/message_lane_loop.rs @@ -29,7 +29,7 @@ use std::{collections::BTreeMap, fmt::Debug, future::Future, ops::RangeInclusive use async_trait::async_trait; use futures::{channel::mpsc::unbounded, future::FutureExt, stream::StreamExt}; -use bp_messages::{LaneId, MessageNonce, UnrewardedRelayersState, Weight}; +use bp_messages::{MessageNonce, UnrewardedRelayersState, Weight}; use relay_utils::{ interval, metrics::MetricsParams, process_future_result, relay_loop::Client as RelayClient, retry_backoff, FailedClient, TransactionTracker, @@ -39,12 +39,12 @@ use crate::{ message_lane::{MessageLane, SourceHeaderIdOf, TargetHeaderIdOf}, message_race_delivery::run as run_message_delivery_race, message_race_receiving::run as run_message_receiving_race, - metrics::MessageLaneLoopMetrics, + metrics::{Labeled, MessageLaneLoopMetrics}, }; /// Message lane loop configuration params. #[derive(Debug, Clone)] -pub struct Params { +pub struct Params { /// Id of lane this loop is servicing. pub lane: LaneId, /// Interval at which we ask target node about its updates. @@ -275,13 +275,13 @@ pub struct ClientsState { /// Return prefix that will be used by default to expose Prometheus metrics of the finality proofs /// sync loop. -pub fn metrics_prefix(lane: &LaneId) -> String { - format!("{}_to_{}_MessageLane_{:?}", P::SOURCE_NAME, P::TARGET_NAME, lane) +pub fn metrics_prefix(lane: &P::LaneId) -> String { + format!("{}_to_{}_MessageLane_{}", P::SOURCE_NAME, P::TARGET_NAME, lane.label()) } /// Run message lane service loop. pub async fn run( - params: Params, + params: Params, source_client: impl SourceClient

, target_client: impl TargetClient

, metrics_params: MetricsParams, @@ -309,7 +309,7 @@ pub async fn run( /// Run one-way message delivery loop until connection with target or source node is lost, or exit /// signal is received. async fn run_until_connection_lost, TC: TargetClient

>( - params: Params, + params: Params, source_client: SC, target_client: TC, metrics_msg: Option, @@ -471,9 +471,9 @@ async fn run_until_connection_lost, TC: Targ pub(crate) mod tests { use std::sync::Arc; + use bp_messages::{HashedLaneId, LaneIdType, LegacyLaneId}; use futures::stream::StreamExt; use parking_lot::Mutex; - use relay_utils::{HeaderId, MaybeConnectionError, TrackedTransactionStatus}; use super::*; @@ -504,6 +504,9 @@ pub(crate) mod tests { } } + /// Lane identifier type used for tests. + pub type TestLaneIdType = HashedLaneId; + #[derive(Clone)] pub struct TestMessageLane; @@ -520,6 +523,8 @@ pub(crate) mod tests { type TargetHeaderNumber = TestTargetHeaderNumber; type TargetHeaderHash = TestTargetHeaderHash; + + type LaneId = TestLaneIdType; } #[derive(Clone, Debug)] @@ -957,7 +962,7 @@ pub(crate) mod tests { }; let _ = run( Params { - lane: LaneId::new(1, 2), + lane: TestLaneIdType::try_new(1, 2).unwrap(), source_tick: Duration::from_millis(100), target_tick: Duration::from_millis(100), reconnect_delay: Duration::from_millis(0), @@ -1278,7 +1283,31 @@ pub(crate) mod tests { #[test] fn metrics_prefix_is_valid() { assert!(MessageLaneLoopMetrics::new(Some(&metrics_prefix::( - &LaneId::new(1, 2) + &HashedLaneId::try_new(1, 2).unwrap() + ))) + .is_ok()); + + // with LegacyLaneId + #[derive(Clone)] + pub struct LegacyTestMessageLane; + impl MessageLane for LegacyTestMessageLane { + const SOURCE_NAME: &'static str = "LegacyTestSource"; + const TARGET_NAME: &'static str = "LegacyTestTarget"; + + type MessagesProof = TestMessagesProof; + type MessagesReceivingProof = TestMessagesReceivingProof; + + type SourceChainBalance = TestSourceChainBalance; + type SourceHeaderNumber = TestSourceHeaderNumber; + type SourceHeaderHash = TestSourceHeaderHash; + + type TargetHeaderNumber = TestTargetHeaderNumber; + type TargetHeaderHash = TestTargetHeaderHash; + + type LaneId = LegacyLaneId; + } + assert!(MessageLaneLoopMetrics::new(Some(&metrics_prefix::( + &LegacyLaneId([0, 0, 0, 1]) ))) .is_ok()); } diff --git a/bridges/relays/messages/src/metrics.rs b/bridges/relays/messages/src/metrics.rs index 69d80d178de..2ca10e56d74 100644 --- a/bridges/relays/messages/src/metrics.rs +++ b/bridges/relays/messages/src/metrics.rs @@ -21,7 +21,7 @@ use crate::{ message_lane_loop::{SourceClientState, TargetClientState}, }; -use bp_messages::MessageNonce; +use bp_messages::{HashedLaneId, LegacyLaneId, MessageNonce}; use finality_relay::SyncLoopMetrics; use relay_utils::metrics::{ metric_name, register, GaugeVec, Metric, Opts, PrometheusError, Registry, U64, @@ -146,3 +146,32 @@ impl Metric for MessageLaneLoopMetrics { Ok(()) } } + +/// Provides a label for metrics. +pub trait Labeled { + /// Returns a label. + fn label(&self) -> String; +} + +/// `Labeled` implementation for `LegacyLaneId`. +impl Labeled for LegacyLaneId { + fn label(&self) -> String { + hex::encode(self.0) + } +} + +/// `Labeled` implementation for `HashedLaneId`. +impl Labeled for HashedLaneId { + fn label(&self) -> String { + format!("{:?}", self.inner()) + } +} + +#[test] +fn lane_to_label_works() { + assert_eq!( + "0x0101010101010101010101010101010101010101010101010101010101010101", + HashedLaneId::from_inner(sp_core::H256::from([1u8; 32])).label(), + ); + assert_eq!("00000001", LegacyLaneId([0, 0, 0, 1]).label()); +} diff --git a/bridges/testing/README.md b/bridges/testing/README.md index 158dfd73b1a..89a07c421e3 100644 --- a/bridges/testing/README.md +++ b/bridges/testing/README.md @@ -22,7 +22,7 @@ Prerequisites for running the tests locally: - copy the `substrate-relay` binary, built in the previous step, to `~/local_bridge_testing/bin/substrate-relay`; After that, any test can be run using the `run-test.sh` command. -Example: `./run-new-test.sh 0001-asset-transfer` +Example: `./run-test.sh 0001-asset-transfer` Hopefully, it'll show the "All tests have completed successfully" message in the end. Otherwise, it'll print paths to zombienet diff --git a/bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh b/bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh index 54633449134..e7848fe7163 100755 --- a/bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh +++ b/bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh @@ -53,66 +53,66 @@ ASSET_HUB_ROCOCO_SOVEREIGN_ACCOUNT_AT_BRIDGE_HUB_ROCOCO="5Eg2fntNprdN3FgH4sfEaaZ # Expected sovereign accounts for rewards on BridgeHubs. # # Generated by: -# #[test] -# fn generate_sovereign_accounts_for_rewards() { -# use bp_messages::LaneId; -# use bp_relayers::{PayRewardFromAccount, RewardsAccountOwner, RewardsAccountParams}; -# use sp_core::crypto::Ss58Codec; +##[test] +#fn generate_sovereign_accounts_for_rewards() { +# use bp_messages::LegacyLaneId; +# use bp_relayers::{PayRewardFromAccount, RewardsAccountOwner, RewardsAccountParams}; +# use sp_core::crypto::Ss58Codec; # -# // SS58=42 -# println!( -# "ON_BRIDGE_HUB_ROCOCO_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhwd_ThisChain=\"{}\"", -# frame_support::sp_runtime::AccountId32::new( -# PayRewardFromAccount::<[u8; 32], [u8; 32]>::rewards_account(RewardsAccountParams::new( -# LaneId([0, 0, 0, 2]), -# *b"bhwd", -# RewardsAccountOwner::ThisChain -# )) -# ) -# .to_ss58check_with_version(42_u16.into()) -# ); -# // SS58=42 -# println!( -# "ON_BRIDGE_HUB_ROCOCO_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhwd_BridgedChain=\"{}\"", -# frame_support::sp_runtime::AccountId32::new( -# PayRewardFromAccount::<[u8; 32], [u8; 32]>::rewards_account(RewardsAccountParams::new( -# LaneId([0, 0, 0, 2]), -# *b"bhwd", -# RewardsAccountOwner::BridgedChain -# )) -# ) -# .to_ss58check_with_version(42_u16.into()) -# ); +# // SS58=42 +# println!( +# "ON_BRIDGE_HUB_ROCOCO_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhwd_ThisChain=\"{}\"", +# frame_support::sp_runtime::AccountId32::new( +# PayRewardFromAccount::<[u8; 32], [u8; 32], LegacyLaneId>::rewards_account(RewardsAccountParams::new( +# LegacyLaneId([0, 0, 0, 2]), +# *b"bhwd", +# RewardsAccountOwner::ThisChain +# )) +# ) +# .to_ss58check_with_version(42_u16.into()) +# ); +# // SS58=42 +# println!( +# "ON_BRIDGE_HUB_ROCOCO_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhwd_BridgedChain=\"{}\"", +# frame_support::sp_runtime::AccountId32::new( +# PayRewardFromAccount::<[u8; 32], [u8; 32], LegacyLaneId>::rewards_account(RewardsAccountParams::new( +# LegacyLaneId([0, 0, 0, 2]), +# *b"bhwd", +# RewardsAccountOwner::BridgedChain +# )) +# ) +# .to_ss58check_with_version(42_u16.into()) +# ); # -# // SS58=42 -# println!( -# "ON_BRIDGE_HUB_WESTEND_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhro_ThisChain=\"{}\"", -# frame_support::sp_runtime::AccountId32::new( -# PayRewardFromAccount::<[u8; 32], [u8; 32]>::rewards_account(RewardsAccountParams::new( -# LaneId([0, 0, 0, 2]), -# *b"bhro", -# RewardsAccountOwner::ThisChain -# )) -# ) -# .to_ss58check_with_version(42_u16.into()) -# ); -# // SS58=42 -# println!( -# "ON_BRIDGE_HUB_WESTEND_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhro_BridgedChain=\"{}\"", -# frame_support::sp_runtime::AccountId32::new( -# PayRewardFromAccount::<[u8; 32], [u8; 32]>::rewards_account(RewardsAccountParams::new( -# LaneId([0, 0, 0, 2]), -# *b"bhro", -# RewardsAccountOwner::BridgedChain -# )) -# ) -# .to_ss58check_with_version(42_u16.into()) -# ); -# } -ON_BRIDGE_HUB_ROCOCO_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhwd_ThisChain="5EHnXaT5BhiSGP5hbdsoVGtzi2sQVgpDNToTxLYeQvKoMPEm" -ON_BRIDGE_HUB_ROCOCO_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhwd_BridgedChain="5EHnXaT5BhiSGP5hbdt5EJSapXYbxEv678jyWHEUskCXcjqo" -ON_BRIDGE_HUB_WESTEND_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhro_ThisChain="5EHnXaT5BhiSGP5h9Rg8sgUJqoLym3iEaWUiboT8S9AT5xFh" -ON_BRIDGE_HUB_WESTEND_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhro_BridgedChain="5EHnXaT5BhiSGP5h9RgQci1txJ2BDbp7KBRE9k8xty3BMUSi" +# // SS58=42 +# println!( +# "ON_BRIDGE_HUB_WESTEND_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhro_ThisChain=\"{}\"", +# frame_support::sp_runtime::AccountId32::new( +# PayRewardFromAccount::<[u8; 32], [u8; 32], LegacyLaneId>::rewards_account(RewardsAccountParams::new( +# LegacyLaneId([0, 0, 0, 2]), +# *b"bhro", +# RewardsAccountOwner::ThisChain +# )) +# ) +# .to_ss58check_with_version(42_u16.into()) +# ); +# // SS58=42 +# println!( +# "ON_BRIDGE_HUB_WESTEND_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhro_BridgedChain=\"{}\"", +# frame_support::sp_runtime::AccountId32::new( +# PayRewardFromAccount::<[u8; 32], [u8; 32], LegacyLaneId>::rewards_account(RewardsAccountParams::new( +# LegacyLaneId([0, 0, 0, 2]), +# *b"bhro", +# RewardsAccountOwner::BridgedChain +# )) +# ) +# .to_ss58check_with_version(42_u16.into()) +# ); +#} +ON_BRIDGE_HUB_ROCOCO_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhwd_ThisChain="5EHnXaT5GApse1euZWj9hycMbgjKBCNQL9WEwScL8QDx6mhK" +ON_BRIDGE_HUB_ROCOCO_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhwd_BridgedChain="5EHnXaT5Tnt4A8aiP9CsuAFRhKPjKZJXRrj4a3mtihFvKpTi" +ON_BRIDGE_HUB_WESTEND_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhro_ThisChain="5EHnXaT5GApry9tS6yd1FVusPq8o8bQJGCKyvXTFCoEKk5Z9" +ON_BRIDGE_HUB_WESTEND_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhro_BridgedChain="5EHnXaT5Tnt3VGpEvc6jSgYwVToDGxLRMuYoZ8coo6GHyWbR" LANE_ID="00000002" XCM_VERSION=3 diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/Cargo.toml b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/Cargo.toml index f3c0799ad0f..266d743ca0c 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/Cargo.toml @@ -16,6 +16,12 @@ workspace = true sp-core = { workspace = true } frame-support = { workspace = true } +# Polkadot Dependencies +xcm = { workspace = true } + +# Bridge dependencies +bp-messages = { workspace = true } + # Cumulus parachains-common = { workspace = true, default-features = true } emulated-integration-tests-common = { workspace = true } diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/src/genesis.rs index 3786d529ea6..b9c0c01101c 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/src/genesis.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/src/genesis.rs @@ -21,6 +21,7 @@ use emulated_integration_tests_common::{ accounts, build_genesis_storage, collators, get_account_id_from_seed, SAFE_XCM_VERSION, }; use parachains_common::Balance; +use xcm::latest::prelude::*; pub const ASSETHUB_PARA_ID: u32 = 1000; pub const PARA_ID: u32 = 1013; @@ -66,6 +67,17 @@ pub fn genesis() -> Storage { owner: Some(get_account_id_from_seed::(accounts::BOB)), ..Default::default() }, + xcm_over_bridge_hub_westend: bridge_hub_rococo_runtime::XcmOverBridgeHubWestendConfig { + opened_bridges: vec![ + // open AHR -> AHW bridge + ( + Location::new(1, [Parachain(1000)]), + Junctions::from([Westend.into(), Parachain(1000)]), + Some(bp_messages::LegacyLaneId([0, 0, 0, 2])), + ), + ], + ..Default::default() + }, ethereum_system: bridge_hub_rococo_runtime::EthereumSystemConfig { para_id: PARA_ID.into(), asset_hub_para_id: ASSETHUB_PARA_ID.into(), diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/Cargo.toml index ebcec9641e7..88d7348f50f 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/Cargo.toml @@ -16,6 +16,12 @@ workspace = true sp-core = { workspace = true } frame-support = { workspace = true } +# Polkadot Dependencies +xcm = { workspace = true } + +# Bridge dependencies +bp-messages = { workspace = true } + # Cumulus parachains-common = { workspace = true, default-features = true } emulated-integration-tests-common = { workspace = true } diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/src/genesis.rs index f38f385db65..3ffe3d86b2a 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/src/genesis.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/src/genesis.rs @@ -21,6 +21,7 @@ use emulated_integration_tests_common::{ accounts, build_genesis_storage, collators, get_account_id_from_seed, SAFE_XCM_VERSION, }; use parachains_common::Balance; +use xcm::latest::prelude::*; pub const PARA_ID: u32 = 1002; pub const ASSETHUB_PARA_ID: u32 = 1000; @@ -66,6 +67,17 @@ pub fn genesis() -> Storage { owner: Some(get_account_id_from_seed::(accounts::BOB)), ..Default::default() }, + xcm_over_bridge_hub_rococo: bridge_hub_westend_runtime::XcmOverBridgeHubRococoConfig { + opened_bridges: vec![ + // open AHW -> AHR bridge + ( + Location::new(1, [Parachain(1000)]), + Junctions::from([Rococo.into(), Parachain(1000)]), + Some(bp_messages::LegacyLaneId([0, 0, 0, 2])), + ), + ], + ..Default::default() + }, ethereum_system: bridge_hub_westend_runtime::EthereumSystemConfig { para_id: PARA_ID.into(), asset_hub_para_id: ASSETHUB_PARA_ID.into(), diff --git a/cumulus/parachains/integration-tests/emulated/common/src/impls.rs b/cumulus/parachains/integration-tests/emulated/common/src/impls.rs index 559a16379bb..c0d42cf2758 100644 --- a/cumulus/parachains/integration-tests/emulated/common/src/impls.rs +++ b/cumulus/parachains/integration-tests/emulated/common/src/impls.rs @@ -61,10 +61,10 @@ pub use xcm_emulator::{ // Bridges use bp_messages::{ target_chain::{DispatchMessage, DispatchMessageData, MessageDispatch}, - LaneId, MessageKey, OutboundLaneData, + MessageKey, OutboundLaneData, }; pub use bp_xcm_bridge_hub::XcmBridgeHubCall; -use pallet_bridge_messages::{Config as BridgeMessagesConfig, OutboundLanes, Pallet}; +use pallet_bridge_messages::{Config as BridgeMessagesConfig, LaneIdOf, OutboundLanes, Pallet}; pub use pallet_bridge_messages::{ Instance1 as BridgeMessagesInstance1, Instance2 as BridgeMessagesInstance2, Instance3 as BridgeMessagesInstance3, @@ -75,14 +75,14 @@ pub struct BridgeHubMessageHandler { _marker: std::marker::PhantomData<(S, SI, T, TI)>, } -struct LaneIdWrapper(LaneId); -impl From for BridgeLaneId { - fn from(lane_id: LaneIdWrapper) -> BridgeLaneId { +struct LaneIdWrapper(LaneId); +impl From> for BridgeLaneId { + fn from(lane_id: LaneIdWrapper) -> BridgeLaneId { lane_id.0.encode() } } -impl From for LaneIdWrapper { - fn from(id: BridgeLaneId) -> LaneIdWrapper { +impl From for LaneIdWrapper { + fn from(id: BridgeLaneId) -> LaneIdWrapper { LaneIdWrapper(LaneId::decode(&mut &id[..]).expect("decodable")) } } @@ -154,7 +154,7 @@ where } fn notify_source_message_delivery(lane_id: BridgeLaneId) { - let lane_id = LaneIdWrapper::from(lane_id).0; + let lane_id: LaneIdOf = LaneIdWrapper::from(lane_id).0; let data = OutboundLanes::::get(lane_id).unwrap(); let new_data = OutboundLaneData { oldest_unpruned_nonce: data.oldest_unpruned_nonce + 1, diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/mod.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/mod.rs index b540f55642a..a989881fef0 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/mod.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/mod.rs @@ -231,17 +231,6 @@ pub(crate) fn open_bridge_between_asset_hub_rococo_and_asset_hub_westend() { )), )), ); - BridgeHubRococo::execute_with(|| { - type RuntimeEvent = ::RuntimeEvent; - assert_expected_events!( - BridgeHubRococo, - vec![ - RuntimeEvent::XcmOverBridgeHubWestend( - pallet_xcm_bridge_hub::Event::BridgeOpened { .. } - ) => {}, - ] - ); - }); // open AHW -> AHR BridgeHubWestend::fund_para_sovereign(AssetHubWestend::para_id(), WND * 5); @@ -255,15 +244,4 @@ pub(crate) fn open_bridge_between_asset_hub_rococo_and_asset_hub_westend() { )), )), ); - BridgeHubWestend::execute_with(|| { - type RuntimeEvent = ::RuntimeEvent; - assert_expected_events!( - BridgeHubWestend, - vec![ - RuntimeEvent::XcmOverBridgeHubRococo( - pallet_xcm_bridge_hub::Event::BridgeOpened { .. } - ) => {}, - ] - ); - }); } diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/mod.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/mod.rs index 699641d3328..f037a05a827 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/mod.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/mod.rs @@ -246,17 +246,6 @@ pub(crate) fn open_bridge_between_asset_hub_rococo_and_asset_hub_westend() { )), )), ); - BridgeHubRococo::execute_with(|| { - type RuntimeEvent = ::RuntimeEvent; - assert_expected_events!( - BridgeHubRococo, - vec![ - RuntimeEvent::XcmOverBridgeHubWestend( - pallet_xcm_bridge_hub::Event::BridgeOpened { .. } - ) => {}, - ] - ); - }); // open AHW -> AHR BridgeHubWestend::fund_para_sovereign(AssetHubWestend::para_id(), WND * 5); @@ -270,15 +259,4 @@ pub(crate) fn open_bridge_between_asset_hub_rococo_and_asset_hub_westend() { )), )), ); - BridgeHubWestend::execute_with(|| { - type RuntimeEvent = ::RuntimeEvent; - assert_expected_events!( - BridgeHubWestend, - vec![ - RuntimeEvent::XcmOverBridgeHubRococo( - pallet_xcm_bridge_hub::Event::BridgeOpened { .. } - ) => {}, - ] - ); - }); } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_common_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_common_config.rs index 779cc537ee9..5dca45d326b 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_common_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_common_config.rs @@ -64,11 +64,37 @@ impl pallet_bridge_parachains::Config for Runtim } /// Allows collect and claim rewards for relayers -impl pallet_bridge_relayers::Config for Runtime { +pub type RelayersForLegacyLaneIdsMessagesInstance = (); +impl pallet_bridge_relayers::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Reward = Balance; - type PaymentProcedure = - bp_relayers::PayRewardFromAccount, AccountId>; + type PaymentProcedure = bp_relayers::PayRewardFromAccount< + pallet_balances::Pallet, + AccountId, + Self::LaneId, + >; + type StakeAndSlash = pallet_bridge_relayers::StakeAndSlashNamed< + AccountId, + BlockNumber, + Balances, + RelayerStakeReserveId, + RequiredStakeForStakeAndSlash, + RelayerStakeLease, + >; + type WeightInfo = weights::pallet_bridge_relayers::WeightInfo; + type LaneId = bp_messages::LegacyLaneId; +} + +/// Allows collect and claim rewards for relayers +pub type RelayersForPermissionlessLanesInstance = pallet_bridge_relayers::Instance2; +impl pallet_bridge_relayers::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Reward = Balance; + type PaymentProcedure = bp_relayers::PayRewardFromAccount< + pallet_balances::Pallet, + AccountId, + Self::LaneId, + >; type StakeAndSlash = pallet_bridge_relayers::StakeAndSlashNamed< AccountId, BlockNumber, @@ -78,6 +104,7 @@ impl pallet_bridge_relayers::Config for Runtime { RelayerStakeLease, >; type WeightInfo = weights::pallet_bridge_relayers::WeightInfo; + type LaneId = bp_messages::HashedLaneId; } /// Add GRANDPA bridge pallet to track Rococo Bulletin chain. diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_bulletin_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_bulletin_config.rs index 00d902486c8..c971fa59c68 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_bulletin_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_bulletin_config.rs @@ -20,13 +20,14 @@ //! are reusing Polkadot Bulletin chain primitives everywhere here. use crate::{ - weights, xcm_config::UniversalLocation, AccountId, Balance, Balances, - BridgeRococoBulletinGrandpa, BridgeRococoBulletinMessages, PolkadotXcm, Runtime, RuntimeEvent, - RuntimeHoldReason, XcmOverRococoBulletin, XcmRouter, + bridge_common_config::RelayersForPermissionlessLanesInstance, weights, + xcm_config::UniversalLocation, AccountId, Balance, Balances, BridgeRococoBulletinGrandpa, + BridgeRococoBulletinMessages, PolkadotXcm, Runtime, RuntimeEvent, RuntimeHoldReason, + XcmOverRococoBulletin, XcmRouter, }; use bp_messages::{ source_chain::FromBridgedChainMessagesDeliveryProof, - target_chain::FromBridgedChainMessagesProof, + target_chain::FromBridgedChainMessagesProof, HashedLaneId, }; use bridge_hub_common::xcm_version::XcmVersionOfDestAndRemoteBridge; @@ -34,11 +35,11 @@ use frame_support::{ parameter_types, traits::{Equals, PalletInfoAccess}, }; -use frame_system::EnsureRoot; +use frame_system::{EnsureNever, EnsureRoot}; +use pallet_bridge_messages::LaneIdOf; use pallet_bridge_relayers::extension::{ BridgeRelayersSignedExtension, WithMessagesExtensionConfig, }; -use pallet_xcm::EnsureXcm; use pallet_xcm_bridge_hub::XcmAsPlainPayload; use polkadot_parachain_primitives::primitives::Sibling; use testnet_parachains_constants::rococo::currency::UNITS as ROC; @@ -78,11 +79,11 @@ parameter_types! { } /// Proof of messages, coming from Rococo Bulletin chain. -pub type FromRococoBulletinMessagesProof = - FromBridgedChainMessagesProof; +pub type FromRococoBulletinMessagesProof = + FromBridgedChainMessagesProof>; /// Messages delivery proof for Rococo Bridge Hub -> Rococo Bulletin messages. -pub type ToRococoBulletinMessagesDeliveryProof = - FromBridgedChainMessagesDeliveryProof; +pub type ToRococoBulletinMessagesDeliveryProof = + FromBridgedChainMessagesDeliveryProof>; /// Dispatches received XCM messages from other bridge. type FromRococoBulletinMessageBlobDispatcher = BridgeBlobDispatcher< @@ -99,8 +100,10 @@ pub type OnBridgeHubRococoRefundRococoBulletinMessages = BridgeRelayersSignedExt StrOnBridgeHubRococoRefundRococoBulletinMessages, Runtime, WithRococoBulletinMessagesInstance, + RelayersForPermissionlessLanesInstance, PriorityBoostPerMessage, >, + LaneIdOf, >; bp_runtime::generate_static_str_provider!(OnBridgeHubRococoRefundRococoBulletinMessages); @@ -116,10 +119,10 @@ impl pallet_bridge_messages::Config for Runt type BridgedHeaderChain = BridgeRococoBulletinGrandpa; type OutboundPayload = XcmAsPlainPayload; - type InboundPayload = XcmAsPlainPayload; - type DeliveryPayments = (); + type LaneId = HashedLaneId; + type DeliveryPayments = (); type DeliveryConfirmationPayments = (); type MessageDispatch = XcmOverRococoBulletin; @@ -139,9 +142,9 @@ impl pallet_xcm_bridge_hub::Config for Runtime type DestinationVersion = XcmVersionOfDestAndRemoteBridge; - type AdminOrigin = EnsureRoot; - // Only allow calls from sibling People parachain to directly open the bridge. - type OpenBridgeOrigin = EnsureXcm>; + type ForceOrigin = EnsureRoot; + // We don't want to allow creating bridges for this instance. + type OpenBridgeOrigin = EnsureNever; // Converter aligned with `OpenBridgeOrigin`. type BridgeOriginAccountIdConverter = (ParentIsPreset, SiblingParachainConvertsVia); @@ -230,14 +233,20 @@ mod tests { } #[cfg(feature = "runtime-benchmarks")] -pub(crate) fn open_bridge_for_benchmarks( - with: bp_messages::LaneId, +pub(crate) fn open_bridge_for_benchmarks( + with: pallet_xcm_bridge_hub::LaneIdOf, sibling_para_id: u32, -) -> InteriorLocation { +) -> InteriorLocation +where + R: pallet_xcm_bridge_hub::Config, + XBHI: 'static, + C: xcm_executor::traits::ConvertLocation< + bp_runtime::AccountIdOf>, + >, +{ use pallet_xcm_bridge_hub::{Bridge, BridgeId, BridgeState}; use sp_runtime::traits::Zero; use xcm::VersionedInteriorLocation; - use xcm_executor::traits::ConvertLocation; // insert bridge metadata let lane_id = with; @@ -248,7 +257,7 @@ pub(crate) fn open_bridge_for_benchmarks( let bridge_id = BridgeId::new(&universal_source, &universal_destination); // insert only bridge metadata, because the benchmarks create lanes - pallet_xcm_bridge_hub::Bridges::::insert( + pallet_xcm_bridge_hub::Bridges::::insert( bridge_id, Bridge { bridge_origin_relative_location: alloc::boxed::Box::new( @@ -261,17 +270,12 @@ pub(crate) fn open_bridge_for_benchmarks( VersionedInteriorLocation::from(universal_destination), ), state: BridgeState::Opened, - bridge_owner_account: crate::xcm_config::LocationToAccountId::convert_location( - &sibling_parachain, - ) - .expect("valid AccountId"), - deposit: Balance::zero(), + bridge_owner_account: C::convert_location(&sibling_parachain).expect("valid AccountId"), + deposit: Zero::zero(), lane_id, }, ); - pallet_xcm_bridge_hub::LaneToBridge::::insert( - lane_id, bridge_id, - ); + pallet_xcm_bridge_hub::LaneToBridge::::insert(lane_id, bridge_id); universal_source } @@ -279,13 +283,16 @@ pub(crate) fn open_bridge_for_benchmarks( /// Contains the migration for the PeopleRococo<>RococoBulletin bridge. pub mod migration { use super::*; - use bp_messages::LaneId; use frame_support::traits::ConstBool; - use sp_runtime::Either; parameter_types! { - pub RococoPeopleToRococoBulletinMessagesLane: LaneId = LaneId::from_inner(Either::Right([0, 0, 0, 0])); pub BulletinRococoLocation: InteriorLocation = [GlobalConsensus(RococoBulletinGlobalConsensusNetwork::get())].into(); + pub RococoPeopleToRococoBulletinMessagesLane: HashedLaneId = pallet_xcm_bridge_hub::Pallet::< Runtime, XcmOverPolkadotBulletinInstance >::bridge_locations( + PeopleRococoLocation::get(), + BulletinRococoLocation::get() + ) + .unwrap() + .calculate_lane_id(xcm::latest::VERSION).expect("Valid locations"); } /// Ensure that the existing lanes for the People<>Bulletin bridge are correctly configured. diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_westend_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_westend_config.rs index fc52413a909..8fe04572310 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_westend_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_westend_config.rs @@ -17,7 +17,10 @@ //! Bridge definitions used on BridgeHubRococo for bridging to BridgeHubWestend. use crate::{ - bridge_common_config::{BridgeParachainWestendInstance, DeliveryRewardInBalance}, + bridge_common_config::{ + BridgeParachainWestendInstance, DeliveryRewardInBalance, + RelayersForLegacyLaneIdsMessagesInstance, + }, weights, xcm_config::UniversalLocation, AccountId, Balance, Balances, BridgeWestendMessages, PolkadotXcm, Runtime, RuntimeEvent, @@ -25,20 +28,18 @@ use crate::{ }; use bp_messages::{ source_chain::FromBridgedChainMessagesDeliveryProof, - target_chain::FromBridgedChainMessagesProof, + target_chain::FromBridgedChainMessagesProof, LegacyLaneId, }; use bridge_hub_common::xcm_version::XcmVersionOfDestAndRemoteBridge; use pallet_xcm_bridge_hub::XcmAsPlainPayload; use frame_support::{parameter_types, traits::PalletInfoAccess}; -use frame_system::EnsureRoot; +use frame_system::{EnsureNever, EnsureRoot}; +use pallet_bridge_messages::LaneIdOf; use pallet_bridge_relayers::extension::{ BridgeRelayersSignedExtension, WithMessagesExtensionConfig, }; -use pallet_xcm::EnsureXcm; -use parachains_common::xcm_config::{ - AllSiblingSystemParachains, ParentRelayOrSiblingParachains, RelayOrOtherSystemParachains, -}; +use parachains_common::xcm_config::{AllSiblingSystemParachains, RelayOrOtherSystemParachains}; use polkadot_parachain_primitives::primitives::Sibling; use testnet_parachains_constants::rococo::currency::UNITS as ROC; use xcm::{ @@ -73,11 +74,11 @@ parameter_types! { } /// Proof of messages, coming from Westend. -pub type FromWestendBridgeHubMessagesProof = - FromBridgedChainMessagesProof; +pub type FromWestendBridgeHubMessagesProof = + FromBridgedChainMessagesProof>; /// Messages delivery proof for Rococo Bridge Hub -> Westend Bridge Hub messages. -pub type ToWestendBridgeHubMessagesDeliveryProof = - FromBridgedChainMessagesDeliveryProof; +pub type ToWestendBridgeHubMessagesDeliveryProof = + FromBridgedChainMessagesDeliveryProof>; /// Dispatches received XCM messages from other bridge type FromWestendMessageBlobDispatcher = @@ -90,8 +91,10 @@ pub type OnBridgeHubRococoRefundBridgeHubWestendMessages = BridgeRelayersSignedE StrOnBridgeHubRococoRefundBridgeHubWestendMessages, Runtime, WithBridgeHubWestendMessagesInstance, + RelayersForLegacyLaneIdsMessagesInstance, PriorityBoostPerMessage, >, + LaneIdOf, >; bp_runtime::generate_static_str_provider!(OnBridgeHubRococoRefundBridgeHubWestendMessages); @@ -110,10 +113,10 @@ impl pallet_bridge_messages::Config for Ru >; type OutboundPayload = XcmAsPlainPayload; - type InboundPayload = XcmAsPlainPayload; - type DeliveryPayments = (); + type LaneId = LegacyLaneId; + type DeliveryPayments = (); type DeliveryConfirmationPayments = pallet_bridge_relayers::DeliveryConfirmationPaymentsAdapter< Runtime, WithBridgeHubWestendMessagesInstance, @@ -124,7 +127,8 @@ impl pallet_bridge_messages::Config for Ru type OnMessagesDelivered = XcmOverBridgeHubWestend; } -/// Add support for the export and dispatch of XCM programs. +/// Add support for the export and dispatch of XCM programs withing +/// `WithBridgeHubWestendMessagesInstance`. pub type XcmOverBridgeHubWestendInstance = pallet_xcm_bridge_hub::Instance1; impl pallet_xcm_bridge_hub::Config for Runtime { type RuntimeEvent = RuntimeEvent; @@ -137,9 +141,9 @@ impl pallet_xcm_bridge_hub::Config for Runtime type DestinationVersion = XcmVersionOfDestAndRemoteBridge; - type AdminOrigin = EnsureRoot; - // Only allow calls from relay chains and sibling parachains to directly open the bridge. - type OpenBridgeOrigin = EnsureXcm; + type ForceOrigin = EnsureRoot; + // We don't want to allow creating bridges for this instance with `LegacyLaneId`. + type OpenBridgeOrigin = EnsureNever; // Converter aligned with `OpenBridgeOrigin`. type BridgeOriginAccountIdConverter = (ParentIsPreset, SiblingParachainConvertsVia); @@ -157,14 +161,20 @@ impl pallet_xcm_bridge_hub::Config for Runtime } #[cfg(feature = "runtime-benchmarks")] -pub(crate) fn open_bridge_for_benchmarks( - with: bp_messages::LaneId, +pub(crate) fn open_bridge_for_benchmarks( + with: pallet_xcm_bridge_hub::LaneIdOf, sibling_para_id: u32, -) -> InteriorLocation { +) -> InteriorLocation +where + R: pallet_xcm_bridge_hub::Config, + XBHI: 'static, + C: xcm_executor::traits::ConvertLocation< + bp_runtime::AccountIdOf>, + >, +{ use pallet_xcm_bridge_hub::{Bridge, BridgeId, BridgeState}; use sp_runtime::traits::Zero; use xcm::VersionedInteriorLocation; - use xcm_executor::traits::ConvertLocation; // insert bridge metadata let lane_id = with; @@ -174,7 +184,7 @@ pub(crate) fn open_bridge_for_benchmarks( let bridge_id = BridgeId::new(&universal_source, &universal_destination); // insert only bridge metadata, because the benchmarks create lanes - pallet_xcm_bridge_hub::Bridges::::insert( + pallet_xcm_bridge_hub::Bridges::::insert( bridge_id, Bridge { bridge_origin_relative_location: alloc::boxed::Box::new( @@ -187,17 +197,12 @@ pub(crate) fn open_bridge_for_benchmarks( VersionedInteriorLocation::from(universal_destination), ), state: BridgeState::Opened, - bridge_owner_account: crate::xcm_config::LocationToAccountId::convert_location( - &sibling_parachain, - ) - .expect("valid AccountId"), - deposit: Balance::zero(), + bridge_owner_account: C::convert_location(&sibling_parachain).expect("valid AccountId"), + deposit: Zero::zero(), lane_id, }, ); - pallet_xcm_bridge_hub::LaneToBridge::::insert( - lane_id, bridge_id, - ); + pallet_xcm_bridge_hub::LaneToBridge::::insert(lane_id, bridge_id); universal_source } @@ -297,12 +302,10 @@ mod tests { /// Contains the migration for the AssetHubRococo<>AssetHubWestend bridge. pub mod migration { use super::*; - use bp_messages::LaneId; use frame_support::traits::ConstBool; - use sp_runtime::Either; parameter_types! { - pub AssetHubRococoToAssetHubWestendMessagesLane: LaneId = LaneId::from_inner(Either::Right([0, 0, 0, 2])); + pub AssetHubRococoToAssetHubWestendMessagesLane: LegacyLaneId = LegacyLaneId([0, 0, 0, 2]); pub AssetHubRococoLocation: Location = Location::new(1, [Parachain(bp_asset_hub_rococo::ASSET_HUB_ROCOCO_PARACHAIN_ID)]); pub AssetHubWestendUniversalLocation: InteriorLocation = [GlobalConsensus(WestendGlobalConsensusNetwork::get()), Parachain(bp_asset_hub_westend::ASSET_HUB_WESTEND_PARACHAIN_ID)].into(); } @@ -318,4 +321,75 @@ pub mod migration { AssetHubRococoLocation, AssetHubWestendUniversalLocation, >; + + mod v1_wrong { + use bp_messages::{LaneState, MessageNonce, UnrewardedRelayer}; + use bp_runtime::AccountIdOf; + use codec::{Decode, Encode}; + use pallet_bridge_messages::BridgedChainOf; + use sp_std::collections::vec_deque::VecDeque; + + #[derive(Encode, Decode, Clone, PartialEq, Eq)] + pub(crate) struct StoredInboundLaneData, I: 'static>( + pub(crate) InboundLaneData>>, + ); + #[derive(Encode, Decode, Clone, PartialEq, Eq)] + pub(crate) struct InboundLaneData { + pub state: LaneState, + pub(crate) relayers: VecDeque>, + pub(crate) last_confirmed_nonce: MessageNonce, + } + #[derive(Encode, Decode, Clone, PartialEq, Eq)] + pub(crate) struct OutboundLaneData { + pub state: LaneState, + pub(crate) oldest_unpruned_nonce: MessageNonce, + pub(crate) latest_received_nonce: MessageNonce, + pub(crate) latest_generated_nonce: MessageNonce, + } + } + + mod v1 { + pub use bp_messages::{InboundLaneData, LaneState, OutboundLaneData}; + pub use pallet_bridge_messages::{InboundLanes, OutboundLanes, StoredInboundLaneData}; + } + + /// Fix for v1 migration - corrects data for OutboundLaneData/InboundLaneData (it is needed only + /// for Rococo/Westend). + pub struct FixMessagesV1Migration(sp_std::marker::PhantomData<(T, I)>); + + impl, I: 'static> frame_support::traits::OnRuntimeUpgrade + for FixMessagesV1Migration + { + fn on_runtime_upgrade() -> Weight { + use sp_core::Get; + let mut weight = T::DbWeight::get().reads(1); + + // `InboundLanes` - add state to the old structs + let translate_inbound = + |pre: v1_wrong::StoredInboundLaneData| -> Option> { + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); + Some(v1::StoredInboundLaneData(v1::InboundLaneData { + state: v1::LaneState::Opened, + relayers: pre.0.relayers, + last_confirmed_nonce: pre.0.last_confirmed_nonce, + })) + }; + v1::InboundLanes::::translate_values(translate_inbound); + + // `OutboundLanes` - add state to the old structs + let translate_outbound = + |pre: v1_wrong::OutboundLaneData| -> Option { + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); + Some(v1::OutboundLaneData { + state: v1::LaneState::Opened, + oldest_unpruned_nonce: pre.oldest_unpruned_nonce, + latest_received_nonce: pre.latest_received_nonce, + latest_generated_nonce: pre.latest_generated_nonce, + }) + }; + v1::OutboundLanes::::translate_values(translate_outbound); + + weight + } + } } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/genesis_config_presets.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/genesis_config_presets.rs index e5d61598564..07048d54ab1 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/genesis_config_presets.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/genesis_config_presets.rs @@ -31,6 +31,7 @@ fn bridge_hub_rococo_genesis( id: ParaId, bridges_pallet_owner: Option, asset_hub_para_id: ParaId, + opened_bridges: Vec<(Location, InteriorLocation, Option)>, ) -> serde_json::Value { let config = RuntimeGenesisConfig { balances: BalancesConfig { @@ -71,6 +72,10 @@ fn bridge_hub_rococo_genesis( owner: bridges_pallet_owner.clone(), ..Default::default() }, + xcm_over_bridge_hub_westend: XcmOverBridgeHubWestendConfig { + opened_bridges, + ..Default::default() + }, ethereum_system: EthereumSystemConfig { para_id: id, asset_hub_para_id, @@ -114,6 +119,11 @@ pub fn get_preset(id: &sp_genesis_builder::PresetId) -> Option("Bob")), rococo_runtime_constants::system_parachain::ASSET_HUB_ID.into(), + vec![( + Location::new(1, [Parachain(1000)]), + Junctions::from([Westend.into(), Parachain(1000)]), + Some(bp_messages::LegacyLaneId([0, 0, 0, 2])), + )], ), Ok(sp_genesis_builder::DEV_RUNTIME_PRESET) => bridge_hub_rococo_genesis( // initial collators. @@ -144,6 +154,7 @@ pub fn get_preset(id: &sp_genesis_builder::PresetId) -> Option("Bob")), rococo_runtime_constants::system_parachain::ASSET_HUB_ID.into(), + vec![], ), _ => return None, }; diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs index 177978cee10..bf9bd58669b 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs @@ -43,6 +43,7 @@ use bridge_runtime_common::extensions::{ CheckAndBoostBridgeGrandpaTransactions, CheckAndBoostBridgeParachainsTransactions, }; use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; +use pallet_bridge_messages::LaneIdOf; use sp_api::impl_runtime_apis; use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; use sp_runtime::{ @@ -130,10 +131,7 @@ pub type SignedExtra = ( frame_system::CheckWeight, pallet_transaction_payment::ChargeTransactionPayment, BridgeRejectObsoleteHeadersAndMessages, - ( - bridge_to_westend_config::OnBridgeHubRococoRefundBridgeHubWestendMessages, - bridge_to_bulletin_config::OnBridgeHubRococoRefundRococoBulletinMessages, - ), + (bridge_to_westend_config::OnBridgeHubRococoRefundBridgeHubWestendMessages,), cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim, frame_metadata_hash_extension::CheckMetadataHash, ); @@ -163,6 +161,10 @@ pub type Migrations = ( Runtime, bridge_to_bulletin_config::WithRococoBulletinMessagesInstance, >, + bridge_to_westend_config::migration::FixMessagesV1Migration< + Runtime, + bridge_to_westend_config::WithBridgeHubWestendMessagesInstance, + >, bridge_to_westend_config::migration::StaticToDynamicLanes, bridge_to_bulletin_config::migration::StaticToDynamicLanes, frame_support::migrations::RemoveStorage< @@ -175,6 +177,7 @@ pub type Migrations = ( OutboundLanesCongestedSignalsKey, RocksDbWeight, >, + pallet_bridge_relayers::migration::v1::MigrationToV1, // permanent pallet_xcm::migration::MigrateToLatestXcmVersion, ); @@ -593,6 +596,9 @@ construct_runtime!( // With-Rococo Bulletin bridge hub pallet. XcmOverPolkadotBulletin: pallet_xcm_bridge_hub:: = 62, + // Bridge relayers pallet, used by several bridges here (another instance). + BridgeRelayersForPermissionlessLanes: pallet_bridge_relayers:: = 63, + EthereumInboundQueue: snowbridge_pallet_inbound_queue = 80, EthereumOutboundQueue: snowbridge_pallet_outbound_queue = 81, EthereumBeaconClient: snowbridge_pallet_ethereum_client = 82, @@ -662,7 +668,8 @@ mod benches { [pallet_bridge_parachains, WithinWestend] [pallet_bridge_messages, RococoToWestend] [pallet_bridge_messages, RococoToRococoBulletin] - [pallet_bridge_relayers, BridgeRelayersBench::] + [pallet_bridge_relayers, Legacy] + [pallet_bridge_relayers, PermissionlessLanes] // Ethereum Bridge [snowbridge_pallet_inbound_queue, EthereumInboundQueue] [snowbridge_pallet_outbound_queue, EthereumOutboundQueue] @@ -671,6 +678,11 @@ mod benches { ); } +cumulus_pallet_parachain_system::register_validate_block! { + Runtime = Runtime, + BlockExecutor = cumulus_pallet_aura_ext::BlockExecutor::, +} + impl_runtime_apis! { impl sp_consensus_aura::AuraApi for Runtime { fn slot_duration() -> sp_consensus_aura::SlotDuration { @@ -914,7 +926,7 @@ impl_runtime_apis! { // This is exposed by BridgeHubRococo impl bp_bridge_hub_westend::FromBridgeHubWestendInboundLaneApi for Runtime { fn message_details( - lane: bp_messages::LaneId, + lane: LaneIdOf, messages: Vec<(bp_messages::MessagePayload, bp_messages::OutboundMessageDetails)>, ) -> Vec { bridge_runtime_common::messages_api::inbound_message_details::< @@ -927,7 +939,7 @@ impl_runtime_apis! { // This is exposed by BridgeHubRococo impl bp_bridge_hub_westend::ToBridgeHubWestendOutboundLaneApi for Runtime { fn message_details( - lane: bp_messages::LaneId, + lane: LaneIdOf, begin: bp_messages::MessageNonce, end: bp_messages::MessageNonce, ) -> Vec { @@ -957,7 +969,7 @@ impl_runtime_apis! { impl bp_polkadot_bulletin::FromPolkadotBulletinInboundLaneApi for Runtime { fn message_details( - lane: bp_messages::LaneId, + lane: LaneIdOf, messages: Vec<(bp_messages::MessagePayload, bp_messages::OutboundMessageDetails)>, ) -> Vec { bridge_runtime_common::messages_api::inbound_message_details::< @@ -969,7 +981,7 @@ impl_runtime_apis! { impl bp_polkadot_bulletin::ToPolkadotBulletinOutboundLaneApi for Runtime { fn message_details( - lane: bp_messages::LaneId, + lane: LaneIdOf, begin: bp_messages::MessageNonce, end: bp_messages::MessageNonce, ) -> Vec { @@ -1039,6 +1051,8 @@ impl_runtime_apis! { type WithinWestend = pallet_bridge_parachains::benchmarking::Pallet::; type RococoToWestend = pallet_bridge_messages::benchmarking::Pallet ::; type RococoToRococoBulletin = pallet_bridge_messages::benchmarking::Pallet ::; + type Legacy = BridgeRelayersBench::; + type PermissionlessLanes = BridgeRelayersBench::; let mut list = Vec::::new(); list_benchmarks!(list, extra); @@ -1248,15 +1262,20 @@ impl_runtime_apis! { ); // open bridge - let origin = RuntimeOrigin::from(pallet_xcm::Origin::Xcm(sibling_parachain_location.clone())); - XcmOverBridgeHubWestend::open_bridge( - origin.clone(), - Box::new(VersionedInteriorLocation::from([GlobalConsensus(NetworkId::Westend), Parachain(8765)])), + let bridge_destination_universal_location: InteriorLocation = [GlobalConsensus(NetworkId::Westend), Parachain(8765)].into(); + let locations = XcmOverBridgeHubWestend::bridge_locations( + sibling_parachain_location.clone(), + bridge_destination_universal_location.clone(), + )?; + XcmOverBridgeHubWestend::do_open_bridge( + locations, + bp_messages::LegacyLaneId([1, 2, 3, 4]), + true, ).map_err(|e| { log::error!( "Failed to `XcmOverBridgeHubWestend::open_bridge`({:?}, {:?})`, error: {:?}", - origin, - [GlobalConsensus(NetworkId::Westend), Parachain(8765)], + sibling_parachain_location, + bridge_destination_universal_location, e ); BenchmarkError::Stop("Bridge was not opened!") @@ -1283,6 +1302,8 @@ impl_runtime_apis! { type WithinWestend = pallet_bridge_parachains::benchmarking::Pallet::; type RococoToWestend = pallet_bridge_messages::benchmarking::Pallet ::; type RococoToRococoBulletin = pallet_bridge_messages::benchmarking::Pallet ::; + type Legacy = BridgeRelayersBench::; + type PermissionlessLanes = BridgeRelayersBench::; use bridge_runtime_common::messages_benchmarking::{ prepare_message_delivery_proof_from_grandpa_chain, @@ -1313,12 +1334,16 @@ impl_runtime_apis! { } fn prepare_message_proof( - params: MessageProofParams, - ) -> (bridge_to_westend_config::FromWestendBridgeHubMessagesProof, Weight) { + params: MessageProofParams>, + ) -> (bridge_to_westend_config::FromWestendBridgeHubMessagesProof, Weight) { use cumulus_primitives_core::XcmpMessageSource; assert!(XcmpQueue::take_outbound_messages(usize::MAX).is_empty()); ParachainSystem::open_outbound_hrmp_channel_for_benchmarks_or_tests(42.into()); - let universal_source = bridge_to_westend_config::open_bridge_for_benchmarks(params.lane, 42); + let universal_source = bridge_to_westend_config::open_bridge_for_benchmarks::< + Runtime, + bridge_to_westend_config::XcmOverBridgeHubWestendInstance, + xcm_config::LocationToAccountId, + >(params.lane, 42); prepare_message_proof_from_parachain::< Runtime, bridge_common_config::BridgeGrandpaWestendInstance, @@ -1327,9 +1352,13 @@ impl_runtime_apis! { } fn prepare_message_delivery_proof( - params: MessageDeliveryProofParams, - ) -> bridge_to_westend_config::ToWestendBridgeHubMessagesDeliveryProof { - let _ = bridge_to_westend_config::open_bridge_for_benchmarks(params.lane, 42); + params: MessageDeliveryProofParams>, + ) -> bridge_to_westend_config::ToWestendBridgeHubMessagesDeliveryProof { + let _ = bridge_to_westend_config::open_bridge_for_benchmarks::< + Runtime, + bridge_to_westend_config::XcmOverBridgeHubWestendInstance, + xcm_config::LocationToAccountId, + >(params.lane, 42); prepare_message_delivery_proof_from_parachain::< Runtime, bridge_common_config::BridgeGrandpaWestendInstance, @@ -1350,12 +1379,16 @@ impl_runtime_apis! { } fn prepare_message_proof( - params: MessageProofParams, - ) -> (bridge_to_bulletin_config::FromRococoBulletinMessagesProof, Weight) { + params: MessageProofParams>, + ) -> (bridge_to_bulletin_config::FromRococoBulletinMessagesProof, Weight) { use cumulus_primitives_core::XcmpMessageSource; assert!(XcmpQueue::take_outbound_messages(usize::MAX).is_empty()); ParachainSystem::open_outbound_hrmp_channel_for_benchmarks_or_tests(42.into()); - let universal_source = bridge_to_bulletin_config::open_bridge_for_benchmarks(params.lane, 42); + let universal_source = bridge_to_bulletin_config::open_bridge_for_benchmarks::< + Runtime, + bridge_to_bulletin_config::XcmOverPolkadotBulletinInstance, + xcm_config::LocationToAccountId, + >(params.lane, 42); prepare_message_proof_from_grandpa_chain::< Runtime, bridge_common_config::BridgeGrandpaRococoBulletinInstance, @@ -1364,9 +1397,13 @@ impl_runtime_apis! { } fn prepare_message_delivery_proof( - params: MessageDeliveryProofParams, - ) -> bridge_to_bulletin_config::ToRococoBulletinMessagesDeliveryProof { - let _ = bridge_to_bulletin_config::open_bridge_for_benchmarks(params.lane, 42); + params: MessageDeliveryProofParams>, + ) -> bridge_to_bulletin_config::ToRococoBulletinMessagesDeliveryProof { + let _ = bridge_to_bulletin_config::open_bridge_for_benchmarks::< + Runtime, + bridge_to_bulletin_config::XcmOverPolkadotBulletinInstance, + xcm_config::LocationToAccountId, + >(params.lane, 42); prepare_message_delivery_proof_from_grandpa_chain::< Runtime, bridge_common_config::BridgeGrandpaRococoBulletinInstance, @@ -1411,16 +1448,36 @@ impl_runtime_apis! { } } - impl BridgeRelayersConfig for Runtime { + impl BridgeRelayersConfig for Runtime { fn prepare_rewards_account( - account_params: bp_relayers::RewardsAccountParams, + account_params: bp_relayers::RewardsAccountParams<>::LaneId>, reward: Balance, ) { let rewards_account = bp_relayers::PayRewardFromAccount::< Balances, - AccountId + AccountId, + >::LaneId, >::rewards_account(account_params); - Self::deposit_account(rewards_account, reward); + >::deposit_account(rewards_account, reward); + } + + fn deposit_account(account: AccountId, balance: Balance) { + use frame_support::traits::fungible::Mutate; + Balances::mint_into(&account, balance.saturating_add(ExistentialDeposit::get())).unwrap(); + } + } + + impl BridgeRelayersConfig for Runtime { + fn prepare_rewards_account( + account_params: bp_relayers::RewardsAccountParams<>::LaneId>, + reward: Balance, + ) { + let rewards_account = bp_relayers::PayRewardFromAccount::< + Balances, + AccountId, + >::LaneId, + >::rewards_account(account_params); + >::deposit_account(rewards_account, reward); } fn deposit_account(account: AccountId, balance: Balance) { @@ -1465,11 +1522,6 @@ impl_runtime_apis! { } } -cumulus_pallet_parachain_system::register_validate_block! { - Runtime = Runtime, - BlockExecutor = cumulus_pallet_aura_ext::BlockExecutor::, -} - #[cfg(test)] mod tests { use super::*; @@ -1497,7 +1549,6 @@ mod tests { BridgeRejectObsoleteHeadersAndMessages, ( bridge_to_westend_config::OnBridgeHubRococoRefundBridgeHubWestendMessages::default(), - bridge_to_bulletin_config::OnBridgeHubRococoRefundRococoBulletinMessages::default(), ), cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim::new(), frame_metadata_hash_extension::CheckMetadataHash::new(false), diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/snowbridge.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/snowbridge.rs index c7b5850f9ff..7a0f1462e7a 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/snowbridge.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/snowbridge.rs @@ -18,7 +18,6 @@ use bp_polkadot_core::Signature; use bridge_hub_rococo_runtime::{ - bridge_to_bulletin_config::OnBridgeHubRococoRefundRococoBulletinMessages, bridge_to_westend_config::OnBridgeHubRococoRefundBridgeHubWestendMessages, xcm_config::XcmConfig, AllPalletsWithoutSystem, BridgeRejectObsoleteHeadersAndMessages, Executive, MessageQueueServiceWeight, Runtime, RuntimeCall, RuntimeEvent, SessionKeys, @@ -183,10 +182,7 @@ fn construct_extrinsic( frame_system::CheckWeight::::new(), pallet_transaction_payment::ChargeTransactionPayment::::from(0), BridgeRejectObsoleteHeadersAndMessages::default(), - ( - OnBridgeHubRococoRefundBridgeHubWestendMessages::default(), - OnBridgeHubRococoRefundRococoBulletinMessages::default(), - ), + (OnBridgeHubRococoRefundBridgeHubWestendMessages::default(),), cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim::new(), frame_metadata_hash_extension::CheckMetadataHash::::new(false), ); diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs index 982c9fec663..002e31174cb 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs @@ -62,10 +62,7 @@ fn construct_extrinsic( frame_system::CheckWeight::::new(), pallet_transaction_payment::ChargeTransactionPayment::::from(0), BridgeRejectObsoleteHeadersAndMessages::default(), - ( - bridge_to_westend_config::OnBridgeHubRococoRefundBridgeHubWestendMessages::default(), - bridge_to_bulletin_config::OnBridgeHubRococoRefundRococoBulletinMessages::default(), - ), + (bridge_to_westend_config::OnBridgeHubRococoRefundBridgeHubWestendMessages::default(),), cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim::new(), frame_metadata_hash_extension::CheckMetadataHash::new(false), ); @@ -145,8 +142,10 @@ fn change_required_stake_by_governance_works() { mod bridge_hub_westend_tests { use super::*; + use bp_messages::LegacyLaneId; use bridge_common_config::{ BridgeGrandpaWestendInstance, BridgeParachainWestendInstance, DeliveryRewardInBalance, + RelayersForLegacyLaneIdsMessagesInstance, }; use bridge_hub_test_utils::test_cases::from_parachain; use bridge_to_westend_config::{ @@ -174,6 +173,7 @@ mod bridge_hub_westend_tests { BridgeGrandpaWestendInstance, BridgeParachainWestendInstance, WithBridgeHubWestendMessagesInstance, + RelayersForLegacyLaneIdsMessagesInstance, >; #[test] @@ -338,7 +338,16 @@ mod bridge_hub_westend_tests { XcmOverBridgeHubWestendInstance, LocationToAccountId, TokenLocation, - >(SiblingParachainLocation::get(), BridgedUniversalLocation::get()).1 + >( + SiblingParachainLocation::get(), + BridgedUniversalLocation::get(), + |locations, fee| { + bridge_hub_test_utils::open_bridge_with_storage::< + Runtime, + XcmOverBridgeHubWestendInstance + >(locations, fee, LegacyLaneId([0, 0, 0, 1])) + } + ).1 }, ) } @@ -393,10 +402,20 @@ mod bridge_hub_westend_tests { XcmOverBridgeHubWestendInstance, LocationToAccountId, TokenLocation, - >(SiblingParachainLocation::get(), BridgedUniversalLocation::get()) + >( + SiblingParachainLocation::get(), + BridgedUniversalLocation::get(), + |locations, fee| { + bridge_hub_test_utils::open_bridge_with_storage::< + Runtime, + XcmOverBridgeHubWestendInstance, + >(locations, fee, LegacyLaneId([0, 0, 0, 1])) + }, + ) .1 }, construct_and_apply_extrinsic, + true, ) } @@ -417,10 +436,20 @@ mod bridge_hub_westend_tests { XcmOverBridgeHubWestendInstance, LocationToAccountId, TokenLocation, - >(SiblingParachainLocation::get(), BridgedUniversalLocation::get()) + >( + SiblingParachainLocation::get(), + BridgedUniversalLocation::get(), + |locations, fee| { + bridge_hub_test_utils::open_bridge_with_storage::< + Runtime, + XcmOverBridgeHubWestendInstance, + >(locations, fee, LegacyLaneId([0, 0, 0, 1])) + }, + ) .1 }, construct_and_apply_extrinsic, + false, ) } @@ -482,30 +511,13 @@ mod bridge_hub_westend_tests { ), ) } - - #[test] - fn open_and_close_bridge_works() { - let origins = [SiblingParachainLocation::get(), SiblingSystemParachainLocation::get()]; - - for origin in origins { - bridge_hub_test_utils::test_cases::open_and_close_bridge_works::< - Runtime, - XcmOverBridgeHubWestendInstance, - LocationToAccountId, - TokenLocation, - >( - collator_session_keys(), - bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID, - origin, - BridgedUniversalLocation::get(), - ) - } - } } mod bridge_hub_bulletin_tests { use super::*; + use bp_messages::{HashedLaneId, LaneIdType}; use bridge_common_config::BridgeGrandpaRococoBulletinInstance; + use bridge_hub_rococo_runtime::bridge_common_config::RelayersForPermissionlessLanesInstance; use bridge_hub_test_utils::test_cases::from_grandpa_chain; use bridge_to_bulletin_config::{ RococoBulletinGlobalConsensusNetwork, RococoBulletinGlobalConsensusNetworkLocation, @@ -527,6 +539,7 @@ mod bridge_hub_bulletin_tests { AllPalletsWithoutSystem, BridgeGrandpaRococoBulletinInstance, WithRococoBulletinMessagesInstance, + RelayersForPermissionlessLanesInstance, >; #[test] @@ -589,7 +602,16 @@ mod bridge_hub_bulletin_tests { XcmOverPolkadotBulletinInstance, LocationToAccountId, TokenLocation, - >(SiblingPeopleParachainLocation::get(), BridgedBulletinLocation::get()).1 + >( + SiblingPeopleParachainLocation::get(), + BridgedBulletinLocation::get(), + |locations, fee| { + bridge_hub_test_utils::open_bridge_with_storage::< + Runtime, + XcmOverPolkadotBulletinInstance + >(locations, fee, HashedLaneId::try_new(1, 2).unwrap()) + } + ).1 }, ) } @@ -643,10 +665,20 @@ mod bridge_hub_bulletin_tests { XcmOverPolkadotBulletinInstance, LocationToAccountId, TokenLocation, - >(SiblingPeopleParachainLocation::get(), BridgedBulletinLocation::get()) + >( + SiblingPeopleParachainLocation::get(), + BridgedBulletinLocation::get(), + |locations, fee| { + bridge_hub_test_utils::open_bridge_with_storage::< + Runtime, + XcmOverPolkadotBulletinInstance, + >(locations, fee, HashedLaneId::try_new(1, 2).unwrap()) + }, + ) .1 }, construct_and_apply_extrinsic, + false, ) } @@ -666,29 +698,20 @@ mod bridge_hub_bulletin_tests { XcmOverPolkadotBulletinInstance, LocationToAccountId, TokenLocation, - >(SiblingPeopleParachainLocation::get(), BridgedBulletinLocation::get()) + >( + SiblingPeopleParachainLocation::get(), + BridgedBulletinLocation::get(), + |locations, fee| { + bridge_hub_test_utils::open_bridge_with_storage::< + Runtime, + XcmOverPolkadotBulletinInstance, + >(locations, fee, HashedLaneId::try_new(1, 2).unwrap()) + }, + ) .1 }, construct_and_apply_extrinsic, + false, ) } - - #[test] - fn open_and_close_bridge_works() { - let origins = [SiblingPeopleParachainLocation::get()]; - - for origin in origins { - bridge_hub_test_utils::test_cases::open_and_close_bridge_works::< - Runtime, - XcmOverPolkadotBulletinInstance, - LocationToAccountId, - TokenLocation, - >( - collator_session_keys(), - bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID, - origin, - BridgedBulletinLocation::get(), - ) - } - } } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_common_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_common_config.rs index 9bae106395a..0872d0498f8 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_common_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_common_config.rs @@ -22,6 +22,7 @@ //! GRANDPA tracking pallet only needs to be aware of one chain. use super::{weights, AccountId, Balance, Balances, BlockNumber, Runtime, RuntimeEvent}; +use bp_messages::LegacyLaneId; use frame_support::parameter_types; parameter_types! { @@ -33,11 +34,15 @@ parameter_types! { } /// Allows collect and claim rewards for relayers -impl pallet_bridge_relayers::Config for Runtime { +pub type RelayersForLegacyLaneIdsMessagesInstance = (); +impl pallet_bridge_relayers::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Reward = Balance; - type PaymentProcedure = - bp_relayers::PayRewardFromAccount, AccountId>; + type PaymentProcedure = bp_relayers::PayRewardFromAccount< + pallet_balances::Pallet, + AccountId, + Self::LaneId, + >; type StakeAndSlash = pallet_bridge_relayers::StakeAndSlashNamed< AccountId, BlockNumber, @@ -47,4 +52,5 @@ impl pallet_bridge_relayers::Config for Runtime { RelayerStakeLease, >; type WeightInfo = weights::pallet_bridge_relayers::WeightInfo; + type LaneId = LegacyLaneId; } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_rococo_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_rococo_config.rs index 2d9e8f66427..e45654bc62b 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_rococo_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_rococo_config.rs @@ -17,13 +17,15 @@ //! Bridge definitions used on BridgeHub with the Westend flavor. use crate::{ - bridge_common_config::DeliveryRewardInBalance, weights, xcm_config::UniversalLocation, + bridge_common_config::{DeliveryRewardInBalance, RelayersForLegacyLaneIdsMessagesInstance}, + weights, + xcm_config::UniversalLocation, AccountId, Balance, Balances, BridgeRococoMessages, PolkadotXcm, Runtime, RuntimeEvent, RuntimeHoldReason, XcmOverBridgeHubRococo, XcmRouter, }; use bp_messages::{ source_chain::FromBridgedChainMessagesDeliveryProof, - target_chain::FromBridgedChainMessagesProof, + target_chain::FromBridgedChainMessagesProof, LegacyLaneId, }; use bp_parachains::SingleParaStoredHeaderDataBuilder; use bridge_hub_common::xcm_version::XcmVersionOfDestAndRemoteBridge; @@ -33,14 +35,12 @@ use frame_support::{ parameter_types, traits::{ConstU32, PalletInfoAccess}, }; -use frame_system::EnsureRoot; +use frame_system::{EnsureNever, EnsureRoot}; +use pallet_bridge_messages::LaneIdOf; use pallet_bridge_relayers::extension::{ BridgeRelayersSignedExtension, WithMessagesExtensionConfig, }; -use pallet_xcm::EnsureXcm; -use parachains_common::xcm_config::{ - AllSiblingSystemParachains, ParentRelayOrSiblingParachains, RelayOrOtherSystemParachains, -}; +use parachains_common::xcm_config::{AllSiblingSystemParachains, RelayOrOtherSystemParachains}; use polkadot_parachain_primitives::primitives::Sibling; use testnet_parachains_constants::westend::currency::UNITS as WND; use xcm::{ @@ -81,11 +81,11 @@ parameter_types! { } /// Proof of messages, coming from Rococo. -pub type FromRococoBridgeHubMessagesProof = - FromBridgedChainMessagesProof; +pub type FromRococoBridgeHubMessagesProof = + FromBridgedChainMessagesProof>; /// Messages delivery proof for Rococo Bridge Hub -> Westend Bridge Hub messages. -pub type ToRococoBridgeHubMessagesDeliveryProof = - FromBridgedChainMessagesDeliveryProof; +pub type ToRococoBridgeHubMessagesDeliveryProof = + FromBridgedChainMessagesDeliveryProof>; /// Dispatches received XCM messages from other bridge type FromRococoMessageBlobDispatcher = @@ -98,8 +98,10 @@ pub type OnBridgeHubWestendRefundBridgeHubRococoMessages = BridgeRelayersSignedE StrOnBridgeHubWestendRefundBridgeHubRococoMessages, Runtime, WithBridgeHubRococoMessagesInstance, + RelayersForLegacyLaneIdsMessagesInstance, PriorityBoostPerMessage, >, + LaneIdOf, >; bp_runtime::generate_static_str_provider!(OnBridgeHubWestendRefundBridgeHubRococoMessages); @@ -142,10 +144,10 @@ impl pallet_bridge_messages::Config for Run >; type OutboundPayload = XcmAsPlainPayload; - type InboundPayload = XcmAsPlainPayload; - type DeliveryPayments = (); + type LaneId = LegacyLaneId; + type DeliveryPayments = (); type DeliveryConfirmationPayments = pallet_bridge_relayers::DeliveryConfirmationPaymentsAdapter< Runtime, WithBridgeHubRococoMessagesInstance, @@ -168,9 +170,9 @@ impl pallet_xcm_bridge_hub::Config for Runtime { type MessageExportPrice = (); type DestinationVersion = XcmVersionOfDestAndRemoteBridge; - type AdminOrigin = EnsureRoot; - // Only allow calls from relay chains and sibling parachains to directly open the bridge. - type OpenBridgeOrigin = EnsureXcm; + type ForceOrigin = EnsureRoot; + // We don't want to allow creating bridges for this instance with `LegacyLaneId`. + type OpenBridgeOrigin = EnsureNever; // Converter aligned with `OpenBridgeOrigin`. type BridgeOriginAccountIdConverter = (ParentIsPreset, SiblingParachainConvertsVia); @@ -188,14 +190,20 @@ impl pallet_xcm_bridge_hub::Config for Runtime { } #[cfg(feature = "runtime-benchmarks")] -pub(crate) fn open_bridge_for_benchmarks( - with: bp_messages::LaneId, +pub(crate) fn open_bridge_for_benchmarks( + with: pallet_xcm_bridge_hub::LaneIdOf, sibling_para_id: u32, -) -> InteriorLocation { +) -> InteriorLocation +where + R: pallet_xcm_bridge_hub::Config, + XBHI: 'static, + C: xcm_executor::traits::ConvertLocation< + bp_runtime::AccountIdOf>, + >, +{ use pallet_xcm_bridge_hub::{Bridge, BridgeId, BridgeState}; use sp_runtime::traits::Zero; use xcm::VersionedInteriorLocation; - use xcm_executor::traits::ConvertLocation; // insert bridge metadata let lane_id = with; @@ -205,7 +213,7 @@ pub(crate) fn open_bridge_for_benchmarks( let bridge_id = BridgeId::new(&universal_source, &universal_destination); // insert only bridge metadata, because the benchmarks create lanes - pallet_xcm_bridge_hub::Bridges::::insert( + pallet_xcm_bridge_hub::Bridges::::insert( bridge_id, Bridge { bridge_origin_relative_location: alloc::boxed::Box::new( @@ -218,17 +226,12 @@ pub(crate) fn open_bridge_for_benchmarks( VersionedInteriorLocation::from(universal_destination), ), state: BridgeState::Opened, - bridge_owner_account: crate::xcm_config::LocationToAccountId::convert_location( - &sibling_parachain, - ) - .expect("valid AccountId"), - deposit: Balance::zero(), + bridge_owner_account: C::convert_location(&sibling_parachain).expect("valid AccountId"), + deposit: Zero::zero(), lane_id, }, ); - pallet_xcm_bridge_hub::LaneToBridge::::insert( - lane_id, bridge_id, - ); + pallet_xcm_bridge_hub::LaneToBridge::::insert(lane_id, bridge_id); universal_source } @@ -327,12 +330,11 @@ mod tests { /// Contains the migration for the AssetHubWestend<>AssetHubRococo bridge. pub mod migration { use super::*; - use bp_messages::LaneId; + use bp_messages::LegacyLaneId; use frame_support::traits::ConstBool; - use sp_runtime::Either; parameter_types! { - pub AssetHubWestendToAssetHubRococoMessagesLane: LaneId = LaneId::from_inner(Either::Right([0, 0, 0, 2])); + pub AssetHubWestendToAssetHubRococoMessagesLane: LegacyLaneId = LegacyLaneId([0, 0, 0, 2]); pub AssetHubWestendLocation: Location = Location::new(1, [Parachain(bp_asset_hub_westend::ASSET_HUB_WESTEND_PARACHAIN_ID)]); pub AssetHubRococoUniversalLocation: InteriorLocation = [GlobalConsensus(RococoGlobalConsensusNetwork::get()), Parachain(bp_asset_hub_rococo::ASSET_HUB_ROCOCO_PARACHAIN_ID)].into(); } @@ -348,4 +350,75 @@ pub mod migration { AssetHubWestendLocation, AssetHubRococoUniversalLocation, >; + + mod v1_wrong { + use bp_messages::{LaneState, MessageNonce, UnrewardedRelayer}; + use bp_runtime::AccountIdOf; + use codec::{Decode, Encode}; + use pallet_bridge_messages::BridgedChainOf; + use sp_std::collections::vec_deque::VecDeque; + + #[derive(Encode, Decode, Clone, PartialEq, Eq)] + pub(crate) struct StoredInboundLaneData, I: 'static>( + pub(crate) InboundLaneData>>, + ); + #[derive(Encode, Decode, Clone, PartialEq, Eq)] + pub(crate) struct InboundLaneData { + pub state: LaneState, + pub(crate) relayers: VecDeque>, + pub(crate) last_confirmed_nonce: MessageNonce, + } + #[derive(Encode, Decode, Clone, PartialEq, Eq)] + pub(crate) struct OutboundLaneData { + pub state: LaneState, + pub(crate) oldest_unpruned_nonce: MessageNonce, + pub(crate) latest_received_nonce: MessageNonce, + pub(crate) latest_generated_nonce: MessageNonce, + } + } + + mod v1 { + pub use bp_messages::{InboundLaneData, LaneState, OutboundLaneData}; + pub use pallet_bridge_messages::{InboundLanes, OutboundLanes, StoredInboundLaneData}; + } + + /// Fix for v1 migration - corrects data for OutboundLaneData/InboundLaneData (it is needed only + /// for Rococo/Westend). + pub struct FixMessagesV1Migration(sp_std::marker::PhantomData<(T, I)>); + + impl, I: 'static> frame_support::traits::OnRuntimeUpgrade + for FixMessagesV1Migration + { + fn on_runtime_upgrade() -> Weight { + use sp_core::Get; + let mut weight = T::DbWeight::get().reads(1); + + // `InboundLanes` - add state to the old structs + let translate_inbound = + |pre: v1_wrong::StoredInboundLaneData| -> Option> { + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); + Some(v1::StoredInboundLaneData(v1::InboundLaneData { + state: v1::LaneState::Opened, + relayers: pre.0.relayers, + last_confirmed_nonce: pre.0.last_confirmed_nonce, + })) + }; + v1::InboundLanes::::translate_values(translate_inbound); + + // `OutboundLanes` - add state to the old structs + let translate_outbound = + |pre: v1_wrong::OutboundLaneData| -> Option { + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); + Some(v1::OutboundLaneData { + state: v1::LaneState::Opened, + oldest_unpruned_nonce: pre.oldest_unpruned_nonce, + latest_received_nonce: pre.latest_received_nonce, + latest_generated_nonce: pre.latest_generated_nonce, + }) + }; + v1::OutboundLanes::::translate_values(translate_outbound); + + weight + } + } } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/genesis_config_presets.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/genesis_config_presets.rs index 4c948656d9f..0b270e58433 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/genesis_config_presets.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/genesis_config_presets.rs @@ -31,6 +31,7 @@ fn bridge_hub_westend_genesis( id: ParaId, bridges_pallet_owner: Option, asset_hub_para_id: ParaId, + opened_bridges: Vec<(Location, InteriorLocation, Option)>, ) -> serde_json::Value { let config = RuntimeGenesisConfig { balances: BalancesConfig { @@ -71,6 +72,10 @@ fn bridge_hub_westend_genesis( owner: bridges_pallet_owner.clone(), ..Default::default() }, + xcm_over_bridge_hub_rococo: XcmOverBridgeHubRococoConfig { + opened_bridges, + ..Default::default() + }, ethereum_system: EthereumSystemConfig { para_id: id, asset_hub_para_id, @@ -114,6 +119,11 @@ pub fn get_preset(id: &sp_genesis_builder::PresetId) -> Option("Bob")), westend_runtime_constants::system_parachain::ASSET_HUB_ID.into(), + vec![( + Location::new(1, [Parachain(1000)]), + Junctions::from([Rococo.into(), Parachain(1000)]), + Some(bp_messages::LegacyLaneId([0, 0, 0, 2])), + )], ), Ok(sp_genesis_builder::DEV_RUNTIME_PRESET) => bridge_hub_westend_genesis( // initial collators. @@ -144,6 +154,7 @@ pub fn get_preset(id: &sp_genesis_builder::PresetId) -> Option("Bob")), westend_runtime_constants::system_parachain::ASSET_HUB_ID.into(), + vec![], ), _ => return None, }; diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs index 5c40506442d..87152d30977 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs @@ -82,6 +82,7 @@ use xcm_runtime_apis::{ }; use bp_runtime::HeaderId; +use pallet_bridge_messages::LaneIdOf; #[cfg(any(feature = "std", test))] pub use sp_runtime::BuildStorage; @@ -147,12 +148,17 @@ pub type Migrations = ( Runtime, bridge_to_rococo_config::WithBridgeHubRococoMessagesInstance, >, + bridge_to_rococo_config::migration::FixMessagesV1Migration< + Runtime, + bridge_to_rococo_config::WithBridgeHubRococoMessagesInstance, + >, bridge_to_rococo_config::migration::StaticToDynamicLanes, frame_support::migrations::RemoveStorage< BridgeRococoMessagesPalletName, OutboundLanesCongestedSignalsKey, RocksDbWeight, >, + pallet_bridge_relayers::migration::v1::MigrationToV1, // permanent pallet_xcm::migration::MigrateToLatestXcmVersion, snowbridge_pallet_system::migration::v0::InitializeOnUpgrade< @@ -851,7 +857,7 @@ impl_runtime_apis! { impl bp_bridge_hub_rococo::FromBridgeHubRococoInboundLaneApi for Runtime { fn message_details( - lane: bp_messages::LaneId, + lane: LaneIdOf, messages: Vec<(bp_messages::MessagePayload, bp_messages::OutboundMessageDetails)>, ) -> Vec { bridge_runtime_common::messages_api::inbound_message_details::< @@ -863,7 +869,7 @@ impl_runtime_apis! { impl bp_bridge_hub_rococo::ToBridgeHubRococoOutboundLaneApi for Runtime { fn message_details( - lane: bp_messages::LaneId, + lane: LaneIdOf, begin: bp_messages::MessageNonce, end: bp_messages::MessageNonce, ) -> Vec { @@ -1141,15 +1147,20 @@ impl_runtime_apis! { ); // open bridge - let origin = RuntimeOrigin::from(pallet_xcm::Origin::Xcm(sibling_parachain_location.clone())); - XcmOverBridgeHubRococo::open_bridge( - origin.clone(), - alloc::boxed::Box::new(VersionedInteriorLocation::from([GlobalConsensus(NetworkId::Rococo), Parachain(8765)])), + let bridge_destination_universal_location: InteriorLocation = [GlobalConsensus(NetworkId::Rococo), Parachain(8765)].into(); + let locations = XcmOverBridgeHubRococo::bridge_locations( + sibling_parachain_location.clone(), + bridge_destination_universal_location.clone(), + )?; + XcmOverBridgeHubRococo::do_open_bridge( + locations, + bp_messages::LegacyLaneId([1, 2, 3, 4]), + true, ).map_err(|e| { log::error!( "Failed to `XcmOverBridgeHubRococo::open_bridge`({:?}, {:?})`, error: {:?}", - origin, - [GlobalConsensus(NetworkId::Rococo), Parachain(8765)], + sibling_parachain_location, + bridge_destination_universal_location, e ); BenchmarkError::Stop("Bridge was not opened!") @@ -1203,12 +1214,16 @@ impl_runtime_apis! { } fn prepare_message_proof( - params: MessageProofParams, - ) -> (bridge_to_rococo_config::FromRococoBridgeHubMessagesProof, Weight) { + params: MessageProofParams>, + ) -> (bridge_to_rococo_config::FromRococoBridgeHubMessagesProof, Weight) { use cumulus_primitives_core::XcmpMessageSource; assert!(XcmpQueue::take_outbound_messages(usize::MAX).is_empty()); ParachainSystem::open_outbound_hrmp_channel_for_benchmarks_or_tests(42.into()); - let universal_source = bridge_to_rococo_config::open_bridge_for_benchmarks(params.lane, 42); + let universal_source = bridge_to_rococo_config::open_bridge_for_benchmarks::< + Runtime, + bridge_to_rococo_config::XcmOverBridgeHubRococoInstance, + xcm_config::LocationToAccountId, + >(params.lane, 42); prepare_message_proof_from_parachain::< Runtime, bridge_to_rococo_config::BridgeGrandpaRococoInstance, @@ -1217,9 +1232,13 @@ impl_runtime_apis! { } fn prepare_message_delivery_proof( - params: MessageDeliveryProofParams, - ) -> bridge_to_rococo_config::ToRococoBridgeHubMessagesDeliveryProof { - let _ = bridge_to_rococo_config::open_bridge_for_benchmarks(params.lane, 42); + params: MessageDeliveryProofParams>, + ) -> bridge_to_rococo_config::ToRococoBridgeHubMessagesDeliveryProof { + let _ = bridge_to_rococo_config::open_bridge_for_benchmarks::< + Runtime, + bridge_to_rococo_config::XcmOverBridgeHubRococoInstance, + xcm_config::LocationToAccountId, + >(params.lane, 42); prepare_message_delivery_proof_from_parachain::< Runtime, bridge_to_rococo_config::BridgeGrandpaRococoInstance, @@ -1264,14 +1283,15 @@ impl_runtime_apis! { } } - impl BridgeRelayersConfig for Runtime { + impl BridgeRelayersConfig for Runtime { fn prepare_rewards_account( - account_params: bp_relayers::RewardsAccountParams, + account_params: bp_relayers::RewardsAccountParams<>::LaneId>, reward: Balance, ) { let rewards_account = bp_relayers::PayRewardFromAccount::< Balances, - AccountId + AccountId, + >::LaneId, >::rewards_account(account_params); Self::deposit_account(rewards_account, reward); } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs index 4391b069cf0..4ff388f4ba2 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs @@ -16,8 +16,12 @@ #![cfg(test)] +use bp_messages::LegacyLaneId; use bp_polkadot_core::Signature; -use bridge_common_config::{DeliveryRewardInBalance, RequiredStakeForStakeAndSlash}; +use bridge_common_config::{ + DeliveryRewardInBalance, RelayersForLegacyLaneIdsMessagesInstance, + RequiredStakeForStakeAndSlash, +}; use bridge_hub_test_utils::{test_cases::from_parachain, SlotDurations}; use bridge_hub_westend_runtime::{ bridge_common_config, bridge_to_rococo_config, @@ -63,6 +67,7 @@ type RuntimeTestsAdapter = from_parachain::WithRemoteParachainHelperAdapter< BridgeGrandpaRococoInstance, BridgeParachainRococoInstance, WithBridgeHubRococoMessagesInstance, + RelayersForLegacyLaneIdsMessagesInstance, >; parameter_types! { @@ -235,7 +240,15 @@ fn handle_export_message_from_system_parachain_add_to_outbound_queue_works() { XcmOverBridgeHubRococoInstance, LocationToAccountId, WestendLocation, - >(SiblingParachainLocation::get(), BridgedUniversalLocation::get()).1 + >( + SiblingParachainLocation::get(), + BridgedUniversalLocation::get(), + |locations, fee| { + bridge_hub_test_utils::open_bridge_with_storage::< + Runtime, XcmOverBridgeHubRococoInstance + >(locations, fee, LegacyLaneId([0, 0, 0, 1])) + } + ).1 }, ) } @@ -288,10 +301,16 @@ fn relayed_incoming_message_works() { XcmOverBridgeHubRococoInstance, LocationToAccountId, WestendLocation, - >(SiblingParachainLocation::get(), BridgedUniversalLocation::get()) + >(SiblingParachainLocation::get(), BridgedUniversalLocation::get(), |locations, fee| { + bridge_hub_test_utils::open_bridge_with_storage::< + Runtime, + XcmOverBridgeHubRococoInstance, + >(locations, fee, LegacyLaneId([0, 0, 0, 1])) + }) .1 }, construct_and_apply_extrinsic, + true, ) } @@ -312,10 +331,16 @@ fn free_relay_extrinsic_works() { XcmOverBridgeHubRococoInstance, LocationToAccountId, WestendLocation, - >(SiblingParachainLocation::get(), BridgedUniversalLocation::get()) + >(SiblingParachainLocation::get(), BridgedUniversalLocation::get(), |locations, fee| { + bridge_hub_test_utils::open_bridge_with_storage::< + Runtime, + XcmOverBridgeHubRococoInstance, + >(locations, fee, LegacyLaneId([0, 0, 0, 1])) + }) .1 }, construct_and_apply_extrinsic, + true, ) } @@ -377,22 +402,3 @@ pub fn can_calculate_fee_for_standalone_message_confirmation_transaction() { ), ) } - -#[test] -fn open_and_close_bridge_works() { - let origins = [SiblingParachainLocation::get(), SiblingSystemParachainLocation::get()]; - - for origin in origins { - bridge_hub_test_utils::test_cases::open_and_close_bridge_works::< - Runtime, - XcmOverBridgeHubRococoInstance, - LocationToAccountId, - WestendLocation, - >( - collator_session_keys(), - bp_bridge_hub_westend::BRIDGE_HUB_WESTEND_PARACHAIN_ID, - origin, - BridgedUniversalLocation::get(), - ) - } -} diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/test-utils/Cargo.toml index 8c048a0d2db..915b3090092 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/test-utils/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/Cargo.toml @@ -37,6 +37,7 @@ parachains-runtimes-test-utils = { workspace = true } xcm = { workspace = true } xcm-builder = { workspace = true } xcm-executor = { workspace = true } +pallet-xcm = { workspace = true } # Bridges bp-header-chain = { workspace = true } @@ -81,6 +82,7 @@ std = [ "pallet-timestamp/std", "pallet-utility/std", "pallet-xcm-bridge-hub/std", + "pallet-xcm/std", "parachains-common/std", "parachains-runtimes-test-utils/std", "sp-core/std", diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/lib.rs index b8d6d87051c..bc28df0eb82 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/lib.rs @@ -24,7 +24,9 @@ extern crate alloc; pub use bp_test_utils::test_header; pub use parachains_runtimes_test_utils::*; use sp_runtime::Perbill; -pub use test_cases::helpers::ensure_opened_bridge; +pub use test_cases::helpers::{ + ensure_opened_bridge, open_bridge_with_extrinsic, open_bridge_with_storage, +}; /// A helper function for comparing the actual value of a fee constant with its estimated value. The /// estimated value can be overestimated (`overestimate_in_percent`), and if the difference to the diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/from_grandpa_chain.rs b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/from_grandpa_chain.rs index 72743eaa41d..320f3030b60 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/from_grandpa_chain.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/from_grandpa_chain.rs @@ -24,12 +24,12 @@ use crate::{ use alloc::{boxed::Box, vec}; use bp_header_chain::ChainWithGrandpa; -use bp_messages::{LaneId, UnrewardedRelayersState}; +use bp_messages::UnrewardedRelayersState; use bp_relayers::{RewardsAccountOwner, RewardsAccountParams}; use bp_xcm_bridge_hub::XcmAsPlainPayload; use frame_support::traits::{OnFinalize, OnInitialize}; use frame_system::pallet_prelude::BlockNumberFor; -use pallet_bridge_messages::{BridgedChainOf, ThisChainOf}; +use pallet_bridge_messages::{BridgedChainOf, LaneIdOf, ThisChainOf}; use parachains_runtimes_test_utils::{ AccountIdOf, BasicParachainRuntime, CollatorSessionKeys, RuntimeCallOf, SlotDurations, }; @@ -50,7 +50,7 @@ pub trait WithRemoteGrandpaChainHelper { Self::MPI, InboundPayload = XcmAsPlainPayload, OutboundPayload = XcmAsPlainPayload, - > + pallet_bridge_relayers::Config; + > + pallet_bridge_relayers::Config>; /// All pallets of this chain, excluding system pallet. type AllPalletsWithoutSystem: OnInitialize> + OnFinalize>; @@ -58,15 +58,18 @@ pub trait WithRemoteGrandpaChainHelper { type GPI: 'static; /// Instance of the `pallet-bridge-messages`, used to bridge with remote GRANDPA chain. type MPI: 'static; + /// Instance of the `pallet-bridge-relayers`, used to collect rewards from messages `MPI` + /// instance. + type RPI: 'static; } /// Adapter struct that implements [`WithRemoteGrandpaChainHelper`]. -pub struct WithRemoteGrandpaChainHelperAdapter( - core::marker::PhantomData<(Runtime, AllPalletsWithoutSystem, GPI, MPI)>, +pub struct WithRemoteGrandpaChainHelperAdapter( + core::marker::PhantomData<(Runtime, AllPalletsWithoutSystem, GPI, MPI, RPI)>, ); -impl WithRemoteGrandpaChainHelper - for WithRemoteGrandpaChainHelperAdapter +impl WithRemoteGrandpaChainHelper + for WithRemoteGrandpaChainHelperAdapter where Runtime: BasicParachainRuntime + cumulus_pallet_xcmp_queue::Config @@ -75,16 +78,18 @@ where MPI, InboundPayload = XcmAsPlainPayload, OutboundPayload = XcmAsPlainPayload, - > + pallet_bridge_relayers::Config, + > + pallet_bridge_relayers::Config>, AllPalletsWithoutSystem: OnInitialize> + OnFinalize>, GPI: 'static, MPI: 'static, + RPI: 'static, { type Runtime = Runtime; type AllPalletsWithoutSystem = AllPalletsWithoutSystem; type GPI = GPI; type MPI = MPI; + type RPI = RPI; } /// Test-case makes sure that Runtime can dispatch XCM messages submitted by relayer, @@ -96,11 +101,12 @@ pub fn relayed_incoming_message_works( runtime_para_id: u32, sibling_parachain_id: u32, local_relay_chain_id: NetworkId, - prepare_configuration: impl Fn() -> LaneId, + prepare_configuration: impl Fn() -> LaneIdOf, construct_and_apply_extrinsic: fn( sp_keyring::AccountKeyring, RuntimeCallOf, ) -> sp_runtime::DispatchOutcome, + expect_rewards: bool, ) where RuntimeHelper: WithRemoteGrandpaChainHelper, AccountIdOf: From, @@ -140,6 +146,7 @@ pub fn relayed_incoming_message_works( test_data::from_grandpa_chain::make_complex_relayer_delivery_proofs::< BridgedChainOf, ThisChainOf, + LaneIdOf, >( lane_id, xcm.into(), @@ -172,14 +179,18 @@ pub fn relayed_incoming_message_works( lane_id, 1, ), - helpers::VerifyRelayerRewarded::::expect_relayer_reward( - relayer_id_at_this_chain, - RewardsAccountParams::new( - lane_id, - bridged_chain_id, - RewardsAccountOwner::ThisChain, - ), - ), + if expect_rewards { + helpers::VerifyRelayerRewarded::::expect_relayer_reward( + relayer_id_at_this_chain, + RewardsAccountParams::new( + lane_id, + bridged_chain_id, + RewardsAccountOwner::ThisChain, + ), + ) + } else { + Box::new(()) + } )), ), ] @@ -197,11 +208,12 @@ pub fn free_relay_extrinsic_works( runtime_para_id: u32, sibling_parachain_id: u32, local_relay_chain_id: NetworkId, - prepare_configuration: impl Fn() -> LaneId, + prepare_configuration: impl Fn() -> LaneIdOf, construct_and_apply_extrinsic: fn( sp_keyring::AccountKeyring, RuntimeCallOf, ) -> sp_runtime::DispatchOutcome, + expect_rewards: bool, ) where RuntimeHelper: WithRemoteGrandpaChainHelper, RuntimeHelper::Runtime: pallet_balances::Config, @@ -263,6 +275,7 @@ pub fn free_relay_extrinsic_works( test_data::from_grandpa_chain::make_complex_relayer_delivery_proofs::< BridgedChainOf, ThisChainOf, + LaneIdOf, >( lane_id, xcm.into(), @@ -301,14 +314,18 @@ pub fn free_relay_extrinsic_works( lane_id, 1, ), - helpers::VerifyRelayerRewarded::::expect_relayer_reward( - relayer_id_at_this_chain, - RewardsAccountParams::new( - lane_id, - bridged_chain_id, - RewardsAccountOwner::ThisChain, - ), - ), + if expect_rewards { + helpers::VerifyRelayerRewarded::::expect_relayer_reward( + relayer_id_at_this_chain, + RewardsAccountParams::new( + lane_id, + bridged_chain_id, + RewardsAccountOwner::ThisChain, + ), + ) + } else { + Box::new(()) + } )), ), ] @@ -325,7 +342,7 @@ pub fn complex_relay_extrinsic_works( runtime_para_id: u32, sibling_parachain_id: u32, local_relay_chain_id: NetworkId, - prepare_configuration: impl Fn() -> LaneId, + prepare_configuration: impl Fn() -> LaneIdOf, construct_and_apply_extrinsic: fn( sp_keyring::AccountKeyring, RuntimeCallOf, @@ -372,6 +389,7 @@ pub fn complex_relay_extrinsic_works( test_data::from_grandpa_chain::make_complex_relayer_delivery_proofs::< BridgedChainOf, ThisChainOf, + LaneIdOf, >( lane_id, xcm.into(), @@ -382,9 +400,10 @@ pub fn complex_relay_extrinsic_works( ); let relay_chain_header_hash = relay_chain_header.hash(); - vec![( - pallet_utility::Call::::batch_all { - calls: vec![ + vec![ + ( + pallet_utility::Call::::batch_all { + calls: vec![ BridgeGrandpaCall::::submit_finality_proof { finality_target: Box::new(relay_chain_header), justification: grandpa_justification, @@ -396,27 +415,33 @@ pub fn complex_relay_extrinsic_works( dispatch_weight: Weight::from_parts(1000000000, 0), }.into(), ], - } - .into(), - Box::new(( - helpers::VerifySubmitGrandpaFinalityProofOutcome::< - RuntimeHelper::Runtime, - RuntimeHelper::GPI, - >::expect_best_header_hash(relay_chain_header_hash), - helpers::VerifySubmitMessagesProofOutcome::< - RuntimeHelper::Runtime, - RuntimeHelper::MPI, - >::expect_last_delivered_nonce(lane_id, 1), - helpers::VerifyRelayerRewarded::::expect_relayer_reward( - relayer_id_at_this_chain, - RewardsAccountParams::new( - lane_id, - bridged_chain_id, - RewardsAccountOwner::ThisChain, + } + .into(), + Box::new( + ( + helpers::VerifySubmitGrandpaFinalityProofOutcome::< + RuntimeHelper::Runtime, + RuntimeHelper::GPI, + >::expect_best_header_hash(relay_chain_header_hash), + helpers::VerifySubmitMessagesProofOutcome::< + RuntimeHelper::Runtime, + RuntimeHelper::MPI, + >::expect_last_delivered_nonce(lane_id, 1), + helpers::VerifyRelayerRewarded::< + RuntimeHelper::Runtime, + RuntimeHelper::RPI, + >::expect_relayer_reward( + relayer_id_at_this_chain, + RewardsAccountParams::new( + lane_id, + bridged_chain_id, + RewardsAccountOwner::ThisChain, + ), + ), ), ), - )), - )] + ), + ] }, ); } @@ -446,8 +471,9 @@ where test_data::from_grandpa_chain::make_complex_relayer_delivery_proofs::< BridgedChainOf, ThisChainOf, + LaneIdOf, >( - LaneId::new(1, 2), + LaneIdOf::::default(), vec![Instruction::<()>::ClearOrigin; 1_024].into(), 1, [GlobalConsensus(Polkadot), Parachain(1_000)].into(), @@ -502,8 +528,9 @@ where BridgedChainOf, ThisChainOf, (), + LaneIdOf, >( - LaneId::new(1, 2), + LaneIdOf::::default(), 1u32.into(), AccountId32::from(Alice.public()).into(), unrewarded_relayers.clone(), @@ -550,8 +577,9 @@ where test_data::from_grandpa_chain::make_complex_relayer_delivery_proofs::< BridgedChainOf, ThisChainOf, + LaneIdOf, >( - LaneId::new(1, 2), + LaneIdOf::::default(), vec![Instruction::<()>::ClearOrigin; 1_024].into(), 1, [GlobalConsensus(Polkadot), Parachain(1_000)].into(), @@ -602,8 +630,9 @@ where BridgedChainOf, ThisChainOf, (), + LaneIdOf, >( - LaneId::new(1, 2), + LaneIdOf::::default(), 1u32.into(), AccountId32::from(Alice.public()).into(), unrewarded_relayers.clone(), diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/from_parachain.rs b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/from_parachain.rs index 82edcacdcab..1da901e0bcd 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/from_parachain.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/from_parachain.rs @@ -24,14 +24,14 @@ use crate::{ use alloc::{boxed::Box, vec}; use bp_header_chain::ChainWithGrandpa; -use bp_messages::{LaneId, UnrewardedRelayersState}; +use bp_messages::UnrewardedRelayersState; use bp_polkadot_core::parachains::ParaHash; use bp_relayers::{RewardsAccountOwner, RewardsAccountParams}; use bp_runtime::{Chain, Parachain}; use bp_xcm_bridge_hub::XcmAsPlainPayload; use frame_support::traits::{OnFinalize, OnInitialize}; use frame_system::pallet_prelude::BlockNumberFor; -use pallet_bridge_messages::{BridgedChainOf, ThisChainOf}; +use pallet_bridge_messages::{BridgedChainOf, LaneIdOf, ThisChainOf}; use parachains_runtimes_test_utils::{ AccountIdOf, BasicParachainRuntime, CollatorSessionKeys, RuntimeCallOf, SlotDurations, }; @@ -53,7 +53,7 @@ pub trait WithRemoteParachainHelper { Self::MPI, InboundPayload = XcmAsPlainPayload, OutboundPayload = XcmAsPlainPayload, - > + pallet_bridge_relayers::Config; + > + pallet_bridge_relayers::Config>; /// All pallets of this chain, excluding system pallet. type AllPalletsWithoutSystem: OnInitialize> + OnFinalize>; @@ -63,15 +63,18 @@ pub trait WithRemoteParachainHelper { type PPI: 'static; /// Instance of the `pallet-bridge-messages`, used to bridge with remote parachain. type MPI: 'static; + /// Instance of the `pallet-bridge-relayers`, used to collect rewards from messages `MPI` + /// instance. + type RPI: 'static; } /// Adapter struct that implements `WithRemoteParachainHelper`. -pub struct WithRemoteParachainHelperAdapter( - core::marker::PhantomData<(Runtime, AllPalletsWithoutSystem, GPI, PPI, MPI)>, +pub struct WithRemoteParachainHelperAdapter( + core::marker::PhantomData<(Runtime, AllPalletsWithoutSystem, GPI, PPI, MPI, RPI)>, ); -impl WithRemoteParachainHelper - for WithRemoteParachainHelperAdapter +impl WithRemoteParachainHelper + for WithRemoteParachainHelperAdapter where Runtime: BasicParachainRuntime + cumulus_pallet_xcmp_queue::Config @@ -81,19 +84,20 @@ where MPI, InboundPayload = XcmAsPlainPayload, OutboundPayload = XcmAsPlainPayload, - > + pallet_bridge_relayers::Config, + > + pallet_bridge_relayers::Config>, AllPalletsWithoutSystem: OnInitialize> + OnFinalize>, GPI: 'static, PPI: 'static, MPI: 'static, - // MB: MessageBridge, + RPI: 'static, { type Runtime = Runtime; type AllPalletsWithoutSystem = AllPalletsWithoutSystem; type GPI = GPI; type PPI = PPI; type MPI = MPI; + type RPI = RPI; } /// Test-case makes sure that Runtime can dispatch XCM messages submitted by relayer, @@ -106,11 +110,12 @@ pub fn relayed_incoming_message_works( bridged_para_id: u32, sibling_parachain_id: u32, local_relay_chain_id: NetworkId, - prepare_configuration: impl Fn() -> LaneId, + prepare_configuration: impl Fn() -> LaneIdOf, construct_and_apply_extrinsic: fn( sp_keyring::AccountKeyring, ::RuntimeCall, ) -> sp_runtime::DispatchOutcome, + expect_rewards: bool, ) where RuntimeHelper: WithRemoteParachainHelper, AccountIdOf: From, @@ -161,6 +166,7 @@ pub fn relayed_incoming_message_works( >::BridgedChain, BridgedChainOf, ThisChainOf, + LaneIdOf, >( lane_id, xcm.into(), @@ -208,14 +214,18 @@ pub fn relayed_incoming_message_works( lane_id, 1, ), - helpers::VerifyRelayerRewarded::::expect_relayer_reward( - relayer_id_at_this_chain, - RewardsAccountParams::new( - lane_id, - bridged_chain_id, - RewardsAccountOwner::ThisChain, - ), - ), + if expect_rewards { + helpers::VerifyRelayerRewarded::::expect_relayer_reward( + relayer_id_at_this_chain, + RewardsAccountParams::new( + lane_id, + bridged_chain_id, + RewardsAccountOwner::ThisChain, + ), + ) + } else { + Box::new(()) + } )), ), ] @@ -234,11 +244,12 @@ pub fn free_relay_extrinsic_works( bridged_para_id: u32, sibling_parachain_id: u32, local_relay_chain_id: NetworkId, - prepare_configuration: impl Fn() -> LaneId, + prepare_configuration: impl Fn() -> LaneIdOf, construct_and_apply_extrinsic: fn( sp_keyring::AccountKeyring, ::RuntimeCall, ) -> sp_runtime::DispatchOutcome, + expect_rewards: bool, ) where RuntimeHelper: WithRemoteParachainHelper, RuntimeHelper::Runtime: pallet_balances::Config, @@ -312,6 +323,7 @@ pub fn free_relay_extrinsic_works( >::BridgedChain, BridgedChainOf, ThisChainOf, + LaneIdOf, >( lane_id, xcm.into(), @@ -353,10 +365,10 @@ pub fn free_relay_extrinsic_works( bridged_para_id, parachain_head_hash, ), - /*helpers::VerifyRelayerBalance::::expect_relayer_balance( + helpers::VerifyRelayerBalance::::expect_relayer_balance( relayer_id_at_this_chain.clone(), initial_relayer_balance, - ),*/ + ), )), ), ( @@ -371,14 +383,18 @@ pub fn free_relay_extrinsic_works( lane_id, 1, ), - helpers::VerifyRelayerRewarded::::expect_relayer_reward( - relayer_id_at_this_chain, - RewardsAccountParams::new( - lane_id, - bridged_chain_id, - RewardsAccountOwner::ThisChain, - ), - ), + if expect_rewards { + helpers::VerifyRelayerRewarded::::expect_relayer_reward( + relayer_id_at_this_chain, + RewardsAccountParams::new( + lane_id, + bridged_chain_id, + RewardsAccountOwner::ThisChain, + ), + ) + } else { + Box::new(()) + } )), ), ] @@ -396,7 +412,7 @@ pub fn complex_relay_extrinsic_works( bridged_para_id: u32, sibling_parachain_id: u32, local_relay_chain_id: NetworkId, - prepare_configuration: impl Fn() -> LaneId, + prepare_configuration: impl Fn() -> LaneIdOf, construct_and_apply_extrinsic: fn( sp_keyring::AccountKeyring, ::RuntimeCall, @@ -454,6 +470,7 @@ pub fn complex_relay_extrinsic_works( >::BridgedChain, BridgedChainOf, ThisChainOf, + LaneIdOf, >( lane_id, xcm.into(), @@ -468,9 +485,10 @@ pub fn complex_relay_extrinsic_works( let parachain_head_hash = parachain_head.hash(); let relay_chain_header_hash = relay_chain_header.hash(); let relay_chain_header_number = *relay_chain_header.number(); - vec![( - pallet_utility::Call::::batch_all { - calls: vec![ + vec![ + ( + pallet_utility::Call::::batch_all { + calls: vec![ BridgeGrandpaCall::::submit_finality_proof { finality_target: Box::new(relay_chain_header), justification: grandpa_justification, @@ -487,31 +505,37 @@ pub fn complex_relay_extrinsic_works( dispatch_weight: Weight::from_parts(1000000000, 0), }.into(), ], - } - .into(), - Box::new(( - helpers::VerifySubmitGrandpaFinalityProofOutcome::< - RuntimeHelper::Runtime, - RuntimeHelper::GPI, - >::expect_best_header_hash(relay_chain_header_hash), - helpers::VerifySubmitParachainHeaderProofOutcome::< - RuntimeHelper::Runtime, - RuntimeHelper::PPI, - >::expect_best_header_hash(bridged_para_id, parachain_head_hash), - helpers::VerifySubmitMessagesProofOutcome::< - RuntimeHelper::Runtime, - RuntimeHelper::MPI, - >::expect_last_delivered_nonce(lane_id, 1), - helpers::VerifyRelayerRewarded::::expect_relayer_reward( - relayer_id_at_this_chain, - RewardsAccountParams::new( - lane_id, - bridged_chain_id, - RewardsAccountOwner::ThisChain, + } + .into(), + Box::new( + ( + helpers::VerifySubmitGrandpaFinalityProofOutcome::< + RuntimeHelper::Runtime, + RuntimeHelper::GPI, + >::expect_best_header_hash(relay_chain_header_hash), + helpers::VerifySubmitParachainHeaderProofOutcome::< + RuntimeHelper::Runtime, + RuntimeHelper::PPI, + >::expect_best_header_hash(bridged_para_id, parachain_head_hash), + helpers::VerifySubmitMessagesProofOutcome::< + RuntimeHelper::Runtime, + RuntimeHelper::MPI, + >::expect_last_delivered_nonce(lane_id, 1), + helpers::VerifyRelayerRewarded::< + RuntimeHelper::Runtime, + RuntimeHelper::RPI, + >::expect_relayer_reward( + relayer_id_at_this_chain, + RewardsAccountParams::new( + lane_id, + bridged_chain_id, + RewardsAccountOwner::ThisChain, + ), + ), ), ), - )), - )] + ), + ] }, ); } @@ -551,8 +575,9 @@ where >::BridgedChain, BridgedChainOf, ThisChainOf, + LaneIdOf >( - LaneId::new(1, 2), + LaneIdOf::::default(), vec![Instruction::<()>::ClearOrigin; 1_024].into(), 1, [GlobalConsensus(Polkadot), Parachain(1_000)].into(), @@ -621,8 +646,9 @@ where >::BridgedChain, BridgedChainOf, ThisChainOf, + LaneIdOf, >( - LaneId::new(1, 2), + LaneIdOf::::default(), 1, 5, 1_000, @@ -683,8 +709,9 @@ where >::BridgedChain, BridgedChainOf, ThisChainOf, + LaneIdOf, >( - LaneId::new(1, 2), + LaneIdOf::::default(), vec![Instruction::<()>::ClearOrigin; 1_024].into(), 1, [GlobalConsensus(Polkadot), Parachain(1_000)].into(), @@ -738,8 +765,9 @@ where >::BridgedChain, BridgedChainOf, ThisChainOf, + LaneIdOf, >( - LaneId::new(1, 2), + LaneIdOf::::default(), 1, 5, 1_000, diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/helpers.rs b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/helpers.rs index c343e9b3e09..aac60bba0b5 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/helpers.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/helpers.rs @@ -19,7 +19,7 @@ use crate::test_cases::{bridges_prelude::*, run_test, RuntimeHelper}; use asset_test_utils::BasicParachainRuntime; -use bp_messages::{LaneId, MessageNonce}; +use bp_messages::MessageNonce; use bp_polkadot_core::parachains::{ParaHash, ParaId}; use bp_relayers::RewardsAccountParams; use bp_runtime::Chain; @@ -33,7 +33,7 @@ use frame_support::{ }; use frame_system::pallet_prelude::BlockNumberFor; use pallet_bridge_grandpa::{BridgedBlockHash, BridgedHeader}; -use pallet_bridge_messages::BridgedChainOf; +use pallet_bridge_messages::{BridgedChainOf, LaneIdOf}; use parachains_common::AccountId; use parachains_runtimes_test_utils::{ mock_open_hrmp_channel, AccountIdOf, CollatorSessionKeys, RuntimeCallOf, SlotDurations, @@ -132,8 +132,8 @@ where } /// Checks that the latest delivered nonce in the bridge messages pallet equals to given one. -pub struct VerifySubmitMessagesProofOutcome { - lane: LaneId, +pub struct VerifySubmitMessagesProofOutcome, MPI: 'static> { + lane: LaneIdOf, expected_nonce: MessageNonce, _marker: PhantomData<(Runtime, MPI)>, } @@ -145,7 +145,7 @@ where { /// Expect given delivered nonce to be the latest after transaction. pub fn expect_last_delivered_nonce( - lane: LaneId, + lane: LaneIdOf, expected_nonce: MessageNonce, ) -> Box { Box::new(Self { lane, expected_nonce, _marker: PhantomData }) @@ -167,30 +167,32 @@ where } /// Verifies that relayer is rewarded at this chain. -pub struct VerifyRelayerRewarded { +pub struct VerifyRelayerRewarded, RPI: 'static> { relayer: Runtime::AccountId, - reward_params: RewardsAccountParams, + reward_params: RewardsAccountParams, } -impl VerifyRelayerRewarded +impl VerifyRelayerRewarded where - Runtime: pallet_bridge_relayers::Config, + Runtime: pallet_bridge_relayers::Config, + RPI: 'static, { /// Expect given delivered nonce to be the latest after transaction. pub fn expect_relayer_reward( relayer: Runtime::AccountId, - reward_params: RewardsAccountParams, + reward_params: RewardsAccountParams, ) -> Box { Box::new(Self { relayer, reward_params }) } } -impl VerifyTransactionOutcome for VerifyRelayerRewarded +impl VerifyTransactionOutcome for VerifyRelayerRewarded where - Runtime: pallet_bridge_relayers::Config, + Runtime: pallet_bridge_relayers::Config, + RPI: 'static, { fn verify_outcome(&self) { - assert!(pallet_bridge_relayers::RelayerRewards::::get( + assert!(pallet_bridge_relayers::RelayerRewards::::get( &self.relayer, &self.reward_params, ) @@ -388,7 +390,12 @@ fn execute_and_verify_calls( /// Helper function to open the bridge/lane for `source` and `destination` while ensuring all /// required balances are placed into the SA of the source. -pub fn ensure_opened_bridge(source: Location, destination: InteriorLocation) -> (BridgeLocations, LaneId) +pub fn ensure_opened_bridge< + Runtime, + XcmOverBridgePalletInstance, + LocationToAccountId, + TokenLocation> +(source: Location, destination: InteriorLocation, bridge_opener: impl Fn(BridgeLocations, Asset)) -> (BridgeLocations, pallet_xcm_bridge_hub::LaneIdOf) where Runtime: BasicParachainRuntime + BridgeXcmOverBridgeConfig, XcmOverBridgePalletInstance: 'static, @@ -425,34 +432,74 @@ TokenLocation: Get{ let _ = >::mint_into(&source_account_id, balance_needed) .expect("mint_into passes"); + // call the bridge opener + bridge_opener(*locations.clone(), buy_execution_fee); + + // check opened bridge + let bridge = pallet_xcm_bridge_hub::Bridges::::get( + locations.bridge_id(), + ) + .expect("opened bridge"); + + // check state + assert_ok!( + pallet_xcm_bridge_hub::Pallet::::do_try_state() + ); + + // return locations + (*locations, bridge.lane_id) +} + +/// Utility for opening bridge with dedicated `pallet_xcm_bridge_hub`'s extrinsic. +pub fn open_bridge_with_extrinsic( + locations: BridgeLocations, + buy_execution_fee: Asset, +) where + Runtime: frame_system::Config + + pallet_xcm_bridge_hub::Config + + cumulus_pallet_parachain_system::Config + + pallet_xcm::Config, + XcmOverBridgePalletInstance: 'static, + ::RuntimeCall: + GetDispatchInfo + From>, +{ // open bridge with `Transact` call let open_bridge_call = RuntimeCallOf::::from(BridgeXcmOverBridgeCall::< Runtime, XcmOverBridgePalletInstance, >::open_bridge { - bridge_destination_universal_location: Box::new(destination.into()), + bridge_destination_universal_location: Box::new( + locations.bridge_destination_universal_location().clone().into(), + ), }); // execute XCM as source origin would do with `Transact -> Origin::Xcm` assert_ok!(RuntimeHelper::::execute_as_origin_xcm( + locations.bridge_origin_relative_location().clone(), open_bridge_call, - source.clone(), buy_execution_fee ) .ensure_complete()); +} - let bridge = pallet_xcm_bridge_hub::Bridges::::get( - locations.bridge_id(), - ) - .expect("opened bridge"); - - // check state +/// Utility for opening bridge directly inserting data to the storage (used only for legacy +/// purposes). +pub fn open_bridge_with_storage( + locations: BridgeLocations, + _buy_execution_fee: Asset, + lane_id: pallet_xcm_bridge_hub::LaneIdOf, +) where + Runtime: pallet_xcm_bridge_hub::Config, + XcmOverBridgePalletInstance: 'static, +{ + // insert bridge data directly to the storage assert_ok!( - pallet_xcm_bridge_hub::Pallet::::do_try_state() + pallet_xcm_bridge_hub::Pallet::::do_open_bridge( + Box::new(locations), + lane_id, + true + ) ); - - // return locations - (*locations, bridge.lane_id) } /// Helper function to close the bridge/lane for `source` and `destination`. @@ -504,8 +551,8 @@ TokenLocation: Get{ // execute XCM as source origin would do with `Transact -> Origin::Xcm` assert_ok!(RuntimeHelper::::execute_as_origin_xcm( - close_bridge_call, source.clone(), + close_bridge_call, buy_execution_fee ) .ensure_complete()); diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/mod.rs b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/mod.rs index de117982b26..663558f5fd5 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/mod.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/mod.rs @@ -29,7 +29,7 @@ use crate::{test_cases::bridges_prelude::*, test_data}; use asset_test_utils::BasicParachainRuntime; use bp_messages::{ target_chain::{DispatchMessage, DispatchMessageData, MessageDispatch}, - LaneId, LaneState, MessageKey, MessagesOperatingMode, OutboundLaneData, + LaneState, MessageKey, MessagesOperatingMode, OutboundLaneData, }; use bp_runtime::BasicOperatingMode; use bp_xcm_bridge_hub::{Bridge, BridgeState, XcmAsPlainPayload}; @@ -71,11 +71,13 @@ pub(crate) mod bridges_prelude { // Re-export test_case from assets pub use asset_test_utils::include_teleports_for_native_asset_works; +use pallet_bridge_messages::LaneIdOf; pub type RuntimeHelper = parachains_runtimes_test_utils::RuntimeHelper; // Re-export test_case from `parachains-runtimes-test-utils` +use crate::test_cases::helpers::open_bridge_with_extrinsic; pub use parachains_runtimes_test_utils::test_cases::{ change_storage_constant_by_governance_works, set_storage_keys_by_governance_works, }; @@ -326,7 +328,7 @@ pub fn handle_export_message_from_system_parachain_to_outbound_queue_works< export_message_instruction: fn() -> Instruction, existential_deposit: Option, maybe_paid_export_message: Option, - prepare_configuration: impl Fn() -> LaneId, + prepare_configuration: impl Fn() -> LaneIdOf, ) where Runtime: BasicParachainRuntime + BridgeMessagesConfig, XcmConfig: xcm_executor::Config, @@ -469,7 +471,7 @@ pub fn message_dispatch_routing_works< run_test::(collator_session_key, runtime_para_id, vec![], || { prepare_configuration(); - let dummy_lane_id = LaneId::new(1, 2); + let dummy_lane_id = LaneIdOf::::default(); let mut alice = [0u8; 32]; alice[0] = 1; @@ -714,7 +716,11 @@ pub fn open_and_close_bridge_works(source.clone(), destination.clone()) + >( + source.clone(), + destination.clone(), + open_bridge_with_extrinsic:: + ) .0 .bridge_id(), locations.bridge_id() diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_data/from_grandpa_chain.rs b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_data/from_grandpa_chain.rs index 2940c4e00f4..7461085330f 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_data/from_grandpa_chain.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_data/from_grandpa_chain.rs @@ -20,8 +20,8 @@ use crate::test_data::prepare_inbound_xcm; use bp_messages::{ source_chain::FromBridgedChainMessagesDeliveryProof, - target_chain::FromBridgedChainMessagesProof, ChainWithMessages, LaneId, LaneState, - MessageNonce, UnrewardedRelayersState, + target_chain::FromBridgedChainMessagesProof, ChainWithMessages, LaneState, MessageNonce, + UnrewardedRelayersState, }; use bp_runtime::{AccountIdOf, BlockNumberOf, Chain, HeaderOf, UnverifiedStorageProofParams}; use bp_test_utils::make_default_justification; @@ -40,7 +40,7 @@ use pallet_bridge_messages::{ encode_all_messages, encode_lane_data, prepare_message_delivery_storage_proof, prepare_messages_storage_proof, }, - BridgedChainOf, + BridgedChainOf, LaneIdOf, }; use sp_runtime::DigestItem; @@ -48,7 +48,10 @@ use sp_runtime::DigestItem; pub fn make_complex_relayer_delivery_batch( bridged_header: BridgedHeader, bridged_justification: GrandpaJustification>, - message_proof: FromBridgedChainMessagesProof>>, + message_proof: FromBridgedChainMessagesProof< + HashOf>, + LaneIdOf, + >, relayer_id_at_bridged_chain: InboundRelayerId, ) -> pallet_utility::Call where @@ -82,6 +85,7 @@ pub fn make_complex_relayer_confirmation_batch( bridged_justification: GrandpaJustification>, message_delivery_proof: FromBridgedChainMessagesDeliveryProof< HashOf>, + LaneIdOf, >, relayers_state: UnrewardedRelayersState, ) -> pallet_utility::Call @@ -111,7 +115,10 @@ where /// Prepare a call with message proof. pub fn make_standalone_relayer_delivery_call( - message_proof: FromBridgedChainMessagesProof>>, + message_proof: FromBridgedChainMessagesProof< + HashOf>, + LaneIdOf, + >, relayer_id_at_bridged_chain: InboundRelayerId, ) -> Runtime::RuntimeCall where @@ -134,6 +141,7 @@ where pub fn make_standalone_relayer_confirmation_call( message_delivery_proof: FromBridgedChainMessagesDeliveryProof< HashOf>, + LaneIdOf, >, relayers_state: UnrewardedRelayersState, ) -> Runtime::RuntimeCall @@ -152,7 +160,7 @@ where } /// Prepare storage proofs of messages, stored at the (bridged) source GRANDPA chain. -pub fn make_complex_relayer_delivery_proofs( +pub fn make_complex_relayer_delivery_proofs( lane_id: LaneId, xcm_message: Xcm<()>, message_nonce: MessageNonce, @@ -162,17 +170,18 @@ pub fn make_complex_relayer_delivery_proofs ) -> ( HeaderOf, GrandpaJustification>, - FromBridgedChainMessagesProof>, + FromBridgedChainMessagesProof, LaneId>, ) where BridgedChain: ChainWithGrandpa, ThisChainWithMessages: ChainWithMessages, + LaneId: Copy + Encode, { // prepare message let message_payload = prepare_inbound_xcm(xcm_message, message_destination); // prepare storage proof containing message let (state_root, storage_proof) = - prepare_messages_storage_proof::( + prepare_messages_storage_proof::( lane_id, message_nonce..=message_nonce, None, @@ -206,6 +215,7 @@ pub fn make_complex_relayer_confirmation_proofs< BridgedChain, ThisChainWithMessages, InnerXcmRuntimeCall, + LaneId, >( lane_id: LaneId, header_number: BlockNumberOf, @@ -214,15 +224,16 @@ pub fn make_complex_relayer_confirmation_proofs< ) -> ( HeaderOf, GrandpaJustification>, - FromBridgedChainMessagesDeliveryProof>, + FromBridgedChainMessagesDeliveryProof, LaneId>, ) where BridgedChain: ChainWithGrandpa, ThisChainWithMessages: ChainWithMessages, + LaneId: Copy + Encode, { // prepare storage proof containing message delivery proof let (state_root, storage_proof) = - prepare_message_delivery_storage_proof::( + prepare_message_delivery_storage_proof::( lane_id, InboundLaneData { state: LaneState::Opened, diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_data/from_parachain.rs b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_data/from_parachain.rs index aefbc0dbd0a..a6659b8241d 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_data/from_parachain.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_data/from_parachain.rs @@ -20,7 +20,7 @@ use super::{from_grandpa_chain::make_complex_bridged_grandpa_header_proof, prepa use bp_messages::{ source_chain::FromBridgedChainMessagesDeliveryProof, - target_chain::FromBridgedChainMessagesProof, ChainWithMessages, LaneId, LaneState, + target_chain::FromBridgedChainMessagesProof, ChainWithMessages, LaneState, UnrewardedRelayersState, Weight, }; use bp_parachains::{RelayBlockHash, RelayBlockNumber}; @@ -43,7 +43,7 @@ use pallet_bridge_messages::{ encode_all_messages, encode_lane_data, prepare_message_delivery_storage_proof, prepare_messages_storage_proof, }, - BridgedChainOf, + BridgedChainOf, LaneIdOf, }; use sp_runtime::SaturatedConversion; @@ -53,7 +53,7 @@ pub fn make_complex_relayer_delivery_batch( grandpa_justification: GrandpaJustification>, parachain_heads: Vec<(ParaId, ParaHash)>, para_heads_proof: ParaHeadsProof, - message_proof: FromBridgedChainMessagesProof, + message_proof: FromBridgedChainMessagesProof>, relayer_id_at_bridged_chain: InboundRelayerId, ) -> pallet_utility::Call where @@ -106,7 +106,7 @@ pub fn make_complex_relayer_confirmation_batch( grandpa_justification: GrandpaJustification>, parachain_heads: Vec<(ParaId, ParaHash)>, para_heads_proof: ParaHeadsProof, - message_delivery_proof: FromBridgedChainMessagesDeliveryProof, + message_delivery_proof: FromBridgedChainMessagesDeliveryProof>, relayers_state: UnrewardedRelayersState, ) -> pallet_utility::Call where @@ -154,7 +154,7 @@ where /// Prepare a call with message proof. pub fn make_standalone_relayer_delivery_call( - message_proof: FromBridgedChainMessagesProof, + message_proof: FromBridgedChainMessagesProof>, relayer_id_at_bridged_chain: InboundRelayerId, ) -> Runtime::RuntimeCall where @@ -174,7 +174,7 @@ where /// Prepare a call with message delivery proof. pub fn make_standalone_relayer_confirmation_call( - message_delivery_proof: FromBridgedChainMessagesDeliveryProof, + message_delivery_proof: FromBridgedChainMessagesDeliveryProof>, relayers_state: UnrewardedRelayersState, ) -> Runtime::RuntimeCall where @@ -195,6 +195,7 @@ pub fn make_complex_relayer_delivery_proofs< BridgedRelayChain, BridgedParachain, ThisChainWithMessages, + LaneId, >( lane_id: LaneId, xcm_message: Xcm<()>, @@ -210,19 +211,20 @@ pub fn make_complex_relayer_delivery_proofs< ParaHead, Vec<(ParaId, ParaHash)>, ParaHeadsProof, - FromBridgedChainMessagesProof, + FromBridgedChainMessagesProof, ) where BridgedRelayChain: bp_runtime::Chain + ChainWithGrandpa, BridgedParachain: bp_runtime::Chain + Parachain, ThisChainWithMessages: ChainWithMessages, + LaneId: Copy + Encode, { // prepare message let message_payload = prepare_inbound_xcm(xcm_message, message_destination); // prepare para storage proof containing message let (para_state_root, para_storage_proof) = - prepare_messages_storage_proof::( + prepare_messages_storage_proof::( lane_id, message_nonce..=message_nonce, None, @@ -266,6 +268,7 @@ pub fn make_complex_relayer_confirmation_proofs< BridgedRelayChain, BridgedParachain, ThisChainWithMessages, + LaneId, >( lane_id: LaneId, para_header_number: u32, @@ -279,17 +282,18 @@ pub fn make_complex_relayer_confirmation_proofs< ParaHead, Vec<(ParaId, ParaHash)>, ParaHeadsProof, - FromBridgedChainMessagesDeliveryProof, + FromBridgedChainMessagesDeliveryProof, ) where BridgedRelayChain: bp_runtime::Chain + ChainWithGrandpa, BridgedParachain: bp_runtime::Chain + Parachain, ThisChainWithMessages: ChainWithMessages, + LaneId: Copy + Encode, { // prepare para storage proof containing message delivery proof let (para_state_root, para_storage_proof) = - prepare_message_delivery_storage_proof::( + prepare_message_delivery_storage_proof::( lane_id, InboundLaneData { state: LaneState::Opened, diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_data/mod.rs b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_data/mod.rs index 106eacd799c..c34188af506 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_data/mod.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_data/mod.rs @@ -21,7 +21,7 @@ pub mod from_parachain; use bp_messages::{ target_chain::{DispatchMessage, DispatchMessageData}, - LaneId, MessageKey, + MessageKey, }; use codec::Encode; use frame_support::traits::Get; @@ -65,11 +65,11 @@ pub(crate) fn dummy_xcm() -> Xcm<()> { vec![Trap(42)].into() } -pub(crate) fn dispatch_message( +pub(crate) fn dispatch_message( lane_id: LaneId, nonce: MessageNonce, payload: Vec, -) -> DispatchMessage> { +) -> DispatchMessage, LaneId> { DispatchMessage { key: MessageKey { lane_id, nonce }, data: DispatchMessageData { payload: Ok(payload) }, diff --git a/cumulus/parachains/runtimes/test-utils/src/lib.rs b/cumulus/parachains/runtimes/test-utils/src/lib.rs index fe75b2b6e72..3b38eee244f 100644 --- a/cumulus/parachains/runtimes/test-utils/src/lib.rs +++ b/cumulus/parachains/runtimes/test-utils/src/lib.rs @@ -465,8 +465,8 @@ impl< } pub fn execute_as_origin_xcm( - call: Call, origin: Location, + call: Call, buy_execution_fee: Asset, ) -> Outcome { // prepare `Transact` xcm diff --git a/cumulus/polkadot-parachain/Cargo.toml b/cumulus/polkadot-parachain/Cargo.toml index ad88be60d74..d52bc639b3b 100644 --- a/cumulus/polkadot-parachain/Cargo.toml +++ b/cumulus/polkadot-parachain/Cargo.toml @@ -57,6 +57,9 @@ xcm = { workspace = true, default-features = true } # Cumulus cumulus-primitives-core = { workspace = true, default-features = true } +# Bridges +bp-messages = { workspace = true, default-features = true } + [build-dependencies] substrate-build-script-utils = { workspace = true, default-features = true } diff --git a/docker/dockerfiles/bridges_zombienet_tests_injected.Dockerfile b/docker/dockerfiles/bridges_zombienet_tests_injected.Dockerfile index 55b9156e6a0..2198da13a4b 100644 --- a/docker/dockerfiles/bridges_zombienet_tests_injected.Dockerfile +++ b/docker/dockerfiles/bridges_zombienet_tests_injected.Dockerfile @@ -1,7 +1,7 @@ # this image is built on top of existing Zombienet image ARG ZOMBIENET_IMAGE # this image uses substrate-relay image built elsewhere -ARG SUBSTRATE_RELAY_IMAGE=docker.io/paritytech/substrate-relay:v1.6.8 +ARG SUBSTRATE_RELAY_IMAGE=docker.io/paritytech/substrate-relay:v1.6.10 # metadata ARG VCS_REF diff --git a/polkadot/xcm/xcm-executor/src/traits/export.rs b/polkadot/xcm/xcm-executor/src/traits/export.rs index 78aa68ce264..b356e0da7df 100644 --- a/polkadot/xcm/xcm-executor/src/traits/export.rs +++ b/polkadot/xcm/xcm-executor/src/traits/export.rs @@ -20,7 +20,7 @@ use xcm::latest::prelude::*; /// spoofed origin. This essentially defines the behaviour of the `ExportMessage` XCM instruction. /// /// This is quite different to `SendXcm`; `SendXcm` assumes that the local side's location will be -/// preserved to be represented as the value of the Origin register in the messages execution. +/// preserved to be represented as the value of the Origin register during the message's execution. /// /// This trait on the other hand assumes that we do not necessarily want the Origin register to /// contain the local (i.e. the caller chain's) location, since it will generally be exporting a @@ -44,8 +44,8 @@ pub trait ExportXcm { /// The `destination` and `message` must be `Some` (or else an error will be returned) and they /// may only be consumed if the `Err` is not `NotApplicable`. /// - /// If it is not a destination which can be reached with this type but possibly could by others, - /// then this *MUST* return `NotApplicable`. Any other error will cause the tuple + /// If it is not a destination that can be reached with this type, but possibly could be with + /// others, then this *MUST* return `NotApplicable`. Any other error will cause the tuple /// implementation (used to compose routing systems from different delivery agents) to exit /// early without trying alternative means of delivery. fn validate( diff --git a/prdoc/pr_5649.prdoc b/prdoc/pr_5649.prdoc new file mode 100644 index 00000000000..1f4c97aa175 --- /dev/null +++ b/prdoc/pr_5649.prdoc @@ -0,0 +1,49 @@ +title: "Bridges lane id agnostic for backwards compatibility" + +doc: +- audience: Runtime Dev + description: | + This PR improves support for handling `LaneId` backwards compatibility with the previously merged [PR](https://github.com/paritytech/polkadot-sdk/pull/4949). + If `pallet_bridge_messages` or `pallet_bridge_relayers` used `LaneId([u8; 4])` previously, they should now set `type LaneId = LegacyLaneId;`. + +crates: +- name: bridge-runtime-common + bump: patch +- name: bp-runtime + bump: patch +- name: staging-xcm-executor + bump: none +- name: parachains-runtimes-test-utils + bump: patch +- name: bp-messages + bump: major +- name: bp-relayers + bump: major +- name: bp-xcm-bridge-hub + bump: major +- name: pallet-bridge-messages + bump: patch +- name: pallet-bridge-relayers + bump: patch +- name: pallet-xcm-bridge-hub + bump: major +- name: emulated-integration-tests-common + bump: patch +- name: bp-bridge-hub-kusama + bump: patch +- name: bp-bridge-hub-polkadot + bump: patch +- name: bp-bridge-hub-rococo + bump: patch +- name: bp-bridge-hub-westend + bump: patch +- name: bp-polkadot-bulletin + bump: patch +- name: bridge-hub-rococo-runtime + bump: major +- name: bridge-hub-westend-runtime + bump: patch +- name: polkadot-parachain-bin + bump: none +- name: bridge-hub-test-utils + bump: major -- GitLab From 286af23bc29da7d1e95b7d97a0a76547dba52189 Mon Sep 17 00:00:00 2001 From: Andrei Sandu <54316454+sandreim@users.noreply.github.com> Date: Wed, 25 Sep 2024 17:09:41 +0300 Subject: [PATCH 104/125] approval-voting: remove dead test code (#5815) Minor cleanup. --------- Signed-off-by: Andrei Sandu --- polkadot/node/core/approval-voting/src/tests.rs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/polkadot/node/core/approval-voting/src/tests.rs b/polkadot/node/core/approval-voting/src/tests.rs index 65aa4f894c2..8aa78df5495 100644 --- a/polkadot/node/core/approval-voting/src/tests.rs +++ b/polkadot/node/core/approval-voting/src/tests.rs @@ -39,7 +39,7 @@ use polkadot_node_subsystem::{ }; use polkadot_node_subsystem_test_helpers as test_helpers; use polkadot_node_subsystem_util::TimeoutExt; -use polkadot_overseer::{HeadSupportsParachains, SpawnGlue}; +use polkadot_overseer::SpawnGlue; use polkadot_primitives::{ ApprovalVote, CandidateCommitments, CandidateEvent, CoreIndex, DisputeStatement, GroupIndex, Header, Id as ParaId, IndexedVec, NodeFeatures, ValidDisputeStatementKind, ValidationCode, @@ -48,7 +48,6 @@ use polkadot_primitives::{ use std::{cmp::max, time::Duration}; use assert_matches::assert_matches; -use async_trait::async_trait; use parking_lot::Mutex; use sp_keyring::sr25519::Keyring as Sr25519Keyring; use sp_keystore::Keystore; @@ -131,15 +130,6 @@ pub mod test_constants { pub(crate) const TEST_CONFIG: DatabaseConfig = DatabaseConfig { col_approval_data: DATA_COL }; } -struct MockSupportsParachains; - -#[async_trait] -impl HeadSupportsParachains for MockSupportsParachains { - async fn head_supports_parachains(&self, _head: &Hash) -> bool { - true - } -} - fn slot_to_tick(t: impl Into) -> Tick { slot_number_to_tick(SLOT_DURATION_MILLIS, t.into()) } -- GitLab From cc6a5130bed92d4ddc073b0ea1d006360e98ddd5 Mon Sep 17 00:00:00 2001 From: liamaharon Date: Thu, 26 Sep 2024 00:38:21 +1000 Subject: [PATCH 105/125] MBM `try-runtime` support (#4251) # MBM try-runtime support This MR adds support to the try-runtime trait such that the try-runtime-CLI will be able to support MBM testing [here](https://github.com/paritytech/try-runtime-cli/pull/90). It mainly adds two feature-gated hooks to the `SteppedMigration` hook to facilitate testing. These hooks are named `pre_upgrade` and `post_upgrade` and have the same signature and implications as for single-block migrations. ## Integration To make use of this in your Multi-Block-Migration, just implement the two new hooks and test pre- and post-conditions in them: ```rust #[cfg(feature = "try-runtime")] fn pre_upgrade() -> Result, frame_support::sp_runtime::TryRuntimeError> { // ... } #[cfg(feature = "try-runtime")] fn post_upgrade(prev: Vec) -> Result<(), frame_support::sp_runtime::TryRuntimeError> { // ... } ``` You may return an error or panic in these functions to indicate failure. This will then show up in the try-runtime-CLI and can be used in CI for testing. Changes: - Adds `try-runtime` gated methods `pre_upgrade` and `post_upgrade` on `SteppedMigration` - Adds `try-runtime` gated methods `nth_pre_upgrade` and `nth_post_upgrade` on `SteppedMigrations` - Modifies `pallet_migrations` implementation to run pre_upgrade and post_upgrade steps at the appropriate times, and panic in the event of migration failure. --------- Signed-off-by: Oliver Tale-Yazdi Signed-off-by: georgepisaltu Co-authored-by: Oliver Tale-Yazdi Co-authored-by: claravanstaden Co-authored-by: ggwpez Co-authored-by: georgepisaltu --- .gitlab/pipeline/test.yml | 24 +++++ Cargo.lock | 1 + .../runtimes/people/people-rococo/src/lib.rs | 1 + prdoc/pr_4251.prdoc | 79 +++++++++++++++ .../src/migrations/v1/mod.rs | 43 ++++++++ substrate/frame/migrations/Cargo.toml | 1 + substrate/frame/migrations/src/lib.rs | 64 ++++++++++-- .../frame/migrations/src/mock_helpers.rs | 33 +++++++ substrate/frame/migrations/src/tests.rs | 98 ++++++++++++++++++- substrate/frame/support/src/migrations.rs | 96 +++++++++++++++++- .../support/src/traits/try_runtime/mod.rs | 4 +- 11 files changed, 432 insertions(+), 12 deletions(-) create mode 100644 prdoc/pr_4251.prdoc diff --git a/.gitlab/pipeline/test.yml b/.gitlab/pipeline/test.yml index 00a0aa2c977..6fb35c61e48 100644 --- a/.gitlab/pipeline/test.yml +++ b/.gitlab/pipeline/test.yml @@ -110,6 +110,30 @@ test-linux-stable-codecov: codecovcli -v do-upload -f target/coverage/result/report-${CI_NODE_INDEX}.lcov --disable-search -t ${CODECOV_TOKEN} -r paritytech/polkadot-sdk --commit-sha ${CI_COMMIT_SHA} --fail-on-error --git-service github; fi +# some tests do not run with `try-runtime` feature enabled +# https://github.com/paritytech/polkadot-sdk/pull/4251#discussion_r1624282143 +test-linux-stable-no-try-runtime: + stage: test + extends: + - .docker-env + - .common-refs + - .run-immediately + - .pipeline-stopper-artifacts + variables: + RUST_TOOLCHAIN: stable + # Enable debug assertions since we are running optimized builds for testing + # but still want to have debug assertions. + RUSTFLAGS: "-Cdebug-assertions=y -Dwarnings" + script: + - > + time cargo nextest run \ + --workspace \ + --locked \ + --release \ + --no-fail-fast \ + --cargo-quiet \ + --features experimental,riscv,ci-only-tests + test-doc: stage: test extends: diff --git a/Cargo.lock b/Cargo.lock index 69eabeb04e2..61f485bcecb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11840,6 +11840,7 @@ dependencies = [ name = "pallet-migrations" version = "1.0.0" dependencies = [ + "cfg-if", "docify", "frame-benchmarking", "frame-executive", diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs b/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs index 150152964b9..16e023ad3dc 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs @@ -111,6 +111,7 @@ pub type UncheckedExtrinsic = /// Migrations to apply on runtime upgrade. pub type Migrations = ( pallet_collator_selection::migration::v2::MigrationToV2, + cumulus_pallet_xcmp_queue::migration::v5::MigrateV4ToV5, // permanent pallet_xcm::migration::MigrateToLatestXcmVersion, ); diff --git a/prdoc/pr_4251.prdoc b/prdoc/pr_4251.prdoc new file mode 100644 index 00000000000..4d4fcd73469 --- /dev/null +++ b/prdoc/pr_4251.prdoc @@ -0,0 +1,79 @@ +title: MBM `try-runtime` support +doc: +- audience: Runtime Dev + description: | + # MBM try-runtime support + + This MR adds support to the try-runtime + trait such that the try-runtime-CLI will be able to support MBM testing [here](https://github.com/paritytech/try-runtime-cli/pull/90). + It mainly adds two feature-gated hooks to the `SteppedMigration` hook to facilitate + testing. These hooks are named `pre_upgrade` and `post_upgrade` and have the + same signature and implications as for single-block migrations. + + ## Integration + + To make use of this in your Multi-Block-Migration, just implement the two new hooks and test pre- and post-conditions in them: + + ```rust + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, frame_support::sp_runtime::TryRuntimeError> + { + // ... + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(prev: Vec) -> Result<(), frame_support::sp_runtime::TryRuntimeError> { + // ... + } + ``` + + You may return an error or panic in these functions to indicate failure. + This will then show up in the try-runtime-CLI and can be used in CI for testing. + + + Changes: + - Adds `try-runtime` gated methods `pre_upgrade` and `post_upgrade` + on `SteppedMigration` + - Adds `try-runtime` gated methods `nth_pre_upgrade` + and `nth_post_upgrade` on `SteppedMigrations` + - Modifies `pallet_migrations` + implementation to run pre_upgrade and post_upgrade steps at the appropriate times, and panic in the event of migration failure. +crates: +- name: asset-hub-rococo-runtime + bump: minor +- name: asset-hub-westend-runtime + bump: minor +- name: bridge-hub-rococo-runtime + bump: minor +- name: bridge-hub-westend-runtime + bump: minor +- name: collectives-westend-runtime + bump: minor +- name: contracts-rococo-runtime + bump: minor +- name: coretime-rococo-runtime + bump: minor +- name: coretime-westend-runtime + bump: minor +- name: people-rococo-runtime + bump: minor +- name: people-westend-runtime + bump: minor +- name: penpal-runtime + bump: minor +- name: polkadot-parachain-bin + bump: minor +- name: rococo-runtime + bump: minor +- name: westend-runtime + bump: minor +- name: frame-executive + bump: minor +- name: pallet-migrations + bump: minor +- name: frame-support + bump: minor +- name: frame-system + bump: minor +- name: frame-try-runtime + bump: minor diff --git a/substrate/frame/examples/multi-block-migrations/src/migrations/v1/mod.rs b/substrate/frame/examples/multi-block-migrations/src/migrations/v1/mod.rs index 2016b03de45..6243846d86b 100644 --- a/substrate/frame/examples/multi-block-migrations/src/migrations/v1/mod.rs +++ b/substrate/frame/examples/multi-block-migrations/src/migrations/v1/mod.rs @@ -21,6 +21,8 @@ //! [`v0::MyMap`](`crate::migrations::v1::v0::MyMap`) storage map, transforms them, //! and inserts them into the [`MyMap`](`crate::pallet::MyMap`) storage map. +extern crate alloc; + use super::PALLET_MIGRATIONS_ID; use crate::pallet::{Config, MyMap}; use frame_support::{ @@ -29,6 +31,12 @@ use frame_support::{ weights::WeightMeter, }; +#[cfg(feature = "try-runtime")] +use alloc::collections::btree_map::BTreeMap; + +#[cfg(feature = "try-runtime")] +use alloc::vec::Vec; + mod benchmarks; mod tests; pub mod weights; @@ -115,4 +123,39 @@ impl SteppedMigration for LazyMigrationV1 Result, frame_support::sp_runtime::TryRuntimeError> { + use codec::Encode; + + // Return the state of the storage before the migration. + Ok(v0::MyMap::::iter().collect::>().encode()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(prev: Vec) -> Result<(), frame_support::sp_runtime::TryRuntimeError> { + use codec::Decode; + + // Check the state of the storage after the migration. + let prev_map = BTreeMap::::decode(&mut &prev[..]) + .expect("Failed to decode the previous storage state"); + + // Check the len of prev and post are the same. + assert_eq!( + MyMap::::iter().count(), + prev_map.len(), + "Migration failed: the number of items in the storage after the migration is not the same as before" + ); + + for (key, value) in prev_map { + let new_value = + MyMap::::get(key).expect("Failed to get the value after the migration"); + assert_eq!( + value as u64, new_value, + "Migration failed: the value after the migration is not the same as before" + ); + } + + Ok(()) + } } diff --git a/substrate/frame/migrations/Cargo.toml b/substrate/frame/migrations/Cargo.toml index 5fbed74a440..a32e48e6528 100644 --- a/substrate/frame/migrations/Cargo.toml +++ b/substrate/frame/migrations/Cargo.toml @@ -12,6 +12,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } +cfg-if = { workspace = true } docify = { workspace = true } impl-trait-for-tuples = { workspace = true } log = { workspace = true, default-features = true } diff --git a/substrate/frame/migrations/src/lib.rs b/substrate/frame/migrations/src/lib.rs index 1823e5a2f95..d9490e7dcfe 100644 --- a/substrate/frame/migrations/src/lib.rs +++ b/substrate/frame/migrations/src/lib.rs @@ -70,21 +70,26 @@ //! points to the currently active migration and stores its inner cursor. The inner cursor can then //! be used by the migration to store its inner state and advance. Each time when the migration //! returns `Some(cursor)`, it signals the pallet that it is not done yet. +//! //! The cursor is reset on each runtime upgrade. This ensures that it starts to execute at the //! first migration in the vector. The pallets cursor is only ever incremented or set to `Stuck` //! once it encounters an error (Goal 4). Once in the stuck state, the pallet will stay stuck until //! it is fixed through manual governance intervention. +//! //! As soon as the cursor of the pallet becomes `Some(_)`; [`MultiStepMigrator::ongoing`] returns //! `true` (Goal 2). This can be used by upstream code to possibly pause transactions. //! In `on_initialize` the pallet will load the current migration and check whether it was already //! executed in the past by checking for membership of its ID in the [`Historic`] set. Historic //! migrations are skipped without causing an error. Each successfully executed migration is added //! to this set (Goal 5). +//! //! This proceeds until no more migrations remain. At that point, the event `UpgradeCompleted` is //! emitted (Goal 1). +//! //! The execution of each migration happens by calling [`SteppedMigration::transactional_step`]. //! This function wraps the inner `step` function into a transactional layer to allow rollback in //! the error case (Goal 6). +//! //! Weight limits must be checked by the migration itself. The pallet provides a [`WeightMeter`] for //! that purpose. The pallet may return [`SteppedMigrationError::InsufficientWeight`] at any point. //! In that scenario, one of two things will happen: if that migration was exclusively executed @@ -156,11 +161,15 @@ use core::ops::ControlFlow; use frame_support::{ defensive, defensive_assert, migrations::*, + pallet_prelude::*, traits::Get, weights::{Weight, WeightMeter}, BoundedVec, }; -use frame_system::{pallet_prelude::BlockNumberFor, Pallet as System}; +use frame_system::{ + pallet_prelude::{BlockNumberFor, *}, + Pallet as System, +}; use sp_runtime::Saturating; /// Points to the next migration to execute. @@ -262,6 +271,7 @@ pub type IdentifierOf = BoundedVec::IdentifierMaxLen>; pub type ActiveCursorOf = ActiveCursor, BlockNumberFor>; /// Trait for a tuple of No-OP migrations with one element. +#[impl_trait_for_tuples::impl_for_tuples(30)] pub trait MockedMigrations: SteppedMigrations { /// The migration should fail after `n` steps. fn set_fail_after(n: u32); @@ -269,11 +279,24 @@ pub trait MockedMigrations: SteppedMigrations { fn set_success_after(n: u32); } +#[cfg(feature = "try-runtime")] +/// Wrapper for pre-upgrade bytes, allowing us to impl MEL on it. +/// +/// For `try-runtime` testing only. +#[derive(Debug, Clone, Eq, PartialEq, Encode, Decode, scale_info::TypeInfo, Default)] +struct PreUpgradeBytesWrapper(pub Vec); + +/// Data stored by the pre-upgrade hook of the MBMs. Only used for `try-runtime` testing. +/// +/// Define this outside of the pallet so it is not confused with actual storage. +#[cfg(feature = "try-runtime")] +#[frame_support::storage_alias] +type PreUpgradeBytes = + StorageMap, Twox64Concat, IdentifierOf, PreUpgradeBytesWrapper, ValueQuery>; + #[frame_support::pallet] pub mod pallet { use super::*; - use frame_support::pallet_prelude::*; - use frame_system::pallet_prelude::*; #[pallet::pallet] pub struct Pallet(_); @@ -701,6 +724,16 @@ impl Pallet { } let max_steps = T::Migrations::nth_max_steps(cursor.index); + + // If this is the first time running this migration, exec the pre-upgrade hook. + #[cfg(feature = "try-runtime")] + if !PreUpgradeBytes::::contains_key(&bounded_id) { + let bytes = T::Migrations::nth_pre_upgrade(cursor.index) + .expect("Invalid cursor.index") + .expect("Pre-upgrade failed"); + PreUpgradeBytes::::insert(&bounded_id, PreUpgradeBytesWrapper(bytes)); + } + let next_cursor = T::Migrations::nth_transactional_step( cursor.index, cursor.inner_cursor.clone().map(|c| c.into_inner()), @@ -735,6 +768,16 @@ impl Pallet { }, Ok(None) => { // A migration is done when it returns cursor `None`. + + // Run post-upgrade checks. + #[cfg(feature = "try-runtime")] + T::Migrations::nth_post_upgrade( + cursor.index, + PreUpgradeBytes::::get(&bounded_id).0, + ) + .expect("Invalid cursor.index.") + .expect("Post-upgrade failed."); + Self::deposit_event(Event::MigrationCompleted { index: cursor.index, took }); Historic::::insert(&bounded_id, ()); cursor.goto_next_migration(System::::block_number()); @@ -759,14 +802,21 @@ impl Pallet { } /// Fail the current runtime upgrade, caused by `migration`. + /// + /// When the `try-runtime` feature is enabled, this function will panic. + // Allow unreachable code so it can compile without warnings when `try-runtime` is enabled. fn upgrade_failed(migration: Option) { use FailedMigrationHandling::*; Self::deposit_event(Event::UpgradeFailed); - match T::FailedMigrationHandler::failed(migration) { - KeepStuck => Cursor::::set(Some(MigrationCursor::Stuck)), - ForceUnstuck => Cursor::::kill(), - Ignore => {}, + if cfg!(feature = "try-runtime") { + panic!("Migration with index {:?} failed.", migration); + } else { + match T::FailedMigrationHandler::failed(migration) { + KeepStuck => Cursor::::set(Some(MigrationCursor::Stuck)), + ForceUnstuck => Cursor::::kill(), + Ignore => {}, + } } } diff --git a/substrate/frame/migrations/src/mock_helpers.rs b/substrate/frame/migrations/src/mock_helpers.rs index 9d3b4d1193f..a03c70051d3 100644 --- a/substrate/frame/migrations/src/mock_helpers.rs +++ b/substrate/frame/migrations/src/mock_helpers.rs @@ -43,6 +43,12 @@ pub enum MockedMigrationKind { /// Cause an [`SteppedMigrationError::InsufficientWeight`] error after its number of steps /// elapsed. HighWeightAfter(Weight), + /// PreUpgrade should fail. + #[cfg(feature = "try-runtime")] + PreUpgradeFail, + /// PostUpgrade should fail. + #[cfg(feature = "try-runtime")] + PostUpgradeFail, } use MockedMigrationKind::*; // C style @@ -99,6 +105,8 @@ impl SteppedMigrations for MockedMigrations { Err(SteppedMigrationError::Failed) }, TimeoutAfter => unreachable!(), + #[cfg(feature = "try-runtime")] + PreUpgradeFail | PostUpgradeFail => Ok(None), }) } @@ -115,6 +123,31 @@ impl SteppedMigrations for MockedMigrations { MIGRATIONS::get().get(n as usize).map(|(_, s)| Some(*s)) } + #[cfg(feature = "try-runtime")] + fn nth_pre_upgrade(n: u32) -> Option, sp_runtime::TryRuntimeError>> { + let (kind, _) = MIGRATIONS::get()[n as usize]; + + if let PreUpgradeFail = kind { + return Some(Err("Some pre-upgrade error".into())) + } + + Some(Ok(vec![])) + } + + #[cfg(feature = "try-runtime")] + fn nth_post_upgrade( + n: u32, + _state: Vec, + ) -> Option> { + let (kind, _) = MIGRATIONS::get()[n as usize]; + + if let PostUpgradeFail = kind { + return Some(Err("Some post-upgrade error".into())) + } + + Some(Ok(())) + } + fn cursor_max_encoded_len() -> usize { 65_536 } diff --git a/substrate/frame/migrations/src/tests.rs b/substrate/frame/migrations/src/tests.rs index 73ca2a9a09c..55f212bcf37 100644 --- a/substrate/frame/migrations/src/tests.rs +++ b/substrate/frame/migrations/src/tests.rs @@ -17,12 +17,13 @@ #![cfg(test)] +use frame_support::{pallet_prelude::Weight, traits::OnRuntimeUpgrade}; + use crate::{ mock::{Test as T, *}, mock_helpers::{MockedMigrationKind::*, *}, Cursor, Event, FailedMigrationHandling, MigrationCursor, }; -use frame_support::{pallet_prelude::Weight, traits::OnRuntimeUpgrade}; #[docify::export] #[test] @@ -86,6 +87,7 @@ fn simple_multiple_works() { } #[test] +#[cfg_attr(feature = "try-runtime", should_panic)] fn failing_migration_sets_cursor_to_stuck() { test_closure(|| { FailedUpgradeResponse::set(FailedMigrationHandling::KeepStuck); @@ -116,6 +118,7 @@ fn failing_migration_sets_cursor_to_stuck() { } #[test] +#[cfg_attr(feature = "try-runtime", should_panic)] fn failing_migration_force_unstuck_works() { test_closure(|| { FailedUpgradeResponse::set(FailedMigrationHandling::ForceUnstuck); @@ -148,6 +151,7 @@ fn failing_migration_force_unstuck_works() { /// A migration that reports not getting enough weight errors if it is the first one to run in that /// block. #[test] +#[cfg_attr(feature = "try-runtime", should_panic)] fn high_weight_migration_singular_fails() { test_closure(|| { MockedMigrations::set(vec![(HighWeightAfter(Weight::zero()), 2)]); @@ -176,6 +180,7 @@ fn high_weight_migration_singular_fails() { /// A migration that reports of not getting enough weight is retried once, if it is not the first /// one to run in a block. #[test] +#[cfg_attr(feature = "try-runtime", should_panic)] fn high_weight_migration_retries_once() { test_closure(|| { MockedMigrations::set(vec![(SucceedAfter, 0), (HighWeightAfter(Weight::zero()), 0)]); @@ -205,6 +210,7 @@ fn high_weight_migration_retries_once() { // Note: Same as `high_weight_migration_retries_once` but with different required weight for the // migration. #[test] +#[cfg_attr(feature = "try-runtime", should_panic)] fn high_weight_migration_permanently_overweight_fails() { test_closure(|| { MockedMigrations::set(vec![(SucceedAfter, 0), (HighWeightAfter(Weight::MAX), 0)]); @@ -300,6 +306,7 @@ fn historic_skipping_works() { /// When another upgrade happens while a migration is still running, it should set the cursor to /// stuck. #[test] +#[cfg_attr(feature = "try-runtime", should_panic)] fn upgrade_fails_when_migration_active() { test_closure(|| { MockedMigrations::set(vec![(SucceedAfter, 10)]); @@ -326,6 +333,7 @@ fn upgrade_fails_when_migration_active() { } #[test] +#[cfg_attr(feature = "try-runtime", should_panic)] fn migration_timeout_errors() { test_closure(|| { MockedMigrations::set(vec![(TimeoutAfter, 3)]); @@ -358,3 +366,91 @@ fn migration_timeout_errors() { assert_eq!(upgrades_started_completed_failed(), (0, 0, 1)); }); } + +#[cfg(feature = "try-runtime")] +#[test] +fn try_runtime_success_case() { + use Event::*; + test_closure(|| { + // Add three migrations, each taking one block longer than the previous. + MockedMigrations::set(vec![(SucceedAfter, 0), (SucceedAfter, 1), (SucceedAfter, 2)]); + + System::set_block_number(1); + Migrations::on_runtime_upgrade(); + run_to_block(10); + + // Check that we got all events. + assert_events(vec![ + UpgradeStarted { migrations: 3 }, + MigrationCompleted { index: 0, took: 1 }, + MigrationAdvanced { index: 1, took: 0 }, + MigrationCompleted { index: 1, took: 1 }, + MigrationAdvanced { index: 2, took: 0 }, + MigrationAdvanced { index: 2, took: 1 }, + MigrationCompleted { index: 2, took: 2 }, + UpgradeCompleted, + ]); + }); +} + +#[test] +#[cfg(feature = "try-runtime")] +#[should_panic] +fn try_runtime_pre_upgrade_failure() { + test_closure(|| { + // Add three migrations, it should fail after the second one. + MockedMigrations::set(vec![(SucceedAfter, 0), (PreUpgradeFail, 1), (SucceedAfter, 2)]); + + System::set_block_number(1); + Migrations::on_runtime_upgrade(); + + // should panic + run_to_block(10); + }); +} + +#[test] +#[cfg(feature = "try-runtime")] +#[should_panic] +fn try_runtime_post_upgrade_failure() { + test_closure(|| { + // Add three migrations, it should fail after the second one. + MockedMigrations::set(vec![(SucceedAfter, 0), (PostUpgradeFail, 1), (SucceedAfter, 2)]); + + System::set_block_number(1); + Migrations::on_runtime_upgrade(); + + // should panic + run_to_block(10); + }); +} + +#[test] +#[cfg(feature = "try-runtime")] +#[should_panic] +fn try_runtime_migration_failure() { + test_closure(|| { + // Add three migrations, it should fail after the second one. + MockedMigrations::set(vec![(SucceedAfter, 0), (FailAfter, 5), (SucceedAfter, 10)]); + + System::set_block_number(1); + Migrations::on_runtime_upgrade(); + + // should panic + run_to_block(10); + }); +} + +#[test] +fn try_runtime_no_migrations() { + test_closure(|| { + MockedMigrations::set(vec![]); + + System::set_block_number(1); + Migrations::on_runtime_upgrade(); + + run_to_block(10); + + assert_eq!(System::events().len(), 0); + }); +} diff --git a/substrate/frame/support/src/migrations.rs b/substrate/frame/support/src/migrations.rs index 0eabf9d0ee1..905d6143e4f 100644 --- a/substrate/frame/support/src/migrations.rs +++ b/substrate/frame/support/src/migrations.rs @@ -529,6 +529,25 @@ pub trait SteppedMigration { }) .map_err(|()| SteppedMigrationError::Failed)? } + + /// Hook for testing that is run before the migration is started. + /// + /// Returns some bytes which are passed into `post_upgrade` after the migration is completed. + /// This is not run for the real migration, so panicking is not an issue here. + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, sp_runtime::TryRuntimeError> { + Ok(Vec::new()) + } + + /// Hook for testing that is run after the migration is completed. + /// + /// Should be used to verify the state of the chain after the migration. The `state` parameter + /// is the return value from `pre_upgrade`. This is not run for the real migration, so panicking + /// is not an issue here. + #[cfg(feature = "try-runtime")] + fn post_upgrade(_state: Vec) -> Result<(), sp_runtime::TryRuntimeError> { + Ok(()) + } } /// Error that can occur during a [`SteppedMigration`]. @@ -700,6 +719,19 @@ pub trait SteppedMigrations { meter: &mut WeightMeter, ) -> Option>, SteppedMigrationError>>; + /// Call the pre-upgrade hooks of the `n`th migration. + /// + /// Returns `None` if the index is out of bounds. + #[cfg(feature = "try-runtime")] + fn nth_pre_upgrade(n: u32) -> Option, sp_runtime::TryRuntimeError>>; + + /// Call the post-upgrade hooks of the `n`th migration. + /// + /// Returns `None` if the index is out of bounds. + #[cfg(feature = "try-runtime")] + fn nth_post_upgrade(n: u32, _state: Vec) + -> Option>; + /// The maximal encoded length across all cursors. fn cursor_max_encoded_len() -> usize; @@ -763,6 +795,19 @@ impl SteppedMigrations for () { None } + #[cfg(feature = "try-runtime")] + fn nth_pre_upgrade(_n: u32) -> Option, sp_runtime::TryRuntimeError>> { + Some(Ok(Vec::new())) + } + + #[cfg(feature = "try-runtime")] + fn nth_post_upgrade( + _n: u32, + _state: Vec, + ) -> Option> { + Some(Ok(())) + } + fn cursor_max_encoded_len() -> usize { 0 } @@ -792,11 +837,11 @@ impl SteppedMigrations for T { } fn nth_step( - _n: u32, + n: u32, cursor: Option>, meter: &mut WeightMeter, ) -> Option>, SteppedMigrationError>> { - if !_n.is_zero() { + if !n.is_zero() { defensive!("nth_step should only be called with n==0"); return None } @@ -835,6 +880,23 @@ impl SteppedMigrations for T { ) } + #[cfg(feature = "try-runtime")] + fn nth_pre_upgrade(n: u32) -> Option, sp_runtime::TryRuntimeError>> { + if n != 0 { + defensive!("nth_pre_upgrade should only be called with n==0"); + } + + Some(T::pre_upgrade()) + } + + #[cfg(feature = "try-runtime")] + fn nth_post_upgrade(n: u32, state: Vec) -> Option> { + if n != 0 { + defensive!("nth_post_upgrade should only be called with n==0"); + } + Some(T::post_upgrade(state)) + } + fn cursor_max_encoded_len() -> usize { T::Cursor::max_encoded_len() } @@ -900,6 +962,36 @@ impl SteppedMigrations for Tuple { None } + #[cfg(feature = "try-runtime")] + fn nth_pre_upgrade(n: u32) -> Option, sp_runtime::TryRuntimeError>> { + let mut i = 0; + + for_tuples! ( #( + if (i + Tuple::len()) > n { + return Tuple::nth_pre_upgrade(n - i) + } + + i += Tuple::len(); + )* ); + + None + } + + #[cfg(feature = "try-runtime")] + fn nth_post_upgrade(n: u32, state: Vec) -> Option> { + let mut i = 0; + + for_tuples! ( #( + if (i + Tuple::len()) > n { + return Tuple::nth_post_upgrade(n - i, state) + } + + i += Tuple::len(); + )* ); + + None + } + fn nth_max_steps(n: u32) -> Option> { let mut i = 0; diff --git a/substrate/frame/support/src/traits/try_runtime/mod.rs b/substrate/frame/support/src/traits/try_runtime/mod.rs index 09c33c01440..284ba3d7422 100644 --- a/substrate/frame/support/src/traits/try_runtime/mod.rs +++ b/substrate/frame/support/src/traits/try_runtime/mod.rs @@ -28,7 +28,7 @@ use sp_arithmetic::traits::AtLeast32BitUnsigned; use sp_runtime::TryRuntimeError; /// Which state tests to execute. -#[derive(codec::Encode, codec::Decode, Clone, scale_info::TypeInfo)] +#[derive(codec::Encode, codec::Decode, Clone, scale_info::TypeInfo, PartialEq)] pub enum Select { /// None of them. None, @@ -95,7 +95,7 @@ impl std::str::FromStr for Select { } /// Select which checks should be run when trying a runtime upgrade upgrade. -#[derive(codec::Encode, codec::Decode, Clone, Debug, Copy, scale_info::TypeInfo)] +#[derive(codec::Encode, codec::Decode, Clone, Debug, Copy, scale_info::TypeInfo, PartialEq)] pub enum UpgradeCheckSelect { /// Run no checks. None, -- GitLab From b5ac7a9d59298eddcd0b6e9470afed7cc9e403d4 Mon Sep 17 00:00:00 2001 From: Adrian Catangiu Date: Wed, 25 Sep 2024 17:38:48 +0300 Subject: [PATCH 106/125] xcm-executor: validate destinations for ReserveWithdraw and Teleport transfers (#5660) This change adds the required validation for stronger UX guarantees when using `InitiateReserveWithdraw` or `InitiateTeleport` XCM instructions. Execution of the instructions will fail if the local chain is not configured to trust the "destination" or "reserve" chain as a reserve/trusted-teleporter for the provided "assets". With this change, misuse of `InitiateReserveWithdraw`/`InitiateTeleport` fails on origin with no overall side-effects, rather than failing on destination (with side-effects to origin's assets issuance). The commit also makes the same validations for pallet-xcm transfers, and adds regression tests. --------- Signed-off-by: Adrian Catangiu Co-authored-by: Branislav Kontur --- .../tests/assets/asset-hub-rococo/src/lib.rs | 1 + .../src/tests/hybrid_transfers.rs | 11 +++- .../src/tests/reserve_transfer.rs | 55 +++++++++++++++++++ .../asset-hub-rococo/src/tests/teleport.rs | 51 +++++++++++++++++ .../tests/assets/asset-hub-westend/src/lib.rs | 1 + .../src/tests/hybrid_transfers.rs | 11 +++- .../src/tests/reserve_transfer.rs | 55 +++++++++++++++++++ .../asset-hub-westend/src/tests/teleport.rs | 51 +++++++++++++++++ .../bridges/bridge-hub-rococo/src/lib.rs | 1 + .../src/tests/asset_transfers.rs | 10 ++++ .../bridges/bridge-hub-westend/src/lib.rs | 6 +- .../src/tests/asset_transfers.rs | 10 ++++ polkadot/xcm/pallet-xcm/src/lib.rs | 20 +++++++ polkadot/xcm/xcm-builder/tests/scenarios.rs | 20 ++----- polkadot/xcm/xcm-executor/src/lib.rs | 23 ++++++-- .../example/src/relay_chain/xcm_config/mod.rs | 3 +- .../src/relay_chain/xcm_config/teleporter.rs | 26 +++++++++ prdoc/pr_5660.prdoc | 30 ++++++++++ 18 files changed, 360 insertions(+), 25 deletions(-) create mode 100644 polkadot/xcm/xcm-simulator/example/src/relay_chain/xcm_config/teleporter.rs create mode 100644 prdoc/pr_5660.prdoc diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/lib.rs index f4fe1478f3e..0e43108a417 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/lib.rs @@ -51,6 +51,7 @@ mod imports { pub use rococo_system_emulated_network::{ asset_hub_rococo_emulated_chain::{ asset_hub_rococo_runtime::{ + self, xcm_config::{ self as ahr_xcm_config, TokenLocation as RelayLocation, XcmConfig as AssetHubRococoXcmConfig, diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/hybrid_transfers.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/hybrid_transfers.rs index 7ff6d6c193c..7bb25d7cec6 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/hybrid_transfers.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/hybrid_transfers.rs @@ -449,7 +449,16 @@ fn transfer_foreign_assets_from_para_to_para_through_asset_hub() { let sov_of_receiver_on_ah = AssetHubRococo::sovereign_account_id_of(receiver_as_seen_by_ah); let wnd_to_send = ASSET_HUB_ROCOCO_ED * 10_000_000; - // Configure destination chain to trust AH as reserve of WND + // Configure source and destination chains to trust AH as reserve of WND + PenpalA::execute_with(|| { + assert_ok!(::System::set_storage( + ::RuntimeOrigin::root(), + vec![( + PenpalCustomizableAssetFromSystemAssetHub::key().to_vec(), + Location::new(2, [GlobalConsensus(Westend)]).encode(), + )], + )); + }); PenpalB::execute_with(|| { assert_ok!(::System::set_storage( ::RuntimeOrigin::root(), diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reserve_transfer.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reserve_transfer.rs index faff5f7660c..8aad4b392b2 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reserve_transfer.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reserve_transfer.rs @@ -1548,3 +1548,58 @@ fn reserve_transfer_usdt_from_para_to_para_through_asset_hub() { // Receiver's balance is increased assert!(receiver_assets_after > receiver_assets_before); } + +/// Reserve Withdraw Native Asset from AssetHub to Parachain fails. +#[test] +fn reserve_withdraw_from_untrusted_reserve_fails() { + // Init values for Parachain Origin + let destination = AssetHubRococo::sibling_location_of(PenpalA::para_id()); + let signed_origin = + ::RuntimeOrigin::signed(AssetHubRococoSender::get().into()); + let roc_to_send: Balance = ROCOCO_ED * 10000; + let roc_location = RelayLocation::get(); + + // Assets to send + let assets: Vec = vec![(roc_location.clone(), roc_to_send).into()]; + let fee_id: AssetId = roc_location.into(); + + // this should fail + AssetHubRococo::execute_with(|| { + let result = ::PolkadotXcm::transfer_assets_using_type_and_then( + signed_origin.clone(), + bx!(destination.clone().into()), + bx!(assets.clone().into()), + bx!(TransferType::DestinationReserve), + bx!(fee_id.into()), + bx!(TransferType::DestinationReserve), + bx!(VersionedXcm::from(Xcm::<()>::new())), + Unlimited, + ); + assert_err!( + result, + DispatchError::Module(sp_runtime::ModuleError { + index: 31, + error: [22, 0, 0, 0], + message: Some("InvalidAssetUnsupportedReserve") + }) + ); + }); + + // this should also fail + AssetHubRococo::execute_with(|| { + let xcm: Xcm = Xcm(vec![ + WithdrawAsset(assets.into()), + InitiateReserveWithdraw { + assets: Wild(All), + reserve: destination, + xcm: Xcm::<()>::new(), + }, + ]); + let result = ::PolkadotXcm::execute( + signed_origin, + bx!(xcm::VersionedXcm::V4(xcm)), + Weight::MAX, + ); + assert!(result.is_err()); + }); +} diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/teleport.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/teleport.rs index c8da801a14b..d6cf819118e 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/teleport.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/teleport.rs @@ -527,3 +527,54 @@ fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() { system_para_to_para_transfer_assets, ); } + +/// Teleport Native Asset from AssetHub to Parachain fails. +#[test] +fn teleport_to_untrusted_chain_fails() { + // Init values for Parachain Origin + let destination = AssetHubRococo::sibling_location_of(PenpalA::para_id()); + let signed_origin = + ::RuntimeOrigin::signed(AssetHubRococoSender::get().into()); + let roc_to_send: Balance = ROCOCO_ED * 10000; + let roc_location = RelayLocation::get(); + + // Assets to send + let assets: Vec = vec![(roc_location.clone(), roc_to_send).into()]; + let fee_id: AssetId = roc_location.into(); + + // this should fail + AssetHubRococo::execute_with(|| { + let result = ::PolkadotXcm::transfer_assets_using_type_and_then( + signed_origin.clone(), + bx!(destination.clone().into()), + bx!(assets.clone().into()), + bx!(TransferType::Teleport), + bx!(fee_id.into()), + bx!(TransferType::Teleport), + bx!(VersionedXcm::from(Xcm::<()>::new())), + Unlimited, + ); + assert_err!( + result, + DispatchError::Module(sp_runtime::ModuleError { + index: 31, + error: [2, 0, 0, 0], + message: Some("Filtered") + }) + ); + }); + + // this should also fail + AssetHubRococo::execute_with(|| { + let xcm: Xcm = Xcm(vec![ + WithdrawAsset(assets.into()), + InitiateTeleport { assets: Wild(All), dest: destination, xcm: Xcm::<()>::new() }, + ]); + let result = ::PolkadotXcm::execute( + signed_origin, + bx!(xcm::VersionedXcm::V4(xcm)), + Weight::MAX, + ); + assert!(result.is_err()); + }); +} diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs index f568fb4101d..d0016b5a1b1 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs @@ -48,6 +48,7 @@ mod imports { pub use westend_system_emulated_network::{ asset_hub_westend_emulated_chain::{ asset_hub_westend_runtime::{ + self, xcm_config::{ self as ahw_xcm_config, WestendLocation as RelayLocation, XcmConfig as AssetHubWestendXcmConfig, diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/hybrid_transfers.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/hybrid_transfers.rs index 975bacea7b4..4d6cdd9a94d 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/hybrid_transfers.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/hybrid_transfers.rs @@ -450,7 +450,16 @@ fn transfer_foreign_assets_from_para_to_para_through_asset_hub() { let sov_of_receiver_on_ah = AssetHubWestend::sovereign_account_id_of(receiver_as_seen_by_ah); let roc_to_send = ASSET_HUB_WESTEND_ED * 10_000_000; - // Configure destination chain to trust AH as reserve of ROC + // Configure source and destination chains to trust AH as reserve of ROC + PenpalA::execute_with(|| { + assert_ok!(::System::set_storage( + ::RuntimeOrigin::root(), + vec![( + PenpalCustomizableAssetFromSystemAssetHub::key().to_vec(), + Location::new(2, [GlobalConsensus(Rococo)]).encode(), + )], + )); + }); PenpalB::execute_with(|| { assert_ok!(::System::set_storage( ::RuntimeOrigin::root(), diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs index 53b6939298d..0100e8e34ef 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs @@ -1552,3 +1552,58 @@ fn reserve_transfer_usdt_from_para_to_para_through_asset_hub() { // Receiver's balance is increased assert!(receiver_assets_after > receiver_assets_before); } + +/// Reserve Withdraw Native Asset from AssetHub to Parachain fails. +#[test] +fn reserve_withdraw_from_untrusted_reserve_fails() { + // Init values for Parachain Origin + let destination = AssetHubWestend::sibling_location_of(PenpalA::para_id()); + let signed_origin = + ::RuntimeOrigin::signed(AssetHubWestendSender::get().into()); + let roc_to_send: Balance = WESTEND_ED * 10000; + let roc_location = RelayLocation::get(); + + // Assets to send + let assets: Vec = vec![(roc_location.clone(), roc_to_send).into()]; + let fee_id: AssetId = roc_location.into(); + + // this should fail + AssetHubWestend::execute_with(|| { + let result = ::PolkadotXcm::transfer_assets_using_type_and_then( + signed_origin.clone(), + bx!(destination.clone().into()), + bx!(assets.clone().into()), + bx!(TransferType::DestinationReserve), + bx!(fee_id.into()), + bx!(TransferType::DestinationReserve), + bx!(VersionedXcm::from(Xcm::<()>::new())), + Unlimited, + ); + assert_err!( + result, + DispatchError::Module(sp_runtime::ModuleError { + index: 31, + error: [22, 0, 0, 0], + message: Some("InvalidAssetUnsupportedReserve") + }) + ); + }); + + // this should also fail + AssetHubWestend::execute_with(|| { + let xcm: Xcm = Xcm(vec![ + WithdrawAsset(assets.into()), + InitiateReserveWithdraw { + assets: Wild(All), + reserve: destination, + xcm: Xcm::<()>::new(), + }, + ]); + let result = ::PolkadotXcm::execute( + signed_origin, + bx!(xcm::VersionedXcm::V4(xcm)), + Weight::MAX, + ); + assert!(result.is_err()); + }); +} diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/teleport.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/teleport.rs index 15d39858acc..ddb82a954a8 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/teleport.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/teleport.rs @@ -530,3 +530,54 @@ fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() { system_para_to_para_transfer_assets, ); } + +/// Teleport Native Asset from AssetHub to Parachain fails. +#[test] +fn teleport_to_untrusted_chain_fails() { + // Init values for Parachain Origin + let destination = AssetHubWestend::sibling_location_of(PenpalA::para_id()); + let signed_origin = + ::RuntimeOrigin::signed(AssetHubWestendSender::get().into()); + let roc_to_send: Balance = WESTEND_ED * 10000; + let roc_location = RelayLocation::get(); + + // Assets to send + let assets: Vec = vec![(roc_location.clone(), roc_to_send).into()]; + let fee_id: AssetId = roc_location.into(); + + // this should fail + AssetHubWestend::execute_with(|| { + let result = ::PolkadotXcm::transfer_assets_using_type_and_then( + signed_origin.clone(), + bx!(destination.clone().into()), + bx!(assets.clone().into()), + bx!(TransferType::Teleport), + bx!(fee_id.into()), + bx!(TransferType::Teleport), + bx!(VersionedXcm::from(Xcm::<()>::new())), + Unlimited, + ); + assert_err!( + result, + DispatchError::Module(sp_runtime::ModuleError { + index: 31, + error: [2, 0, 0, 0], + message: Some("Filtered") + }) + ); + }); + + // this should also fail + AssetHubWestend::execute_with(|| { + let xcm: Xcm = Xcm(vec![ + WithdrawAsset(assets.into()), + InitiateTeleport { assets: Wild(All), dest: destination, xcm: Xcm::<()>::new() }, + ]); + let result = ::PolkadotXcm::execute( + signed_origin, + bx!(xcm::VersionedXcm::V4(xcm)), + Weight::MAX, + ); + assert!(result.is_err()); + }); +} diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/lib.rs index 9d54d431e83..e83b2807678 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/lib.rs @@ -16,6 +16,7 @@ #[cfg(test)] mod imports { // Substrate + pub use codec::Encode; pub use frame_support::{assert_err, assert_ok, pallet_prelude::DispatchResult}; pub use sp_runtime::DispatchError; diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/asset_transfers.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/asset_transfers.rs index 6df51c5f704..11930798da4 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/asset_transfers.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/asset_transfers.rs @@ -432,6 +432,16 @@ fn send_back_wnds_from_penpal_rococo_through_asset_hub_rococo_to_asset_hub_weste ASSET_MIN_BALANCE, vec![(sender.clone(), amount * 2)], ); + // Configure source Penpal chain to trust local AH as reserve of bridged WND + PenpalA::execute_with(|| { + assert_ok!(::System::set_storage( + ::RuntimeOrigin::root(), + vec![( + PenpalCustomizableAssetFromSystemAssetHub::key().to_vec(), + wnd_at_rococo_parachains.encode(), + )], + )); + }); // fund the AHR's SA on AHW with the WND tokens held in reserve let sov_ahr_on_ahw = AssetHubWestend::sovereign_account_of_parachain_on_other_global_consensus( diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/lib.rs index 5e0462d1488..d9b92cb11e9 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/lib.rs @@ -16,6 +16,7 @@ #[cfg(test)] mod imports { // Substrate + pub use codec::Encode; pub use frame_support::{assert_err, assert_ok, pallet_prelude::DispatchResult}; pub use sp_runtime::DispatchError; @@ -52,7 +53,10 @@ mod imports { BridgeHubWestendParaPallet as BridgeHubWestendPallet, BridgeHubWestendXcmConfig, }, penpal_emulated_chain::{ - penpal_runtime::xcm_config::UniversalLocation as PenpalUniversalLocation, + penpal_runtime::xcm_config::{ + CustomizableAssetFromSystemAssetHub as PenpalCustomizableAssetFromSystemAssetHub, + UniversalLocation as PenpalUniversalLocation, + }, PenpalAssetOwner, PenpalBParaPallet as PenpalBPallet, }, westend_emulated_chain::{ diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/asset_transfers.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/asset_transfers.rs index c3f81175da2..6492c520234 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/asset_transfers.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/asset_transfers.rs @@ -452,6 +452,16 @@ fn send_back_rocs_from_penpal_westend_through_asset_hub_westend_to_asset_hub_roc ASSET_MIN_BALANCE, vec![(sender.clone(), amount * 2)], ); + // Configure source Penpal chain to trust local AH as reserve of bridged ROC + PenpalB::execute_with(|| { + assert_ok!(::System::set_storage( + ::RuntimeOrigin::root(), + vec![( + PenpalCustomizableAssetFromSystemAssetHub::key().to_vec(), + roc_at_westend_parachains.encode(), + )], + )); + }); // fund the AHW's SA on AHR with the ROC tokens held in reserve let sov_ahw_on_ahr = AssetHubRococo::sovereign_account_of_parachain_on_other_global_consensus( diff --git a/polkadot/xcm/pallet-xcm/src/lib.rs b/polkadot/xcm/pallet-xcm/src/lib.rs index 05d9046ab19..ee448c3df60 100644 --- a/polkadot/xcm/pallet-xcm/src/lib.rs +++ b/polkadot/xcm/pallet-xcm/src/lib.rs @@ -1939,6 +1939,10 @@ impl Pallet { ) -> Result<(Xcm<::RuntimeCall>, Xcm<()>), Error> { let value = (origin, vec![fees.clone()]); ensure!(T::XcmReserveTransferFilter::contains(&value), Error::::Filtered); + ensure!( + ::IsReserve::contains(&fees, &dest), + Error::::InvalidAssetUnsupportedReserve + ); let context = T::UniversalLocation::get(); let reanchored_fees = fees @@ -1973,6 +1977,12 @@ impl Pallet { let value = (origin, assets); ensure!(T::XcmReserveTransferFilter::contains(&value), Error::::Filtered); let (_, assets) = value; + for asset in assets.iter() { + ensure!( + ::IsReserve::contains(&asset, &dest), + Error::::InvalidAssetUnsupportedReserve + ); + } // max assets is `assets` (+ potentially separately handled fee) let max_assets = @@ -2079,6 +2089,10 @@ impl Pallet { ) -> Result<(Xcm<::RuntimeCall>, Xcm<()>), Error> { let value = (origin, vec![fees.clone()]); ensure!(T::XcmTeleportFilter::contains(&value), Error::::Filtered); + ensure!( + ::IsTeleporter::contains(&fees, &dest), + Error::::Filtered + ); let context = T::UniversalLocation::get(); let reanchored_fees = fees @@ -2134,6 +2148,12 @@ impl Pallet { let value = (origin, assets); ensure!(T::XcmTeleportFilter::contains(&value), Error::::Filtered); let (_, assets) = value; + for asset in assets.iter() { + ensure!( + ::IsTeleporter::contains(&asset, &dest), + Error::::Filtered + ); + } // max assets is `assets` (+ potentially separately handled fee) let max_assets = diff --git a/polkadot/xcm/xcm-builder/tests/scenarios.rs b/polkadot/xcm/xcm-builder/tests/scenarios.rs index ee1aeffbb4e..99c14f5bba1 100644 --- a/polkadot/xcm/xcm-builder/tests/scenarios.rs +++ b/polkadot/xcm/xcm-builder/tests/scenarios.rs @@ -22,7 +22,7 @@ use mock::{ }; use polkadot_parachain_primitives::primitives::Id as ParaId; use sp_runtime::traits::AccountIdConversion; -use xcm::latest::prelude::*; +use xcm::latest::{prelude::*, Error::UntrustedTeleportLocation}; use xcm_executor::XcmExecutor; pub const ALICE: AccountId = AccountId::new([0u8; 32]); @@ -217,7 +217,7 @@ fn teleport_to_asset_hub_works() { ]; let weight = BaseXcmWeight::get() * 3; - // teleports are allowed to community chains, even in the absence of trust from their side. + // teleports are not allowed to other chains, in the absence of trust from their side let message = Xcm(vec![ WithdrawAsset((Here, amount).into()), buy_execution(), @@ -235,16 +235,7 @@ fn teleport_to_asset_hub_works() { weight, Weight::zero(), ); - assert_eq!(r, Outcome::Complete { used: weight }); - let expected_msg = Xcm(vec![ReceiveTeleportedAsset((Parent, amount).into()), ClearOrigin] - .into_iter() - .chain(teleport_effects.clone().into_iter()) - .collect()); - let expected_hash = fake_message_hash(&expected_msg); - assert_eq!( - mock::sent_xcm(), - vec![(Parachain(other_para_id).into(), expected_msg, expected_hash,)] - ); + assert_eq!(r, Outcome::Incomplete { used: weight, error: UntrustedTeleportLocation }); // teleports are allowed from asset hub to kusama. let message = Xcm(vec![ @@ -274,10 +265,7 @@ fn teleport_to_asset_hub_works() { let expected_hash = fake_message_hash(&expected_msg); assert_eq!( mock::sent_xcm(), - vec![ - (Parachain(other_para_id).into(), expected_msg.clone(), expected_hash,), - (Parachain(asset_hub_id).into(), expected_msg, expected_hash,) - ] + vec![(Parachain(asset_hub_id).into(), expected_msg, expected_hash,)] ); }); } diff --git a/polkadot/xcm/xcm-executor/src/lib.rs b/polkadot/xcm/xcm-executor/src/lib.rs index a8110ca3d19..14920f36445 100644 --- a/polkadot/xcm/xcm-executor/src/lib.rs +++ b/polkadot/xcm/xcm-executor/src/lib.rs @@ -1002,13 +1002,18 @@ impl XcmExecutor { InitiateReserveWithdraw { assets, reserve, xcm } => { let old_holding = self.holding.clone(); let result = Config::TransactionalProcessor::process(|| { + let assets = self.holding.saturating_take(assets); + // Must ensure that we recognise the assets as being managed by the destination. + #[cfg(not(feature = "runtime-benchmarks"))] + for asset in assets.assets_iter() { + ensure!( + Config::IsReserve::contains(&asset, &reserve), + XcmError::UntrustedReserveLocation + ); + } // Note that here we are able to place any assets which could not be reanchored // back into Holding. - let assets = Self::reanchored( - self.holding.saturating_take(assets), - &reserve, - Some(&mut self.holding), - ); + let assets = Self::reanchored(assets, &reserve, Some(&mut self.holding)); let mut message = vec![WithdrawAsset(assets), ClearOrigin]; message.extend(xcm.0.into_iter()); self.send(reserve, Xcm(message), FeeReason::InitiateReserveWithdraw)?; @@ -1024,6 +1029,14 @@ impl XcmExecutor { let result = (|| -> Result<(), XcmError> { // We must do this first in order to resolve wildcards. let assets = self.holding.saturating_take(assets); + // Must ensure that we have teleport trust with destination for these assets. + #[cfg(not(feature = "runtime-benchmarks"))] + for asset in assets.assets_iter() { + ensure!( + Config::IsTeleporter::contains(&asset, &dest), + XcmError::UntrustedTeleportLocation + ); + } for asset in assets.assets_iter() { // We should check that the asset can actually be teleported out (for this // to be in error, there would need to be an accounting violation by diff --git a/polkadot/xcm/xcm-simulator/example/src/relay_chain/xcm_config/mod.rs b/polkadot/xcm/xcm-simulator/example/src/relay_chain/xcm_config/mod.rs index c5d5fa66732..6218915cd12 100644 --- a/polkadot/xcm/xcm-simulator/example/src/relay_chain/xcm_config/mod.rs +++ b/polkadot/xcm/xcm-simulator/example/src/relay_chain/xcm_config/mod.rs @@ -19,6 +19,7 @@ pub mod barrier; pub mod constants; pub mod location_converter; pub mod origin_converter; +pub mod teleporter; pub mod weigher; use crate::relay_chain::{RuntimeCall, XcmPallet}; @@ -36,7 +37,7 @@ impl Config for XcmConfig { type AssetTransactor = asset_transactor::AssetTransactor; type OriginConverter = origin_converter::OriginConverter; type IsReserve = (); - type IsTeleporter = (); + type IsTeleporter = teleporter::TrustedTeleporters; type UniversalLocation = constants::UniversalLocation; type Barrier = barrier::Barrier; type Weigher = weigher::Weigher; diff --git a/polkadot/xcm/xcm-simulator/example/src/relay_chain/xcm_config/teleporter.rs b/polkadot/xcm/xcm-simulator/example/src/relay_chain/xcm_config/teleporter.rs new file mode 100644 index 00000000000..92e5065044e --- /dev/null +++ b/polkadot/xcm/xcm-simulator/example/src/relay_chain/xcm_config/teleporter.rs @@ -0,0 +1,26 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +use frame_support::parameter_types; +use xcm::latest::prelude::*; + +parameter_types! { + pub NftCollectionOnRelay: AssetFilter + = Wild(AllOf { fun: WildNonFungible, id: AssetId(GeneralIndex(1).into()) }); + pub NftCollectionForChild: (AssetFilter, Location) + = (NftCollectionOnRelay::get(), Parachain(1).into()); +} +pub type TrustedTeleporters = xcm_builder::Case; diff --git a/prdoc/pr_5660.prdoc b/prdoc/pr_5660.prdoc new file mode 100644 index 00000000000..fce791cebb6 --- /dev/null +++ b/prdoc/pr_5660.prdoc @@ -0,0 +1,30 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "xcm-executor: validate destinations for ReserveWithdraw and Teleport transfers" + +doc: + - audience: + - Runtime User + - Runtime Dev + description: | + This change adds the required validation for stronger UX guarantees when using + `InitiateReserveWithdraw` or `InitiateTeleport` XCM instructions. Execution of + the instructions will fail if the local chain is not configured to trust the + "destination" or "reserve" chain as a reserve/trusted-teleporter for the provided + "assets". + With this change, misuse of `InitiateReserveWithdraw`/`InitiateTeleport` fails on + origin with no overall side-effects, rather than failing on destination (with + side-effects to origin's assets issuance). + The commit also makes the same validations for pallet-xcm transfers, and adds + regression tests. + +crates: + - name: staging-xcm-executor + bump: patch + - name: staging-xcm-builder + bump: patch + - name: pallet-xcm + bump: patch + - name: xcm-simulator-example + bump: patch -- GitLab From c77095f51119d2eccdc54d2f3518bed0ffbd6d53 Mon Sep 17 00:00:00 2001 From: Cyrill Leutwiler Date: Wed, 25 Sep 2024 18:28:59 +0200 Subject: [PATCH 107/125] [pallet-revive] last call return data API (#5779) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR introduces 2 new syscalls: `return_data_size` and `return_data_copy`, resembling the semantics of the EVM `RETURNDATASIZE` and `RETURNDATACOPY` opcodes. The ownership of `ExecReturnValue` (the return data) has moved to the `Frame`. This allows implementing the new contract API functionality in ext with no additional copies. Returned data is passed via contract memory, memory is (will be) metered, hence the amount of returned data can not be statically known, so we should avoid storing copies of the returned data if we can. By moving the ownership of the exectuables return value into the `Frame` struct we achieve this. A zero-copy implementation of those APIs would be technically possible without that internal change by making the callsite in the runtime responsible for moving the returned data into the frame after any call. However, resetting the stored output needs to be handled in ext, since plain transfers will _not_ affect the stored return data (and we don't want to handle this special call case inside the `runtime` API). This has drawbacks: - It can not be tested easily in the mock. - It introduces an inconsistency where resetting the stored output is handled in ext, but the runtime API is responsible to store it back correctly after any calls made. Instead, with ownership of the data in `Frame`, both can be handled in a single place. Handling both in `fn run()` is more natural and leaves less room for runtime API bugs. The returned output is reset each time _before_ running any executable in a nested stack. This change should not incur any overhead to the overall memory usage as _only_ the returned data from the last executed frame will be kept around at any time. --------- Signed-off-by: Cyrill Leutwiler Signed-off-by: xermicus Co-authored-by: command-bot <> Co-authored-by: PG Herveou --- prdoc/pr_5779.prdoc | 38 ++ .../fixtures/contracts/return_data_api.rs | 166 +++++++++ substrate/frame/revive/src/exec.rs | 348 +++++++++++++----- substrate/frame/revive/src/tests.rs | 29 ++ substrate/frame/revive/src/wasm/runtime.rs | 144 +++++--- substrate/frame/revive/uapi/src/host.rs | 14 + .../frame/revive/uapi/src/host/riscv32.rs | 14 + 7 files changed, 619 insertions(+), 134 deletions(-) create mode 100644 prdoc/pr_5779.prdoc create mode 100644 substrate/frame/revive/fixtures/contracts/return_data_api.rs diff --git a/prdoc/pr_5779.prdoc b/prdoc/pr_5779.prdoc new file mode 100644 index 00000000000..659a3a19f69 --- /dev/null +++ b/prdoc/pr_5779.prdoc @@ -0,0 +1,38 @@ +title: "[pallet-revive] last call return data API" + +doc: + - audience: Runtime Dev + description: | + This PR introduces 2 new syscall: `return_data_size` and `return_data_copy`, + resembling the semantics of the EVM `RETURNDATASIZE` and `RETURNDATACOPY` opcodes. + + The ownership of `ExecReturnValue` (the return data) has moved to the `Frame`. + This allows implementing the new contract API surface functionality in ext with no additional copies. + Returned data is passed via contract memory, memory is (will be) metered, + hence the amount of returned data can not be statically known, + so we should avoid storing copies of the returned data if we can. + By moving the ownership of the exectuables return value into the `Frame` struct we achieve this. + + A zero-copy implementation of those APIs would be technically possible without that internal change by making + the callsite in the runtime responsible for moving the returned data into the frame after any call. + However, resetting the stored output needs to be handled in ext, since plain transfers will _not_ affect the + stored return data (and we don't want to handle this special call case inside the `runtime` API). + This has drawbacks: + - It can not be tested easily in the mock. + - It introduces an inconsistency where resetting the stored output is handled in ext, + but the runtime API is responsible to store it back correctly after any calls made. + Instead, with ownership of the data in `Frame`, both can be handled in a single place. + Handling both in `fn run()` is more natural and leaves less room for runtime API bugs. + + The returned output is reset each time _before_ running any executable in a nested stack. + This change should not incur any overhead to the overall memory usage as _only_ the returned data from the last + executed frame will be kept around at any time. + +crates: + - name: pallet-revive + bump: major + - name: pallet-revive-fixtures + bump: minor + - name: pallet-revive-uapi + bump: minor + \ No newline at end of file diff --git a/substrate/frame/revive/fixtures/contracts/return_data_api.rs b/substrate/frame/revive/fixtures/contracts/return_data_api.rs new file mode 100644 index 00000000000..846396b0944 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/return_data_api.rs @@ -0,0 +1,166 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This tests that the `return_data_size` and `return_data_copy` APIs work. +//! +//! It does so by calling and instantiating the "return_with_data" fixture, +//! which always echoes back the input[4..] regardless of the call outcome. +//! +//! We also check that the saved return data is properly reset after a trap +//! and unaffected by plain transfers. + +#![no_std] +#![no_main] + +use common::{input, u256_bytes}; +use uapi::{HostFn, HostFnImpl as api}; + +const INPUT_BUF_SIZE: usize = 128; +static INPUT_DATA: [u8; INPUT_BUF_SIZE] = [0xFF; INPUT_BUF_SIZE]; +/// The "return_with_data" fixture echoes back 4 bytes less than the input +const OUTPUT_BUF_SIZE: usize = INPUT_BUF_SIZE - 4; +static OUTPUT_DATA: [u8; OUTPUT_BUF_SIZE] = [0xEE; OUTPUT_BUF_SIZE]; + +fn assert_return_data_after_call(input: &[u8]) { + assert_return_data_size_of(OUTPUT_BUF_SIZE as u64); + assert_plain_transfer_does_not_reset(OUTPUT_BUF_SIZE as u64); + assert_return_data_copy(&input[4..]); + reset_return_data(); +} + +/// Assert that what we get from [api::return_data_copy] matches `whole_return_data`, +/// either fully or partially with an offset and limited size. +fn assert_return_data_copy(whole_return_data: &[u8]) { + // The full return data should match + let mut buf = OUTPUT_DATA; + let mut full = &mut buf[..whole_return_data.len()]; + api::return_data_copy(&mut full, 0); + assert_eq!(whole_return_data, full); + + // Partial return data should match + let mut buf = OUTPUT_DATA; + let offset = 5; // we just pick some offset + let size = 32; // we just pick some size + let mut partial = &mut buf[offset..offset + size]; + api::return_data_copy(&mut partial, offset as u32); + assert_eq!(*partial, whole_return_data[offset..offset + size]); +} + +/// This function panics in a recursive contract call context. +fn recursion_guard() -> [u8; 20] { + let mut caller_address = [0u8; 20]; + api::caller(&mut caller_address); + + let mut own_address = [0u8; 20]; + api::address(&mut own_address); + + assert_ne!(caller_address, own_address); + + own_address +} + +/// Call ourselves recursively, which panics the callee and thus resets the return data. +fn reset_return_data() { + api::call( + uapi::CallFlags::ALLOW_REENTRY, + &recursion_guard(), + 0u64, + 0u64, + None, + &[0u8; 32], + &[0u8; 32], + None, + ) + .unwrap_err(); + assert_return_data_size_of(0); +} + +/// Assert [api::return_data_size] to match the `expected` value. +fn assert_return_data_size_of(expected: u64) { + let mut return_data_size = [0xff; 32]; + api::return_data_size(&mut return_data_size); + assert_eq!(return_data_size, u256_bytes(expected)); +} + +/// Assert [api::return_data_size] to match the `expected` value after a plain transfer +/// (plain transfers don't issue a call and so should not reset the return data) +fn assert_plain_transfer_does_not_reset(expected: u64) { + api::transfer(&[0; 20], &u256_bytes(128)).unwrap(); + assert_return_data_size_of(expected); +} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!(code_hash: &[u8; 32],); + + // We didn't do anything yet; return data size should be 0 + assert_return_data_size_of(0); + + recursion_guard(); + + let mut address_buf = [0; 20]; + let construct_input = |exit_flag| { + let mut input = INPUT_DATA; + input[0] = exit_flag; + input[9] = 7; + input[17 / 2] = 127; + input[89 / 2] = 127; + input + }; + let mut instantiate = |exit_flag| { + api::instantiate( + code_hash, + 0u64, + 0u64, + None, + &[0; 32], + &construct_input(exit_flag), + Some(&mut address_buf), + None, + None, + ) + }; + let call = |exit_flag, address_buf| { + api::call( + uapi::CallFlags::empty(), + address_buf, + 0u64, + 0u64, + None, + &[0; 32], + &construct_input(exit_flag), + None, + ) + }; + + instantiate(0).unwrap(); + assert_return_data_after_call(&construct_input(0)[..]); + + instantiate(1).unwrap_err(); + assert_return_data_after_call(&construct_input(1)[..]); + + call(0, &address_buf).unwrap(); + assert_return_data_after_call(&construct_input(0)[..]); + + call(1, &address_buf).unwrap_err(); + assert_return_data_after_call(&construct_input(1)[..]); +} diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index 233658696c8..2e48bab2925 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -189,16 +189,12 @@ pub trait Ext: sealing::Sealed { input_data: Vec, allows_reentry: bool, read_only: bool, - ) -> Result; + ) -> Result<(), ExecError>; /// Execute code in the current frame. /// /// Returns the code size of the called contract. - fn delegate_call( - &mut self, - code: H256, - input_data: Vec, - ) -> Result; + fn delegate_call(&mut self, code: H256, input_data: Vec) -> Result<(), ExecError>; /// Instantiate a contract from the given code. /// @@ -213,7 +209,7 @@ pub trait Ext: sealing::Sealed { value: U256, input_data: Vec, salt: Option<&[u8; 32]>, - ) -> Result<(H160, ExecReturnValue), ExecError>; + ) -> Result; /// Transfer all funds to `beneficiary` and delete the contract. /// @@ -427,6 +423,12 @@ pub trait Ext: sealing::Sealed { /// Check if running in read-only context. fn is_read_only(&self) -> bool; + + /// Returns an immutable reference to the output of the last executed call frame. + fn last_frame_output(&self) -> &ExecReturnValue; + + /// Returns a mutable reference to the output of the last executed call frame. + fn last_frame_output_mut(&mut self) -> &mut ExecReturnValue; } /// Describes the different functions that can be exported by an [`Executable`]. @@ -547,6 +549,8 @@ struct Frame { read_only: bool, /// The caller of the currently executing frame which was spawned by `delegate_call`. delegate_caller: Option>, + /// The output of the last executed call frame. + last_frame_output: ExecReturnValue, } /// Used in a delegate call frame arguments in order to override the executable and caller. @@ -731,7 +735,7 @@ where value, debug_message, )? { - stack.run(executable, input_data) + stack.run(executable, input_data).map(|_| stack.first_frame.last_frame_output) } else { Self::transfer_no_contract(&origin, &dest, value) } @@ -772,7 +776,9 @@ where )? .expect(FRAME_ALWAYS_EXISTS_ON_INSTANTIATE); let address = T::AddressMapper::to_address(&stack.top_frame().account_id); - stack.run(executable, input_data).map(|ret| (address, ret)) + stack + .run(executable, input_data) + .map(|_| (address, stack.first_frame.last_frame_output)) } #[cfg(all(feature = "runtime-benchmarks", feature = "riscv"))] @@ -865,7 +871,7 @@ where { contract } else { - return Ok(None) + return Ok(None); } }; @@ -911,6 +917,7 @@ where nested_storage: storage_meter.nested(deposit_limit), allows_reentry: true, read_only, + last_frame_output: Default::default(), }; Ok(Some((frame, executable))) @@ -965,12 +972,24 @@ where /// Run the current (top) frame. /// /// This can be either a call or an instantiate. - fn run(&mut self, executable: E, input_data: Vec) -> ExecResult { + fn run(&mut self, executable: E, input_data: Vec) -> Result<(), ExecError> { let frame = self.top_frame(); let entry_point = frame.entry_point; let delegated_code_hash = if frame.delegate_caller.is_some() { Some(*executable.code_hash()) } else { None }; + // The output of the caller frame will be replaced by the output of this run. + // It is also not accessible from nested frames. + // Hence we drop it early to save the memory. + let frames_len = self.frames.len(); + if let Some(caller_frame) = match frames_len { + 0 => None, + 1 => Some(&mut self.first_frame.last_frame_output), + _ => self.frames.get_mut(frames_len - 2).map(|frame| &mut frame.last_frame_output), + } { + *caller_frame = Default::default(); + } + self.transient_storage.start_transaction(); let do_transaction = || { @@ -1109,7 +1128,9 @@ where } self.pop_frame(success); - output + output.map(|output| { + self.top_frame_mut().last_frame_output = output; + }) } /// Remove the current (top) frame from the stack. @@ -1285,7 +1306,7 @@ where input_data: Vec, allows_reentry: bool, read_only: bool, - ) -> ExecResult { + ) -> Result<(), ExecError> { // Before pushing the new frame: Protect the caller contract against reentrancy attacks. // It is important to do this before calling `allows_reentry` so that a direct recursion // is caught by it. @@ -1323,7 +1344,8 @@ where &Origin::from_account_id(self.account_id().clone()), &dest, value, - ) + )?; + Ok(()) } }; @@ -1336,11 +1358,7 @@ where result } - fn delegate_call( - &mut self, - code_hash: H256, - input_data: Vec, - ) -> Result { + fn delegate_call(&mut self, code_hash: H256, input_data: Vec) -> Result<(), ExecError> { let executable = E::from_storage(code_hash, self.gas_meter_mut())?; let top_frame = self.top_frame_mut(); let contract_info = top_frame.contract_info().clone(); @@ -1368,7 +1386,7 @@ where value: U256, input_data: Vec, salt: Option<&[u8; 32]>, - ) -> Result<(H160, ExecReturnValue), ExecError> { + ) -> Result { let executable = E::from_storage(code_hash, self.gas_meter_mut())?; let sender = &self.top_frame().account_id; let executable = self.push_frame( @@ -1385,7 +1403,7 @@ where )?; let address = T::AddressMapper::to_address(&self.top_frame().account_id); self.run(executable.expect(FRAME_ALWAYS_EXISTS_ON_INSTANTIATE), input_data) - .map(|ret| (address, ret)) + .map(|_| address) } fn terminate(&mut self, beneficiary: &H160) -> DispatchResult { @@ -1690,6 +1708,14 @@ where fn is_read_only(&self) -> bool { self.top_frame().read_only } + + fn last_frame_output(&self) -> &ExecReturnValue { + &self.top_frame().last_frame_output + } + + fn last_frame_output_mut(&mut self) -> &mut ExecReturnValue { + &mut self.top_frame_mut().last_frame_output + } } mod sealing { @@ -2353,15 +2379,17 @@ mod tests { // ALICE is the origin of the call stack assert!(ctx.ext.caller_is_origin()); // BOB calls CHARLIE - ctx.ext.call( - Weight::zero(), - U256::zero(), - &CHARLIE_ADDR, - U256::zero(), - vec![], - true, - false, - ) + ctx.ext + .call( + Weight::zero(), + U256::zero(), + &CHARLIE_ADDR, + U256::zero(), + vec![], + true, + false, + ) + .map(|_| ctx.ext.last_frame_output().clone()) }); ExtBuilder::default().build().execute_with(|| { @@ -2447,15 +2475,17 @@ mod tests { // root is the origin of the call stack. assert!(ctx.ext.caller_is_root()); // BOB calls CHARLIE. - ctx.ext.call( - Weight::zero(), - U256::zero(), - &CHARLIE_ADDR, - U256::zero(), - vec![], - true, - false, - ) + ctx.ext + .call( + Weight::zero(), + U256::zero(), + &CHARLIE_ADDR, + U256::zero(), + vec![], + true, + false, + ) + .map(|_| ctx.ext.last_frame_output().clone()) }); ExtBuilder::default().build().execute_with(|| { @@ -2666,6 +2696,7 @@ mod tests { vec![], Some(&[48; 32]), ) + .map(|address| (address, ctx.ext.last_frame_output().clone())) .unwrap(); *instantiated_contract_address.borrow_mut() = Some(address); @@ -2841,15 +2872,17 @@ mod tests { assert_eq!(info.storage_byte_deposit, 0); info.storage_byte_deposit = 42; assert_eq!( - ctx.ext.call( - Weight::zero(), - U256::zero(), - &CHARLIE_ADDR, - U256::zero(), - vec![], - true, - false - ), + ctx.ext + .call( + Weight::zero(), + U256::zero(), + &CHARLIE_ADDR, + U256::zero(), + vec![], + true, + false + ) + .map(|_| ctx.ext.last_frame_output().clone()), exec_trapped() ); assert_eq!(ctx.ext.contract_info().storage_byte_deposit, 42); @@ -3095,6 +3128,7 @@ mod tests { let dest = H160::from_slice(ctx.input_data.as_ref()); ctx.ext .call(Weight::zero(), U256::zero(), &dest, U256::zero(), vec![], false, false) + .map(|_| ctx.ext.last_frame_output().clone()) }); let code_charlie = MockLoader::insert(Call, |_, _| exec_success()); @@ -3137,15 +3171,17 @@ mod tests { fn call_deny_reentry() { let code_bob = MockLoader::insert(Call, |ctx, _| { if ctx.input_data[0] == 0 { - ctx.ext.call( - Weight::zero(), - U256::zero(), - &CHARLIE_ADDR, - U256::zero(), - vec![], - false, - false, - ) + ctx.ext + .call( + Weight::zero(), + U256::zero(), + &CHARLIE_ADDR, + U256::zero(), + vec![], + false, + false, + ) + .map(|_| ctx.ext.last_frame_output().clone()) } else { exec_success() } @@ -3153,15 +3189,9 @@ mod tests { // call BOB with input set to '1' let code_charlie = MockLoader::insert(Call, |ctx, _| { - ctx.ext.call( - Weight::zero(), - U256::zero(), - &BOB_ADDR, - U256::zero(), - vec![1], - true, - false, - ) + ctx.ext + .call(Weight::zero(), U256::zero(), &BOB_ADDR, U256::zero(), vec![1], true, false) + .map(|_| ctx.ext.last_frame_output().clone()) }); ExtBuilder::default().build().execute_with(|| { @@ -3360,7 +3390,7 @@ mod tests { let alice_nonce = System::account_nonce(&ALICE); assert_eq!(System::account_nonce(ctx.ext.account_id()), 0); assert_eq!(ctx.ext.caller().account_id().unwrap(), &ALICE); - let (addr, _) = ctx + let addr = ctx .ext .instantiate( Weight::zero(), @@ -3916,15 +3946,17 @@ mod tests { Ok(WriteOutcome::New) ); assert_eq!( - ctx.ext.call( - Weight::zero(), - U256::zero(), - &CHARLIE_ADDR, - U256::zero(), - vec![], - true, - false, - ), + ctx.ext + .call( + Weight::zero(), + U256::zero(), + &CHARLIE_ADDR, + U256::zero(), + vec![], + true, + false, + ) + .map(|_| ctx.ext.last_frame_output().clone()), exec_success() ); assert_eq!(ctx.ext.get_transient_storage(storage_key_1), Some(vec![3])); @@ -4020,15 +4052,17 @@ mod tests { Ok(WriteOutcome::New) ); assert_eq!( - ctx.ext.call( - Weight::zero(), - U256::zero(), - &CHARLIE_ADDR, - U256::zero(), - vec![], - true, - false - ), + ctx.ext + .call( + Weight::zero(), + U256::zero(), + &CHARLIE_ADDR, + U256::zero(), + vec![], + true, + false + ) + .map(|_| ctx.ext.last_frame_output().clone()), exec_trapped() ); assert_eq!(ctx.ext.get_transient_storage(storage_key), Some(vec![1, 2])); @@ -4102,4 +4136,148 @@ mod tests { assert_matches!(result, Ok(_)); }); } + + #[test] + fn last_frame_output_works_on_instantiate() { + let ok_ch = MockLoader::insert(Constructor, move |_, _| { + Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: vec![127] }) + }); + let revert_ch = MockLoader::insert(Constructor, move |_, _| { + Ok(ExecReturnValue { flags: ReturnFlags::REVERT, data: vec![70] }) + }); + let trap_ch = MockLoader::insert(Constructor, |_, _| Err("It's a trap!".into())); + let instantiator_ch = MockLoader::insert(Call, { + move |ctx, _| { + let value = ::Currency::minimum_balance().into(); + + // Successful instantiation should set the output + let address = ctx + .ext + .instantiate(Weight::zero(), U256::zero(), ok_ch, value, vec![], None) + .unwrap(); + assert_eq!( + ctx.ext.last_frame_output(), + &ExecReturnValue { flags: ReturnFlags::empty(), data: vec![127] } + ); + + // Plain transfers should not set the output + ctx.ext.transfer(&address, U256::from(1)).unwrap(); + assert_eq!( + ctx.ext.last_frame_output(), + &ExecReturnValue { flags: ReturnFlags::empty(), data: vec![127] } + ); + + // Reverted instantiation should set the output + ctx.ext + .instantiate(Weight::zero(), U256::zero(), revert_ch, value, vec![], None) + .unwrap(); + assert_eq!( + ctx.ext.last_frame_output(), + &ExecReturnValue { flags: ReturnFlags::REVERT, data: vec![70] } + ); + + // Trapped instantiation should clear the output + ctx.ext + .instantiate(Weight::zero(), U256::zero(), trap_ch, value, vec![], None) + .unwrap_err(); + assert_eq!( + ctx.ext.last_frame_output(), + &ExecReturnValue { flags: ReturnFlags::empty(), data: vec![] } + ); + + exec_success() + } + }); + + ExtBuilder::default() + .with_code_hashes(MockLoader::code_hashes()) + .existential_deposit(15) + .build() + .execute_with(|| { + set_balance(&ALICE, 1000); + set_balance(&BOB_CONTRACT_ID, 100); + place_contract(&BOB, instantiator_ch); + let origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new(&origin, 200, 0).unwrap(); + + MockStack::run_call( + origin, + BOB_ADDR, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + 0, + vec![], + None, + ) + .unwrap() + }); + } + + #[test] + fn last_frame_output_works_on_nested_call() { + // Call stack: BOB -> CHARLIE(revert) -> BOB' (success) + let code_bob = MockLoader::insert(Call, |ctx, _| { + if ctx.input_data.is_empty() { + // We didn't do anything yet + assert_eq!( + ctx.ext.last_frame_output(), + &ExecReturnValue { flags: ReturnFlags::empty(), data: vec![] } + ); + + ctx.ext + .call( + Weight::zero(), + U256::zero(), + &CHARLIE_ADDR, + U256::zero(), + vec![], + true, + false, + ) + .unwrap(); + assert_eq!( + ctx.ext.last_frame_output(), + &ExecReturnValue { flags: ReturnFlags::REVERT, data: vec![70] } + ); + } + + Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: vec![127] }) + }); + let code_charlie = MockLoader::insert(Call, |ctx, _| { + // We didn't do anything yet + assert_eq!( + ctx.ext.last_frame_output(), + &ExecReturnValue { flags: ReturnFlags::empty(), data: vec![] } + ); + + assert!(ctx + .ext + .call(Weight::zero(), U256::zero(), &BOB_ADDR, U256::zero(), vec![99], true, false) + .is_ok()); + assert_eq!( + ctx.ext.last_frame_output(), + &ExecReturnValue { flags: ReturnFlags::empty(), data: vec![127] } + ); + + Ok(ExecReturnValue { flags: ReturnFlags::REVERT, data: vec![70] }) + }); + + ExtBuilder::default().build().execute_with(|| { + place_contract(&BOB, code_bob); + place_contract(&CHARLIE, code_charlie); + let origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new(&origin, 0, 0).unwrap(); + + let result = MockStack::run_call( + origin, + BOB_ADDR, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + 0, + vec![0], + None, + ); + assert_matches!(result, Ok(_)); + }); + } } diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index d06cdcfd465..5c5d144f24a 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -4325,4 +4325,33 @@ mod run_tests { assert_eq!(received.result.data, chain_id.encode()); }); } + + #[test] + fn return_data_api_works() { + let (code_return_data_api, _) = compile_module("return_data_api").unwrap(); + let (code_return_with_data, hash_return_with_data) = + compile_module("return_with_data").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Upload the io echoing fixture for later use + assert_ok!(Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + code_return_with_data, + deposit_limit::(), + )); + + // Create fixture: Constructor does nothing + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code_return_data_api)) + .build_and_unwrap_contract(); + + // Call the contract: It will issue calls and deploys, asserting on + assert_ok!(builder::call(addr) + .value(10 * 1024) + .data(hash_return_with_data.encode()) + .build()); + }); + } } diff --git a/substrate/frame/revive/src/wasm/runtime.rs b/substrate/frame/revive/src/wasm/runtime.rs index ebc407adacd..4b5a9a04eb7 100644 --- a/substrate/frame/revive/src/wasm/runtime.rs +++ b/substrate/frame/revive/src/wasm/runtime.rs @@ -28,7 +28,7 @@ use crate::{ }; use alloc::{boxed::Box, vec, vec::Vec}; use codec::{Decode, DecodeLimit, Encode, MaxEncodedLen}; -use core::{fmt, marker::PhantomData}; +use core::{fmt, marker::PhantomData, mem}; use frame_support::{ dispatch::DispatchInfo, ensure, pallet_prelude::DispatchResultWithPostInfo, parameter_types, traits::Get, weights::Weight, @@ -237,8 +237,8 @@ parameter_types! { const XcmExecutionFailed: ReturnErrorCode = ReturnErrorCode::XcmExecutionFailed; } -impl From for ReturnErrorCode { - fn from(from: ExecReturnValue) -> Self { +impl From<&ExecReturnValue> for ReturnErrorCode { + fn from(from: &ExecReturnValue) -> Self { if from.flags.contains(ReturnFlags::REVERT) { Self::CalleeReverted } else { @@ -769,20 +769,16 @@ impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { } } - /// Fallible conversion of a `ExecResult` to `ReturnErrorCode`. - fn exec_into_return_code(from: ExecResult) -> Result { + /// Fallible conversion of a `ExecError` to `ReturnErrorCode`. + fn exec_error_into_return_code(from: ExecError) -> Result { use crate::exec::ErrorOrigin::Callee; - let ExecError { error, origin } = match from { - Ok(retval) => return Ok(retval.into()), - Err(err) => err, - }; - - match (error, origin) { + match (from.error, from.origin) { (_, Callee) => Ok(ReturnErrorCode::CalleeTrapped), (err, _) => Self::err_into_return_code(err), } } + fn decode_key(&self, memory: &M, key_ptr: u32, key_len: u32) -> Result { let res = match key_len { SENTINEL => { @@ -1036,28 +1032,32 @@ impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { }, }; - // `TAIL_CALL` only matters on an `OK` result. Otherwise the call stack comes to - // a halt anyways without anymore code being executed. - if flags.contains(CallFlags::TAIL_CALL) { - if let Ok(return_value) = call_outcome { + match call_outcome { + // `TAIL_CALL` only matters on an `OK` result. Otherwise the call stack comes to + // a halt anyways without anymore code being executed. + Ok(_) if flags.contains(CallFlags::TAIL_CALL) => { + let output = mem::take(self.ext.last_frame_output_mut()); return Err(TrapReason::Return(ReturnData { - flags: return_value.flags.bits(), - data: return_value.data, + flags: output.flags.bits(), + data: output.data, })); - } - } - - if let Ok(output) = &call_outcome { - self.write_sandbox_output( - memory, - output_ptr, - output_len_ptr, - &output.data, - true, - |len| Some(RuntimeCosts::CopyToContract(len)), - )?; + }, + Ok(_) => { + let output = mem::take(self.ext.last_frame_output_mut()); + let write_result = self.write_sandbox_output( + memory, + output_ptr, + output_len_ptr, + &output.data, + true, + |len| Some(RuntimeCosts::CopyToContract(len)), + ); + *self.ext.last_frame_output_mut() = output; + write_result?; + Ok(self.ext.last_frame_output().into()) + }, + Err(err) => Ok(Self::exec_error_into_return_code(err)?), } - Ok(Self::exec_into_return_code(call_outcome)?) } fn instantiate( @@ -1086,34 +1086,40 @@ impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { let salt: [u8; 32] = memory.read_array(salt_ptr)?; Some(salt) }; - let instantiate_outcome = self.ext.instantiate( + + match self.ext.instantiate( weight, deposit_limit, code_hash, value, input_data, salt.as_ref(), - ); - if let Ok((address, output)) = &instantiate_outcome { - if !output.flags.contains(ReturnFlags::REVERT) { - self.write_fixed_sandbox_output( + ) { + Ok(address) => { + if !self.ext.last_frame_output().flags.contains(ReturnFlags::REVERT) { + self.write_fixed_sandbox_output( + memory, + address_ptr, + &address.as_bytes(), + true, + already_charged, + )?; + } + let output = mem::take(self.ext.last_frame_output_mut()); + let write_result = self.write_sandbox_output( memory, - address_ptr, - &address.as_bytes(), + output_ptr, + output_len_ptr, + &output.data, true, - already_charged, - )?; - } - self.write_sandbox_output( - memory, - output_ptr, - output_len_ptr, - &output.data, - true, - |len| Some(RuntimeCosts::CopyToContract(len)), - )?; + |len| Some(RuntimeCosts::CopyToContract(len)), + ); + *self.ext.last_frame_output_mut() = output; + write_result?; + Ok(self.ext.last_frame_output().into()) + }, + Err(err) => Ok(Self::exec_error_into_return_code(err)?), } - Ok(Self::exec_into_return_code(instantiate_outcome.map(|(_, retval)| retval))?) } fn terminate(&mut self, memory: &M, beneficiary_ptr: u32) -> Result<(), TrapReason> { @@ -1993,4 +1999,44 @@ pub mod env { self.ext.unlock_delegate_dependency(&code_hash)?; Ok(()) } + + /// Stores the length of the data returned by the last call into the supplied buffer. + /// See [`pallet_revive_uapi::HostFn::return_data_size`]. + #[api_version(0)] + fn return_data_size(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { + Ok(self.write_fixed_sandbox_output( + memory, + out_ptr, + &as_bytes(U256::from(self.ext.last_frame_output().data.len())), + false, + |len| Some(RuntimeCosts::CopyToContract(len)), + )?) + } + + /// Stores data returned by the last call, starting from `offset`, into the supplied buffer. + /// See [`pallet_revive_uapi::HostFn::return_data`]. + #[api_version(0)] + fn return_data_copy( + &mut self, + memory: &mut M, + out_ptr: u32, + out_len_ptr: u32, + offset: u32, + ) -> Result<(), TrapReason> { + let output = mem::take(self.ext.last_frame_output_mut()); + let result = if offset as usize > output.data.len() { + Err(Error::::OutOfBounds.into()) + } else { + self.write_sandbox_output( + memory, + out_ptr, + out_len_ptr, + &output.data[offset as usize..], + false, + |len| Some(RuntimeCosts::CopyToContract(len)), + ) + }; + *self.ext.last_frame_output_mut() = output; + Ok(result?) + } } diff --git a/substrate/frame/revive/uapi/src/host.rs b/substrate/frame/revive/uapi/src/host.rs index 57a03332670..816fdec3aaa 100644 --- a/substrate/frame/revive/uapi/src/host.rs +++ b/substrate/frame/revive/uapi/src/host.rs @@ -620,6 +620,20 @@ pub trait HostFn: private::Sealed { /// Returns `ReturnCode::Success` when the message was successfully sent. When the XCM /// execution fails, `ReturnErrorCode::XcmSendFailed` is returned. fn xcm_send(dest: &[u8], msg: &[u8], output: &mut [u8; 32]) -> Result; + + /// Stores the size of the returned data of the last contract call or instantiation. + /// + /// # Parameters + /// + /// - `output`: A reference to the output buffer to write the size. + fn return_data_size(output: &mut [u8; 32]); + + /// Stores the returned data of the last contract call or contract instantiation. + /// + /// # Parameters + /// - `output`: A reference to the output buffer to write the data. + /// - `offset`: Byte offset into the returned data + fn return_data_copy(output: &mut &mut [u8], offset: u32); } mod private { diff --git a/substrate/frame/revive/uapi/src/host/riscv32.rs b/substrate/frame/revive/uapi/src/host/riscv32.rs index a60c338e8bd..d5ea94c1a91 100644 --- a/substrate/frame/revive/uapi/src/host/riscv32.rs +++ b/substrate/frame/revive/uapi/src/host/riscv32.rs @@ -131,6 +131,8 @@ mod sys { msg_len: u32, out_ptr: *mut u8, ) -> ReturnCode; + pub fn return_data_size(out_ptr: *mut u8); + pub fn return_data_copy(out_ptr: *mut u8, out_len_ptr: *mut u32, offset: u32); } } @@ -548,4 +550,16 @@ impl HostFn for HostFnImpl { }; ret_code.into() } + + fn return_data_size(output: &mut [u8; 32]) { + unsafe { sys::return_data_size(output.as_mut_ptr()) }; + } + + fn return_data_copy(output: &mut &mut [u8], offset: u32) { + let mut output_len = output.len() as u32; + { + unsafe { sys::return_data_copy(output.as_mut_ptr(), &mut output_len, offset) }; + } + extract_from_slice(output, output_len as usize); + } } -- GitLab From 1f3e3978c4afd6f9985854a0f4327f8c8c7c71b9 Mon Sep 17 00:00:00 2001 From: Andrei Sandu <54316454+sandreim@users.noreply.github.com> Date: Thu, 26 Sep 2024 11:37:42 +0300 Subject: [PATCH 108/125] chore: bump runtime api version to v11 (#5824) A change that I missed to add in https://github.com/paritytech/polkadot-sdk/pull/5525 . --------- Signed-off-by: Andrei Sandu --- .../parachains/src/runtime_api_impl/mod.rs | 2 +- .../src/runtime_api_impl/{v10.rs => v11.rs} | 43 +++++++++++++++++- .../src/runtime_api_impl/vstaging.rs | 45 ------------------- polkadot/runtime/rococo/src/lib.rs | 8 ++-- polkadot/runtime/test-runtime/src/lib.rs | 16 +++---- polkadot/runtime/westend/src/lib.rs | 8 ++-- prdoc/pr_5824.prdoc | 17 +++++++ 7 files changed, 72 insertions(+), 67 deletions(-) rename polkadot/runtime/parachains/src/runtime_api_impl/{v10.rs => v11.rs} (92%) create mode 100644 prdoc/pr_5824.prdoc diff --git a/polkadot/runtime/parachains/src/runtime_api_impl/mod.rs b/polkadot/runtime/parachains/src/runtime_api_impl/mod.rs index ed2e95b3cfa..ad80856e239 100644 --- a/polkadot/runtime/parachains/src/runtime_api_impl/mod.rs +++ b/polkadot/runtime/parachains/src/runtime_api_impl/mod.rs @@ -26,5 +26,5 @@ //! 2. Move methods from `vstaging` to `v3`. The new stable version should include all methods from //! `vstaging` tagged with the new version number (e.g. all `v3` methods). -pub mod v10; +pub mod v11; pub mod vstaging; diff --git a/polkadot/runtime/parachains/src/runtime_api_impl/v10.rs b/polkadot/runtime/parachains/src/runtime_api_impl/v11.rs similarity index 92% rename from polkadot/runtime/parachains/src/runtime_api_impl/v10.rs rename to polkadot/runtime/parachains/src/runtime_api_impl/v11.rs index ead825b38f0..51e33001609 100644 --- a/polkadot/runtime/parachains/src/runtime_api_impl/v10.rs +++ b/polkadot/runtime/parachains/src/runtime_api_impl/v11.rs @@ -14,7 +14,7 @@ //! A module exporting runtime API implementation functions for all runtime APIs using `v5` //! primitives. //! -//! Runtimes implementing the v10 runtime API are recommended to forward directly to these +//! Runtimes implementing the v11 runtime API are recommended to forward directly to these //! functions. use crate::{ @@ -22,7 +22,11 @@ use crate::{ scheduler::{self, CoreOccupied}, session_info, shared, }; -use alloc::{collections::btree_map::BTreeMap, vec, vec::Vec}; +use alloc::{ + collections::{btree_map::BTreeMap, vec_deque::VecDeque}, + vec, + vec::Vec, +}; use frame_support::traits::{GetStorageVersion, StorageVersion}; use frame_system::pallet_prelude::*; use polkadot_primitives::{ @@ -547,3 +551,38 @@ pub fn node_features() -> NodeFeatures { pub fn approval_voting_params() -> ApprovalVotingParams { configuration::ActiveConfig::::get().approval_voting_params } + +/// Returns the claimqueue from the scheduler +pub fn claim_queue() -> BTreeMap> { + let now = >::block_number() + One::one(); + + // This is needed so that the claim queue always has the right size (equal to + // scheduling_lookahead). Otherwise, if a candidate is backed in the same block where the + // previous candidate is included, the claim queue will have already pop()-ed the next item + // from the queue and the length would be `scheduling_lookahead - 1`. + >::free_cores_and_fill_claim_queue(Vec::new(), now); + let config = configuration::ActiveConfig::::get(); + // Extra sanity, config should already never be smaller than 1: + let n_lookahead = config.scheduler_params.lookahead.max(1); + + scheduler::ClaimQueue::::get() + .into_iter() + .map(|(core_index, entries)| { + // on cores timing out internal claim queue size may be temporarily longer than it + // should be as the timed out assignment might got pushed back to an already full claim + // queue: + ( + core_index, + entries.into_iter().map(|e| e.para_id()).take(n_lookahead as usize).collect(), + ) + }) + .collect() +} + +/// Returns all the candidates that are pending availability for a given `ParaId`. +/// Deprecates `candidate_pending_availability` in favor of supporting elastic scaling. +pub fn candidates_pending_availability( + para_id: ParaId, +) -> Vec> { + >::candidates_pending_availability(para_id) +} diff --git a/polkadot/runtime/parachains/src/runtime_api_impl/vstaging.rs b/polkadot/runtime/parachains/src/runtime_api_impl/vstaging.rs index a3440f686e9..d01b543630c 100644 --- a/polkadot/runtime/parachains/src/runtime_api_impl/vstaging.rs +++ b/polkadot/runtime/parachains/src/runtime_api_impl/vstaging.rs @@ -15,48 +15,3 @@ // along with Polkadot. If not, see . //! Put implementations of functions from staging APIs here. - -use crate::{configuration, inclusion, initializer, scheduler}; -use alloc::{ - collections::{btree_map::BTreeMap, vec_deque::VecDeque}, - vec::Vec, -}; -use polkadot_primitives::{ - vstaging::CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreIndex, Id as ParaId, -}; -use sp_runtime::traits::One; - -/// Returns the claimqueue from the scheduler -pub fn claim_queue() -> BTreeMap> { - let now = >::block_number() + One::one(); - - // This is needed so that the claim queue always has the right size (equal to - // scheduling_lookahead). Otherwise, if a candidate is backed in the same block where the - // previous candidate is included, the claim queue will have already pop()-ed the next item - // from the queue and the length would be `scheduling_lookahead - 1`. - >::free_cores_and_fill_claim_queue(Vec::new(), now); - let config = configuration::ActiveConfig::::get(); - // Extra sanity, config should already never be smaller than 1: - let n_lookahead = config.scheduler_params.lookahead.max(1); - - scheduler::ClaimQueue::::get() - .into_iter() - .map(|(core_index, entries)| { - // on cores timing out internal claim queue size may be temporarily longer than it - // should be as the timed out assignment might got pushed back to an already full claim - // queue: - ( - core_index, - entries.into_iter().map(|e| e.para_id()).take(n_lookahead as usize).collect(), - ) - }) - .collect() -} - -/// Returns all the candidates that are pending availability for a given `ParaId`. -/// Deprecates `candidate_pending_availability` in favor of supporting elastic scaling. -pub fn candidates_pending_availability( - para_id: ParaId, -) -> Vec> { - >::candidates_pending_availability(para_id) -} diff --git a/polkadot/runtime/rococo/src/lib.rs b/polkadot/runtime/rococo/src/lib.rs index e5396034a44..5fd219c193c 100644 --- a/polkadot/runtime/rococo/src/lib.rs +++ b/polkadot/runtime/rococo/src/lib.rs @@ -66,9 +66,7 @@ use polkadot_runtime_parachains::{ initializer as parachains_initializer, on_demand as parachains_on_demand, origin as parachains_origin, paras as parachains_paras, paras_inherent as parachains_paras_inherent, - runtime_api_impl::{ - v10 as parachains_runtime_api_impl, vstaging as vstaging_parachains_runtime_api_impl, - }, + runtime_api_impl::v11 as parachains_runtime_api_impl, scheduler as parachains_scheduler, session_info as parachains_session_info, shared as parachains_shared, }; @@ -2057,11 +2055,11 @@ sp_api::impl_runtime_apis! { } fn claim_queue() -> BTreeMap> { - vstaging_parachains_runtime_api_impl::claim_queue::() + parachains_runtime_api_impl::claim_queue::() } fn candidates_pending_availability(para_id: ParaId) -> Vec> { - vstaging_parachains_runtime_api_impl::candidates_pending_availability::(para_id) + parachains_runtime_api_impl::candidates_pending_availability::(para_id) } } diff --git a/polkadot/runtime/test-runtime/src/lib.rs b/polkadot/runtime/test-runtime/src/lib.rs index b0323156911..30ef2de3165 100644 --- a/polkadot/runtime/test-runtime/src/lib.rs +++ b/polkadot/runtime/test-runtime/src/lib.rs @@ -33,13 +33,11 @@ use pallet_transaction_payment::FungibleAdapter; use polkadot_runtime_parachains::{ assigner_parachains as parachains_assigner_parachains, configuration as parachains_configuration, - configuration::ActiveConfigHrmpChannelSizeAndCapacityRatio, - disputes as parachains_disputes, - disputes::slashing as parachains_slashing, - dmp as parachains_dmp, hrmp as parachains_hrmp, inclusion as parachains_inclusion, - initializer as parachains_initializer, origin as parachains_origin, paras as parachains_paras, - paras_inherent as parachains_paras_inherent, - runtime_api_impl::{v10 as runtime_impl, vstaging as vstaging_parachains_runtime_api_impl}, + configuration::ActiveConfigHrmpChannelSizeAndCapacityRatio, disputes as parachains_disputes, + disputes::slashing as parachains_slashing, dmp as parachains_dmp, hrmp as parachains_hrmp, + inclusion as parachains_inclusion, initializer as parachains_initializer, + origin as parachains_origin, paras as parachains_paras, + paras_inherent as parachains_paras_inherent, runtime_api_impl::v11 as runtime_impl, scheduler as parachains_scheduler, session_info as parachains_session_info, shared as parachains_shared, }; @@ -1003,11 +1001,11 @@ sp_api::impl_runtime_apis! { } fn claim_queue() -> BTreeMap> { - vstaging_parachains_runtime_api_impl::claim_queue::() + runtime_impl::claim_queue::() } fn candidates_pending_availability(para_id: ParaId) -> Vec> { - vstaging_parachains_runtime_api_impl::candidates_pending_availability::(para_id) + runtime_impl::candidates_pending_availability::(para_id) } } diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index 6719a037384..d6ed11d0787 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -83,9 +83,7 @@ use polkadot_runtime_parachains::{ initializer as parachains_initializer, on_demand as parachains_on_demand, origin as parachains_origin, paras as parachains_paras, paras_inherent as parachains_paras_inherent, reward_points as parachains_reward_points, - runtime_api_impl::{ - v10 as parachains_runtime_api_impl, vstaging as vstaging_parachains_runtime_api_impl, - }, + runtime_api_impl::v11 as parachains_runtime_api_impl, scheduler as parachains_scheduler, session_info as parachains_session_info, shared as parachains_shared, }; @@ -2088,11 +2086,11 @@ sp_api::impl_runtime_apis! { } fn claim_queue() -> BTreeMap> { - vstaging_parachains_runtime_api_impl::claim_queue::() + parachains_runtime_api_impl::claim_queue::() } fn candidates_pending_availability(para_id: ParaId) -> Vec> { - vstaging_parachains_runtime_api_impl::candidates_pending_availability::(para_id) + parachains_runtime_api_impl::candidates_pending_availability::(para_id) } } diff --git a/prdoc/pr_5824.prdoc b/prdoc/pr_5824.prdoc new file mode 100644 index 00000000000..136cd6bfee8 --- /dev/null +++ b/prdoc/pr_5824.prdoc @@ -0,0 +1,17 @@ +title: "Bump parachains runtime API to v11" + +doc: + - audience: [ Node Dev, Runtime Dev ] + description: | + This PR promotes all staging methods in v10 to stable and releases v11 stable runtime + APIs. + +crates: + - name: polkadot-runtime-parachains + bump: major + - name: rococo-runtime + bump: patch + - name: westend-runtime + bump: patch + - name: polkadot-test-runtime + bump: patch -- GitLab From f6d08e637694729df7cddb166954ee90c2da3ce1 Mon Sep 17 00:00:00 2001 From: Maksym H <1177472+mordamax@users.noreply.github.com> Date: Thu, 26 Sep 2024 12:06:05 +0100 Subject: [PATCH 109/125] add riscv feature to /cmd bench by default (#5828) Closes #5714 --- .github/scripts/cmd/cmd.py | 2 +- .github/scripts/cmd/test_cmd.py | 30 +++++++++++++------------- .github/workflows/runtimes-matrix.json | 13 +++++++++++ 3 files changed, 29 insertions(+), 16 deletions(-) diff --git a/.github/scripts/cmd/cmd.py b/.github/scripts/cmd/cmd.py index f7dd88df4bd..b5835f1f308 100755 --- a/.github/scripts/cmd/cmd.py +++ b/.github/scripts/cmd/cmd.py @@ -100,7 +100,7 @@ def main(): # loop over remaining runtimes to collect available pallets for runtime in runtimesMatrix.values(): - os.system(f"forklift cargo build -p {runtime['package']} --profile {profile} --features runtime-benchmarks") + os.system(f"forklift cargo build -p {runtime['package']} --profile {profile} --features={runtime['bench_features']}") print(f'-- listing pallets for benchmark for {runtime["name"]}') wasm_file = f"target/{profile}/wbuild/{runtime['package']}/{runtime['package'].replace('-', '_')}.wasm" output = os.popen( diff --git a/.github/scripts/cmd/test_cmd.py b/.github/scripts/cmd/test_cmd.py index a2f29b075da..f4e70903347 100644 --- a/.github/scripts/cmd/test_cmd.py +++ b/.github/scripts/cmd/test_cmd.py @@ -7,10 +7,10 @@ import argparse # Mock data for runtimes-matrix.json mock_runtimes_matrix = [ - {"name": "dev", "package": "kitchensink-runtime", "path": "substrate/frame", "header": "substrate/HEADER-APACHE2", "template": "substrate/.maintain/frame-weight-template.hbs"}, - {"name": "westend", "package": "westend-runtime", "path": "polkadot/runtime/westend", "header": "polkadot/file_header.txt", "template": "polkadot/xcm/pallet-xcm-benchmarks/template.hbs"}, - {"name": "rococo", "package": "rococo-runtime", "path": "polkadot/runtime/rococo", "header": "polkadot/file_header.txt", "template": "polkadot/xcm/pallet-xcm-benchmarks/template.hbs"}, - {"name": "asset-hub-westend", "package": "asset-hub-westend-runtime", "path": "cumulus/parachains/runtimes/assets/asset-hub-westend", "header": "cumulus/file_header.txt", "template": "cumulus/templates/xcm-bench-template.hbs"}, + {"name": "dev", "package": "kitchensink-runtime", "path": "substrate/frame", "header": "substrate/HEADER-APACHE2", "template": "substrate/.maintain/frame-weight-template.hbs", "bench_features": "runtime-benchmarks,riscv"}, + {"name": "westend", "package": "westend-runtime", "path": "polkadot/runtime/westend", "header": "polkadot/file_header.txt", "template": "polkadot/xcm/pallet-xcm-benchmarks/template.hbs", "bench_features": "runtime-benchmarks"}, + {"name": "rococo", "package": "rococo-runtime", "path": "polkadot/runtime/rococo", "header": "polkadot/file_header.txt", "template": "polkadot/xcm/pallet-xcm-benchmarks/template.hbs", "bench_features": "runtime-benchmarks"}, + {"name": "asset-hub-westend", "package": "asset-hub-westend-runtime", "path": "cumulus/parachains/runtimes/assets/asset-hub-westend", "header": "cumulus/file_header.txt", "template": "cumulus/templates/xcm-bench-template.hbs", "bench_features": "runtime-benchmarks"}, ] def get_mock_bench_output(runtime, pallets, output_path, header, template = None): @@ -84,10 +84,10 @@ class TestCmd(unittest.TestCase): expected_calls = [ # Build calls - call("forklift cargo build -p kitchensink-runtime --profile release --features runtime-benchmarks"), - call("forklift cargo build -p westend-runtime --profile release --features runtime-benchmarks"), - call("forklift cargo build -p rococo-runtime --profile release --features runtime-benchmarks"), - call("forklift cargo build -p asset-hub-westend-runtime --profile release --features runtime-benchmarks"), + call("forklift cargo build -p kitchensink-runtime --profile release --features=runtime-benchmarks,riscv"), + call("forklift cargo build -p westend-runtime --profile release --features=runtime-benchmarks"), + call("forklift cargo build -p rococo-runtime --profile release --features=runtime-benchmarks"), + call("forklift cargo build -p asset-hub-westend-runtime --profile release --features=runtime-benchmarks"), call(get_mock_bench_output('kitchensink', 'pallet_balances', './substrate/frame/balances/src/weights.rs', os.path.abspath('substrate/HEADER-APACHE2'), "substrate/.maintain/frame-weight-template.hbs")), call(get_mock_bench_output('westend', 'pallet_balances', './polkadot/runtime/westend/src/weights', os.path.abspath('polkadot/file_header.txt'))), @@ -118,7 +118,7 @@ class TestCmd(unittest.TestCase): expected_calls = [ # Build calls - call("forklift cargo build -p westend-runtime --profile release --features runtime-benchmarks"), + call("forklift cargo build -p westend-runtime --profile release --features=runtime-benchmarks"), # Westend runtime calls call(get_mock_bench_output('westend', 'pallet_balances', './polkadot/runtime/westend/src/weights', header_path)), @@ -149,7 +149,7 @@ class TestCmd(unittest.TestCase): expected_calls = [ # Build calls - call("forklift cargo build -p westend-runtime --profile release --features runtime-benchmarks"), + call("forklift cargo build -p westend-runtime --profile release --features=runtime-benchmarks"), # Westend runtime calls call(get_mock_bench_output( @@ -185,8 +185,8 @@ class TestCmd(unittest.TestCase): expected_calls = [ # Build calls - call("forklift cargo build -p westend-runtime --profile release --features runtime-benchmarks"), - call("forklift cargo build -p rococo-runtime --profile release --features runtime-benchmarks"), + call("forklift cargo build -p westend-runtime --profile release --features=runtime-benchmarks"), + call("forklift cargo build -p rococo-runtime --profile release --features=runtime-benchmarks"), # Westend runtime calls call(get_mock_bench_output('westend', 'pallet_staking', './polkadot/runtime/westend/src/weights', header_path)), call(get_mock_bench_output('westend', 'pallet_balances', './polkadot/runtime/westend/src/weights', header_path)), @@ -220,7 +220,7 @@ class TestCmd(unittest.TestCase): expected_calls = [ # Build calls - call("forklift cargo build -p kitchensink-runtime --profile release --features runtime-benchmarks"), + call("forklift cargo build -p kitchensink-runtime --profile release --features=runtime-benchmarks,riscv"), # Westend runtime calls call(get_mock_bench_output( 'kitchensink', @@ -254,7 +254,7 @@ class TestCmd(unittest.TestCase): expected_calls = [ # Build calls - call("forklift cargo build -p asset-hub-westend-runtime --profile release --features runtime-benchmarks"), + call("forklift cargo build -p asset-hub-westend-runtime --profile release --features=runtime-benchmarks"), # Asset-hub-westend runtime calls call(get_mock_bench_output( 'asset-hub-westend', @@ -288,7 +288,7 @@ class TestCmd(unittest.TestCase): expected_calls = [ # Build calls - call("forklift cargo build -p asset-hub-westend-runtime --profile release --features runtime-benchmarks"), + call("forklift cargo build -p asset-hub-westend-runtime --profile release --features=runtime-benchmarks"), # Asset-hub-westend runtime calls call(get_mock_bench_output( 'asset-hub-westend', diff --git a/.github/workflows/runtimes-matrix.json b/.github/workflows/runtimes-matrix.json index 102437876da..b868a410a16 100644 --- a/.github/workflows/runtimes-matrix.json +++ b/.github/workflows/runtimes-matrix.json @@ -5,6 +5,7 @@ "path": "substrate/frame", "header": "substrate/HEADER-APACHE2", "template": "substrate/.maintain/frame-weight-template.hbs", + "bench_features": "runtime-benchmarks,riscv", "uri": null, "is_relay": false }, @@ -15,6 +16,7 @@ "header": "polkadot/file_header.txt", "template": "polkadot/xcm/pallet-xcm-benchmarks/template.hbs", "uri": "wss://try-runtime-westend.polkadot.io:443", + "bench_features": "runtime-benchmarks", "is_relay": true }, { @@ -24,6 +26,7 @@ "header": "polkadot/file_header.txt", "template": "polkadot/xcm/pallet-xcm-benchmarks/template.hbs", "uri": "wss://try-runtime-rococo.polkadot.io:443", + "bench_features": "runtime-benchmarks", "is_relay": true }, { @@ -32,6 +35,7 @@ "path": "cumulus/parachains/runtimes/assets/asset-hub-westend", "header": "cumulus/file_header.txt", "template": "cumulus/templates/xcm-bench-template.hbs", + "bench_features": "runtime-benchmarks", "uri": "wss://westend-asset-hub-rpc.polkadot.io:443", "is_relay": false }, @@ -49,6 +53,7 @@ "package": "bridge-hub-rococo-runtime", "path": "cumulus/parachains/runtimes/bridges/bridge-hub-rococo", "header": "cumulus/file_header.txt", + "bench_features": "runtime-benchmarks", "template": "cumulus/templates/xcm-bench-template.hbs", "uri": "wss://rococo-bridge-hub-rpc.polkadot.io:443", "is_relay": false @@ -58,6 +63,7 @@ "package": "bridge-hub-rococo-runtime", "path": "cumulus/parachains/runtimes/bridges/bridge-hub-westend", "header": "cumulus/file_header.txt", + "bench_features": "runtime-benchmarks", "template": "cumulus/templates/xcm-bench-template.hbs", "uri": "wss://westend-bridge-hub-rpc.polkadot.io:443", "is_relay": false @@ -67,6 +73,7 @@ "package": "collectives-westend-runtime", "path": "cumulus/parachains/runtimes/collectives/collectives-westend", "header": "cumulus/file_header.txt", + "bench_features": "runtime-benchmarks", "template": "cumulus/templates/xcm-bench-template.hbs", "uri": "wss://westend-collectives-rpc.polkadot.io:443" }, @@ -75,6 +82,7 @@ "package": "contracts-rococo-runtime", "path": "cumulus/parachains/runtimes/contracts/contracts-rococo", "header": "cumulus/file_header.txt", + "bench_features": "runtime-benchmarks", "template": "cumulus/templates/xcm-bench-template.hbs", "uri": "wss://rococo-contracts-rpc.polkadot.io:443", "is_relay": false @@ -84,6 +92,7 @@ "package": "coretime-rococo-runtime", "path": "cumulus/parachains/runtimes/coretime/coretime-rococo", "header": "cumulus/file_header.txt", + "bench_features": "runtime-benchmarks", "template": "cumulus/templates/xcm-bench-template.hbs", "uri": "wss://rococo-coretime-rpc.polkadot.io:443", "is_relay": false @@ -93,6 +102,7 @@ "package": "coretime-westend-runtime", "path": "cumulus/parachains/runtimes/coretime/coretime-westend", "header": "cumulus/file_header.txt", + "bench_features": "runtime-benchmarks", "template": "cumulus/templates/xcm-bench-template.hbs", "uri": "wss://westend-coretime-rpc.polkadot.io:443", "is_relay": false @@ -102,6 +112,7 @@ "package": "glutton-westend-runtime", "path": "cumulus/parachains/runtimes/gluttons/glutton-westend", "header": "cumulus/file_header.txt", + "bench_features": "runtime-benchmarks", "template": "cumulus/templates/xcm-bench-template.hbs", "uri": null, "is_relay": false @@ -111,6 +122,7 @@ "package": "people-rococo-runtime", "path": "cumulus/parachains/runtimes/people/people-rococo", "header": "cumulus/file_header.txt", + "bench_features": "runtime-benchmarks", "template": "cumulus/templates/xcm-bench-template.hbs", "uri": "wss://rococo-people-rpc.polkadot.io:443", "is_relay": false @@ -120,6 +132,7 @@ "package": "people-westend-runtime", "path": "cumulus/parachains/runtimes/people/people-westend", "header": "cumulus/file_header.txt", + "bench_features": "runtime-benchmarks", "template": "cumulus/templates/xcm-bench-template.hbs", "uri": "wss://westend-people-rpc.polkadot.io:443", "is_relay": false -- GitLab From b16237ad6f019667a59b0e3e726f6ac20e2d0a1c Mon Sep 17 00:00:00 2001 From: Alexandru Gheorghe <49718502+alexggh@users.noreply.github.com> Date: Thu, 26 Sep 2024 15:11:00 +0300 Subject: [PATCH 110/125] [5 / 5] Introduce approval-voting-parallel (#4849) This is the implementation of the approach described here: https://github.com/paritytech/polkadot-sdk/issues/1617#issuecomment-2150321612 & https://github.com/paritytech/polkadot-sdk/issues/1617#issuecomment-2154357547 & https://github.com/paritytech/polkadot-sdk/issues/1617#issuecomment-2154721395. ## Description of changes The end goal is to have an architecture where we have single subsystem(`approval-voting-parallel`) and multiple worker types that would full-fill the work that currently is fulfilled by the `approval-distribution` and `approval-voting` subsystems. The main loop of the new subsystem would do just the distribution of work to the workers. The new subsystem will have: - N approval-distribution workers: This would do the work that is currently being done by the approval-distribution subsystem and in addition to that will also perform the crypto-checks that an assignment is valid and that a vote is correctly signed. Work is assigned via the following formula: `worker_index = msg.validator % WORKER_COUNT`, this guarantees that all assignments and approvals from the same validator reach the same worker. - 1 approval-voting worker: This would receive an already valid message and do everything the approval-voting currently does, except the crypto-checking that has been moved already to the approval-distribution worker. On the hot path of processing messages **no** synchronisation and waiting is needed between approval-distribution and approval-voting workers. Screenshot 2024-06-07 at 11 28 08 ## Guidelines for reading The full implementation is broken in 5 PRs and all of them are self-contained and improve things incrementally even without the parallelisation being implemented/enabled, the reason this approach was taken instead of a big-bang PR, is to make things easier to review and reduced the risk of breaking this critical subsystems. After reading the full description of this PR, the changes should be read in the following order: 1. https://github.com/paritytech/polkadot-sdk/pull/4848, some other micro-optimizations for networks with a high number of validators. This change gives us a speed up by itself without any other changes. 2. https://github.com/paritytech/polkadot-sdk/pull/4845 , this contains only interface changes to decouple the subsystem from the `Context` and be able to run multiple instances of the subsystem on different threads. **No functional changes** 3. https://github.com/paritytech/polkadot-sdk/pull/4928, moving of the crypto checks from approval-voting in approval-distribution, so that the approval-distribution has no reason to wait after approval-voting anymore. This change gives us a speed up by itself without any other changes. 4. https://github.com/paritytech/polkadot-sdk/pull/4846, interface changes to make approval-voting runnable on a separate thread. **No functional changes** 5. This PR, where we instantiate an `approval-voting-parallel` subsystem that runs on different workers the logic currently in `approval-distribution` and `approval-voting`. 6. The next step after this changes get merged and deploy would be to bring all the files from approval-distribution, approval-voting, approval-voting-parallel into a single rust crate, to make it easier to maintain and understand the structure. ## Results Running subsystem-benchmarks with 1000 validators 100 fully ocuppied cores and triggering all assignments and approvals for all tranches #### Approval does not lags behind. Master ``` Chain selection approved after 72500 ms hash=0x0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a ``` With this PoC ``` Chain selection approved after 3500 ms hash=0x0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a ``` #### Gathering enough assignments Enough assignments are gathered in less than 500ms, so that gives un a guarantee that un-necessary work does not get triggered, on master on the same benchmark because the subsystems fall behind on work, that number goes above 32 seconds on master. Screenshot 2024-06-20 at 15 48 22 #### Cpu usage: Master ``` CPU usage, seconds total per block approval-distribution 96.9436 9.6944 approval-voting 117.4676 11.7468 test-environment 44.0092 4.4009 ``` With this PoC ``` CPU usage, seconds total per block approval-distribution 0.0014 0.0001 --- unused approval-voting 0.0437 0.0044. --- unused approval-voting-parallel 5.9560 0.5956 approval-voting-parallel-0 22.9073 2.2907 approval-voting-parallel-1 23.0417 2.3042 approval-voting-parallel-2 22.0445 2.2045 approval-voting-parallel-3 22.7234 2.2723 approval-voting-parallel-4 21.9788 2.1979 approval-voting-parallel-5 23.0601 2.3060 approval-voting-parallel-6 22.4805 2.2481 approval-voting-parallel-7 21.8330 2.1833 approval-voting-parallel-db 37.1954 3.7195. --- the approval-voting thread. ``` # Enablement strategy Because just some trivial plumbing is needed in approval-distribution and approval-voting to be able to run things in parallel and because this subsystems plays a critical part in the system this PR proposes that we keep both ways of running the approval work, as separated subsystems and just a single subsystem(`approval-voting-parallel`) which has multiple workers for the distribution work and one worker for the approval-voting work and switch between them with a comandline flag. The benefits for this is twofold. 1. With the same polkadot binary we can easily switch just a few validators to use the parallel approach and gradually make this the default way of running, if now issues arise. 2. In the worst case scenario were it becomes the default way of running things, but we discover there are critical issues with it we have the path to quickly disable it by asking validators to adjust their command line flags. # Next steps - [x] Make sure through various testing we are not missing anything - [x] Polish the implementations to make them production ready - [x] Add Unittest Tests for approval-voting-parallel. - [x] Define and implement the strategy for rolling this change, so that the blast radius is minimal(single validator) in case there are problems with the implementation. - [x] Versi long running tests. - [x] Add relevant metrics. @ordian @eskimor @sandreim @AndreiEres, let me know what you think. --------- Signed-off-by: Alexandru Gheorghe --- .gitlab/pipeline/zombienet/polkadot.yml | 8 + Cargo.lock | 46 + Cargo.toml | 2 + .../src/lib.rs | 1 + polkadot/cli/src/cli.rs | 7 + polkadot/cli/src/command.rs | 1 + .../core/approval-voting-parallel/Cargo.toml | 55 + .../core/approval-voting-parallel/src/lib.rs | 957 +++++++++++++ .../approval-voting-parallel/src/metrics.rs | 236 ++++ .../approval-voting-parallel/src/tests.rs | 1178 +++++++++++++++++ .../approval-voting-regression-bench.rs | 1 + .../dispute-coordinator/src/initialized.rs | 33 +- .../node/core/dispute-coordinator/src/lib.rs | 4 +- .../core/dispute-coordinator/src/tests.rs | 1 + .../network/approval-distribution/src/lib.rs | 9 +- .../approval-distribution/src/metrics.rs | 63 +- .../approval-distribution/src/tests.rs | 75 +- polkadot/node/network/bridge/src/rx/mod.rs | 66 +- polkadot/node/network/bridge/src/rx/tests.rs | 1 + polkadot/node/overseer/src/dummy.rs | 4 + polkadot/node/overseer/src/lib.rs | 30 +- polkadot/node/overseer/src/tests.rs | 11 +- polkadot/node/primitives/src/approval/mod.rs | 2 +- polkadot/node/service/Cargo.toml | 2 + polkadot/node/service/src/lib.rs | 13 + polkadot/node/service/src/overseer.rs | 261 +++- .../node/service/src/relay_chain_selection.rs | 65 +- polkadot/node/service/src/tests.rs | 1 + polkadot/node/subsystem-bench/Cargo.toml | 1 + .../examples/approvals_no_shows.yaml | 1 + .../examples/approvals_throughput.yaml | 1 + .../approvals_throughput_best_case.yaml | 1 + .../src/lib/approval/helpers.rs | 29 +- .../subsystem-bench/src/lib/approval/mod.rs | 170 ++- .../src/lib/availability/mod.rs | 13 +- .../node/subsystem-bench/src/lib/display.rs | 17 + .../subsystem-bench/src/lib/environment.rs | 30 +- .../subsystem-bench/src/lib/mock/dummy.rs | 1 + .../node/subsystem-bench/src/lib/mock/mod.rs | 1 + .../src/lib/mock/network_bridge.rs | 27 +- .../subsystem-bench/src/lib/statement/mod.rs | 5 +- .../node/subsystem-bench/src/lib/usage.rs | 6 +- polkadot/node/subsystem-types/src/messages.rs | 97 ++ polkadot/node/test/service/src/lib.rs | 2 + .../adder/collator/src/main.rs | 1 + .../undying/collator/src/main.rs | 1 + .../node/approval/approval-voting-parallel.md | 30 + .../0009-approval-voting-coalescing.toml | 2 +- .../0016-approval-voting-parallel.toml | 120 ++ .../0016-approval-voting-parallel.zndsl | 35 + prdoc/pr_4849.prdoc | 47 + umbrella/Cargo.toml | 7 +- umbrella/src/lib.rs | 4 + 53 files changed, 3565 insertions(+), 217 deletions(-) create mode 100644 polkadot/node/core/approval-voting-parallel/Cargo.toml create mode 100644 polkadot/node/core/approval-voting-parallel/src/lib.rs create mode 100644 polkadot/node/core/approval-voting-parallel/src/metrics.rs create mode 100644 polkadot/node/core/approval-voting-parallel/src/tests.rs create mode 100644 polkadot/roadmap/implementers-guide/src/node/approval/approval-voting-parallel.md create mode 100644 polkadot/zombienet_tests/functional/0016-approval-voting-parallel.toml create mode 100644 polkadot/zombienet_tests/functional/0016-approval-voting-parallel.zndsl create mode 100644 prdoc/pr_4849.prdoc diff --git a/.gitlab/pipeline/zombienet/polkadot.yml b/.gitlab/pipeline/zombienet/polkadot.yml index 93fc4bbb578..e25bc4ca229 100644 --- a/.gitlab/pipeline/zombienet/polkadot.yml +++ b/.gitlab/pipeline/zombienet/polkadot.yml @@ -223,6 +223,14 @@ zombienet-polkadot-functional-0015-coretime-shared-core: --local-dir="${LOCAL_DIR}/functional" --test="0015-coretime-shared-core.zndsl" +zombienet-polkadot-functional-0016-approval-voting-parallel: + extends: + - .zombienet-polkadot-common + script: + - /home/nonroot/zombie-net/scripts/ci/run-test-local-env-manager.sh + --local-dir="${LOCAL_DIR}/functional" + --test="0016-approval-voting-parallel.zndsl" + zombienet-polkadot-smoke-0001-parachains-smoke-test: extends: - .zombienet-polkadot-common diff --git a/Cargo.lock b/Cargo.lock index 61f485bcecb..c20c8f71c80 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14268,6 +14268,49 @@ dependencies = [ "tracing-gum", ] +[[package]] +name = "polkadot-node-core-approval-voting-parallel" +version = "7.0.0" +dependencies = [ + "assert_matches", + "async-trait", + "futures", + "futures-timer", + "itertools 0.11.0", + "kvdb-memorydb", + "log", + "parking_lot 0.12.3", + "polkadot-approval-distribution", + "polkadot-node-core-approval-voting", + "polkadot-node-jaeger", + "polkadot-node-metrics", + "polkadot-node-network-protocol", + "polkadot-node-primitives", + "polkadot-node-subsystem", + "polkadot-node-subsystem-test-helpers", + "polkadot-node-subsystem-util", + "polkadot-overseer", + "polkadot-primitives", + "polkadot-primitives-test-helpers", + "polkadot-subsystem-bench", + "rand", + "rand_chacha", + "rand_core 0.6.4", + "sc-keystore", + "schnorrkel 0.11.4", + "sp-application-crypto 30.0.0", + "sp-consensus", + "sp-consensus-babe", + "sp-consensus-slots", + "sp-core 28.0.0", + "sp-keyring", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", + "sp-tracing 16.0.0", + "thiserror", + "tracing-gum", +] + [[package]] name = "polkadot-node-core-av-store" version = "7.0.0" @@ -15397,6 +15440,7 @@ dependencies = [ "polkadot-network-bridge", "polkadot-node-collation-generation", "polkadot-node-core-approval-voting", + "polkadot-node-core-approval-voting-parallel", "polkadot-node-core-av-store", "polkadot-node-core-backing", "polkadot-node-core-bitfield-signing", @@ -15736,6 +15780,7 @@ dependencies = [ "polkadot-network-bridge", "polkadot-node-collation-generation", "polkadot-node-core-approval-voting", + "polkadot-node-core-approval-voting-parallel", "polkadot-node-core-av-store", "polkadot-node-core-backing", "polkadot-node-core-bitfield-signing", @@ -15892,6 +15937,7 @@ dependencies = [ "polkadot-availability-recovery", "polkadot-erasure-coding", "polkadot-node-core-approval-voting", + "polkadot-node-core-approval-voting-parallel", "polkadot-node-core-av-store", "polkadot-node-core-chain-api", "polkadot-node-metrics", diff --git a/Cargo.toml b/Cargo.toml index b7c9c0cdcbf..c92254242fc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -158,6 +158,7 @@ members = [ "polkadot/erasure-coding/fuzzer", "polkadot/node/collation-generation", "polkadot/node/core/approval-voting", + "polkadot/node/core/approval-voting-parallel", "polkadot/node/core/av-store", "polkadot/node/core/backing", "polkadot/node/core/bitfield-signing", @@ -1032,6 +1033,7 @@ polkadot-gossip-support = { path = "polkadot/node/network/gossip-support", defau polkadot-network-bridge = { path = "polkadot/node/network/bridge", default-features = false } polkadot-node-collation-generation = { path = "polkadot/node/collation-generation", default-features = false } polkadot-node-core-approval-voting = { path = "polkadot/node/core/approval-voting", default-features = false } +polkadot-node-core-approval-voting-parallel = { path = "polkadot/node/core/approval-voting-parallel", default-features = false } polkadot-node-core-av-store = { path = "polkadot/node/core/av-store", default-features = false } polkadot-node-core-backing = { path = "polkadot/node/core/backing", default-features = false } polkadot-node-core-bitfield-signing = { path = "polkadot/node/core/bitfield-signing", default-features = false } diff --git a/cumulus/client/relay-chain-inprocess-interface/src/lib.rs b/cumulus/client/relay-chain-inprocess-interface/src/lib.rs index 4fea055203d..f0a082dce53 100644 --- a/cumulus/client/relay-chain-inprocess-interface/src/lib.rs +++ b/cumulus/client/relay-chain-inprocess-interface/src/lib.rs @@ -379,6 +379,7 @@ fn build_polkadot_full_node( execute_workers_max_num: None, prepare_workers_hard_max_num: None, prepare_workers_soft_max_num: None, + enable_approval_voting_parallel: false, }, )?; diff --git a/polkadot/cli/src/cli.rs b/polkadot/cli/src/cli.rs index 3e5a6ccdd3c..1445ade08e2 100644 --- a/polkadot/cli/src/cli.rs +++ b/polkadot/cli/src/cli.rs @@ -151,6 +151,13 @@ pub struct RunCmd { /// TESTING ONLY: disable the version check between nodes and workers. #[arg(long, hide = true)] pub disable_worker_version_check: bool, + + /// Enable approval-voting message processing in parallel. + /// + ///**Dangerous!** This is an experimental feature and should not be used in production, unless + /// explicitly advised to. + #[arg(long)] + pub enable_approval_voting_parallel: bool, } #[allow(missing_docs)] diff --git a/polkadot/cli/src/command.rs b/polkadot/cli/src/command.rs index 89e21bf135b..16576e4b272 100644 --- a/polkadot/cli/src/command.rs +++ b/polkadot/cli/src/command.rs @@ -244,6 +244,7 @@ where execute_workers_max_num: cli.run.execute_workers_max_num, prepare_workers_hard_max_num: cli.run.prepare_workers_hard_max_num, prepare_workers_soft_max_num: cli.run.prepare_workers_soft_max_num, + enable_approval_voting_parallel: cli.run.enable_approval_voting_parallel, }, ) .map(|full| full.task_manager)?; diff --git a/polkadot/node/core/approval-voting-parallel/Cargo.toml b/polkadot/node/core/approval-voting-parallel/Cargo.toml new file mode 100644 index 00000000000..e62062eab40 --- /dev/null +++ b/polkadot/node/core/approval-voting-parallel/Cargo.toml @@ -0,0 +1,55 @@ +[package] +name = "polkadot-node-core-approval-voting-parallel" +version = "7.0.0" +authors.workspace = true +edition.workspace = true +license.workspace = true +description = "Approval Voting Subsystem running approval work in parallel" + +[lints] +workspace = true + +[dependencies] +async-trait = { workspace = true } +futures = { workspace = true } +futures-timer = { workspace = true } +gum = { workspace = true } +itertools = { workspace = true } +thiserror = { workspace = true } + +polkadot-node-core-approval-voting = { workspace = true, default-features = true } +polkadot-approval-distribution = { workspace = true, default-features = true } +polkadot-node-subsystem = { workspace = true, default-features = true } +polkadot-node-subsystem-util = { workspace = true, default-features = true } +polkadot-overseer = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } +polkadot-node-primitives = { workspace = true, default-features = true } +polkadot-node-jaeger = { workspace = true, default-features = true } +polkadot-node-network-protocol = { workspace = true, default-features = true } +polkadot-node-metrics = { workspace = true, default-features = true } + +sc-keystore = { workspace = true, default-features = false } +sp-consensus = { workspace = true, default-features = false } +sp-consensus-slots = { workspace = true, default-features = false } +sp-application-crypto = { workspace = true, default-features = false, features = ["full_crypto"] } +sp-runtime = { workspace = true, default-features = false } + +rand = { workspace = true } +rand_core = { workspace = true } +rand_chacha = { workspace = true } + +[dev-dependencies] +async-trait = { workspace = true } +parking_lot = { workspace = true } +sp-keyring = { workspace = true, default-features = true } +sp-keystore = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } +sp-consensus-babe = { workspace = true, default-features = true } +sp-tracing = { workspace = true } +polkadot-node-subsystem-test-helpers = { workspace = true, default-features = true } +assert_matches = { workspace = true } +kvdb-memorydb = { workspace = true } +polkadot-primitives-test-helpers = { workspace = true, default-features = true } +log = { workspace = true, default-features = true } +polkadot-subsystem-bench = { workspace = true, default-features = true } +schnorrkel = { workspace = true, default-features = true } diff --git a/polkadot/node/core/approval-voting-parallel/src/lib.rs b/polkadot/node/core/approval-voting-parallel/src/lib.rs new file mode 100644 index 00000000000..18c73cfba1f --- /dev/null +++ b/polkadot/node/core/approval-voting-parallel/src/lib.rs @@ -0,0 +1,957 @@ +// Copyright (C) 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 . + +//! The Approval Voting Parallel Subsystem. +//! +//! This subsystem is responsible for orchestrating the work done by +//! approval-voting and approval-distribution subsystem, so they can +//! do their work in parallel, rather than serially, when they are run +//! as independent subsystems. +use itertools::Itertools; +use metrics::{Meters, MetricsWatcher}; +use polkadot_node_core_approval_voting::{Config, RealAssignmentCriteria}; +use polkadot_node_metrics::metered::{ + self, channel, unbounded, MeteredReceiver, MeteredSender, UnboundedMeteredReceiver, + UnboundedMeteredSender, +}; + +use polkadot_node_primitives::{ + approval::time::{Clock, SystemClock}, + DISPUTE_WINDOW, +}; +use polkadot_node_subsystem::{ + messages::{ApprovalDistributionMessage, ApprovalVotingMessage, ApprovalVotingParallelMessage}, + overseer, FromOrchestra, SpawnedSubsystem, SubsystemError, SubsystemResult, +}; +use polkadot_node_subsystem_util::{ + self, + database::Database, + runtime::{Config as RuntimeInfoConfig, RuntimeInfo}, +}; +use polkadot_overseer::{OverseerSignal, Priority, SubsystemSender, TimeoutExt}; +use polkadot_primitives::{CandidateIndex, Hash, ValidatorIndex, ValidatorSignature}; +use rand::SeedableRng; + +use sc_keystore::LocalKeystore; +use sp_consensus::SyncOracle; + +use futures::{channel::oneshot, prelude::*, StreamExt}; +pub use metrics::Metrics; +use polkadot_node_core_approval_voting::{ + approval_db::common::Config as DatabaseConfig, ApprovalVotingWorkProvider, +}; +use std::{ + collections::{HashMap, HashSet}, + fmt::Debug, + sync::Arc, + time::Duration, +}; +use stream::{select_with_strategy, PollNext, SelectWithStrategy}; +pub mod metrics; + +#[cfg(test)] +mod tests; + +pub(crate) const LOG_TARGET: &str = "parachain::approval-voting-parallel"; +// Value rather arbitrarily: Should not be hit in practice, it exists to more easily diagnose dead +// lock issues for example. +const WAIT_FOR_SIGS_GATHER_TIMEOUT: Duration = Duration::from_millis(2000); + +/// The number of workers used for running the approval-distribution logic. +pub const APPROVAL_DISTRIBUTION_WORKER_COUNT: usize = 4; + +/// The default channel size for the workers, can be overridden by the user through +/// `overseer_channel_capacity_override` +pub const DEFAULT_WORKERS_CHANNEL_SIZE: usize = 64000 / APPROVAL_DISTRIBUTION_WORKER_COUNT; + +fn prio_right<'a>(_val: &'a mut ()) -> PollNext { + PollNext::Right +} + +/// The approval voting parallel subsystem. +pub struct ApprovalVotingParallelSubsystem { + /// `LocalKeystore` is needed for assignment keys, but not necessarily approval keys. + /// + /// We do a lot of VRF signing and need the keys to have low latency. + keystore: Arc, + db_config: DatabaseConfig, + slot_duration_millis: u64, + db: Arc, + sync_oracle: Box, + metrics: Metrics, + spawner: Arc, + clock: Arc, + overseer_message_channel_capacity_override: Option, +} + +impl ApprovalVotingParallelSubsystem { + /// Create a new approval voting subsystem with the given keystore, config, and database. + pub fn with_config( + config: Config, + db: Arc, + keystore: Arc, + sync_oracle: Box, + metrics: Metrics, + spawner: impl overseer::gen::Spawner + 'static + Clone, + overseer_message_channel_capacity_override: Option, + ) -> Self { + ApprovalVotingParallelSubsystem::with_config_and_clock( + config, + db, + keystore, + sync_oracle, + metrics, + Arc::new(SystemClock {}), + spawner, + overseer_message_channel_capacity_override, + ) + } + + /// Create a new approval voting subsystem with the given keystore, config, clock, and database. + pub fn with_config_and_clock( + config: Config, + db: Arc, + keystore: Arc, + sync_oracle: Box, + metrics: Metrics, + clock: Arc, + spawner: impl overseer::gen::Spawner + 'static, + overseer_message_channel_capacity_override: Option, + ) -> Self { + ApprovalVotingParallelSubsystem { + keystore, + slot_duration_millis: config.slot_duration_millis, + db, + db_config: DatabaseConfig { col_approval_data: config.col_approval_data }, + sync_oracle, + metrics, + spawner: Arc::new(spawner), + clock, + overseer_message_channel_capacity_override, + } + } + + /// The size of the channel used for the workers. + fn workers_channel_size(&self) -> usize { + self.overseer_message_channel_capacity_override + .unwrap_or(DEFAULT_WORKERS_CHANNEL_SIZE) + } +} + +#[overseer::subsystem(ApprovalVotingParallel, error = SubsystemError, prefix = self::overseer)] +impl ApprovalVotingParallelSubsystem { + fn start(self, ctx: Context) -> SpawnedSubsystem { + let future = run::(ctx, self) + .map_err(|e| SubsystemError::with_origin("approval-voting-parallel", e)) + .boxed(); + + SpawnedSubsystem { name: "approval-voting-parallel-subsystem", future } + } +} + +// It starts worker for the approval voting subsystem and the `APPROVAL_DISTRIBUTION_WORKER_COUNT` +// workers for the approval distribution subsystem. +// +// It returns handles that can be used to send messages to the workers. +#[overseer::contextbounds(ApprovalVotingParallel, prefix = self::overseer)] +async fn start_workers( + ctx: &mut Context, + subsystem: ApprovalVotingParallelSubsystem, + metrics_watcher: &mut MetricsWatcher, +) -> SubsystemResult<(ToWorker, Vec>)> +where +{ + gum::info!(target: LOG_TARGET, "Starting approval distribution workers"); + + // Build approval voting handles. + let (to_approval_voting_worker, approval_voting_work_provider) = build_worker_handles( + "approval-voting-parallel-db".into(), + subsystem.workers_channel_size(), + metrics_watcher, + prio_right, + ); + let mut to_approval_distribution_workers = Vec::new(); + let slot_duration_millis = subsystem.slot_duration_millis; + + for i in 0..APPROVAL_DISTRIBUTION_WORKER_COUNT { + let mut network_sender = ctx.sender().clone(); + let mut runtime_api_sender = ctx.sender().clone(); + let mut approval_distribution_to_approval_voting = to_approval_voting_worker.clone(); + + let approval_distr_instance = + polkadot_approval_distribution::ApprovalDistribution::new_with_clock( + subsystem.metrics.approval_distribution_metrics(), + subsystem.slot_duration_millis, + subsystem.clock.clone(), + Arc::new(RealAssignmentCriteria {}), + ); + let task_name = format!("approval-voting-parallel-{}", i); + let (to_approval_distribution_worker, mut approval_distribution_work_provider) = + build_worker_handles( + task_name.clone(), + subsystem.workers_channel_size(), + metrics_watcher, + prio_right, + ); + + metrics_watcher.watch(task_name.clone(), to_approval_distribution_worker.meter()); + + subsystem.spawner.spawn_blocking( + task_name.leak(), + Some("approval-voting-parallel"), + Box::pin(async move { + let mut state = + polkadot_approval_distribution::State::with_config(slot_duration_millis); + let mut rng = rand::rngs::StdRng::from_entropy(); + let mut session_info_provider = RuntimeInfo::new_with_config(RuntimeInfoConfig { + keystore: None, + session_cache_lru_size: DISPUTE_WINDOW.get(), + }); + + loop { + let message = match approval_distribution_work_provider.next().await { + Some(message) => message, + None => { + gum::info!( + target: LOG_TARGET, + "Approval distribution stream finished, most likely shutting down", + ); + break; + }, + }; + if approval_distr_instance + .handle_from_orchestra( + message, + &mut approval_distribution_to_approval_voting, + &mut network_sender, + &mut runtime_api_sender, + &mut state, + &mut rng, + &mut session_info_provider, + ) + .await + { + gum::info!( + target: LOG_TARGET, + "Approval distribution worker {}, exiting because of shutdown", i + ); + }; + } + }), + ); + to_approval_distribution_workers.push(to_approval_distribution_worker); + } + + gum::info!(target: LOG_TARGET, "Starting approval voting workers"); + + let sender = ctx.sender().clone(); + let to_approval_distribution = ApprovalVotingToApprovalDistribution(sender.clone()); + polkadot_node_core_approval_voting::start_approval_worker( + approval_voting_work_provider, + sender.clone(), + to_approval_distribution, + polkadot_node_core_approval_voting::Config { + slot_duration_millis: subsystem.slot_duration_millis, + col_approval_data: subsystem.db_config.col_approval_data, + }, + subsystem.db.clone(), + subsystem.keystore.clone(), + subsystem.sync_oracle, + subsystem.metrics.approval_voting_metrics(), + subsystem.spawner.clone(), + "approval-voting-parallel-db", + "approval-voting-parallel", + subsystem.clock.clone(), + ) + .await?; + + Ok((to_approval_voting_worker, to_approval_distribution_workers)) +} + +// The main run function of the approval parallel voting subsystem. +#[overseer::contextbounds(ApprovalVotingParallel, prefix = self::overseer)] +async fn run( + mut ctx: Context, + subsystem: ApprovalVotingParallelSubsystem, +) -> SubsystemResult<()> { + let mut metrics_watcher = MetricsWatcher::new(subsystem.metrics.clone()); + gum::info!( + target: LOG_TARGET, + "Starting workers" + ); + + let (to_approval_voting_worker, to_approval_distribution_workers) = + start_workers(&mut ctx, subsystem, &mut metrics_watcher).await?; + + gum::info!( + target: LOG_TARGET, + "Starting main subsystem loop" + ); + + run_main_loop(ctx, to_approval_voting_worker, to_approval_distribution_workers, metrics_watcher) + .await +} + +// Main loop of the subsystem, it shouldn't include any logic just dispatching of messages to +// the workers. +// +// It listens for messages from the overseer and dispatches them to the workers. +#[overseer::contextbounds(ApprovalVotingParallel, prefix = self::overseer)] +async fn run_main_loop( + mut ctx: Context, + mut to_approval_voting_worker: ToWorker, + mut to_approval_distribution_workers: Vec>, + metrics_watcher: MetricsWatcher, +) -> SubsystemResult<()> { + loop { + futures::select! { + next_msg = ctx.recv().fuse() => { + let next_msg = match next_msg { + Ok(msg) => msg, + Err(err) => { + gum::info!(target: LOG_TARGET, ?err, "Approval voting parallel subsystem received an error"); + return Err(err); + } + }; + + match next_msg { + FromOrchestra::Signal(msg) => { + if matches!(msg, OverseerSignal::ActiveLeaves(_)) { + metrics_watcher.collect_metrics(); + } + + for worker in to_approval_distribution_workers.iter_mut() { + worker + .send_signal(msg.clone()).await?; + } + + to_approval_voting_worker.send_signal(msg.clone()).await?; + if matches!(msg, OverseerSignal::Conclude) { + break; + } + }, + FromOrchestra::Communication { msg } => match msg { + // The message the approval voting subsystem would've handled. + ApprovalVotingParallelMessage::ApprovedAncestor(_, _,_) | + ApprovalVotingParallelMessage::GetApprovalSignaturesForCandidate(_, _) => { + to_approval_voting_worker.send_message( + msg.try_into().expect( + "Message is one of ApprovedAncestor, GetApprovalSignaturesForCandidate + and that can be safely converted to ApprovalVotingMessage; qed" + ) + ).await; + }, + // Now the message the approval distribution subsystem would've handled and need to + // be forwarded to the workers. + ApprovalVotingParallelMessage::NewBlocks(msg) => { + for worker in to_approval_distribution_workers.iter_mut() { + worker + .send_message( + ApprovalDistributionMessage::NewBlocks(msg.clone()), + ) + .await; + } + }, + ApprovalVotingParallelMessage::DistributeAssignment(assignment, claimed) => { + let worker = assigned_worker_for_validator(assignment.validator, &mut to_approval_distribution_workers); + worker + .send_message( + ApprovalDistributionMessage::DistributeAssignment(assignment, claimed) + ) + .await; + + }, + ApprovalVotingParallelMessage::DistributeApproval(vote) => { + let worker = assigned_worker_for_validator(vote.validator, &mut to_approval_distribution_workers); + worker + .send_message( + ApprovalDistributionMessage::DistributeApproval(vote) + ).await; + + }, + ApprovalVotingParallelMessage::NetworkBridgeUpdate(msg) => { + if let polkadot_node_subsystem::messages::NetworkBridgeEvent::PeerMessage( + peer_id, + msg, + ) = msg + { + let (all_msgs_from_same_validator, messages_split_by_validator) = validator_index_for_msg(msg); + + for (validator_index, msg) in all_msgs_from_same_validator.into_iter().chain(messages_split_by_validator.into_iter().flatten()) { + let worker = assigned_worker_for_validator(validator_index, &mut to_approval_distribution_workers); + + worker + .send_message( + ApprovalDistributionMessage::NetworkBridgeUpdate( + polkadot_node_subsystem::messages::NetworkBridgeEvent::PeerMessage( + peer_id, msg, + ), + ), + ).await; + } + } else { + for worker in to_approval_distribution_workers.iter_mut() { + worker + .send_message_with_priority::( + ApprovalDistributionMessage::NetworkBridgeUpdate(msg.clone()), + ).await; + } + } + }, + ApprovalVotingParallelMessage::GetApprovalSignatures(indices, tx) => { + handle_get_approval_signatures(&mut ctx, &mut to_approval_distribution_workers, indices, tx).await; + }, + ApprovalVotingParallelMessage::ApprovalCheckingLagUpdate(lag) => { + for worker in to_approval_distribution_workers.iter_mut() { + worker + .send_message( + ApprovalDistributionMessage::ApprovalCheckingLagUpdate(lag) + ).await; + } + }, + }, + }; + + }, + }; + } + Ok(()) +} + +// It sends a message to all approval workers to get the approval signatures for the requested +// candidates and then merges them all together and sends them back to the requester. +#[overseer::contextbounds(ApprovalVotingParallel, prefix = self::overseer)] +async fn handle_get_approval_signatures( + ctx: &mut Context, + to_approval_distribution_workers: &mut Vec>, + requested_candidates: HashSet<(Hash, CandidateIndex)>, + result_channel: oneshot::Sender< + HashMap, ValidatorSignature)>, + >, +) { + let mut sigs = HashMap::new(); + let mut signatures_channels = Vec::new(); + for worker in to_approval_distribution_workers.iter_mut() { + let (tx, rx) = oneshot::channel(); + worker.send_unbounded_message(ApprovalDistributionMessage::GetApprovalSignatures( + requested_candidates.clone(), + tx, + )); + signatures_channels.push(rx); + } + + let gather_signatures = async move { + let Some(results) = futures::future::join_all(signatures_channels) + .timeout(WAIT_FOR_SIGS_GATHER_TIMEOUT) + .await + else { + gum::warn!( + target: LOG_TARGET, + "Waiting for approval signatures timed out - dead lock?" + ); + return; + }; + + for result in results { + let worker_sigs = match result { + Ok(sigs) => sigs, + Err(_) => { + gum::error!( + target: LOG_TARGET, + "Getting approval signatures failed, oneshot got closed" + ); + continue; + }, + }; + sigs.extend(worker_sigs); + } + + if let Err(_) = result_channel.send(sigs) { + gum::debug!( + target: LOG_TARGET, + "Sending back approval signatures failed, oneshot got closed" + ); + } + }; + + if let Err(err) = ctx.spawn("approval-voting-gather-signatures", Box::pin(gather_signatures)) { + gum::warn!(target: LOG_TARGET, "Failed to spawn gather signatures task: {:?}", err); + } +} + +// Returns the worker that should receive the message for the given validator. +fn assigned_worker_for_validator( + validator: ValidatorIndex, + to_approval_distribution_workers: &mut Vec>, +) -> &mut ToWorker { + let worker_index = validator.0 as usize % to_approval_distribution_workers.len(); + to_approval_distribution_workers + .get_mut(worker_index) + .expect("Worker index is obtained modulo len; qed") +} + +// Returns the validators that initially created this assignments/votes, the validator index +// is later used to decide which approval-distribution worker should receive the message. +// +// Because this is on the hot path and we don't want to be unnecessarily slow, it contains two logic +// paths. The ultra fast path where all messages have the same validator index and we don't do +// any cloning or allocation and the path where we need to split the messages into multiple +// messages, because they have different validator indices, where we do need to clone and allocate. +// In practice most of the message will fall on the ultra fast path. +fn validator_index_for_msg( + msg: polkadot_node_network_protocol::ApprovalDistributionMessage, +) -> ( + Option<(ValidatorIndex, polkadot_node_network_protocol::ApprovalDistributionMessage)>, + Option>, +) { + match msg { + polkadot_node_network_protocol::Versioned::V1(ref message) => match message { + polkadot_node_network_protocol::v1::ApprovalDistributionMessage::Assignments(msgs) => + if let Ok(validator) = msgs.iter().map(|(msg, _)| msg.validator).all_equal_value() { + (Some((validator, msg)), None) + } else { + let split = msgs + .iter() + .map(|(msg, claimed_candidates)| { + ( + msg.validator, + polkadot_node_network_protocol::Versioned::V1( + polkadot_node_network_protocol::v1::ApprovalDistributionMessage::Assignments( + vec![(msg.clone(), *claimed_candidates)] + ), + ), + ) + }) + .collect_vec(); + (None, Some(split)) + }, + polkadot_node_network_protocol::v1::ApprovalDistributionMessage::Approvals(msgs) => + if let Ok(validator) = msgs.iter().map(|msg| msg.validator).all_equal_value() { + (Some((validator, msg)), None) + } else { + let split = msgs + .iter() + .map(|vote| { + ( + vote.validator, + polkadot_node_network_protocol::Versioned::V1( + polkadot_node_network_protocol::v1::ApprovalDistributionMessage::Approvals( + vec![vote.clone()] + ), + ), + ) + }) + .collect_vec(); + (None, Some(split)) + }, + }, + polkadot_node_network_protocol::Versioned::V2(ref message) => match message { + polkadot_node_network_protocol::v2::ApprovalDistributionMessage::Assignments(msgs) => + if let Ok(validator) = msgs.iter().map(|(msg, _)| msg.validator).all_equal_value() { + (Some((validator, msg)), None) + } else { + let split = msgs + .iter() + .map(|(msg, claimed_candidates)| { + ( + msg.validator, + polkadot_node_network_protocol::Versioned::V2( + polkadot_node_network_protocol::v2::ApprovalDistributionMessage::Assignments( + vec![(msg.clone(), *claimed_candidates)] + ), + ), + ) + }) + .collect_vec(); + (None, Some(split)) + }, + + polkadot_node_network_protocol::v2::ApprovalDistributionMessage::Approvals(msgs) => + if let Ok(validator) = msgs.iter().map(|msg| msg.validator).all_equal_value() { + (Some((validator, msg)), None) + } else { + let split = msgs + .iter() + .map(|vote| { + ( + vote.validator, + polkadot_node_network_protocol::Versioned::V2( + polkadot_node_network_protocol::v2::ApprovalDistributionMessage::Approvals( + vec![vote.clone()] + ), + ), + ) + }) + .collect_vec(); + (None, Some(split)) + }, + }, + polkadot_node_network_protocol::Versioned::V3(ref message) => match message { + polkadot_node_network_protocol::v3::ApprovalDistributionMessage::Assignments(msgs) => + if let Ok(validator) = msgs.iter().map(|(msg, _)| msg.validator).all_equal_value() { + (Some((validator, msg)), None) + } else { + let split = msgs + .iter() + .map(|(msg, claimed_candidates)| { + ( + msg.validator, + polkadot_node_network_protocol::Versioned::V3( + polkadot_node_network_protocol::v3::ApprovalDistributionMessage::Assignments( + vec![(msg.clone(), claimed_candidates.clone())] + ), + ), + ) + }) + .collect_vec(); + (None, Some(split)) + }, + polkadot_node_network_protocol::v3::ApprovalDistributionMessage::Approvals(msgs) => + if let Ok(validator) = msgs.iter().map(|msg| msg.validator).all_equal_value() { + (Some((validator, msg)), None) + } else { + let split = msgs + .iter() + .map(|vote| { + ( + vote.validator, + polkadot_node_network_protocol::Versioned::V3( + polkadot_node_network_protocol::v3::ApprovalDistributionMessage::Approvals( + vec![vote.clone()] + ), + ), + ) + }) + .collect_vec(); + (None, Some(split)) + }, + }, + } +} + +/// A handler object that both type of workers use for receiving work. +/// +/// In practive this is just a wrapper over two channels Receiver, that is injected into +/// approval-voting worker and approval-distribution workers. +type WorkProvider = WorkProviderImpl< + SelectWithStrategy< + MeteredReceiver>, + UnboundedMeteredReceiver>, + Clos, + State, + >, +>; + +pub struct WorkProviderImpl(T); + +impl Stream for WorkProviderImpl +where + T: Stream> + Unpin + Send, +{ + type Item = FromOrchestra; + + fn poll_next( + mut self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + self.0.poll_next_unpin(cx) + } +} + +#[async_trait::async_trait] +impl ApprovalVotingWorkProvider for WorkProviderImpl +where + T: Stream> + Unpin + Send, +{ + async fn recv(&mut self) -> SubsystemResult> { + self.0.next().await.ok_or(SubsystemError::Context( + "ApprovalVotingWorkProviderImpl: Channel closed".to_string(), + )) + } +} + +impl WorkProvider +where + M: Send + Sync + 'static, + Clos: FnMut(&mut State) -> PollNext, + State: Default, +{ + // Constructs a work providers from the channels handles. + fn from_rx_worker(rx: RxWorker, prio: Clos) -> Self { + let prioritised = select_with_strategy(rx.0, rx.1, prio); + WorkProviderImpl(prioritised) + } +} + +/// Just a wrapper for implementing `overseer::SubsystemSender` and +/// `overseer::SubsystemSender`. +/// +/// The instance of this struct can be injected into the workers, so they can talk +/// directly with each other without intermediating in this subsystem loop. +pub struct ToWorker( + MeteredSender>, + UnboundedMeteredSender>, +); + +impl Clone for ToWorker { + fn clone(&self) -> Self { + Self(self.0.clone(), self.1.clone()) + } +} + +impl ToWorker { + async fn send_signal(&mut self, signal: OverseerSignal) -> Result<(), SubsystemError> { + self.1 + .unbounded_send(FromOrchestra::Signal(signal)) + .map_err(|err| SubsystemError::QueueError(err.into_send_error())) + } + + fn meter(&self) -> Meters { + Meters::new(self.0.meter(), self.1.meter()) + } +} + +impl overseer::SubsystemSender for ToWorker { + fn send_message<'life0, 'async_trait>( + &'life0 mut self, + msg: T, + ) -> ::core::pin::Pin< + Box + ::core::marker::Send + 'async_trait>, + > + where + 'life0: 'async_trait, + Self: 'async_trait, + { + async { + if let Err(err) = + self.0.send(polkadot_overseer::FromOrchestra::Communication { msg }).await + { + gum::error!( + target: LOG_TARGET, + "Failed to send message to approval voting worker: {:?}, subsystem is probably shutting down.", + err + ); + } + } + .boxed() + } + + fn try_send_message(&mut self, msg: T) -> Result<(), metered::TrySendError> { + self.0 + .try_send(polkadot_overseer::FromOrchestra::Communication { msg }) + .map_err(|result| { + let is_full = result.is_full(); + let msg = match result.into_inner() { + polkadot_overseer::FromOrchestra::Signal(_) => + panic!("Cannot happen variant is never built"), + polkadot_overseer::FromOrchestra::Communication { msg } => msg, + }; + if is_full { + metered::TrySendError::Full(msg) + } else { + metered::TrySendError::Closed(msg) + } + }) + } + + fn send_messages<'life0, 'async_trait, I>( + &'life0 mut self, + msgs: I, + ) -> ::core::pin::Pin< + Box + ::core::marker::Send + 'async_trait>, + > + where + I: IntoIterator + Send, + I::IntoIter: Send, + I: 'async_trait, + 'life0: 'async_trait, + Self: 'async_trait, + { + async { + for msg in msgs { + self.send_message(msg).await; + } + } + .boxed() + } + + fn send_unbounded_message(&mut self, msg: T) { + if let Err(err) = + self.1.unbounded_send(polkadot_overseer::FromOrchestra::Communication { msg }) + { + gum::error!( + target: LOG_TARGET, + "Failed to send unbounded message to approval voting worker: {:?}, subsystem is probably shutting down.", + err + ); + } + } + + fn send_message_with_priority<'life0, 'async_trait, P>( + &'life0 mut self, + msg: T, + ) -> ::core::pin::Pin< + Box + ::core::marker::Send + 'async_trait>, + > + where + P: 'async_trait + Priority, + 'life0: 'async_trait, + Self: 'async_trait, + { + match P::priority() { + polkadot_overseer::PriorityLevel::Normal => self.send_message(msg), + polkadot_overseer::PriorityLevel::High => + async { self.send_unbounded_message(msg) }.boxed(), + } + } + + fn try_send_message_with_priority( + &mut self, + msg: T, + ) -> Result<(), metered::TrySendError> { + match P::priority() { + polkadot_overseer::PriorityLevel::Normal => self.try_send_message(msg), + polkadot_overseer::PriorityLevel::High => Ok(self.send_unbounded_message(msg)), + } + } +} + +/// Handles that are used by an worker to receive work. +pub struct RxWorker( + MeteredReceiver>, + UnboundedMeteredReceiver>, +); + +// Build all the necessary channels for sending messages to an worker +// and for the worker to receive them. +fn build_channels( + channel_name: String, + channel_size: usize, + metrics_watcher: &mut MetricsWatcher, +) -> (ToWorker, RxWorker) { + let (tx_work, rx_work) = channel::>(channel_size); + let (tx_work_unbounded, rx_work_unbounded) = unbounded::>(); + let to_worker = ToWorker(tx_work, tx_work_unbounded); + + metrics_watcher.watch(channel_name, to_worker.meter()); + + (to_worker, RxWorker(rx_work, rx_work_unbounded)) +} + +/// Build the worker handles used for interacting with the workers. +/// +/// `ToWorker` is used for sending messages to the workers. +/// `WorkProvider` is used by the workers for receiving the messages. +fn build_worker_handles( + channel_name: String, + channel_size: usize, + metrics_watcher: &mut MetricsWatcher, + prio_right: Clos, +) -> (ToWorker, WorkProvider) +where + M: Send + Sync + 'static, + Clos: FnMut(&mut State) -> PollNext, + State: Default, +{ + let (to_worker, rx_worker) = build_channels(channel_name, channel_size, metrics_watcher); + (to_worker, WorkProviderImpl::from_rx_worker(rx_worker, prio_right)) +} + +/// Just a wrapper for implementing `overseer::SubsystemSender`, so +/// that we can inject into the approval voting subsystem. +#[derive(Clone)] +pub struct ApprovalVotingToApprovalDistribution>( + S, +); + +impl> + overseer::SubsystemSender for ApprovalVotingToApprovalDistribution +{ + #[allow(clippy::type_complexity, clippy::type_repetition_in_bounds)] + fn send_message<'life0, 'async_trait>( + &'life0 mut self, + msg: ApprovalDistributionMessage, + ) -> ::core::pin::Pin< + Box + ::core::marker::Send + 'async_trait>, + > + where + 'life0: 'async_trait, + Self: 'async_trait, + { + self.0.send_message(msg.into()) + } + + fn try_send_message( + &mut self, + msg: ApprovalDistributionMessage, + ) -> Result<(), metered::TrySendError> { + self.0.try_send_message(msg.into()).map_err(|err| match err { + // Safe to unwrap because it was built from the same type. + metered::TrySendError::Closed(msg) => + metered::TrySendError::Closed(msg.try_into().unwrap()), + metered::TrySendError::Full(msg) => + metered::TrySendError::Full(msg.try_into().unwrap()), + }) + } + + #[allow(clippy::type_complexity, clippy::type_repetition_in_bounds)] + fn send_messages<'life0, 'async_trait, I>( + &'life0 mut self, + msgs: I, + ) -> ::core::pin::Pin< + Box + ::core::marker::Send + 'async_trait>, + > + where + I: IntoIterator + Send, + I::IntoIter: Send, + I: 'async_trait, + 'life0: 'async_trait, + Self: 'async_trait, + { + self.0.send_messages(msgs.into_iter().map(|msg| msg.into())) + } + + fn send_unbounded_message(&mut self, msg: ApprovalDistributionMessage) { + self.0.send_unbounded_message(msg.into()) + } + + fn send_message_with_priority<'life0, 'async_trait, P>( + &'life0 mut self, + msg: ApprovalDistributionMessage, + ) -> ::core::pin::Pin< + Box + ::core::marker::Send + 'async_trait>, + > + where + P: 'async_trait + Priority, + 'life0: 'async_trait, + Self: 'async_trait, + { + self.0.send_message_with_priority::

(msg.into()) + } + + fn try_send_message_with_priority( + &mut self, + msg: ApprovalDistributionMessage, + ) -> Result<(), metered::TrySendError> { + self.0.try_send_message_with_priority::

(msg.into()).map_err(|err| match err { + // Safe to unwrap because it was built from the same type. + metered::TrySendError::Closed(msg) => + metered::TrySendError::Closed(msg.try_into().unwrap()), + metered::TrySendError::Full(msg) => + metered::TrySendError::Full(msg.try_into().unwrap()), + }) + } +} diff --git a/polkadot/node/core/approval-voting-parallel/src/metrics.rs b/polkadot/node/core/approval-voting-parallel/src/metrics.rs new file mode 100644 index 00000000000..1b4ab4bd9b8 --- /dev/null +++ b/polkadot/node/core/approval-voting-parallel/src/metrics.rs @@ -0,0 +1,236 @@ +// Copyright (C) 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 . + +//! The Metrics for Approval Voting Parallel Subsystem. + +use std::collections::HashMap; + +use polkadot_node_metrics::{metered::Meter, metrics}; +use polkadot_overseer::prometheus; + +#[derive(Default, Clone)] +pub struct Metrics(Option); + +/// Approval Voting parallel metrics. +#[derive(Clone)] +pub struct MetricsInner { + // The inner metrics of the approval distribution workers. + approval_distribution: polkadot_approval_distribution::metrics::Metrics, + // The inner metrics of the approval voting workers. + approval_voting: polkadot_node_core_approval_voting::Metrics, + + // Time of flight metrics for bounded channels. + to_worker_bounded_tof: prometheus::HistogramVec, + // Number of elements sent to the worker's bounded queue. + to_worker_bounded_sent: prometheus::GaugeVec, + // Number of elements received by the worker's bounded queue. + to_worker_bounded_received: prometheus::GaugeVec, + // Number of times senders blocked while sending messages to the worker. + to_worker_bounded_blocked: prometheus::GaugeVec, + // Time of flight metrics for unbounded channels. + to_worker_unbounded_tof: prometheus::HistogramVec, + // Number of elements sent to the worker's unbounded queue. + to_worker_unbounded_sent: prometheus::GaugeVec, + // Number of elements received by the worker's unbounded queue. + to_worker_unbounded_received: prometheus::GaugeVec, +} + +impl Metrics { + /// Get the approval distribution metrics. + pub fn approval_distribution_metrics( + &self, + ) -> polkadot_approval_distribution::metrics::Metrics { + self.0 + .as_ref() + .map(|metrics_inner| metrics_inner.approval_distribution.clone()) + .unwrap_or_default() + } + + /// Get the approval voting metrics. + pub fn approval_voting_metrics(&self) -> polkadot_node_core_approval_voting::Metrics { + self.0 + .as_ref() + .map(|metrics_inner| metrics_inner.approval_voting.clone()) + .unwrap_or_default() + } +} + +impl metrics::Metrics for Metrics { + /// Try to register the metrics. + fn try_register( + registry: &prometheus::Registry, + ) -> std::result::Result { + Ok(Metrics(Some(MetricsInner { + approval_distribution: polkadot_approval_distribution::metrics::Metrics::try_register( + registry, + )?, + approval_voting: polkadot_node_core_approval_voting::Metrics::try_register(registry)?, + to_worker_bounded_tof: prometheus::register( + prometheus::HistogramVec::new( + prometheus::HistogramOpts::new( + "polkadot_approval_voting_parallel_worker_bounded_tof", + "Duration spent in a particular approval voting worker channel from entrance to removal", + ) + .buckets(vec![ + 0.0001, 0.0004, 0.0016, 0.0064, 0.0256, 0.1024, 0.4096, 1.6384, 3.2768, + 4.9152, 6.5536, + ]), + &["worker_name"], + )?, + registry, + )?, + to_worker_bounded_sent: prometheus::register( + prometheus::GaugeVec::::new( + prometheus::Opts::new( + "polkadot_approval_voting_parallel_worker_bounded_sent", + "Number of elements sent to approval voting workers' bounded queues", + ), + &["worker_name"], + )?, + registry, + )?, + to_worker_bounded_received: prometheus::register( + prometheus::GaugeVec::::new( + prometheus::Opts::new( + "polkadot_approval_voting_parallel_worker_bounded_received", + "Number of elements received by approval voting workers' bounded queues", + ), + &["worker_name"], + )?, + registry, + )?, + to_worker_bounded_blocked: prometheus::register( + prometheus::GaugeVec::::new( + prometheus::Opts::new( + "polkadot_approval_voting_parallel_worker_bounded_blocked", + "Number of times approval voting workers blocked while sending messages to a subsystem", + ), + &["worker_name"], + )?, + registry, + )?, + to_worker_unbounded_tof: prometheus::register( + prometheus::HistogramVec::new( + prometheus::HistogramOpts::new( + "polkadot_approval_voting_parallel_worker_unbounded_tof", + "Duration spent in a particular approval voting worker channel from entrance to removal", + ) + .buckets(vec![ + 0.0001, 0.0004, 0.0016, 0.0064, 0.0256, 0.1024, 0.4096, 1.6384, 3.2768, + 4.9152, 6.5536, + ]), + &["worker_name"], + )?, + registry, + )?, + to_worker_unbounded_sent: prometheus::register( + prometheus::GaugeVec::::new( + prometheus::Opts::new( + "polkadot_approval_voting_parallel_worker_unbounded_sent", + "Number of elements sent to approval voting workers' unbounded queues", + ), + &["worker_name"], + )?, + registry, + )?, + to_worker_unbounded_received: prometheus::register( + prometheus::GaugeVec::::new( + prometheus::Opts::new( + "polkadot_approval_voting_parallel_worker_unbounded_received", + "Number of elements received by approval voting workers' unbounded queues", + ), + &["worker_name"], + )?, + registry, + )?, + }))) + } +} + +/// The meters to watch. +#[derive(Clone)] +pub struct Meters { + bounded: Meter, + unbounded: Meter, +} + +impl Meters { + pub fn new(bounded: &Meter, unbounded: &Meter) -> Self { + Self { bounded: bounded.clone(), unbounded: unbounded.clone() } + } +} + +/// A metrics watcher that watches the meters and updates the metrics. +pub struct MetricsWatcher { + to_watch: HashMap, + metrics: Metrics, +} + +impl MetricsWatcher { + /// Create a new metrics watcher. + pub fn new(metrics: Metrics) -> Self { + Self { to_watch: HashMap::new(), metrics } + } + + /// Watch the meters of a worker with this name. + pub fn watch(&mut self, worker_name: String, meters: Meters) { + self.to_watch.insert(worker_name, meters); + } + + /// Collect all the metrics. + pub fn collect_metrics(&self) { + for (name, meter) in &self.to_watch { + let bounded_readouts = meter.bounded.read(); + let unbounded_readouts = meter.unbounded.read(); + if let Some(metrics) = self.metrics.0.as_ref() { + metrics + .to_worker_bounded_sent + .with_label_values(&[name]) + .set(bounded_readouts.sent as u64); + + metrics + .to_worker_bounded_received + .with_label_values(&[name]) + .set(bounded_readouts.received as u64); + + metrics + .to_worker_bounded_blocked + .with_label_values(&[name]) + .set(bounded_readouts.blocked as u64); + + metrics + .to_worker_unbounded_sent + .with_label_values(&[name]) + .set(unbounded_readouts.sent as u64); + + metrics + .to_worker_unbounded_received + .with_label_values(&[name]) + .set(unbounded_readouts.received as u64); + + let hist_bounded = metrics.to_worker_bounded_tof.with_label_values(&[name]); + for tof in bounded_readouts.tof { + hist_bounded.observe(tof.as_f64()); + } + + let hist_unbounded = metrics.to_worker_unbounded_tof.with_label_values(&[name]); + for tof in unbounded_readouts.tof { + hist_unbounded.observe(tof.as_f64()); + } + } + } + } +} diff --git a/polkadot/node/core/approval-voting-parallel/src/tests.rs b/polkadot/node/core/approval-voting-parallel/src/tests.rs new file mode 100644 index 00000000000..215a707147f --- /dev/null +++ b/polkadot/node/core/approval-voting-parallel/src/tests.rs @@ -0,0 +1,1178 @@ +// Copyright (C) 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 . + +//! The tests for Approval Voting Parallel Subsystem. + +use std::{ + collections::{HashMap, HashSet}, + future::Future, + sync::Arc, + time::Duration, +}; + +use crate::{ + build_worker_handles, metrics::MetricsWatcher, prio_right, run_main_loop, start_workers, + validator_index_for_msg, ApprovalVotingParallelSubsystem, Metrics, WorkProvider, +}; +use assert_matches::assert_matches; +use futures::{channel::oneshot, future, stream::PollNext, StreamExt}; +use itertools::Itertools; +use polkadot_node_core_approval_voting::{ApprovalVotingWorkProvider, Config}; +use polkadot_node_network_protocol::{peer_set::ValidationVersion, ObservedRole, PeerId, View}; +use polkadot_node_primitives::approval::{ + time::SystemClock, + v1::{ + AssignmentCert, AssignmentCertKind, IndirectAssignmentCert, IndirectSignedApprovalVote, + RELAY_VRF_MODULO_CONTEXT, + }, + v2::{ + AssignmentCertKindV2, AssignmentCertV2, CoreBitfield, IndirectAssignmentCertV2, + IndirectSignedApprovalVoteV2, + }, +}; +use polkadot_node_subsystem::{ + messages::{ApprovalDistributionMessage, ApprovalVotingMessage, ApprovalVotingParallelMessage}, + FromOrchestra, +}; +use polkadot_node_subsystem_test_helpers::{mock::new_leaf, TestSubsystemContext}; +use polkadot_overseer::{ActiveLeavesUpdate, OverseerSignal, SpawnGlue, TimeoutExt}; +use polkadot_primitives::{CandidateHash, CoreIndex, Hash, ValidatorIndex}; +use sc_keystore::{Keystore, LocalKeystore}; +use sp_consensus::SyncOracle; +use sp_consensus_babe::{VrfPreOutput, VrfProof, VrfSignature}; +use sp_core::{testing::TaskExecutor, H256}; +use sp_keyring::Sr25519Keyring; +type VirtualOverseer = + polkadot_node_subsystem_test_helpers::TestSubsystemContextHandle; + +const SLOT_DURATION_MILLIS: u64 = 6000; + +pub mod test_constants { + pub(crate) const DATA_COL: u32 = 0; + pub(crate) const NUM_COLUMNS: u32 = 1; +} + +fn fake_assignment_cert(block_hash: Hash, validator: ValidatorIndex) -> IndirectAssignmentCert { + let ctx = schnorrkel::signing_context(RELAY_VRF_MODULO_CONTEXT); + let msg = b"WhenParachains?"; + let mut prng = rand_core::OsRng; + let keypair = schnorrkel::Keypair::generate_with(&mut prng); + let (inout, proof, _) = keypair.vrf_sign(ctx.bytes(msg)); + let preout = inout.to_preout(); + + IndirectAssignmentCert { + block_hash, + validator, + cert: AssignmentCert { + kind: AssignmentCertKind::RelayVRFModulo { sample: 1 }, + vrf: VrfSignature { pre_output: VrfPreOutput(preout), proof: VrfProof(proof) }, + }, + } +} + +fn fake_assignment_cert_v2( + block_hash: Hash, + validator: ValidatorIndex, + core_bitfield: CoreBitfield, +) -> IndirectAssignmentCertV2 { + let ctx = schnorrkel::signing_context(RELAY_VRF_MODULO_CONTEXT); + let msg = b"WhenParachains?"; + let mut prng = rand_core::OsRng; + let keypair = schnorrkel::Keypair::generate_with(&mut prng); + let (inout, proof, _) = keypair.vrf_sign(ctx.bytes(msg)); + let preout = inout.to_preout(); + + IndirectAssignmentCertV2 { + block_hash, + validator, + cert: AssignmentCertV2 { + kind: AssignmentCertKindV2::RelayVRFModuloCompact { core_bitfield }, + vrf: VrfSignature { pre_output: VrfPreOutput(preout), proof: VrfProof(proof) }, + }, + } +} + +/// Creates a meaningless signature +pub fn dummy_signature() -> polkadot_primitives::ValidatorSignature { + sp_core::crypto::UncheckedFrom::unchecked_from([1u8; 64]) +} + +fn build_subsystem( + sync_oracle: Box, +) -> ( + ApprovalVotingParallelSubsystem, + TestSubsystemContext>, + VirtualOverseer, +) { + sp_tracing::init_for_tests(); + + let pool = sp_core::testing::TaskExecutor::new(); + let (context, virtual_overseer) = polkadot_node_subsystem_test_helpers::make_subsystem_context::< + ApprovalVotingParallelMessage, + _, + >(pool.clone()); + + let keystore = LocalKeystore::in_memory(); + let _ = keystore.sr25519_generate_new( + polkadot_primitives::PARACHAIN_KEY_TYPE_ID, + Some(&Sr25519Keyring::Alice.to_seed()), + ); + + let clock = Arc::new(SystemClock {}); + let db = kvdb_memorydb::create(test_constants::NUM_COLUMNS); + let db = polkadot_node_subsystem_util::database::kvdb_impl::DbAdapter::new(db, &[]); + + ( + ApprovalVotingParallelSubsystem::with_config_and_clock( + Config { + col_approval_data: test_constants::DATA_COL, + slot_duration_millis: SLOT_DURATION_MILLIS, + }, + Arc::new(db), + Arc::new(keystore), + sync_oracle, + Metrics::default(), + clock.clone(), + SpawnGlue(pool), + None, + ), + context, + virtual_overseer, + ) +} + +#[derive(Clone)] +struct TestSyncOracle {} + +impl SyncOracle for TestSyncOracle { + fn is_major_syncing(&self) -> bool { + false + } + + fn is_offline(&self) -> bool { + unimplemented!("not used in network bridge") + } +} + +fn test_harness( + num_approval_distro_workers: usize, + prio_right: Clos, + subsystem_gracefully_exits: bool, + test_fn: impl FnOnce( + VirtualOverseer, + WorkProvider, + Vec>, + ) -> T, +) where + T: Future, + Clos: Clone + FnMut(&mut State) -> PollNext, + State: Default, +{ + let (subsystem, context, virtual_overseer) = build_subsystem(Box::new(TestSyncOracle {})); + let mut metrics_watcher = MetricsWatcher::new(subsystem.metrics.clone()); + let channel_size = 5; + + let (to_approval_voting_worker, approval_voting_work_provider) = + build_worker_handles::( + "to_approval_voting_worker".into(), + channel_size, + &mut metrics_watcher, + prio_right.clone(), + ); + + let approval_distribution_channels = { 0..num_approval_distro_workers } + .into_iter() + .map(|worker_index| { + build_worker_handles::( + format!("to_approval_distro/{}", worker_index), + channel_size, + &mut metrics_watcher, + prio_right.clone(), + ) + }) + .collect_vec(); + + let to_approval_distribution_workers = + approval_distribution_channels.iter().map(|(tx, _)| tx.clone()).collect_vec(); + let approval_distribution_work_providers = + approval_distribution_channels.into_iter().map(|(_, rx)| rx).collect_vec(); + + let subsystem = async move { + let result = run_main_loop( + context, + to_approval_voting_worker, + to_approval_distribution_workers, + metrics_watcher, + ) + .await; + + if subsystem_gracefully_exits && result.is_err() { + result + } else { + Ok(()) + } + }; + + let test_fut = test_fn( + virtual_overseer, + approval_voting_work_provider, + approval_distribution_work_providers, + ); + + futures::pin_mut!(test_fut); + futures::pin_mut!(subsystem); + + futures::executor::block_on(future::join( + async move { + let _overseer = test_fut.await; + }, + subsystem, + )) + .1 + .unwrap(); +} + +const TIMEOUT: Duration = Duration::from_millis(2000); + +async fn overseer_signal(overseer: &mut VirtualOverseer, signal: OverseerSignal) { + overseer + .send(FromOrchestra::Signal(signal)) + .timeout(TIMEOUT) + .await + .expect(&format!("{:?} is more than enough for sending signals.", TIMEOUT)); +} + +async fn overseer_message(overseer: &mut VirtualOverseer, msg: ApprovalVotingParallelMessage) { + overseer + .send(FromOrchestra::Communication { msg }) + .timeout(TIMEOUT) + .await + .expect(&format!("{:?} is more than enough for sending signals.", TIMEOUT)); +} + +async fn run_start_workers() { + let (subsystem, mut context, _) = build_subsystem(Box::new(TestSyncOracle {})); + let mut metrics_watcher = MetricsWatcher::new(subsystem.metrics.clone()); + let _workers = start_workers(&mut context, subsystem, &mut metrics_watcher).await.unwrap(); +} + +// Test starting the workers succeeds. +#[test] +fn start_workers_succeeds() { + futures::executor::block_on(run_start_workers()); +} + +// Test main loop forwards messages to the correct worker for all type of messages. +#[test] +fn test_main_loop_forwards_correctly() { + let num_approval_distro_workers = 4; + test_harness( + num_approval_distro_workers, + prio_right, + true, + |mut overseer, mut approval_voting_work_provider, mut rx_approval_distribution_workers| async move { + // 1. Check Signals are correctly forwarded to the workers. + let signal = OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::start_work(new_leaf( + Hash::random(), + 1, + ))); + overseer_signal(&mut overseer, signal.clone()).await; + let approval_voting_receives = approval_voting_work_provider.recv().await.unwrap(); + assert_matches!(approval_voting_receives, FromOrchestra::Signal(_)); + for rx_approval_distribution_worker in rx_approval_distribution_workers.iter_mut() { + let approval_distribution_receives = + rx_approval_distribution_worker.next().await.unwrap(); + assert_matches!(approval_distribution_receives, FromOrchestra::Signal(_)); + } + + let (test_tx, _rx) = oneshot::channel(); + let test_hash = Hash::random(); + let test_block_nr = 2; + overseer_message( + &mut overseer, + ApprovalVotingParallelMessage::ApprovedAncestor(test_hash, test_block_nr, test_tx), + ) + .await; + assert_matches!( + approval_voting_work_provider.recv().await.unwrap(), + FromOrchestra::Communication { + msg: ApprovalVotingMessage::ApprovedAncestor(hash, block_nr, _) + } => { + assert_eq!(hash, test_hash); + assert_eq!(block_nr, test_block_nr); + } + ); + for rx_approval_distribution_worker in rx_approval_distribution_workers.iter_mut() { + assert!(rx_approval_distribution_worker + .next() + .timeout(Duration::from_millis(200)) + .await + .is_none()); + } + + // 2. Check GetApprovalSignaturesForCandidate is correctly forwarded to the workers. + let (test_tx, _rx) = oneshot::channel(); + let test_hash = CandidateHash(Hash::random()); + overseer_message( + &mut overseer, + ApprovalVotingParallelMessage::GetApprovalSignaturesForCandidate( + test_hash, test_tx, + ), + ) + .await; + + assert_matches!( + approval_voting_work_provider.recv().await.unwrap(), + FromOrchestra::Communication { + msg: ApprovalVotingMessage::GetApprovalSignaturesForCandidate(hash, _) + } => { + assert_eq!(hash, test_hash); + } + ); + + for rx_approval_distribution_worker in rx_approval_distribution_workers.iter_mut() { + assert!(rx_approval_distribution_worker + .next() + .timeout(Duration::from_millis(200)) + .await + .is_none()); + } + + // 3. Check NewBlocks is correctly forwarded to the workers. + overseer_message(&mut overseer, ApprovalVotingParallelMessage::NewBlocks(vec![])).await; + for rx_approval_distribution_worker in rx_approval_distribution_workers.iter_mut() { + assert_matches!(rx_approval_distribution_worker.next().await.unwrap(), + FromOrchestra::Communication { + msg: ApprovalDistributionMessage::NewBlocks(blocks) + } => { + assert!(blocks.is_empty()); + } + ); + } + assert!(approval_voting_work_provider + .recv() + .timeout(Duration::from_millis(200)) + .await + .is_none()); + + // 4. Check DistributeAssignment is correctly forwarded to the workers. + let validator_index = ValidatorIndex(17); + let assignment = + fake_assignment_cert_v2(Hash::random(), validator_index, CoreIndex(1).into()); + overseer_message( + &mut overseer, + ApprovalVotingParallelMessage::DistributeAssignment(assignment.clone(), 1.into()), + ) + .await; + + for (index, rx_approval_distribution_worker) in + rx_approval_distribution_workers.iter_mut().enumerate() + { + if index == validator_index.0 as usize % num_approval_distro_workers { + assert_matches!(rx_approval_distribution_worker.next().await.unwrap(), + FromOrchestra::Communication { + msg: ApprovalDistributionMessage::DistributeAssignment(cert, bitfield) + } => { + assert_eq!(cert, assignment); + assert_eq!(bitfield, 1.into()); + } + ); + } else { + assert!(rx_approval_distribution_worker + .next() + .timeout(Duration::from_millis(200)) + .await + .is_none()); + } + } + assert!(approval_voting_work_provider + .recv() + .timeout(Duration::from_millis(200)) + .await + .is_none()); + + // 5. Check DistributeApproval is correctly forwarded to the workers. + let validator_index = ValidatorIndex(26); + let expected_vote = IndirectSignedApprovalVoteV2 { + block_hash: H256::random(), + candidate_indices: 1.into(), + validator: validator_index, + signature: dummy_signature(), + }; + + overseer_message( + &mut overseer, + ApprovalVotingParallelMessage::DistributeApproval(expected_vote.clone()), + ) + .await; + + for (index, rx_approval_distribution_worker) in + rx_approval_distribution_workers.iter_mut().enumerate() + { + if index == validator_index.0 as usize % num_approval_distro_workers { + assert_matches!(rx_approval_distribution_worker.next().await.unwrap(), + FromOrchestra::Communication { + msg: ApprovalDistributionMessage::DistributeApproval(vote) + } => { + assert_eq!(vote, expected_vote); + } + ); + } else { + assert!(rx_approval_distribution_worker + .next() + .timeout(Duration::from_millis(200)) + .await + .is_none()); + } + } + + // 6. Check NetworkBridgeUpdate::PeerMessage is correctly forwarded just to one of the + // workers. + let approvals = vec![ + IndirectSignedApprovalVoteV2 { + block_hash: H256::random(), + candidate_indices: 1.into(), + validator: validator_index, + signature: dummy_signature(), + }, + IndirectSignedApprovalVoteV2 { + block_hash: H256::random(), + candidate_indices: 2.into(), + validator: validator_index, + signature: dummy_signature(), + }, + ]; + let expected_msg = polkadot_node_network_protocol::Versioned::V3( + polkadot_node_network_protocol::v3::ApprovalDistributionMessage::Approvals( + approvals.clone(), + ), + ); + overseer_message( + &mut overseer, + ApprovalVotingParallelMessage::NetworkBridgeUpdate( + polkadot_node_subsystem::messages::NetworkBridgeEvent::PeerMessage( + PeerId::random(), + expected_msg.clone(), + ), + ), + ) + .await; + + for (index, rx_approval_distribution_worker) in + rx_approval_distribution_workers.iter_mut().enumerate() + { + if index == validator_index.0 as usize % num_approval_distro_workers { + assert_matches!(rx_approval_distribution_worker.next().await.unwrap(), + FromOrchestra::Communication { + msg: ApprovalDistributionMessage::NetworkBridgeUpdate( + polkadot_node_subsystem::messages::NetworkBridgeEvent::PeerMessage( + _, + msg, + ), + ) + } => { + assert_eq!(msg, expected_msg); + } + ); + } else { + assert!(rx_approval_distribution_worker + .next() + .timeout(Duration::from_millis(200)) + .await + .is_none()); + } + } + assert!(approval_voting_work_provider + .recv() + .timeout(Duration::from_millis(200)) + .await + .is_none()); + + assert!(approval_voting_work_provider + .recv() + .timeout(Duration::from_millis(200)) + .await + .is_none()); + + // 7. Check NetworkBridgeUpdate::PeerConnected is correctly forwarded to all workers. + let expected_peer_id = PeerId::random(); + overseer_message( + &mut overseer, + ApprovalVotingParallelMessage::NetworkBridgeUpdate( + polkadot_node_subsystem::messages::NetworkBridgeEvent::PeerConnected( + expected_peer_id, + ObservedRole::Authority, + ValidationVersion::V3.into(), + None, + ), + ), + ) + .await; + + for rx_approval_distribution_worker in rx_approval_distribution_workers.iter_mut() { + assert_matches!(rx_approval_distribution_worker.next().await.unwrap(), + FromOrchestra::Communication { + msg: ApprovalDistributionMessage::NetworkBridgeUpdate( + polkadot_node_subsystem::messages::NetworkBridgeEvent::PeerConnected( + peer_id, + role, + version, + authority_id, + ), + ) + } => { + assert_eq!(peer_id, expected_peer_id); + assert_eq!(role, ObservedRole::Authority); + assert_eq!(version, ValidationVersion::V3.into()); + assert_eq!(authority_id, None); + } + ); + } + assert!(approval_voting_work_provider + .recv() + .timeout(Duration::from_millis(200)) + .await + .is_none()); + + // 8. Check ApprovalCheckingLagUpdate is correctly forwarded to all workers. + overseer_message( + &mut overseer, + ApprovalVotingParallelMessage::ApprovalCheckingLagUpdate(7), + ) + .await; + + for rx_approval_distribution_worker in rx_approval_distribution_workers.iter_mut() { + assert_matches!(rx_approval_distribution_worker.next().await.unwrap(), + FromOrchestra::Communication { + msg: ApprovalDistributionMessage::ApprovalCheckingLagUpdate( + lag + ) + } => { + assert_eq!(lag, 7); + } + ); + } + assert!(approval_voting_work_provider + .recv() + .timeout(Duration::from_millis(200)) + .await + .is_none()); + + overseer_signal(&mut overseer, OverseerSignal::Conclude).await; + + overseer + }, + ); +} + +/// Test GetApprovalSignatures correctly gatheres the signatures from all workers. +#[test] +fn test_handle_get_approval_signatures() { + let num_approval_distro_workers = 4; + + test_harness( + num_approval_distro_workers, + prio_right, + true, + |mut overseer, mut approval_voting_work_provider, mut rx_approval_distribution_workers| async move { + let (tx, rx) = oneshot::channel(); + let first_block = Hash::random(); + let second_block = Hash::random(); + let expected_candidates: HashSet<_> = + vec![(first_block, 2), (second_block, 3)].into_iter().collect(); + + overseer_message( + &mut overseer, + ApprovalVotingParallelMessage::GetApprovalSignatures( + expected_candidates.clone(), + tx, + ), + ) + .await; + + assert!(approval_voting_work_provider + .recv() + .timeout(Duration::from_millis(200)) + .await + .is_none()); + let mut all_votes = HashMap::new(); + for (index, rx_approval_distribution_worker) in + rx_approval_distribution_workers.iter_mut().enumerate() + { + assert_matches!(rx_approval_distribution_worker.next().await.unwrap(), + FromOrchestra::Communication { + msg: ApprovalDistributionMessage::GetApprovalSignatures( + candidates, tx + ) + } => { + assert_eq!(candidates, expected_candidates); + let to_send: HashMap<_, _> = {0..10}.into_iter().map(|validator| { + let validator_index = ValidatorIndex(validator as u32 * num_approval_distro_workers as u32 + index as u32); + (validator_index, (first_block, vec![2, 4], dummy_signature())) + }).collect(); + tx.send(to_send.clone()).unwrap(); + all_votes.extend(to_send.clone()); + + } + ); + } + + let received_votes = rx.await.unwrap(); + assert_eq!(received_votes, all_votes); + overseer_signal(&mut overseer, OverseerSignal::Conclude).await; + + overseer + }, + ) +} + +/// Test subsystem exits with error when approval_voting_work_provider exits. +#[test] +fn test_subsystem_exits_with_error_if_approval_voting_worker_errors() { + let num_approval_distro_workers = 4; + + test_harness( + num_approval_distro_workers, + prio_right, + false, + |overseer, approval_voting_work_provider, _rx_approval_distribution_workers| async move { + // Drop the approval_voting_work_provider to simulate an error. + std::mem::drop(approval_voting_work_provider); + + overseer + }, + ) +} + +/// Test subsystem exits with error when approval_distribution_workers exits. +#[test] +fn test_subsystem_exits_with_error_if_approval_distribution_worker_errors() { + let num_approval_distro_workers = 4; + + test_harness( + num_approval_distro_workers, + prio_right, + false, + |overseer, _approval_voting_work_provider, rx_approval_distribution_workers| async move { + // Drop the approval_distribution_workers to simulate an error. + std::mem::drop(rx_approval_distribution_workers.into_iter().next().unwrap()); + overseer + }, + ) +} + +/// Test signals sent before messages are processed in order. +#[test] +fn test_signal_before_message_keeps_receive_order() { + let num_approval_distro_workers = 4; + + test_harness( + num_approval_distro_workers, + prio_right, + true, + |mut overseer, mut approval_voting_work_provider, mut rx_approval_distribution_workers| async move { + let signal = OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::start_work(new_leaf( + Hash::random(), + 1, + ))); + overseer_signal(&mut overseer, signal.clone()).await; + + let validator_index = ValidatorIndex(17); + let assignment = + fake_assignment_cert_v2(Hash::random(), validator_index, CoreIndex(1).into()); + overseer_message( + &mut overseer, + ApprovalVotingParallelMessage::DistributeAssignment(assignment.clone(), 1.into()), + ) + .await; + + let approval_voting_receives = approval_voting_work_provider.recv().await.unwrap(); + assert_matches!(approval_voting_receives, FromOrchestra::Signal(_)); + let rx_approval_distribution_worker = rx_approval_distribution_workers + .get_mut(validator_index.0 as usize % num_approval_distro_workers) + .unwrap(); + let approval_distribution_receives = + rx_approval_distribution_worker.next().await.unwrap(); + assert_matches!(approval_distribution_receives, FromOrchestra::Signal(_)); + assert_matches!( + rx_approval_distribution_worker.next().await.unwrap(), + FromOrchestra::Communication { + msg: ApprovalDistributionMessage::DistributeAssignment(_, _) + } + ); + + overseer_signal(&mut overseer, OverseerSignal::Conclude).await; + overseer + }, + ) +} + +/// Test signals sent after messages are processed with the highest priority. +#[test] +fn test_signal_is_prioritized_when_unread_messages_in_the_queue() { + let num_approval_distro_workers = 4; + + test_harness( + num_approval_distro_workers, + prio_right, + true, + |mut overseer, mut approval_voting_work_provider, mut rx_approval_distribution_workers| async move { + let validator_index = ValidatorIndex(17); + let assignment = + fake_assignment_cert_v2(Hash::random(), validator_index, CoreIndex(1).into()); + overseer_message( + &mut overseer, + ApprovalVotingParallelMessage::DistributeAssignment(assignment.clone(), 1.into()), + ) + .await; + + let signal = OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::start_work(new_leaf( + Hash::random(), + 1, + ))); + overseer_signal(&mut overseer, signal.clone()).await; + + let approval_voting_receives = approval_voting_work_provider.recv().await.unwrap(); + assert_matches!(approval_voting_receives, FromOrchestra::Signal(_)); + let rx_approval_distribution_worker = rx_approval_distribution_workers + .get_mut(validator_index.0 as usize % num_approval_distro_workers) + .unwrap(); + let approval_distribution_receives = + rx_approval_distribution_worker.next().await.unwrap(); + assert_matches!(approval_distribution_receives, FromOrchestra::Signal(_)); + assert_matches!( + rx_approval_distribution_worker.next().await.unwrap(), + FromOrchestra::Communication { + msg: ApprovalDistributionMessage::DistributeAssignment(_, _) + } + ); + + overseer_signal(&mut overseer, OverseerSignal::Conclude).await; + overseer + }, + ) +} + +/// Test peer view updates have higher priority than normal messages. +#[test] +fn test_peer_view_is_prioritized_when_unread_messages_in_the_queue() { + let num_approval_distro_workers = 4; + + test_harness( + num_approval_distro_workers, + prio_right, + true, + |mut overseer, mut approval_voting_work_provider, mut rx_approval_distribution_workers| async move { + let validator_index = ValidatorIndex(17); + let approvals = vec![ + IndirectSignedApprovalVoteV2 { + block_hash: H256::random(), + candidate_indices: 1.into(), + validator: validator_index, + signature: dummy_signature(), + }, + IndirectSignedApprovalVoteV2 { + block_hash: H256::random(), + candidate_indices: 2.into(), + validator: validator_index, + signature: dummy_signature(), + }, + ]; + let expected_msg = polkadot_node_network_protocol::Versioned::V3( + polkadot_node_network_protocol::v3::ApprovalDistributionMessage::Approvals( + approvals.clone(), + ), + ); + overseer_message( + &mut overseer, + ApprovalVotingParallelMessage::NetworkBridgeUpdate( + polkadot_node_subsystem::messages::NetworkBridgeEvent::PeerMessage( + PeerId::random(), + expected_msg.clone(), + ), + ), + ) + .await; + + overseer_message( + &mut overseer, + ApprovalVotingParallelMessage::NetworkBridgeUpdate( + polkadot_node_subsystem::messages::NetworkBridgeEvent::PeerViewChange( + PeerId::random(), + View::default(), + ), + ), + ) + .await; + + for (index, rx_approval_distribution_worker) in + rx_approval_distribution_workers.iter_mut().enumerate() + { + assert_matches!(rx_approval_distribution_worker.next().await.unwrap(), + FromOrchestra::Communication { + msg: ApprovalDistributionMessage::NetworkBridgeUpdate( + polkadot_node_subsystem::messages::NetworkBridgeEvent::PeerViewChange( + _, + _, + ), + ) + } => { + } + ); + if index == validator_index.0 as usize % num_approval_distro_workers { + assert_matches!(rx_approval_distribution_worker.next().await.unwrap(), + FromOrchestra::Communication { + msg: ApprovalDistributionMessage::NetworkBridgeUpdate( + polkadot_node_subsystem::messages::NetworkBridgeEvent::PeerMessage( + _, + msg, + ), + ) + } => { + assert_eq!(msg, expected_msg); + } + ); + } else { + assert!(rx_approval_distribution_worker + .next() + .timeout(Duration::from_millis(200)) + .await + .is_none()); + } + } + + assert!(approval_voting_work_provider + .recv() + .timeout(Duration::from_millis(200)) + .await + .is_none()); + + overseer_signal(&mut overseer, OverseerSignal::Conclude).await; + overseer + }, + ) +} + +// Test validator_index_for_msg with empty messages. +#[test] +fn test_validator_index_with_empty_message() { + let result = validator_index_for_msg(polkadot_node_network_protocol::Versioned::V1( + polkadot_node_network_protocol::v1::ApprovalDistributionMessage::Assignments(vec![]), + )); + + assert_eq!(result, (None, Some(vec![]))); + + let result = validator_index_for_msg(polkadot_node_network_protocol::Versioned::V2( + polkadot_node_network_protocol::v2::ApprovalDistributionMessage::Assignments(vec![]), + )); + + assert_eq!(result, (None, Some(vec![]))); + + let result = validator_index_for_msg(polkadot_node_network_protocol::Versioned::V3( + polkadot_node_network_protocol::v3::ApprovalDistributionMessage::Assignments(vec![]), + )); + + assert_eq!(result, (None, Some(vec![]))); + + let result = validator_index_for_msg(polkadot_node_network_protocol::Versioned::V1( + polkadot_node_network_protocol::v1::ApprovalDistributionMessage::Approvals(vec![]), + )); + + assert_eq!(result, (None, Some(vec![]))); + + let result = validator_index_for_msg(polkadot_node_network_protocol::Versioned::V2( + polkadot_node_network_protocol::v2::ApprovalDistributionMessage::Approvals(vec![]), + )); + + assert_eq!(result, (None, Some(vec![]))); + + let result = validator_index_for_msg(polkadot_node_network_protocol::Versioned::V3( + polkadot_node_network_protocol::v3::ApprovalDistributionMessage::Approvals(vec![]), + )); + + assert_eq!(result, (None, Some(vec![]))); +} + +// Test validator_index_for_msg when all the messages are originating from the same validator. +#[test] +fn test_validator_index_with_all_messages_from_the_same_validator() { + let validator_index = ValidatorIndex(3); + let v1_assignment = polkadot_node_network_protocol::Versioned::V1( + polkadot_node_network_protocol::v1::ApprovalDistributionMessage::Assignments(vec![ + (fake_assignment_cert(H256::random(), validator_index), 1), + (fake_assignment_cert(H256::random(), validator_index), 3), + ]), + ); + let result = validator_index_for_msg(v1_assignment.clone()); + + assert_eq!(result, (Some((validator_index, v1_assignment)), None)); + + let v1_approval = polkadot_node_network_protocol::Versioned::V1( + polkadot_node_network_protocol::v1::ApprovalDistributionMessage::Approvals(vec![ + IndirectSignedApprovalVote { + block_hash: H256::random(), + candidate_index: 1, + validator: validator_index, + signature: dummy_signature(), + }, + IndirectSignedApprovalVote { + block_hash: H256::random(), + candidate_index: 1, + validator: validator_index, + signature: dummy_signature(), + }, + ]), + ); + let result = validator_index_for_msg(v1_approval.clone()); + + assert_eq!(result, (Some((validator_index, v1_approval)), None)); + + let validator_index = ValidatorIndex(3); + let v2_assignment = polkadot_node_network_protocol::Versioned::V2( + polkadot_node_network_protocol::v2::ApprovalDistributionMessage::Assignments(vec![ + (fake_assignment_cert(H256::random(), validator_index), 1), + (fake_assignment_cert(H256::random(), validator_index), 3), + ]), + ); + let result = validator_index_for_msg(v2_assignment.clone()); + + assert_eq!(result, (Some((validator_index, v2_assignment)), None)); + + let v2_approval = polkadot_node_network_protocol::Versioned::V2( + polkadot_node_network_protocol::v2::ApprovalDistributionMessage::Approvals(vec![ + IndirectSignedApprovalVote { + block_hash: H256::random(), + candidate_index: 1, + validator: validator_index, + signature: dummy_signature(), + }, + IndirectSignedApprovalVote { + block_hash: H256::random(), + candidate_index: 1, + validator: validator_index, + signature: dummy_signature(), + }, + ]), + ); + let result = validator_index_for_msg(v2_approval.clone()); + + assert_eq!(result, (Some((validator_index, v2_approval)), None)); + + let validator_index = ValidatorIndex(3); + let v3_assignment = polkadot_node_network_protocol::Versioned::V3( + polkadot_node_network_protocol::v3::ApprovalDistributionMessage::Assignments(vec![ + ( + fake_assignment_cert_v2(H256::random(), validator_index, CoreIndex(1).into()), + 1.into(), + ), + ( + fake_assignment_cert_v2(H256::random(), validator_index, CoreIndex(3).into()), + 3.into(), + ), + ]), + ); + let result = validator_index_for_msg(v3_assignment.clone()); + + assert_eq!(result, (Some((validator_index, v3_assignment)), None)); + + let v3_approval = polkadot_node_network_protocol::Versioned::V3( + polkadot_node_network_protocol::v3::ApprovalDistributionMessage::Approvals(vec![ + IndirectSignedApprovalVoteV2 { + block_hash: H256::random(), + candidate_indices: 1.into(), + validator: validator_index, + signature: dummy_signature(), + }, + IndirectSignedApprovalVoteV2 { + block_hash: H256::random(), + candidate_indices: 1.into(), + validator: validator_index, + signature: dummy_signature(), + }, + ]), + ); + let result = validator_index_for_msg(v3_approval.clone()); + + assert_eq!(result, (Some((validator_index, v3_approval)), None)); +} + +// Test validator_index_for_msg when all the messages are originating from different validators, +// so the function should split them by validator index, so we can forward them separately to the +// worker they are assigned to. +#[test] +fn test_validator_index_with_messages_from_different_validators() { + let first_validator_index = ValidatorIndex(3); + let second_validator_index = ValidatorIndex(4); + let assignments = vec![ + (fake_assignment_cert(H256::random(), first_validator_index), 1), + (fake_assignment_cert(H256::random(), second_validator_index), 3), + ]; + let v1_assignment = polkadot_node_network_protocol::Versioned::V1( + polkadot_node_network_protocol::v1::ApprovalDistributionMessage::Assignments( + assignments.clone(), + ), + ); + let result = validator_index_for_msg(v1_assignment.clone()); + + assert_matches!(result, (None, Some(_))); + let messsages_split_by_validator = result.1.unwrap(); + assert_eq!(messsages_split_by_validator.len(), assignments.len()); + for (index, (validator_index, message)) in messsages_split_by_validator.into_iter().enumerate() + { + assert_eq!(validator_index, assignments[index].0.validator); + assert_eq!( + message, + polkadot_node_network_protocol::Versioned::V1( + polkadot_node_network_protocol::v1::ApprovalDistributionMessage::Assignments( + assignments.get(index).into_iter().cloned().collect(), + ), + ) + ); + } + + let v2_assignment = polkadot_node_network_protocol::Versioned::V2( + polkadot_node_network_protocol::v2::ApprovalDistributionMessage::Assignments( + assignments.clone(), + ), + ); + let result = validator_index_for_msg(v2_assignment.clone()); + + assert_matches!(result, (None, Some(_))); + let messsages_split_by_validator = result.1.unwrap(); + assert_eq!(messsages_split_by_validator.len(), assignments.len()); + for (index, (validator_index, message)) in messsages_split_by_validator.into_iter().enumerate() + { + assert_eq!(validator_index, assignments[index].0.validator); + assert_eq!( + message, + polkadot_node_network_protocol::Versioned::V2( + polkadot_node_network_protocol::v2::ApprovalDistributionMessage::Assignments( + assignments.get(index).into_iter().cloned().collect(), + ), + ) + ); + } + + let first_validator_index = ValidatorIndex(3); + let second_validator_index = ValidatorIndex(4); + let v2_assignments = vec![ + ( + fake_assignment_cert_v2(H256::random(), first_validator_index, CoreIndex(1).into()), + 1.into(), + ), + ( + fake_assignment_cert_v2(H256::random(), second_validator_index, CoreIndex(3).into()), + 3.into(), + ), + ]; + + let approvals = vec![ + IndirectSignedApprovalVote { + block_hash: H256::random(), + candidate_index: 1, + validator: first_validator_index, + signature: dummy_signature(), + }, + IndirectSignedApprovalVote { + block_hash: H256::random(), + candidate_index: 2, + validator: second_validator_index, + signature: dummy_signature(), + }, + ]; + let v2_approvals = polkadot_node_network_protocol::Versioned::V2( + polkadot_node_network_protocol::v2::ApprovalDistributionMessage::Approvals( + approvals.clone(), + ), + ); + let result = validator_index_for_msg(v2_approvals.clone()); + + assert_matches!(result, (None, Some(_))); + let messsages_split_by_validator = result.1.unwrap(); + assert_eq!(messsages_split_by_validator.len(), approvals.len()); + for (index, (validator_index, message)) in messsages_split_by_validator.into_iter().enumerate() + { + assert_eq!(validator_index, approvals[index].validator); + assert_eq!( + message, + polkadot_node_network_protocol::Versioned::V2( + polkadot_node_network_protocol::v2::ApprovalDistributionMessage::Approvals( + approvals.get(index).into_iter().cloned().collect(), + ), + ) + ); + } + + let v3_assignment = polkadot_node_network_protocol::Versioned::V3( + polkadot_node_network_protocol::v3::ApprovalDistributionMessage::Assignments( + v2_assignments.clone(), + ), + ); + let result = validator_index_for_msg(v3_assignment.clone()); + + assert_matches!(result, (None, Some(_))); + let messsages_split_by_validator = result.1.unwrap(); + assert_eq!(messsages_split_by_validator.len(), v2_assignments.len()); + for (index, (validator_index, message)) in messsages_split_by_validator.into_iter().enumerate() + { + assert_eq!(validator_index, v2_assignments[index].0.validator); + assert_eq!( + message, + polkadot_node_network_protocol::Versioned::V3( + polkadot_node_network_protocol::v3::ApprovalDistributionMessage::Assignments( + v2_assignments.get(index).into_iter().cloned().collect(), + ), + ) + ); + } + + let approvals = vec![ + IndirectSignedApprovalVoteV2 { + block_hash: H256::random(), + candidate_indices: 1.into(), + validator: first_validator_index, + signature: dummy_signature(), + }, + IndirectSignedApprovalVoteV2 { + block_hash: H256::random(), + candidate_indices: 2.into(), + validator: second_validator_index, + signature: dummy_signature(), + }, + ]; + let v3_approvals = polkadot_node_network_protocol::Versioned::V3( + polkadot_node_network_protocol::v3::ApprovalDistributionMessage::Approvals( + approvals.clone(), + ), + ); + let result = validator_index_for_msg(v3_approvals.clone()); + + assert_matches!(result, (None, Some(_))); + let messsages_split_by_validator = result.1.unwrap(); + assert_eq!(messsages_split_by_validator.len(), approvals.len()); + for (index, (validator_index, message)) in messsages_split_by_validator.into_iter().enumerate() + { + assert_eq!(validator_index, approvals[index].validator); + assert_eq!( + message, + polkadot_node_network_protocol::Versioned::V3( + polkadot_node_network_protocol::v3::ApprovalDistributionMessage::Approvals( + approvals.get(index).into_iter().cloned().collect(), + ), + ) + ); + } +} diff --git a/polkadot/node/core/approval-voting/benches/approval-voting-regression-bench.rs b/polkadot/node/core/approval-voting/benches/approval-voting-regression-bench.rs index 0b03f1127ee..db0396a8319 100644 --- a/polkadot/node/core/approval-voting/benches/approval-voting-regression-bench.rs +++ b/polkadot/node/core/approval-voting/benches/approval-voting-regression-bench.rs @@ -53,6 +53,7 @@ fn main() -> Result<(), String> { stop_when_approved: false, workdir_prefix: "/tmp".to_string(), num_no_shows_per_candidate: 0, + approval_voting_parallel_enabled: true, }; println!("Benchmarking..."); diff --git a/polkadot/node/core/dispute-coordinator/src/initialized.rs b/polkadot/node/core/dispute-coordinator/src/initialized.rs index 5096fe5e689..9cf9047b727 100644 --- a/polkadot/node/core/dispute-coordinator/src/initialized.rs +++ b/polkadot/node/core/dispute-coordinator/src/initialized.rs @@ -34,8 +34,9 @@ use polkadot_node_primitives::{ }; use polkadot_node_subsystem::{ messages::{ - ApprovalVotingMessage, BlockDescription, ChainSelectionMessage, DisputeCoordinatorMessage, - DisputeDistributionMessage, ImportStatementsResult, + ApprovalVotingMessage, ApprovalVotingParallelMessage, BlockDescription, + ChainSelectionMessage, DisputeCoordinatorMessage, DisputeDistributionMessage, + ImportStatementsResult, }, overseer, ActivatedLeaf, ActiveLeavesUpdate, FromOrchestra, OverseerSignal, RuntimeApiError, }; @@ -117,6 +118,7 @@ pub(crate) struct Initialized { /// `CHAIN_IMPORT_MAX_BATCH_SIZE` and put the rest here for later processing. chain_import_backlog: VecDeque, metrics: Metrics, + approval_voting_parallel_enabled: bool, } #[overseer::contextbounds(DisputeCoordinator, prefix = self::overseer)] @@ -130,7 +132,13 @@ impl Initialized { highest_session_seen: SessionIndex, gaps_in_cache: bool, ) -> Self { - let DisputeCoordinatorSubsystem { config: _, store: _, keystore, metrics } = subsystem; + let DisputeCoordinatorSubsystem { + config: _, + store: _, + keystore, + metrics, + approval_voting_parallel_enabled, + } = subsystem; let (participation_sender, participation_receiver) = mpsc::channel(1); let participation = Participation::new(participation_sender, metrics.clone()); @@ -148,6 +156,7 @@ impl Initialized { participation_receiver, chain_import_backlog: VecDeque::new(), metrics, + approval_voting_parallel_enabled, } } @@ -1059,9 +1068,21 @@ impl Initialized { // 4. We are waiting (and blocking the whole subsystem) on a response right after - // therefore even with all else failing we will never have more than // one message in flight at any given time. - ctx.send_unbounded_message( - ApprovalVotingMessage::GetApprovalSignaturesForCandidate(candidate_hash, tx), - ); + if self.approval_voting_parallel_enabled { + ctx.send_unbounded_message( + ApprovalVotingParallelMessage::GetApprovalSignaturesForCandidate( + candidate_hash, + tx, + ), + ); + } else { + ctx.send_unbounded_message( + ApprovalVotingMessage::GetApprovalSignaturesForCandidate( + candidate_hash, + tx, + ), + ); + } match rx.await { Err(_) => { gum::warn!( diff --git a/polkadot/node/core/dispute-coordinator/src/lib.rs b/polkadot/node/core/dispute-coordinator/src/lib.rs index 34d9ddf3a97..84408eb9630 100644 --- a/polkadot/node/core/dispute-coordinator/src/lib.rs +++ b/polkadot/node/core/dispute-coordinator/src/lib.rs @@ -122,6 +122,7 @@ pub struct DisputeCoordinatorSubsystem { store: Arc, keystore: Arc, metrics: Metrics, + approval_voting_parallel_enabled: bool, } /// Configuration for the dispute coordinator subsystem. @@ -164,8 +165,9 @@ impl DisputeCoordinatorSubsystem { config: Config, keystore: Arc, metrics: Metrics, + approval_voting_parallel_enabled: bool, ) -> Self { - Self { store, config, keystore, metrics } + Self { store, config, keystore, metrics, approval_voting_parallel_enabled } } /// Initialize and afterwards run `Initialized::run`. diff --git a/polkadot/node/core/dispute-coordinator/src/tests.rs b/polkadot/node/core/dispute-coordinator/src/tests.rs index f97a625a952..b41cdb94b4d 100644 --- a/polkadot/node/core/dispute-coordinator/src/tests.rs +++ b/polkadot/node/core/dispute-coordinator/src/tests.rs @@ -580,6 +580,7 @@ impl TestState { self.config, self.subsystem_keystore.clone(), Metrics::default(), + false, ); let backend = DbBackend::new(self.db.clone(), self.config.column_config(), Metrics::default()); diff --git a/polkadot/node/network/approval-distribution/src/lib.rs b/polkadot/node/network/approval-distribution/src/lib.rs index 971b6de5f8f..2fcb639338e 100644 --- a/polkadot/node/network/approval-distribution/src/lib.rs +++ b/polkadot/node/network/approval-distribution/src/lib.rs @@ -73,7 +73,8 @@ use std::{ time::Duration, }; -mod metrics; +/// Approval distribution metrics. +pub mod metrics; #[cfg(test)] mod tests; @@ -99,7 +100,7 @@ const MAX_BITFIELD_SIZE: usize = 500; pub struct ApprovalDistribution { metrics: Metrics, slot_duration_millis: u64, - clock: Box, + clock: Arc, assignment_criteria: Arc, } @@ -2668,7 +2669,7 @@ impl ApprovalDistribution { Self::new_with_clock( metrics, slot_duration_millis, - Box::new(SystemClock), + Arc::new(SystemClock), assignment_criteria, ) } @@ -2677,7 +2678,7 @@ impl ApprovalDistribution { pub fn new_with_clock( metrics: Metrics, slot_duration_millis: u64, - clock: Box, + clock: Arc, assignment_criteria: Arc, ) -> Self { Self { metrics, slot_duration_millis, clock, assignment_criteria } diff --git a/polkadot/node/network/approval-distribution/src/metrics.rs b/polkadot/node/network/approval-distribution/src/metrics.rs index 10553c35296..2f677ba415e 100644 --- a/polkadot/node/network/approval-distribution/src/metrics.rs +++ b/polkadot/node/network/approval-distribution/src/metrics.rs @@ -79,31 +79,19 @@ impl Metrics { .map(|metrics| metrics.time_import_pending_now_known.start_timer()) } - pub fn on_approval_already_known(&self) { - if let Some(metrics) = &self.0 { - metrics.approvals_received_result.with_label_values(&["known"]).inc() - } - } - - pub fn on_approval_entry_not_found(&self) { - if let Some(metrics) = &self.0 { - metrics.approvals_received_result.with_label_values(&["noapprovalentry"]).inc() - } - } - - pub fn on_approval_recent_outdated(&self) { + pub(crate) fn on_approval_recent_outdated(&self) { if let Some(metrics) = &self.0 { metrics.approvals_received_result.with_label_values(&["outdated"]).inc() } } - pub fn on_approval_invalid_block(&self) { + pub(crate) fn on_approval_invalid_block(&self) { if let Some(metrics) = &self.0 { metrics.approvals_received_result.with_label_values(&["invalidblock"]).inc() } } - pub fn on_approval_unknown_assignment(&self) { + pub(crate) fn on_approval_unknown_assignment(&self) { if let Some(metrics) = &self.0 { metrics .approvals_received_result @@ -112,94 +100,73 @@ impl Metrics { } } - pub fn on_approval_duplicate(&self) { + pub(crate) fn on_approval_duplicate(&self) { if let Some(metrics) = &self.0 { metrics.approvals_received_result.with_label_values(&["duplicate"]).inc() } } - pub fn on_approval_out_of_view(&self) { + pub(crate) fn on_approval_out_of_view(&self) { if let Some(metrics) = &self.0 { metrics.approvals_received_result.with_label_values(&["outofview"]).inc() } } - pub fn on_approval_good_known(&self) { + pub(crate) fn on_approval_good_known(&self) { if let Some(metrics) = &self.0 { metrics.approvals_received_result.with_label_values(&["goodknown"]).inc() } } - pub fn on_approval_bad(&self) { + pub(crate) fn on_approval_bad(&self) { if let Some(metrics) = &self.0 { metrics.approvals_received_result.with_label_values(&["bad"]).inc() } } - pub fn on_approval_unexpected(&self) { - if let Some(metrics) = &self.0 { - metrics.approvals_received_result.with_label_values(&["unexpected"]).inc() - } - } - - pub fn on_approval_bug(&self) { + pub(crate) fn on_approval_bug(&self) { if let Some(metrics) = &self.0 { metrics.approvals_received_result.with_label_values(&["bug"]).inc() } } - pub fn on_assignment_already_known(&self) { - if let Some(metrics) = &self.0 { - metrics.assignments_received_result.with_label_values(&["known"]).inc() - } - } - - pub fn on_assignment_recent_outdated(&self) { + pub(crate) fn on_assignment_recent_outdated(&self) { if let Some(metrics) = &self.0 { metrics.assignments_received_result.with_label_values(&["outdated"]).inc() } } - pub fn on_assignment_invalid_block(&self) { + pub(crate) fn on_assignment_invalid_block(&self) { if let Some(metrics) = &self.0 { metrics.assignments_received_result.with_label_values(&["invalidblock"]).inc() } } - pub fn on_assignment_duplicate(&self) { + pub(crate) fn on_assignment_duplicate(&self) { if let Some(metrics) = &self.0 { metrics.assignments_received_result.with_label_values(&["duplicate"]).inc() } } - pub fn on_assignment_out_of_view(&self) { + pub(crate) fn on_assignment_out_of_view(&self) { if let Some(metrics) = &self.0 { metrics.assignments_received_result.with_label_values(&["outofview"]).inc() } } - pub fn on_assignment_good_known(&self) { + pub(crate) fn on_assignment_good_known(&self) { if let Some(metrics) = &self.0 { metrics.assignments_received_result.with_label_values(&["goodknown"]).inc() } } - pub fn on_assignment_bad(&self) { + pub(crate) fn on_assignment_bad(&self) { if let Some(metrics) = &self.0 { metrics.assignments_received_result.with_label_values(&["bad"]).inc() } } - pub fn on_assignment_duplicatevoting(&self) { - if let Some(metrics) = &self.0 { - metrics - .assignments_received_result - .with_label_values(&["duplicatevoting"]) - .inc() - } - } - - pub fn on_assignment_far(&self) { + pub(crate) fn on_assignment_far(&self) { if let Some(metrics) = &self.0 { metrics.assignments_received_result.with_label_values(&["far"]).inc() } diff --git a/polkadot/node/network/approval-distribution/src/tests.rs b/polkadot/node/network/approval-distribution/src/tests.rs index 4ee9320e0e4..068559dea76 100644 --- a/polkadot/node/network/approval-distribution/src/tests.rs +++ b/polkadot/node/network/approval-distribution/src/tests.rs @@ -54,7 +54,7 @@ type VirtualOverseer = fn test_harness>( assignment_criteria: Arc, - clock: Box, + clock: Arc, mut state: State, test_fn: impl FnOnce(VirtualOverseer) -> T, ) -> State { @@ -555,16 +555,15 @@ fn try_import_the_same_assignment() { let _ = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(SystemClock {}), + Arc::new(SystemClock {}), state_without_reputation_delay(), |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; - // setup peers + setup_peer_with_view(overseer, &peer_a, view![], ValidationVersion::V1).await; setup_peer_with_view(overseer, &peer_b, view![hash], ValidationVersion::V1).await; setup_peer_with_view(overseer, &peer_c, view![hash], ValidationVersion::V1).await; - // Set up a gossip topology, where a, b, c and d are topology neighbors to the node // under testing. let peers_with_optional_peer_id = peers .iter() @@ -661,7 +660,7 @@ fn try_import_the_same_assignment_v2() { let _ = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(SystemClock {}), + Arc::new(SystemClock {}), state_without_reputation_delay(), |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -772,7 +771,7 @@ fn delay_reputation_change() { let _ = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(SystemClock {}), + Arc::new(SystemClock {}), state_with_reputation_delay(), |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -845,7 +844,7 @@ fn spam_attack_results_in_negative_reputation_change() { let _ = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(SystemClock {}), + Arc::new(SystemClock {}), state_without_reputation_delay(), |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -942,7 +941,7 @@ fn peer_sending_us_the_same_we_just_sent_them_is_ok() { let _ = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(SystemClock {}), + Arc::new(SystemClock {}), state_without_reputation_delay(), |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -1043,7 +1042,7 @@ fn import_approval_happy_path_v1_v2_peers() { let _ = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(SystemClock {}), + Arc::new(SystemClock {}), state_without_reputation_delay(), |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -1183,7 +1182,7 @@ fn import_approval_happy_path_v2() { let candidate_hash_second = polkadot_primitives::CandidateHash(Hash::repeat_byte(0xCC)); let _ = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(SystemClock {}), + Arc::new(SystemClock {}), state_without_reputation_delay(), |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -1314,7 +1313,7 @@ fn multiple_assignments_covered_with_one_approval_vote() { let _ = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(SystemClock {}), + Arc::new(SystemClock {}), state_without_reputation_delay(), |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -1524,7 +1523,7 @@ fn unify_with_peer_multiple_assignments_covered_with_one_approval_vote() { let _ = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(SystemClock {}), + Arc::new(SystemClock {}), state_without_reputation_delay(), |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -1723,7 +1722,7 @@ fn import_approval_bad() { let _ = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(SystemClock {}), + Arc::new(SystemClock {}), state_without_reputation_delay(), |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -1810,7 +1809,7 @@ fn update_our_view() { let state = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(SystemClock {}), + Arc::new(SystemClock {}), State::default(), |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -1858,7 +1857,7 @@ fn update_our_view() { let state = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(SystemClock {}), + Arc::new(SystemClock {}), state, |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -1877,7 +1876,7 @@ fn update_our_view() { let state = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(SystemClock {}), + Arc::new(SystemClock {}), state, |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -1905,7 +1904,7 @@ fn update_peer_view() { let state = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(SystemClock {}), + Arc::new(SystemClock {}), State::default(), |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -2004,7 +2003,7 @@ fn update_peer_view() { let state = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(SystemClock {}), + Arc::new(SystemClock {}), state, |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -2064,7 +2063,7 @@ fn update_peer_view() { let finalized_number = 4_000_000_000; let state = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(SystemClock {}), + Arc::new(SystemClock {}), state, |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -2106,7 +2105,7 @@ fn update_peer_authority_id() { let _state = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(SystemClock {}), + Arc::new(SystemClock {}), State::default(), |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -2287,7 +2286,7 @@ fn import_remotely_then_locally() { let _ = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(SystemClock {}), + Arc::new(SystemClock {}), state_without_reputation_delay(), |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -2393,7 +2392,7 @@ fn sends_assignments_even_when_state_is_approved() { let _ = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(SystemClock {}), + Arc::new(SystemClock {}), State::default(), |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -2499,7 +2498,7 @@ fn sends_assignments_even_when_state_is_approved_v2() { let _ = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(SystemClock {}), + Arc::new(SystemClock {}), State::default(), |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -2625,7 +2624,7 @@ fn race_condition_in_local_vs_remote_view_update() { let _ = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(SystemClock {}), + Arc::new(SystemClock {}), state_without_reputation_delay(), |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -2711,7 +2710,7 @@ fn propagates_locally_generated_assignment_to_both_dimensions() { let _ = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(SystemClock {}), + Arc::new(SystemClock {}), State::default(), |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -2841,7 +2840,7 @@ fn propagates_assignments_along_unshared_dimension() { let _ = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(SystemClock {}), + Arc::new(SystemClock {}), state_without_reputation_delay(), |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -3000,7 +2999,7 @@ fn propagates_to_required_after_connect() { let _ = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(SystemClock {}), + Arc::new(SystemClock {}), State::default(), |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -3165,7 +3164,7 @@ fn sends_to_more_peers_after_getting_topology() { let _ = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(SystemClock {}), + Arc::new(SystemClock {}), State::default(), |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -3303,7 +3302,7 @@ fn originator_aggression_l1() { let _ = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(SystemClock {}), + Arc::new(SystemClock {}), state, |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -3484,7 +3483,7 @@ fn non_originator_aggression_l1() { let _ = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(SystemClock {}), + Arc::new(SystemClock {}), state, |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -3609,7 +3608,7 @@ fn non_originator_aggression_l2() { let aggression_l2_threshold = state.aggression_config.l2_threshold.unwrap(); let _ = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(SystemClock {}), + Arc::new(SystemClock {}), state, |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -3794,7 +3793,7 @@ fn resends_messages_periodically() { state.aggression_config.resend_unfinalized_period = Some(2); let _ = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(SystemClock {}), + Arc::new(SystemClock {}), state, |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -3958,7 +3957,7 @@ fn import_versioned_approval() { let candidate_hash = polkadot_primitives::CandidateHash(Hash::repeat_byte(0xBB)); let _ = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(SystemClock {}), + Arc::new(SystemClock {}), state, |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -4131,7 +4130,7 @@ fn batch_test_round(message_count: usize) { let subsystem = ApprovalDistribution::new_with_clock( Default::default(), Default::default(), - Box::new(SystemClock {}), + Arc::new(SystemClock {}), Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), ); let mut rng = rand_chacha::ChaCha12Rng::seed_from_u64(12345); @@ -4318,7 +4317,7 @@ fn subsystem_rejects_assignment_in_future() { let _ = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(89) }), - Box::new(DummyClock {}), + Arc::new(DummyClock {}), state_without_reputation_delay(), |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -4384,7 +4383,7 @@ fn subsystem_rejects_bad_assignments() { Arc::new(MockAssignmentCriteria { tranche: Err(InvalidAssignment(criteria::InvalidAssignmentReason::NullAssignment)), }), - Box::new(DummyClock {}), + Arc::new(DummyClock {}), state_without_reputation_delay(), |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -4447,7 +4446,7 @@ fn subsystem_rejects_wrong_claimed_assignments() { let _ = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(DummyClock {}), + Arc::new(DummyClock {}), state_without_reputation_delay(), |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -4531,7 +4530,7 @@ fn subsystem_accepts_tranche0_duplicate_assignments() { let _ = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(DummyClock {}), + Arc::new(DummyClock {}), state_without_reputation_delay(), |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; diff --git a/polkadot/node/network/bridge/src/rx/mod.rs b/polkadot/node/network/bridge/src/rx/mod.rs index 7745c42f78a..9a2357f02d8 100644 --- a/polkadot/node/network/bridge/src/rx/mod.rs +++ b/polkadot/node/network/bridge/src/rx/mod.rs @@ -45,8 +45,9 @@ use polkadot_node_subsystem::{ errors::SubsystemError, messages::{ network_bridge_event::NewGossipTopology, ApprovalDistributionMessage, - BitfieldDistributionMessage, CollatorProtocolMessage, GossipSupportMessage, - NetworkBridgeEvent, NetworkBridgeRxMessage, StatementDistributionMessage, + ApprovalVotingParallelMessage, BitfieldDistributionMessage, CollatorProtocolMessage, + GossipSupportMessage, NetworkBridgeEvent, NetworkBridgeRxMessage, + StatementDistributionMessage, }, overseer, ActivatedLeaf, ActiveLeavesUpdate, FromOrchestra, OverseerSignal, SpawnedSubsystem, }; @@ -89,6 +90,7 @@ pub struct NetworkBridgeRx { validation_service: Box, collation_service: Box, notification_sinks: Arc>>>, + approval_voting_parallel_enabled: bool, } impl NetworkBridgeRx { @@ -105,6 +107,7 @@ impl NetworkBridgeRx { peerset_protocol_names: PeerSetProtocolNames, mut notification_services: HashMap>, notification_sinks: Arc>>>, + approval_voting_parallel_enabled: bool, ) -> Self { let shared = Shared::default(); @@ -125,6 +128,7 @@ impl NetworkBridgeRx { validation_service, collation_service, notification_sinks, + approval_voting_parallel_enabled, } } } @@ -156,6 +160,7 @@ async fn handle_validation_message( peerset_protocol_names: &PeerSetProtocolNames, notification_service: &mut Box, notification_sinks: &mut Arc>>>, + approval_voting_parallel_enabled: bool, ) where AD: validator_discovery::AuthorityDiscovery + Send, { @@ -276,6 +281,7 @@ async fn handle_validation_message( ], sender, &metrics, + approval_voting_parallel_enabled, ) .await; @@ -329,6 +335,7 @@ async fn handle_validation_message( NetworkBridgeEvent::PeerDisconnected(peer), sender, &metrics, + approval_voting_parallel_enabled, ) .await; } @@ -398,7 +405,13 @@ async fn handle_validation_message( network_service.report_peer(peer, report.into()); } - dispatch_validation_events_to_all(events, sender, &metrics).await; + dispatch_validation_events_to_all( + events, + sender, + &metrics, + approval_voting_parallel_enabled, + ) + .await; }, } } @@ -652,6 +665,7 @@ async fn handle_network_messages( mut validation_service: Box, mut collation_service: Box, mut notification_sinks: Arc>>>, + approval_voting_parallel_enabled: bool, ) -> Result<(), Error> where AD: validator_discovery::AuthorityDiscovery + Send, @@ -669,6 +683,7 @@ where &peerset_protocol_names, &mut validation_service, &mut notification_sinks, + approval_voting_parallel_enabled, ).await, None => return Err(Error::EventStreamConcluded), }, @@ -727,6 +742,7 @@ async fn run_incoming_orchestra_signals( sync_oracle: Box, metrics: Metrics, notification_sinks: Arc>>>, + approval_voting_parallel_enabled: bool, ) -> Result<(), Error> where AD: validator_discovery::AuthorityDiscovery + Clone, @@ -766,6 +782,7 @@ where local_index, }), ctx.sender(), + approval_voting_parallel_enabled, ); }, FromOrchestra::Communication { @@ -787,6 +804,7 @@ where dispatch_validation_event_to_all_unbounded( NetworkBridgeEvent::UpdatedAuthorityIds(peer_id, authority_ids), ctx.sender(), + approval_voting_parallel_enabled, ); }, FromOrchestra::Signal(OverseerSignal::Conclude) => return Ok(()), @@ -826,6 +844,7 @@ where finalized_number, &metrics, ¬ification_sinks, + approval_voting_parallel_enabled, ); note_peers_count(&metrics, &shared); } @@ -875,6 +894,7 @@ where validation_service, collation_service, notification_sinks, + approval_voting_parallel_enabled, } = bridge; let (task, network_event_handler) = handle_network_messages( @@ -887,6 +907,7 @@ where validation_service, collation_service, notification_sinks.clone(), + approval_voting_parallel_enabled, ) .remote_handle(); @@ -900,6 +921,7 @@ where sync_oracle, metrics, notification_sinks, + approval_voting_parallel_enabled, ); futures::pin_mut!(orchestra_signal_handler); @@ -926,6 +948,7 @@ fn update_our_view( finalized_number: BlockNumber, metrics: &Metrics, notification_sinks: &Arc>>>, + approval_voting_parallel_enabled: bool, ) { let new_view = construct_view(live_heads.iter().map(|v| v.hash), finalized_number); @@ -970,6 +993,7 @@ fn update_our_view( dispatch_validation_event_to_all_unbounded( NetworkBridgeEvent::OurViewChange(our_view.clone()), ctx.sender(), + approval_voting_parallel_enabled, ); dispatch_collation_event_to_all_unbounded( @@ -1081,8 +1105,15 @@ async fn dispatch_validation_event_to_all( event: NetworkBridgeEvent, ctx: &mut impl overseer::NetworkBridgeRxSenderTrait, metrics: &Metrics, + approval_voting_parallel_enabled: bool, ) { - dispatch_validation_events_to_all(std::iter::once(event), ctx, metrics).await + dispatch_validation_events_to_all( + std::iter::once(event), + ctx, + metrics, + approval_voting_parallel_enabled, + ) + .await } async fn dispatch_collation_event_to_all( @@ -1095,6 +1126,7 @@ async fn dispatch_collation_event_to_all( fn dispatch_validation_event_to_all_unbounded( event: NetworkBridgeEvent, sender: &mut impl overseer::NetworkBridgeRxSenderTrait, + approval_voting_parallel_enabled: bool, ) { event .focus() @@ -1106,11 +1138,20 @@ fn dispatch_validation_event_to_all_unbounded( .ok() .map(BitfieldDistributionMessage::from) .and_then(|msg| Some(sender.send_unbounded_message(msg))); - event - .focus() - .ok() - .map(ApprovalDistributionMessage::from) - .and_then(|msg| Some(sender.send_unbounded_message(msg))); + + if approval_voting_parallel_enabled { + event + .focus() + .ok() + .map(ApprovalVotingParallelMessage::from) + .and_then(|msg| Some(sender.send_unbounded_message(msg))); + } else { + event + .focus() + .ok() + .map(ApprovalDistributionMessage::from) + .and_then(|msg| Some(sender.send_unbounded_message(msg))); + } event .focus() .ok() @@ -1131,6 +1172,7 @@ async fn dispatch_validation_events_to_all( events: I, sender: &mut impl overseer::NetworkBridgeRxSenderTrait, _metrics: &Metrics, + approval_voting_parallel_enabled: bool, ) where I: IntoIterator>, I::IntoIter: Send, @@ -1160,7 +1202,11 @@ async fn dispatch_validation_events_to_all( for event in events { send_message!(event, StatementDistributionMessage); send_message!(event, BitfieldDistributionMessage); - send_message!(event, ApprovalDistributionMessage); + if approval_voting_parallel_enabled { + send_message!(event, ApprovalVotingParallelMessage); + } else { + send_message!(event, ApprovalDistributionMessage); + } send_message!(event, GossipSupportMessage); } } diff --git a/polkadot/node/network/bridge/src/rx/tests.rs b/polkadot/node/network/bridge/src/rx/tests.rs index 601dca5cb8a..a96817eb254 100644 --- a/polkadot/node/network/bridge/src/rx/tests.rs +++ b/polkadot/node/network/bridge/src/rx/tests.rs @@ -529,6 +529,7 @@ fn test_harness>( validation_service, collation_service, notification_sinks, + approval_voting_parallel_enabled: false, }; let network_bridge = run_network_in(bridge, context) diff --git a/polkadot/node/overseer/src/dummy.rs b/polkadot/node/overseer/src/dummy.rs index fc5f0070773..6f9cd9d0040 100644 --- a/polkadot/node/overseer/src/dummy.rs +++ b/polkadot/node/overseer/src/dummy.rs @@ -88,6 +88,7 @@ pub fn dummy_overseer_builder( DummySubsystem, DummySubsystem, DummySubsystem, + DummySubsystem, >, SubsystemError, > @@ -131,6 +132,7 @@ pub fn one_for_all_overseer_builder( Sub, Sub, Sub, + Sub, >, SubsystemError, > @@ -155,6 +157,7 @@ where + Subsystem, SubsystemError> + Subsystem, SubsystemError> + Subsystem, SubsystemError> + + Subsystem, SubsystemError> + Subsystem, SubsystemError> + Subsystem, SubsystemError> + Subsystem, SubsystemError> @@ -183,6 +186,7 @@ where .statement_distribution(subsystem.clone()) .approval_distribution(subsystem.clone()) .approval_voting(subsystem.clone()) + .approval_voting_parallel(subsystem.clone()) .gossip_support(subsystem.clone()) .dispute_coordinator(subsystem.clone()) .dispute_distribution(subsystem.clone()) diff --git a/polkadot/node/overseer/src/lib.rs b/polkadot/node/overseer/src/lib.rs index 23adf4f4d8a..10a4320433a 100644 --- a/polkadot/node/overseer/src/lib.rs +++ b/polkadot/node/overseer/src/lib.rs @@ -76,13 +76,13 @@ use sc_client_api::{BlockImportNotification, BlockchainEvents, FinalityNotificat use self::messages::{BitfieldSigningMessage, PvfCheckerMessage}; use polkadot_node_subsystem_types::messages::{ - ApprovalDistributionMessage, ApprovalVotingMessage, AvailabilityDistributionMessage, - AvailabilityRecoveryMessage, AvailabilityStoreMessage, BitfieldDistributionMessage, - CandidateBackingMessage, CandidateValidationMessage, ChainApiMessage, ChainSelectionMessage, - CollationGenerationMessage, CollatorProtocolMessage, DisputeCoordinatorMessage, - DisputeDistributionMessage, GossipSupportMessage, NetworkBridgeRxMessage, - NetworkBridgeTxMessage, ProspectiveParachainsMessage, ProvisionerMessage, RuntimeApiMessage, - StatementDistributionMessage, + ApprovalDistributionMessage, ApprovalVotingMessage, ApprovalVotingParallelMessage, + AvailabilityDistributionMessage, AvailabilityRecoveryMessage, AvailabilityStoreMessage, + BitfieldDistributionMessage, CandidateBackingMessage, CandidateValidationMessage, + ChainApiMessage, ChainSelectionMessage, CollationGenerationMessage, CollatorProtocolMessage, + DisputeCoordinatorMessage, DisputeDistributionMessage, GossipSupportMessage, + NetworkBridgeRxMessage, NetworkBridgeTxMessage, ProspectiveParachainsMessage, + ProvisionerMessage, RuntimeApiMessage, StatementDistributionMessage, }; pub use polkadot_node_subsystem_types::{ @@ -550,6 +550,7 @@ pub struct Overseer { BitfieldDistributionMessage, StatementDistributionMessage, ApprovalDistributionMessage, + ApprovalVotingParallelMessage, GossipSupportMessage, DisputeDistributionMessage, CollationGenerationMessage, @@ -595,7 +596,19 @@ pub struct Overseer { RuntimeApiMessage, ])] approval_voting: ApprovalVoting, - + #[subsystem(blocking, message_capacity: 64000, ApprovalVotingParallelMessage, sends: [ + AvailabilityRecoveryMessage, + CandidateValidationMessage, + ChainApiMessage, + ChainSelectionMessage, + DisputeCoordinatorMessage, + RuntimeApiMessage, + NetworkBridgeTxMessage, + ApprovalVotingMessage, + ApprovalDistributionMessage, + ApprovalVotingParallelMessage, + ])] + approval_voting_parallel: ApprovalVotingParallel, #[subsystem(GossipSupportMessage, sends: [ NetworkBridgeTxMessage, NetworkBridgeRxMessage, // TODO @@ -613,6 +626,7 @@ pub struct Overseer { AvailabilityStoreMessage, AvailabilityRecoveryMessage, ChainSelectionMessage, + ApprovalVotingParallelMessage, ])] dispute_coordinator: DisputeCoordinator, diff --git a/polkadot/node/overseer/src/tests.rs b/polkadot/node/overseer/src/tests.rs index 8e78d8fc892..cb0add03e2e 100644 --- a/polkadot/node/overseer/src/tests.rs +++ b/polkadot/node/overseer/src/tests.rs @@ -950,7 +950,7 @@ fn test_prospective_parachains_msg() -> ProspectiveParachainsMessage { // Checks that `stop`, `broadcast_signal` and `broadcast_message` are implemented correctly. #[test] fn overseer_all_subsystems_receive_signals_and_messages() { - const NUM_SUBSYSTEMS: usize = 23; + const NUM_SUBSYSTEMS: usize = 24; // -4 for BitfieldSigning, GossipSupport, AvailabilityDistribution and PvfCheckerSubsystem. const NUM_SUBSYSTEMS_MESSAGED: usize = NUM_SUBSYSTEMS - 4; @@ -1028,6 +1028,11 @@ fn overseer_all_subsystems_receive_signals_and_messages() { handle .send_msg_anon(AllMessages::ApprovalDistribution(test_approval_distribution_msg())) .await; + handle + .send_msg_anon(AllMessages::ApprovalVotingParallel( + test_approval_distribution_msg().into(), + )) + .await; handle .send_msg_anon(AllMessages::ApprovalVoting(test_approval_voting_msg())) .await; @@ -1101,6 +1106,7 @@ fn context_holds_onto_message_until_enough_signals_received() { let (chain_selection_bounded_tx, _) = metered::channel(CHANNEL_CAPACITY); let (pvf_checker_bounded_tx, _) = metered::channel(CHANNEL_CAPACITY); let (prospective_parachains_bounded_tx, _) = metered::channel(CHANNEL_CAPACITY); + let (approval_voting_parallel_tx, _) = metered::channel(CHANNEL_CAPACITY); let (candidate_validation_unbounded_tx, _) = metered::unbounded(); let (candidate_backing_unbounded_tx, _) = metered::unbounded(); @@ -1125,6 +1131,7 @@ fn context_holds_onto_message_until_enough_signals_received() { let (chain_selection_unbounded_tx, _) = metered::unbounded(); let (pvf_checker_unbounded_tx, _) = metered::unbounded(); let (prospective_parachains_unbounded_tx, _) = metered::unbounded(); + let (approval_voting_parallel_unbounded_tx, _) = metered::unbounded(); let channels_out = ChannelsOut { candidate_validation: candidate_validation_bounded_tx.clone(), @@ -1150,6 +1157,7 @@ fn context_holds_onto_message_until_enough_signals_received() { chain_selection: chain_selection_bounded_tx.clone(), pvf_checker: pvf_checker_bounded_tx.clone(), prospective_parachains: prospective_parachains_bounded_tx.clone(), + approval_voting_parallel: approval_voting_parallel_tx.clone(), candidate_validation_unbounded: candidate_validation_unbounded_tx.clone(), candidate_backing_unbounded: candidate_backing_unbounded_tx.clone(), @@ -1174,6 +1182,7 @@ fn context_holds_onto_message_until_enough_signals_received() { chain_selection_unbounded: chain_selection_unbounded_tx.clone(), pvf_checker_unbounded: pvf_checker_unbounded_tx.clone(), prospective_parachains_unbounded: prospective_parachains_unbounded_tx.clone(), + approval_voting_parallel_unbounded: approval_voting_parallel_unbounded_tx.clone(), }; let (mut signal_tx, signal_rx) = metered::channel(CHANNEL_CAPACITY); diff --git a/polkadot/node/primitives/src/approval/mod.rs b/polkadot/node/primitives/src/approval/mod.rs index 79f4cfa9e0b..42342f9889a 100644 --- a/polkadot/node/primitives/src/approval/mod.rs +++ b/polkadot/node/primitives/src/approval/mod.rs @@ -124,7 +124,7 @@ pub mod v1 { } /// Metadata about a block which is now live in the approval protocol. - #[derive(Debug)] + #[derive(Debug, Clone)] pub struct BlockApprovalMeta { /// The hash of the block. pub hash: Hash, diff --git a/polkadot/node/service/Cargo.toml b/polkadot/node/service/Cargo.toml index 89f8212bf9d..8d50b54b2fd 100644 --- a/polkadot/node/service/Cargo.toml +++ b/polkadot/node/service/Cargo.toml @@ -116,6 +116,7 @@ polkadot-gossip-support = { optional = true, workspace = true, default-features polkadot-network-bridge = { optional = true, workspace = true, default-features = true } polkadot-node-collation-generation = { optional = true, workspace = true, default-features = true } polkadot-node-core-approval-voting = { optional = true, workspace = true, default-features = true } +polkadot-node-core-approval-voting-parallel = { optional = true, workspace = true, default-features = true } polkadot-node-core-av-store = { optional = true, workspace = true, default-features = true } polkadot-node-core-backing = { optional = true, workspace = true, default-features = true } polkadot-node-core-bitfield-signing = { optional = true, workspace = true, default-features = true } @@ -160,6 +161,7 @@ full-node = [ "polkadot-network-bridge", "polkadot-node-collation-generation", "polkadot-node-core-approval-voting", + "polkadot-node-core-approval-voting-parallel", "polkadot-node-core-av-store", "polkadot-node-core-backing", "polkadot-node-core-bitfield-signing", diff --git a/polkadot/node/service/src/lib.rs b/polkadot/node/service/src/lib.rs index 79424356880..d029f3be53e 100644 --- a/polkadot/node/service/src/lib.rs +++ b/polkadot/node/service/src/lib.rs @@ -660,6 +660,8 @@ pub struct NewFullParams { #[allow(dead_code)] pub malus_finality_delay: Option, pub hwbench: Option, + /// Enable approval voting processing in parallel. + pub enable_approval_voting_parallel: bool, } #[cfg(feature = "full-node")] @@ -753,6 +755,7 @@ pub fn new_full< execute_workers_max_num, prepare_workers_soft_max_num, prepare_workers_hard_max_num, + enable_approval_voting_parallel, }: NewFullParams, ) -> Result { use polkadot_availability_recovery::FETCH_CHUNKS_THRESHOLD; @@ -784,6 +787,14 @@ pub fn new_full< Some(backoff) }; + // Running approval voting in parallel is enabled by default on all networks except Polkadot and + // Kusama, unless explicitly enabled by the commandline option. + // This is meant to be temporary until we have enough confidence in the new system to enable it + // by default on all networks. + let enable_approval_voting_parallel = (!config.chain_spec.is_kusama() && + !config.chain_spec.is_polkadot()) || + enable_approval_voting_parallel; + let disable_grandpa = config.disable_grandpa; let name = config.network.node_name.clone(); @@ -806,6 +817,7 @@ pub fn new_full< overseer_handle.clone(), metrics, Some(basics.task_manager.spawn_handle()), + enable_approval_voting_parallel, ) } else { SelectRelayChain::new_longest_chain(basics.backend.clone()) @@ -1016,6 +1028,7 @@ pub fn new_full< dispute_coordinator_config, chain_selection_config, fetch_chunks_threshold, + enable_approval_voting_parallel, }) }; diff --git a/polkadot/node/service/src/overseer.rs b/polkadot/node/service/src/overseer.rs index 3c071e34fe1..a98b6bcb308 100644 --- a/polkadot/node/service/src/overseer.rs +++ b/polkadot/node/service/src/overseer.rs @@ -58,6 +58,9 @@ pub use polkadot_network_bridge::{ }; pub use polkadot_node_collation_generation::CollationGenerationSubsystem; pub use polkadot_node_core_approval_voting::ApprovalVotingSubsystem; +pub use polkadot_node_core_approval_voting_parallel::{ + ApprovalVotingParallelSubsystem, Metrics as ApprovalVotingParallelMetrics, +}; pub use polkadot_node_core_av_store::AvailabilityStoreSubsystem; pub use polkadot_node_core_backing::CandidateBackingSubsystem; pub use polkadot_node_core_bitfield_signing::BitfieldSigningSubsystem; @@ -139,9 +142,16 @@ pub struct ExtendedOverseerGenArgs { /// than the value put in here we always try to recovery availability from backers. /// The presence of this parameter here is needed to have different values per chain. pub fetch_chunks_threshold: Option, + /// Enable approval-voting-parallel subsystem and disable the standalone approval-voting and + /// approval-distribution subsystems. + pub enable_approval_voting_parallel: bool, } /// Obtain a prepared validator `Overseer`, that is initialized with all default values. +/// +/// The difference between this function and `validator_with_parallel_overseer_builder` is that this +/// function enables the standalone approval-voting and approval-distribution subsystems +/// and disables the approval-voting-parallel subsystem. pub fn validator_overseer_builder( OverseerGenArgs { runtime_client, @@ -174,6 +184,7 @@ pub fn validator_overseer_builder( dispute_coordinator_config, chain_selection_config, fetch_chunks_threshold, + enable_approval_voting_parallel, }: ExtendedOverseerGenArgs, ) -> Result< InitializedOverseerBuilder< @@ -203,6 +214,7 @@ pub fn validator_overseer_builder( CollatorProtocolSubsystem, ApprovalDistributionSubsystem, ApprovalVotingSubsystem, + DummySubsystem, GossipSupportSubsystem, DisputeCoordinatorSubsystem, DisputeDistributionSubsystem, @@ -223,7 +235,8 @@ where let spawner = SpawnGlue(spawner); let network_bridge_metrics: NetworkBridgeMetrics = Metrics::register(registry)?; - + let approval_voting_parallel_metrics: ApprovalVotingParallelMetrics = + Metrics::register(registry)?; let builder = Overseer::builder() .network_bridge_tx(NetworkBridgeTxSubsystem::new( network_service.clone(), @@ -241,6 +254,7 @@ where peerset_protocol_names, notification_services, notification_sinks, + enable_approval_voting_parallel, )) .availability_distribution(AvailabilityDistributionSubsystem::new( keystore.clone(), @@ -310,18 +324,19 @@ where rand::rngs::StdRng::from_entropy(), )) .approval_distribution(ApprovalDistributionSubsystem::new( - Metrics::register(registry)?, + approval_voting_parallel_metrics.approval_distribution_metrics(), approval_voting_config.slot_duration_millis, Arc::new(RealAssignmentCriteria {}), )) .approval_voting(ApprovalVotingSubsystem::with_config( - approval_voting_config, + approval_voting_config.clone(), parachains_db.clone(), keystore.clone(), Box::new(sync_service.clone()), - Metrics::register(registry)?, + approval_voting_parallel_metrics.approval_voting_metrics(), Arc::new(spawner.clone()), )) + .approval_voting_parallel(DummySubsystem) .gossip_support(GossipSupportSubsystem::new( keystore.clone(), authority_discovery_service.clone(), @@ -332,6 +347,229 @@ where dispute_coordinator_config, keystore.clone(), Metrics::register(registry)?, + enable_approval_voting_parallel, + )) + .dispute_distribution(DisputeDistributionSubsystem::new( + keystore.clone(), + dispute_req_receiver, + authority_discovery_service.clone(), + Metrics::register(registry)?, + )) + .chain_selection(ChainSelectionSubsystem::new(chain_selection_config, parachains_db)) + .prospective_parachains(ProspectiveParachainsSubsystem::new(Metrics::register(registry)?)) + .activation_external_listeners(Default::default()) + .span_per_active_leaf(Default::default()) + .active_leaves(Default::default()) + .supports_parachains(runtime_client) + .metrics(metrics) + .spawner(spawner); + + let builder = if let Some(capacity) = overseer_message_channel_capacity_override { + builder.message_channel_capacity(capacity) + } else { + builder + }; + Ok(builder) +} + +/// Obtain a prepared validator `Overseer`, that is initialized with all default values. +/// +/// The difference between this function and `validator_overseer_builder` is that this +/// function enables the approval-voting-parallel subsystem and disables the standalone +/// approval-voting and approval-distribution subsystems. +pub fn validator_with_parallel_overseer_builder( + OverseerGenArgs { + runtime_client, + network_service, + sync_service, + authority_discovery_service, + collation_req_v1_receiver: _, + collation_req_v2_receiver: _, + available_data_req_receiver, + registry, + spawner, + is_parachain_node, + overseer_message_channel_capacity_override, + req_protocol_names, + peerset_protocol_names, + notification_services, + }: OverseerGenArgs, + ExtendedOverseerGenArgs { + keystore, + parachains_db, + candidate_validation_config, + availability_config, + pov_req_receiver, + chunk_req_v1_receiver, + chunk_req_v2_receiver, + statement_req_receiver, + candidate_req_v2_receiver, + approval_voting_config, + dispute_req_receiver, + dispute_coordinator_config, + chain_selection_config, + fetch_chunks_threshold, + enable_approval_voting_parallel, + }: ExtendedOverseerGenArgs, +) -> Result< + InitializedOverseerBuilder< + SpawnGlue, + Arc, + CandidateValidationSubsystem, + PvfCheckerSubsystem, + CandidateBackingSubsystem, + StatementDistributionSubsystem, + AvailabilityDistributionSubsystem, + AvailabilityRecoverySubsystem, + BitfieldSigningSubsystem, + BitfieldDistributionSubsystem, + ProvisionerSubsystem, + RuntimeApiSubsystem, + AvailabilityStoreSubsystem, + NetworkBridgeRxSubsystem< + Arc, + AuthorityDiscoveryService, + >, + NetworkBridgeTxSubsystem< + Arc, + AuthorityDiscoveryService, + >, + ChainApiSubsystem, + CollationGenerationSubsystem, + CollatorProtocolSubsystem, + DummySubsystem, + DummySubsystem, + ApprovalVotingParallelSubsystem, + GossipSupportSubsystem, + DisputeCoordinatorSubsystem, + DisputeDistributionSubsystem, + ChainSelectionSubsystem, + ProspectiveParachainsSubsystem, + >, + Error, +> +where + RuntimeClient: RuntimeApiSubsystemClient + ChainApiBackend + AuxStore + 'static, + Spawner: 'static + SpawnNamed + Clone + Unpin, +{ + use polkadot_node_subsystem_util::metrics::Metrics; + + let metrics = ::register(registry)?; + let notification_sinks = Arc::new(Mutex::new(HashMap::new())); + + let spawner = SpawnGlue(spawner); + + let network_bridge_metrics: NetworkBridgeMetrics = Metrics::register(registry)?; + let approval_voting_parallel_metrics: ApprovalVotingParallelMetrics = + Metrics::register(registry)?; + let builder = Overseer::builder() + .network_bridge_tx(NetworkBridgeTxSubsystem::new( + network_service.clone(), + authority_discovery_service.clone(), + network_bridge_metrics.clone(), + req_protocol_names.clone(), + peerset_protocol_names.clone(), + notification_sinks.clone(), + )) + .network_bridge_rx(NetworkBridgeRxSubsystem::new( + network_service.clone(), + authority_discovery_service.clone(), + Box::new(sync_service.clone()), + network_bridge_metrics, + peerset_protocol_names, + notification_services, + notification_sinks, + enable_approval_voting_parallel, + )) + .availability_distribution(AvailabilityDistributionSubsystem::new( + keystore.clone(), + IncomingRequestReceivers { + pov_req_receiver, + chunk_req_v1_receiver, + chunk_req_v2_receiver, + }, + req_protocol_names.clone(), + Metrics::register(registry)?, + )) + .availability_recovery(AvailabilityRecoverySubsystem::for_validator( + fetch_chunks_threshold, + available_data_req_receiver, + &req_protocol_names, + Metrics::register(registry)?, + )) + .availability_store(AvailabilityStoreSubsystem::new( + parachains_db.clone(), + availability_config, + Box::new(sync_service.clone()), + Metrics::register(registry)?, + )) + .bitfield_distribution(BitfieldDistributionSubsystem::new(Metrics::register(registry)?)) + .bitfield_signing(BitfieldSigningSubsystem::new( + keystore.clone(), + Metrics::register(registry)?, + )) + .candidate_backing(CandidateBackingSubsystem::new( + keystore.clone(), + Metrics::register(registry)?, + )) + .candidate_validation(CandidateValidationSubsystem::with_config( + candidate_validation_config, + keystore.clone(), + Metrics::register(registry)?, // candidate-validation metrics + Metrics::register(registry)?, // validation host metrics + )) + .pvf_checker(PvfCheckerSubsystem::new(keystore.clone(), Metrics::register(registry)?)) + .chain_api(ChainApiSubsystem::new(runtime_client.clone(), Metrics::register(registry)?)) + .collation_generation(CollationGenerationSubsystem::new(Metrics::register(registry)?)) + .collator_protocol({ + let side = match is_parachain_node { + IsParachainNode::Collator(_) | IsParachainNode::FullNode => + return Err(Error::Overseer(SubsystemError::Context( + "build validator overseer for parachain node".to_owned(), + ))), + IsParachainNode::No => ProtocolSide::Validator { + keystore: keystore.clone(), + eviction_policy: Default::default(), + metrics: Metrics::register(registry)?, + }, + }; + CollatorProtocolSubsystem::new(side) + }) + .provisioner(ProvisionerSubsystem::new(Metrics::register(registry)?)) + .runtime_api(RuntimeApiSubsystem::new( + runtime_client.clone(), + Metrics::register(registry)?, + spawner.clone(), + )) + .statement_distribution(StatementDistributionSubsystem::new( + keystore.clone(), + statement_req_receiver, + candidate_req_v2_receiver, + Metrics::register(registry)?, + rand::rngs::StdRng::from_entropy(), + )) + .approval_distribution(DummySubsystem) + .approval_voting(DummySubsystem) + .approval_voting_parallel(ApprovalVotingParallelSubsystem::with_config( + approval_voting_config, + parachains_db.clone(), + keystore.clone(), + Box::new(sync_service.clone()), + approval_voting_parallel_metrics, + spawner.clone(), + overseer_message_channel_capacity_override, + )) + .gossip_support(GossipSupportSubsystem::new( + keystore.clone(), + authority_discovery_service.clone(), + Metrics::register(registry)?, + )) + .dispute_coordinator(DisputeCoordinatorSubsystem::new( + parachains_db.clone(), + dispute_coordinator_config, + keystore.clone(), + Metrics::register(registry)?, + enable_approval_voting_parallel, )) .dispute_distribution(DisputeDistributionSubsystem::new( keystore.clone(), @@ -407,6 +645,7 @@ pub fn collator_overseer_builder( DummySubsystem, DummySubsystem, DummySubsystem, + DummySubsystem, >, Error, > @@ -439,6 +678,7 @@ where peerset_protocol_names, notification_services, notification_sinks, + false, )) .availability_distribution(DummySubsystem) .availability_recovery(AvailabilityRecoverySubsystem::for_collator( @@ -481,6 +721,7 @@ where .statement_distribution(DummySubsystem) .approval_distribution(DummySubsystem) .approval_voting(DummySubsystem) + .approval_voting_parallel(DummySubsystem) .gossip_support(DummySubsystem) .dispute_coordinator(DummySubsystem) .dispute_distribution(DummySubsystem) @@ -537,9 +778,15 @@ impl OverseerGen for ValidatorOverseerGen { "create validator overseer as mandatory extended arguments were not provided" .to_owned(), )))?; - validator_overseer_builder(args, ext_args)? - .build_with_connector(connector) - .map_err(|e| e.into()) + if ext_args.enable_approval_voting_parallel { + validator_with_parallel_overseer_builder(args, ext_args)? + .build_with_connector(connector) + .map_err(|e| e.into()) + } else { + validator_overseer_builder(args, ext_args)? + .build_with_connector(connector) + .map_err(|e| e.into()) + } } } diff --git a/polkadot/node/service/src/relay_chain_selection.rs b/polkadot/node/service/src/relay_chain_selection.rs index c0b1ce8b0eb..e48874f01ca 100644 --- a/polkadot/node/service/src/relay_chain_selection.rs +++ b/polkadot/node/service/src/relay_chain_selection.rs @@ -39,8 +39,8 @@ use super::{HeaderProvider, HeaderProviderProvider}; use futures::channel::oneshot; use polkadot_node_primitives::MAX_FINALITY_LAG as PRIMITIVES_MAX_FINALITY_LAG; use polkadot_node_subsystem::messages::{ - ApprovalDistributionMessage, ApprovalVotingMessage, ChainSelectionMessage, - DisputeCoordinatorMessage, HighestApprovedAncestorBlock, + ApprovalDistributionMessage, ApprovalVotingMessage, ApprovalVotingParallelMessage, + ChainSelectionMessage, DisputeCoordinatorMessage, HighestApprovedAncestorBlock, }; use polkadot_node_subsystem_util::metrics::{self, prometheus}; use polkadot_overseer::{AllMessages, Handle}; @@ -169,6 +169,7 @@ where overseer: Handle, metrics: Metrics, spawn_handle: Option, + approval_voting_parallel_enabled: bool, ) -> Self { gum::debug!(target: LOG_TARGET, "Using dispute aware relay-chain selection algorithm",); @@ -179,6 +180,7 @@ where overseer, metrics, spawn_handle, + approval_voting_parallel_enabled, )), } } @@ -230,6 +232,7 @@ pub struct SelectRelayChainInner { overseer: OH, metrics: Metrics, spawn_handle: Option, + approval_voting_parallel_enabled: bool, } impl SelectRelayChainInner @@ -244,8 +247,15 @@ where overseer: OH, metrics: Metrics, spawn_handle: Option, + approval_voting_parallel_enabled: bool, ) -> Self { - SelectRelayChainInner { backend, overseer, metrics, spawn_handle } + SelectRelayChainInner { + backend, + overseer, + metrics, + spawn_handle, + approval_voting_parallel_enabled, + } } fn block_header(&self, hash: Hash) -> Result { @@ -284,6 +294,7 @@ where overseer: self.overseer.clone(), metrics: self.metrics.clone(), spawn_handle: self.spawn_handle.clone(), + approval_voting_parallel_enabled: self.approval_voting_parallel_enabled, } } } @@ -448,13 +459,25 @@ where // 2. Constrain according to `ApprovedAncestor`. let (subchain_head, subchain_number, subchain_block_descriptions) = { let (tx, rx) = oneshot::channel(); - overseer - .send_msg( - ApprovalVotingMessage::ApprovedAncestor(subchain_head, target_number, tx), - std::any::type_name::(), - ) - .await; - + if self.approval_voting_parallel_enabled { + overseer + .send_msg( + ApprovalVotingParallelMessage::ApprovedAncestor( + subchain_head, + target_number, + tx, + ), + std::any::type_name::(), + ) + .await; + } else { + overseer + .send_msg( + ApprovalVotingMessage::ApprovedAncestor(subchain_head, target_number, tx), + std::any::type_name::(), + ) + .await; + } match rx .await .map_err(Error::ApprovedAncestorCanceled) @@ -476,13 +499,23 @@ where // task for sending the message to not block here and delay finality. if let Some(spawn_handle) = &self.spawn_handle { let mut overseer_handle = self.overseer.clone(); + let approval_voting_parallel_enabled = self.approval_voting_parallel_enabled; let lag_update_task = async move { - overseer_handle - .send_msg( - ApprovalDistributionMessage::ApprovalCheckingLagUpdate(lag), - std::any::type_name::(), - ) - .await; + if approval_voting_parallel_enabled { + overseer_handle + .send_msg( + ApprovalVotingParallelMessage::ApprovalCheckingLagUpdate(lag), + std::any::type_name::(), + ) + .await; + } else { + overseer_handle + .send_msg( + ApprovalDistributionMessage::ApprovalCheckingLagUpdate(lag), + std::any::type_name::(), + ) + .await; + } }; spawn_handle.spawn( diff --git a/polkadot/node/service/src/tests.rs b/polkadot/node/service/src/tests.rs index 195432bcb75..85b750ddad6 100644 --- a/polkadot/node/service/src/tests.rs +++ b/polkadot/node/service/src/tests.rs @@ -83,6 +83,7 @@ fn test_harness>( context.sender().clone(), Default::default(), None, + false, ); let target_hash = case_vars.target_block; diff --git a/polkadot/node/subsystem-bench/Cargo.toml b/polkadot/node/subsystem-bench/Cargo.toml index ae798cf2640..293df9f6e6d 100644 --- a/polkadot/node/subsystem-bench/Cargo.toml +++ b/polkadot/node/subsystem-bench/Cargo.toml @@ -80,6 +80,7 @@ serde_yaml = { workspace = true } serde_json = { workspace = true } polkadot-node-core-approval-voting = { workspace = true, default-features = true } +polkadot-node-core-approval-voting-parallel = { workspace = true, default-features = true } polkadot-approval-distribution = { workspace = true, default-features = true } sp-consensus-babe = { workspace = true, default-features = true } sp-runtime = { workspace = true } diff --git a/polkadot/node/subsystem-bench/examples/approvals_no_shows.yaml b/polkadot/node/subsystem-bench/examples/approvals_no_shows.yaml index cae1a30914d..1423d324df3 100644 --- a/polkadot/node/subsystem-bench/examples/approvals_no_shows.yaml +++ b/polkadot/node/subsystem-bench/examples/approvals_no_shows.yaml @@ -9,6 +9,7 @@ TestConfiguration: coalesce_tranche_diff: 12 num_no_shows_per_candidate: 10 workdir_prefix: "/tmp/" + approval_voting_parallel_enabled: false n_validators: 500 n_cores: 100 min_pov_size: 1120 diff --git a/polkadot/node/subsystem-bench/examples/approvals_throughput.yaml b/polkadot/node/subsystem-bench/examples/approvals_throughput.yaml index 7edb48e302a..87c6103a5d0 100644 --- a/polkadot/node/subsystem-bench/examples/approvals_throughput.yaml +++ b/polkadot/node/subsystem-bench/examples/approvals_throughput.yaml @@ -9,6 +9,7 @@ TestConfiguration: coalesce_tranche_diff: 12 num_no_shows_per_candidate: 0 workdir_prefix: "/tmp" + approval_voting_parallel_enabled: true n_validators: 500 n_cores: 100 min_pov_size: 1120 diff --git a/polkadot/node/subsystem-bench/examples/approvals_throughput_best_case.yaml b/polkadot/node/subsystem-bench/examples/approvals_throughput_best_case.yaml index 7c24f50e6af..5e2ea3817d1 100644 --- a/polkadot/node/subsystem-bench/examples/approvals_throughput_best_case.yaml +++ b/polkadot/node/subsystem-bench/examples/approvals_throughput_best_case.yaml @@ -8,6 +8,7 @@ TestConfiguration: stop_when_approved: true coalesce_tranche_diff: 12 num_no_shows_per_candidate: 0 + approval_voting_parallel_enabled: false workdir_prefix: "/tmp/" n_validators: 500 n_cores: 100 diff --git a/polkadot/node/subsystem-bench/src/lib/approval/helpers.rs b/polkadot/node/subsystem-bench/src/lib/approval/helpers.rs index 4b2b9169682..a3a475ac6b9 100644 --- a/polkadot/node/subsystem-bench/src/lib/approval/helpers.rs +++ b/polkadot/node/subsystem-bench/src/lib/approval/helpers.rs @@ -21,8 +21,11 @@ use polkadot_node_network_protocol::{ View, }; use polkadot_node_primitives::approval::time::{Clock, SystemClock, Tick}; +use polkadot_node_subsystem::messages::{ + ApprovalDistributionMessage, ApprovalVotingParallelMessage, +}; use polkadot_node_subsystem_types::messages::{ - network_bridge_event::NewGossipTopology, ApprovalDistributionMessage, NetworkBridgeEvent, + network_bridge_event::NewGossipTopology, NetworkBridgeEvent, }; use polkadot_overseer::AllMessages; use polkadot_primitives::{ @@ -121,6 +124,7 @@ pub fn generate_topology(test_authorities: &TestAuthorities) -> SessionGridTopol pub fn generate_new_session_topology( test_authorities: &TestAuthorities, test_node: ValidatorIndex, + approval_voting_parallel_enabled: bool, ) -> Vec { let topology = generate_topology(test_authorities); @@ -129,14 +133,29 @@ pub fn generate_new_session_topology( topology, local_index: Some(test_node), }); - vec![AllMessages::ApprovalDistribution(ApprovalDistributionMessage::NetworkBridgeUpdate(event))] + vec![if approval_voting_parallel_enabled { + AllMessages::ApprovalVotingParallel(ApprovalVotingParallelMessage::NetworkBridgeUpdate( + event, + )) + } else { + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::NetworkBridgeUpdate(event)) + }] } /// Generates a peer view change for the passed `block_hash` -pub fn generate_peer_view_change_for(block_hash: Hash, peer_id: PeerId) -> AllMessages { +pub fn generate_peer_view_change_for( + block_hash: Hash, + peer_id: PeerId, + approval_voting_parallel_enabled: bool, +) -> AllMessages { let network = NetworkBridgeEvent::PeerViewChange(peer_id, View::new([block_hash], 0)); - - AllMessages::ApprovalDistribution(ApprovalDistributionMessage::NetworkBridgeUpdate(network)) + if approval_voting_parallel_enabled { + AllMessages::ApprovalVotingParallel(ApprovalVotingParallelMessage::NetworkBridgeUpdate( + network, + )) + } else { + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::NetworkBridgeUpdate(network)) + } } /// Helper function to create a a signature for the block header. diff --git a/polkadot/node/subsystem-bench/src/lib/approval/mod.rs b/polkadot/node/subsystem-bench/src/lib/approval/mod.rs index 9d85039b888..29ebc4a419a 100644 --- a/polkadot/node/subsystem-bench/src/lib/approval/mod.rs +++ b/polkadot/node/subsystem-bench/src/lib/approval/mod.rs @@ -49,20 +49,21 @@ use itertools::Itertools; use orchestra::TimeoutExt; use overseer::{metrics::Metrics as OverseerMetrics, MetricsTrait}; use polkadot_approval_distribution::ApprovalDistribution; +use polkadot_node_core_approval_voting_parallel::ApprovalVotingParallelSubsystem; use polkadot_node_primitives::approval::time::{ slot_number_to_tick, tick_to_slot_number, Clock, ClockExt, SystemClock, }; use polkadot_node_core_approval_voting::{ - ApprovalVotingSubsystem, Config as ApprovalVotingConfig, Metrics as ApprovalVotingMetrics, - RealAssignmentCriteria, + ApprovalVotingSubsystem, Config as ApprovalVotingConfig, RealAssignmentCriteria, }; use polkadot_node_network_protocol::v3 as protocol_v3; use polkadot_node_primitives::approval::{self, v1::RelayVRFStory}; -use polkadot_node_subsystem::{overseer, AllMessages, Overseer, OverseerConnector, SpawnGlue}; +use polkadot_node_subsystem::{ + messages::{ApprovalDistributionMessage, ApprovalVotingMessage, ApprovalVotingParallelMessage}, + overseer, AllMessages, Overseer, OverseerConnector, SpawnGlue, +}; use polkadot_node_subsystem_test_helpers::mock::new_block_import_info; -use polkadot_node_subsystem_types::messages::{ApprovalDistributionMessage, ApprovalVotingMessage}; -use polkadot_node_subsystem_util::metrics::Metrics; use polkadot_overseer::Handle as OverseerHandleReal; use polkadot_primitives::{ BlockNumber, CandidateEvent, CandidateIndex, CandidateReceipt, Hash, Header, Slot, ValidatorId, @@ -138,6 +139,9 @@ pub struct ApprovalsOptions { /// The number of no shows per candidate #[clap(short, long, default_value_t = 0)] pub num_no_shows_per_candidate: u32, + /// Enable approval voting parallel. + #[clap(short, long, default_value_t = true)] + pub approval_voting_parallel_enabled: bool, } impl ApprovalsOptions { @@ -272,7 +276,7 @@ pub struct ApprovalTestState { /// Total unique sent messages. total_unique_messages: Arc, /// Approval voting metrics. - approval_voting_metrics: ApprovalVotingMetrics, + approval_voting_parallel_metrics: polkadot_node_core_approval_voting_parallel::Metrics, /// The delta ticks from the tick the messages were generated to the the time we start this /// message. delta_tick_from_generated: Arc, @@ -330,7 +334,10 @@ impl ApprovalTestState { total_sent_messages_from_node: Arc::new(AtomicU64::new(0)), total_unique_messages: Arc::new(AtomicU64::new(0)), options, - approval_voting_metrics: ApprovalVotingMetrics::try_register(&dependencies.registry) + approval_voting_parallel_metrics: + polkadot_node_core_approval_voting_parallel::Metrics::try_register( + &dependencies.registry, + ) .unwrap(), delta_tick_from_generated: Arc::new(AtomicU64::new(630720000)), configuration: configuration.clone(), @@ -456,6 +463,14 @@ impl ApprovalTestState { }) .collect() } + + fn subsystem_name(&self) -> &'static str { + if self.options.approval_voting_parallel_enabled { + "approval-voting-parallel-subsystem" + } else { + "approval-distribution-subsystem" + } + } } impl ApprovalTestState { @@ -597,13 +612,16 @@ impl PeerMessageProducer { // so when the approval-distribution answered to it, we know it doesn't have anything // else to process. let (tx, rx) = oneshot::channel(); - let msg = ApprovalDistributionMessage::GetApprovalSignatures(HashSet::new(), tx); - self.send_overseer_message( - AllMessages::ApprovalDistribution(msg), - ValidatorIndex(0), - None, - ) - .await; + let msg = if self.options.approval_voting_parallel_enabled { + AllMessages::ApprovalVotingParallel( + ApprovalVotingParallelMessage::GetApprovalSignatures(HashSet::new(), tx), + ) + } else { + AllMessages::ApprovalDistribution( + ApprovalDistributionMessage::GetApprovalSignatures(HashSet::new(), tx), + ) + }; + self.send_overseer_message(msg, ValidatorIndex(0), None).await; rx.await.expect("Failed to get signatures"); self.notify_done.send(()).expect("Failed to notify main loop"); gum::info!("All messages processed "); @@ -743,7 +761,11 @@ impl PeerMessageProducer { for validator in 1..self.state.test_authorities.validator_authority_id.len() as u32 { let peer_id = self.state.test_authorities.peer_ids.get(validator as usize).unwrap(); let validator = ValidatorIndex(validator); - let view_update = generate_peer_view_change_for(block_info.hash, *peer_id); + let view_update = generate_peer_view_change_for( + block_info.hash, + *peer_id, + self.state.options.approval_voting_parallel_enabled, + ); self.send_overseer_message(view_update, validator, None).await; } @@ -808,24 +830,12 @@ fn build_overseer( let system_clock = PastSystemClock::new(SystemClock {}, state.delta_tick_from_generated.clone()); - let approval_voting = ApprovalVotingSubsystem::with_config_and_clock( - TEST_CONFIG, - Arc::new(db), - Arc::new(keystore), - Box::new(TestSyncOracle {}), - state.approval_voting_metrics.clone(), - Arc::new(system_clock.clone()), - Arc::new(SpawnGlue(spawn_task_handle.clone())), - ); + let keystore = Arc::new(keystore); + let db = Arc::new(db); - let approval_distribution = ApprovalDistribution::new_with_clock( - Metrics::register(Some(&dependencies.registry)).unwrap(), - SLOT_DURATION_MILLIS, - Box::new(system_clock.clone()), - Arc::new(RealAssignmentCriteria {}), - ); let mock_chain_api = MockChainApi::new(state.build_chain_api_state()); - let mock_chain_selection = MockChainSelection { state: state.clone(), clock: system_clock }; + let mock_chain_selection = + MockChainSelection { state: state.clone(), clock: system_clock.clone() }; let mock_runtime_api = MockRuntimeApi::new( config.clone(), state.test_authorities.clone(), @@ -840,11 +850,14 @@ fn build_overseer( network_interface.subsystem_sender(), state.test_authorities.clone(), ); - let mock_rx_bridge = MockNetworkBridgeRx::new(network_receiver, None); + let mock_rx_bridge = MockNetworkBridgeRx::new( + network_receiver, + None, + state.options.approval_voting_parallel_enabled, + ); let overseer_metrics = OverseerMetrics::try_register(&dependencies.registry).unwrap(); - let dummy = dummy_builder!(spawn_task_handle, overseer_metrics) - .replace_approval_distribution(|_| approval_distribution) - .replace_approval_voting(|_| approval_voting) + let task_handle = spawn_task_handle.clone(); + let dummy = dummy_builder!(task_handle, overseer_metrics) .replace_chain_api(|_| mock_chain_api) .replace_chain_selection(|_| mock_chain_selection) .replace_runtime_api(|_| mock_runtime_api) @@ -853,8 +866,45 @@ fn build_overseer( .replace_availability_recovery(|_| MockAvailabilityRecovery::new()) .replace_candidate_validation(|_| MockCandidateValidation::new()); - let (overseer, raw_handle) = - dummy.build_with_connector(overseer_connector).expect("Should not fail"); + let (overseer, raw_handle) = if state.options.approval_voting_parallel_enabled { + let approval_voting_parallel = ApprovalVotingParallelSubsystem::with_config_and_clock( + TEST_CONFIG, + db.clone(), + keystore.clone(), + Box::new(TestSyncOracle {}), + state.approval_voting_parallel_metrics.clone(), + Arc::new(system_clock.clone()), + SpawnGlue(spawn_task_handle.clone()), + None, + ); + dummy + .replace_approval_voting_parallel(|_| approval_voting_parallel) + .build_with_connector(overseer_connector) + .expect("Should not fail") + } else { + let approval_voting = ApprovalVotingSubsystem::with_config_and_clock( + TEST_CONFIG, + db.clone(), + keystore.clone(), + Box::new(TestSyncOracle {}), + state.approval_voting_parallel_metrics.approval_voting_metrics(), + Arc::new(system_clock.clone()), + Arc::new(SpawnGlue(spawn_task_handle.clone())), + ); + + let approval_distribution = ApprovalDistribution::new_with_clock( + state.approval_voting_parallel_metrics.approval_distribution_metrics(), + TEST_CONFIG.slot_duration_millis, + Arc::new(system_clock.clone()), + Arc::new(RealAssignmentCriteria {}), + ); + + dummy + .replace_approval_voting(|_| approval_voting) + .replace_approval_distribution(|_| approval_distribution) + .build_with_connector(overseer_connector) + .expect("Should not fail") + }; let overseer_handle = OverseerHandleReal::new(raw_handle); (overseer, overseer_handle) @@ -943,11 +993,18 @@ pub async fn bench_approvals_run( // First create the initialization messages that make sure that then node under // tests receives notifications about the topology used and the connected peers. let mut initialization_messages = env.network().generate_peer_connected(|e| { - AllMessages::ApprovalDistribution(ApprovalDistributionMessage::NetworkBridgeUpdate(e)) + if state.options.approval_voting_parallel_enabled { + AllMessages::ApprovalVotingParallel(ApprovalVotingParallelMessage::NetworkBridgeUpdate( + e, + )) + } else { + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::NetworkBridgeUpdate(e)) + } }); initialization_messages.extend(generate_new_session_topology( &state.test_authorities, ValidatorIndex(NODE_UNDER_TEST), + state.options.approval_voting_parallel_enabled, )); for message in initialization_messages { env.send_message(message).await; @@ -1012,7 +1069,14 @@ pub async fn bench_approvals_run( state.total_sent_messages_to_node.load(std::sync::atomic::Ordering::SeqCst) as usize; env.wait_until_metric( "polkadot_parachain_subsystem_bounded_received", - Some(("subsystem_name", "approval-distribution-subsystem")), + Some(( + "subsystem_name", + if state.options.approval_voting_parallel_enabled { + "approval-voting-parallel-subsystem" + } else { + "approval-distribution-subsystem" + }, + )), |value| { gum::debug!(target: LOG_TARGET, ?value, ?at_least_messages, "Waiting metric"); value >= at_least_messages as f64 @@ -1029,11 +1093,22 @@ pub async fn bench_approvals_run( CandidateEvent::CandidateIncluded(receipt_fetch, _head, _, _) => { let (tx, rx) = oneshot::channel(); - let msg = ApprovalVotingMessage::GetApprovalSignaturesForCandidate( - receipt_fetch.hash(), - tx, - ); - env.send_message(AllMessages::ApprovalVoting(msg)).await; + let msg = if state.options.approval_voting_parallel_enabled { + AllMessages::ApprovalVotingParallel( + ApprovalVotingParallelMessage::GetApprovalSignaturesForCandidate( + receipt_fetch.hash(), + tx, + ), + ) + } else { + AllMessages::ApprovalVoting( + ApprovalVotingMessage::GetApprovalSignaturesForCandidate( + receipt_fetch.hash(), + tx, + ), + ) + }; + env.send_message(msg).await; let result = rx.await.unwrap(); @@ -1057,7 +1132,7 @@ pub async fn bench_approvals_run( state.total_sent_messages_to_node.load(std::sync::atomic::Ordering::SeqCst) as usize; env.wait_until_metric( "polkadot_parachain_subsystem_bounded_received", - Some(("subsystem_name", "approval-distribution-subsystem")), + Some(("subsystem_name", state.subsystem_name())), |value| { gum::debug!(target: LOG_TARGET, ?value, ?at_least_messages, "Waiting metric"); value >= at_least_messages as f64 @@ -1098,5 +1173,8 @@ pub async fn bench_approvals_run( state.total_unique_messages.load(std::sync::atomic::Ordering::SeqCst) ); - env.collect_resource_usage(&["approval-distribution", "approval-voting"]) + env.collect_resource_usage( + &["approval-distribution", "approval-voting", "approval-voting-parallel"], + true, + ) } diff --git a/polkadot/node/subsystem-bench/src/lib/availability/mod.rs b/polkadot/node/subsystem-bench/src/lib/availability/mod.rs index 32dc8ae2c8d..f28adff315f 100644 --- a/polkadot/node/subsystem-bench/src/lib/availability/mod.rs +++ b/polkadot/node/subsystem-bench/src/lib/availability/mod.rs @@ -210,7 +210,7 @@ pub fn prepare_test( state.test_authorities.clone(), ); let network_bridge_rx = - network_bridge::MockNetworkBridgeRx::new(network_receiver, Some(chunk_req_v2_cfg)); + network_bridge::MockNetworkBridgeRx::new(network_receiver, Some(chunk_req_v2_cfg), false); let runtime_api = MockRuntimeApi::new( state.config.clone(), @@ -372,7 +372,7 @@ pub async fn benchmark_availability_read( ); env.stop().await; - env.collect_resource_usage(&["availability-recovery"]) + env.collect_resource_usage(&["availability-recovery"], false) } pub async fn benchmark_availability_write( @@ -506,9 +506,8 @@ pub async fn benchmark_availability_write( ); env.stop().await; - env.collect_resource_usage(&[ - "availability-distribution", - "bitfield-distribution", - "availability-store", - ]) + env.collect_resource_usage( + &["availability-distribution", "bitfield-distribution", "availability-store"], + false, + ) } diff --git a/polkadot/node/subsystem-bench/src/lib/display.rs b/polkadot/node/subsystem-bench/src/lib/display.rs index b153d54a7c3..c47dd9a0790 100644 --- a/polkadot/node/subsystem-bench/src/lib/display.rs +++ b/polkadot/node/subsystem-bench/src/lib/display.rs @@ -96,6 +96,23 @@ pub struct TestMetric { value: f64, } +impl TestMetric { + pub fn name(&self) -> &str { + &self.name + } + + pub fn value(&self) -> f64 { + self.value + } + + pub fn label_value(&self, label_name: &str) -> Option<&str> { + self.label_names + .iter() + .position(|name| name == label_name) + .and_then(|index| self.label_values.get(index).map(|s| s.as_str())) + } +} + impl Display for TestMetric { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( diff --git a/polkadot/node/subsystem-bench/src/lib/environment.rs b/polkadot/node/subsystem-bench/src/lib/environment.rs index a63f90da50b..4de683ad648 100644 --- a/polkadot/node/subsystem-bench/src/lib/environment.rs +++ b/polkadot/node/subsystem-bench/src/lib/environment.rs @@ -351,10 +351,14 @@ impl TestEnvironment { } } - pub fn collect_resource_usage(&self, subsystems_under_test: &[&str]) -> BenchmarkUsage { + pub fn collect_resource_usage( + &self, + subsystems_under_test: &[&str], + break_down_cpu_usage_per_task: bool, + ) -> BenchmarkUsage { BenchmarkUsage { network_usage: self.network_usage(), - cpu_usage: self.cpu_usage(subsystems_under_test), + cpu_usage: self.cpu_usage(subsystems_under_test, break_down_cpu_usage_per_task), } } @@ -378,7 +382,11 @@ impl TestEnvironment { ] } - fn cpu_usage(&self, subsystems_under_test: &[&str]) -> Vec { + fn cpu_usage( + &self, + subsystems_under_test: &[&str], + break_down_per_task: bool, + ) -> Vec { let test_metrics = super::display::parse_metrics(self.registry()); let mut usage = vec![]; let num_blocks = self.config().num_blocks as f64; @@ -392,6 +400,22 @@ impl TestEnvironment { total: total_cpu, per_block: total_cpu / num_blocks, }); + + if break_down_per_task { + for metric in subsystem_cpu_metrics.all() { + if metric.name() != "substrate_tasks_polling_duration_sum" { + continue; + } + + if let Some(task_name) = metric.label_value("task_name") { + usage.push(ResourceUsage { + resource_name: format!("{}/{}", subsystem, task_name), + total: metric.value(), + per_block: metric.value() / num_blocks, + }); + } + } + } } let test_env_cpu_metrics = diff --git a/polkadot/node/subsystem-bench/src/lib/mock/dummy.rs b/polkadot/node/subsystem-bench/src/lib/mock/dummy.rs index 8783b35f1c0..092a8fc5f4c 100644 --- a/polkadot/node/subsystem-bench/src/lib/mock/dummy.rs +++ b/polkadot/node/subsystem-bench/src/lib/mock/dummy.rs @@ -96,5 +96,6 @@ mock!(NetworkBridgeTx); mock!(ChainApi); mock!(ChainSelection); mock!(ApprovalVoting); +mock!(ApprovalVotingParallel); mock!(ApprovalDistribution); mock!(RuntimeApi); diff --git a/polkadot/node/subsystem-bench/src/lib/mock/mod.rs b/polkadot/node/subsystem-bench/src/lib/mock/mod.rs index da4ac05e33b..2ca47d9fc08 100644 --- a/polkadot/node/subsystem-bench/src/lib/mock/mod.rs +++ b/polkadot/node/subsystem-bench/src/lib/mock/mod.rs @@ -47,6 +47,7 @@ macro_rules! dummy_builder { // All subsystem except approval_voting and approval_distribution are mock subsystems. Overseer::builder() .approval_voting(MockApprovalVoting {}) + .approval_voting_parallel(MockApprovalVotingParallel {}) .approval_distribution(MockApprovalDistribution {}) .availability_recovery(MockAvailabilityRecovery {}) .candidate_validation(MockCandidateValidation {}) diff --git a/polkadot/node/subsystem-bench/src/lib/mock/network_bridge.rs b/polkadot/node/subsystem-bench/src/lib/mock/network_bridge.rs index d70953926d1..f5474a61e3d 100644 --- a/polkadot/node/subsystem-bench/src/lib/mock/network_bridge.rs +++ b/polkadot/node/subsystem-bench/src/lib/mock/network_bridge.rs @@ -24,13 +24,13 @@ use crate::{ use futures::{channel::mpsc::UnboundedSender, FutureExt, StreamExt}; use polkadot_node_network_protocol::Versioned; use polkadot_node_subsystem::{ - messages::NetworkBridgeTxMessage, overseer, SpawnedSubsystem, SubsystemError, -}; -use polkadot_node_subsystem_types::{ messages::{ - ApprovalDistributionMessage, BitfieldDistributionMessage, NetworkBridgeEvent, - StatementDistributionMessage, + ApprovalDistributionMessage, ApprovalVotingParallelMessage, NetworkBridgeTxMessage, }, + overseer, SpawnedSubsystem, SubsystemError, +}; +use polkadot_node_subsystem_types::{ + messages::{BitfieldDistributionMessage, NetworkBridgeEvent, StatementDistributionMessage}, OverseerSignal, }; use sc_network::{request_responses::ProtocolConfig, RequestFailure}; @@ -57,6 +57,8 @@ pub struct MockNetworkBridgeRx { network_receiver: NetworkInterfaceReceiver, /// Chunk request sender chunk_request_sender: Option, + /// Approval voting parallel enabled. + approval_voting_parallel_enabled: bool, } impl MockNetworkBridgeTx { @@ -73,8 +75,9 @@ impl MockNetworkBridgeRx { pub fn new( network_receiver: NetworkInterfaceReceiver, chunk_request_sender: Option, + approval_voting_parallel_enabled: bool, ) -> MockNetworkBridgeRx { - Self { network_receiver, chunk_request_sender } + Self { network_receiver, chunk_request_sender, approval_voting_parallel_enabled } } } @@ -199,9 +202,15 @@ impl MockNetworkBridgeRx { Versioned::V3( polkadot_node_network_protocol::v3::ValidationProtocol::ApprovalDistribution(msg) ) => { - ctx.send_message( - ApprovalDistributionMessage::NetworkBridgeUpdate(NetworkBridgeEvent::PeerMessage(peer_id, polkadot_node_network_protocol::Versioned::V3(msg))) - ).await; + if self.approval_voting_parallel_enabled { + ctx.send_message( + ApprovalVotingParallelMessage::NetworkBridgeUpdate(NetworkBridgeEvent::PeerMessage(peer_id, polkadot_node_network_protocol::Versioned::V3(msg))) + ).await; + } else { + ctx.send_message( + ApprovalDistributionMessage::NetworkBridgeUpdate(NetworkBridgeEvent::PeerMessage(peer_id, polkadot_node_network_protocol::Versioned::V3(msg))) + ).await; + } } Versioned::V3( polkadot_node_network_protocol::v3::ValidationProtocol::StatementDistribution(msg) diff --git a/polkadot/node/subsystem-bench/src/lib/statement/mod.rs b/polkadot/node/subsystem-bench/src/lib/statement/mod.rs index bd47505f56a..e2d50f28568 100644 --- a/polkadot/node/subsystem-bench/src/lib/statement/mod.rs +++ b/polkadot/node/subsystem-bench/src/lib/statement/mod.rs @@ -135,7 +135,8 @@ fn build_overseer( network_interface.subsystem_sender(), state.test_authorities.clone(), ); - let network_bridge_rx = MockNetworkBridgeRx::new(network_receiver, Some(candidate_req_cfg)); + let network_bridge_rx = + MockNetworkBridgeRx::new(network_receiver, Some(candidate_req_cfg), false); let dummy = dummy_builder!(spawn_task_handle, overseer_metrics) .replace_runtime_api(|_| mock_runtime_api) @@ -445,5 +446,5 @@ pub async fn benchmark_statement_distribution( ); env.stop().await; - env.collect_resource_usage(&["statement-distribution"]) + env.collect_resource_usage(&["statement-distribution"], false) } diff --git a/polkadot/node/subsystem-bench/src/lib/usage.rs b/polkadot/node/subsystem-bench/src/lib/usage.rs index 883e9aa7ad0..5f691ae2db3 100644 --- a/polkadot/node/subsystem-bench/src/lib/usage.rs +++ b/polkadot/node/subsystem-bench/src/lib/usage.rs @@ -32,14 +32,14 @@ impl std::fmt::Display for BenchmarkUsage { write!( f, "\n{}\n{}\n\n{}\n{}\n", - format!("{:<32}{:>12}{:>12}", "Network usage, KiB", "total", "per block").blue(), + format!("{:<64}{:>12}{:>12}", "Network usage, KiB", "total", "per block").blue(), self.network_usage .iter() .map(|v| v.to_string()) .sorted() .collect::>() .join("\n"), - format!("{:<32}{:>12}{:>12}", "CPU usage, seconds", "total", "per block").blue(), + format!("{:<64}{:>12}{:>12}", "CPU usage, seconds", "total", "per block").blue(), self.cpu_usage .iter() .map(|v| v.to_string()) @@ -134,7 +134,7 @@ pub struct ResourceUsage { impl std::fmt::Display for ResourceUsage { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{:<32}{:>12.4}{:>12.4}", self.resource_name.cyan(), self.total, self.per_block) + write!(f, "{:<64}{:>12.4}{:>12.4}", self.resource_name.cyan(), self.total, self.per_block) } } diff --git a/polkadot/node/subsystem-types/src/messages.rs b/polkadot/node/subsystem-types/src/messages.rs index 854a9da158b..fafc700e739 100644 --- a/polkadot/node/subsystem-types/src/messages.rs +++ b/polkadot/node/subsystem-types/src/messages.rs @@ -955,6 +955,103 @@ pub struct BlockDescription { pub candidates: Vec, } +/// Message to the approval voting parallel subsystem running both approval-distribution and +/// approval-voting logic in parallel. This is a combination of all the messages ApprovalVoting and +/// ApprovalDistribution subsystems can receive. +/// +/// The reason this exists is, so that we can keep both modes of running in the same polkadot +/// binary, based on the value of `--approval-voting-parallel-enabled`, we decide if we run with two +/// different subsystems for approval-distribution and approval-voting or run the approval-voting +/// parallel which has several parallel workers for the approval-distribution and a worker for +/// approval-voting. +/// +/// This is meant to be a temporary state until we can safely remove running the two subsystems +/// individually. +#[derive(Debug, derive_more::From)] +pub enum ApprovalVotingParallelMessage { + /// Gets mapped into `ApprovalVotingMessage::ApprovedAncestor` + ApprovedAncestor(Hash, BlockNumber, oneshot::Sender>), + + /// Gets mapped into `ApprovalVotingMessage::GetApprovalSignaturesForCandidate` + GetApprovalSignaturesForCandidate( + CandidateHash, + oneshot::Sender, ValidatorSignature)>>, + ), + /// Gets mapped into `ApprovalDistributionMessage::NewBlocks` + NewBlocks(Vec), + /// Gets mapped into `ApprovalDistributionMessage::DistributeAssignment` + DistributeAssignment(IndirectAssignmentCertV2, CandidateBitfield), + /// Gets mapped into `ApprovalDistributionMessage::DistributeApproval` + DistributeApproval(IndirectSignedApprovalVoteV2), + /// An update from the network bridge, gets mapped into + /// `ApprovalDistributionMessage::NetworkBridgeUpdate` + #[from] + NetworkBridgeUpdate(NetworkBridgeEvent), + + /// Gets mapped into `ApprovalDistributionMessage::GetApprovalSignatures` + GetApprovalSignatures( + HashSet<(Hash, CandidateIndex)>, + oneshot::Sender, ValidatorSignature)>>, + ), + /// Gets mapped into `ApprovalDistributionMessage::ApprovalCheckingLagUpdate` + ApprovalCheckingLagUpdate(BlockNumber), +} + +impl TryFrom for ApprovalVotingMessage { + type Error = (); + + fn try_from(msg: ApprovalVotingParallelMessage) -> Result { + match msg { + ApprovalVotingParallelMessage::ApprovedAncestor(hash, number, tx) => + Ok(ApprovalVotingMessage::ApprovedAncestor(hash, number, tx)), + ApprovalVotingParallelMessage::GetApprovalSignaturesForCandidate(candidate, tx) => + Ok(ApprovalVotingMessage::GetApprovalSignaturesForCandidate(candidate, tx)), + _ => Err(()), + } + } +} + +impl TryFrom for ApprovalDistributionMessage { + type Error = (); + + fn try_from(msg: ApprovalVotingParallelMessage) -> Result { + match msg { + ApprovalVotingParallelMessage::NewBlocks(blocks) => + Ok(ApprovalDistributionMessage::NewBlocks(blocks)), + ApprovalVotingParallelMessage::DistributeAssignment(assignment, claimed_cores) => + Ok(ApprovalDistributionMessage::DistributeAssignment(assignment, claimed_cores)), + ApprovalVotingParallelMessage::DistributeApproval(vote) => + Ok(ApprovalDistributionMessage::DistributeApproval(vote)), + ApprovalVotingParallelMessage::NetworkBridgeUpdate(msg) => + Ok(ApprovalDistributionMessage::NetworkBridgeUpdate(msg)), + ApprovalVotingParallelMessage::GetApprovalSignatures(candidate_indicies, tx) => + Ok(ApprovalDistributionMessage::GetApprovalSignatures(candidate_indicies, tx)), + ApprovalVotingParallelMessage::ApprovalCheckingLagUpdate(lag) => + Ok(ApprovalDistributionMessage::ApprovalCheckingLagUpdate(lag)), + _ => Err(()), + } + } +} + +impl From for ApprovalVotingParallelMessage { + fn from(msg: ApprovalDistributionMessage) -> Self { + match msg { + ApprovalDistributionMessage::NewBlocks(blocks) => + ApprovalVotingParallelMessage::NewBlocks(blocks), + ApprovalDistributionMessage::DistributeAssignment(cert, bitfield) => + ApprovalVotingParallelMessage::DistributeAssignment(cert, bitfield), + ApprovalDistributionMessage::DistributeApproval(vote) => + ApprovalVotingParallelMessage::DistributeApproval(vote), + ApprovalDistributionMessage::NetworkBridgeUpdate(msg) => + ApprovalVotingParallelMessage::NetworkBridgeUpdate(msg), + ApprovalDistributionMessage::GetApprovalSignatures(candidate_indicies, tx) => + ApprovalVotingParallelMessage::GetApprovalSignatures(candidate_indicies, tx), + ApprovalDistributionMessage::ApprovalCheckingLagUpdate(lag) => + ApprovalVotingParallelMessage::ApprovalCheckingLagUpdate(lag), + } + } +} + /// Response type to `ApprovalVotingMessage::ApprovedAncestor`. #[derive(Clone, Debug)] pub struct HighestApprovedAncestorBlock { diff --git a/polkadot/node/test/service/src/lib.rs b/polkadot/node/test/service/src/lib.rs index b1238788486..f879aa93df9 100644 --- a/polkadot/node/test/service/src/lib.rs +++ b/polkadot/node/test/service/src/lib.rs @@ -101,6 +101,7 @@ pub fn new_full( execute_workers_max_num: None, prepare_workers_hard_max_num: None, prepare_workers_soft_max_num: None, + enable_approval_voting_parallel: false, }, ), sc_network::config::NetworkBackendType::Litep2p => @@ -123,6 +124,7 @@ pub fn new_full( execute_workers_max_num: None, prepare_workers_hard_max_num: None, prepare_workers_soft_max_num: None, + enable_approval_voting_parallel: false, }, ), } diff --git a/polkadot/parachain/test-parachains/adder/collator/src/main.rs b/polkadot/parachain/test-parachains/adder/collator/src/main.rs index e8588274df2..4660b4d38f7 100644 --- a/polkadot/parachain/test-parachains/adder/collator/src/main.rs +++ b/polkadot/parachain/test-parachains/adder/collator/src/main.rs @@ -98,6 +98,7 @@ fn main() -> Result<()> { execute_workers_max_num: None, prepare_workers_hard_max_num: None, prepare_workers_soft_max_num: None, + enable_approval_voting_parallel: false, }, ) .map_err(|e| e.to_string())?; diff --git a/polkadot/parachain/test-parachains/undying/collator/src/main.rs b/polkadot/parachain/test-parachains/undying/collator/src/main.rs index 7198a831a47..3dfa714e6d1 100644 --- a/polkadot/parachain/test-parachains/undying/collator/src/main.rs +++ b/polkadot/parachain/test-parachains/undying/collator/src/main.rs @@ -100,6 +100,7 @@ fn main() -> Result<()> { execute_workers_max_num: None, prepare_workers_hard_max_num: None, prepare_workers_soft_max_num: None, + enable_approval_voting_parallel: false, }, ) .map_err(|e| e.to_string())?; diff --git a/polkadot/roadmap/implementers-guide/src/node/approval/approval-voting-parallel.md b/polkadot/roadmap/implementers-guide/src/node/approval/approval-voting-parallel.md new file mode 100644 index 00000000000..84661b7bf9b --- /dev/null +++ b/polkadot/roadmap/implementers-guide/src/node/approval/approval-voting-parallel.md @@ -0,0 +1,30 @@ +# Approval voting parallel + +The approval-voting-parallel subsystem acts as an orchestrator for the tasks handled by the [Approval Voting](approval-voting.md) +and [Approval Distribution](approval-distribution.md) subsystems. Initially, these two systems operated separately and interacted +with each other and other subsystems through orchestra. + +With approval-voting-parallel, we have a single subsystem that creates two types of workers: +- Four approval-distribution workers that operate in parallel, each handling tasks based on the validator_index of the message + originator. +- One approval-voting worker that performs the tasks previously managed by the standalone approval-voting subsystem. + +This subsystem does not maintain any state. Instead, it functions as an orchestrator that: +- Spawns and initializes each workers. +- Forwards each message and signal to the appropriate worker. +- Aggregates results for messages that require input from more than one worker, such as GetApprovalSignatures. + +## Forwarding logic + +The messages received and forwarded by approval-voting-parallel split in three categories: +- Signals which need to be forwarded to all workers. +- Messages that only the `approval-voting` worker needs to handle, `ApprovalVotingParallelMessage::ApprovedAncestor` + and `ApprovalVotingParallelMessage::GetApprovalSignaturesForCandidate` +- Control messages that all `approval-distribution` workers need to receive `ApprovalVotingParallelMessage::NewBlocks`, + `ApprovalVotingParallelMessage::ApprovalCheckingLagUpdate` and all network bridge variants `ApprovalVotingParallelMessage::NetworkBridgeUpdate` + except `ApprovalVotingParallelMessage::NetworkBridgeUpdate(NetworkBridgeEvent::PeerMessage)` +- Data messages `ApprovalVotingParallelMessage::NetworkBridgeUpdate(NetworkBridgeEvent::PeerMessage)` which need to be sent + just to a single `approval-distribution` worker based on the ValidatorIndex. The logic for assigning the work is: + ``` + assigned_worker_index = validator_index % number_of_workers; + ``` diff --git a/polkadot/zombienet_tests/functional/0009-approval-voting-coalescing.toml b/polkadot/zombienet_tests/functional/0009-approval-voting-coalescing.toml index 19c7015403d..113de0e73aa 100644 --- a/polkadot/zombienet_tests/functional/0009-approval-voting-coalescing.toml +++ b/polkadot/zombienet_tests/functional/0009-approval-voting-coalescing.toml @@ -18,7 +18,7 @@ requests = { memory = "2G", cpu = "1" } [[relaychain.node_groups]] name = "alice" - args = [ "-lparachain=trace,runtime=debug" ] + args = [ "-lparachain=debug,runtime=debug" ] count = 13 [[parachains]] diff --git a/polkadot/zombienet_tests/functional/0016-approval-voting-parallel.toml b/polkadot/zombienet_tests/functional/0016-approval-voting-parallel.toml new file mode 100644 index 00000000000..c035e23639c --- /dev/null +++ b/polkadot/zombienet_tests/functional/0016-approval-voting-parallel.toml @@ -0,0 +1,120 @@ +[settings] +timeout = 1000 + +[relaychain] +default_image = "{{ZOMBIENET_INTEGRATION_TEST_IMAGE}}" +chain = "rococo-local" + +[relaychain.genesis.runtimeGenesis.patch.configuration.config] + needed_approvals = 4 + relay_vrf_modulo_samples = 2 + +[relaychain.genesis.runtimeGenesis.patch.configuration.config.approval_voting_params] + max_approval_coalesce_count = 5 + +[relaychain.default_resources] +limits = { memory = "4G", cpu = "2" } +requests = { memory = "2G", cpu = "1" } + + [[relaychain.node_groups]] + name = "alice" + args = ["-lparachain=debug,runtime=debug", "--enable-approval-voting-parallel"] + count = 8 + + [[relaychain.node_groups]] + name = "bob" + args = ["-lparachain=debug,runtime=debug"] + count = 7 + +[[parachains]] +id = 2000 +addToGenesis = true +genesis_state_generator = "undying-collator export-genesis-state --pov-size=100000 --pvf-complexity=1" + + [parachains.collator] + name = "collator01" + image = "{{COL_IMAGE}}" + command = "undying-collator" + args = ["-lparachain=debug", "--pov-size=100000", "--pvf-complexity=1", "--parachain-id=2000"] + +[[parachains]] +id = 2001 +addToGenesis = true +genesis_state_generator = "undying-collator export-genesis-state --pov-size=100000 --pvf-complexity=10" + + [parachains.collator] + name = "collator02" + image = "{{COL_IMAGE}}" + command = "undying-collator" + args = ["-lparachain=debug", "--pov-size=100000", "--parachain-id=2001", "--pvf-complexity=10"] + +[[parachains]] +id = 2002 +addToGenesis = true +genesis_state_generator = "undying-collator export-genesis-state --pov-size=100000 --pvf-complexity=100" + + [parachains.collator] + name = "collator03" + image = "{{COL_IMAGE}}" + command = "undying-collator" + args = ["-lparachain=debug", "--pov-size=100000", "--parachain-id=2002", "--pvf-complexity=100"] + +[[parachains]] +id = 2003 +addToGenesis = true +genesis_state_generator = "undying-collator export-genesis-state --pov-size=20000 --pvf-complexity=300" + + [parachains.collator] + name = "collator04" + image = "{{COL_IMAGE}}" + command = "undying-collator" + args = ["-lparachain=debug", "--pov-size=20000", "--parachain-id=2003", "--pvf-complexity=300"] + +[[parachains]] +id = 2004 +addToGenesis = true +genesis_state_generator = "undying-collator export-genesis-state --pov-size=100000 --pvf-complexity=300" + + [parachains.collator] + name = "collator05" + image = "{{COL_IMAGE}}" + command = "undying-collator" + args = ["-lparachain=debug", "--pov-size=100000", "--parachain-id=2004", "--pvf-complexity=300"] + +[[parachains]] +id = 2005 +addToGenesis = true +genesis_state_generator = "undying-collator export-genesis-state --pov-size=20000 --pvf-complexity=400" + + [parachains.collator] + name = "collator06" + image = "{{COL_IMAGE}}" + command = "undying-collator" + args = ["-lparachain=debug", "--pov-size=20000", "--pvf-complexity=400", "--parachain-id=2005"] + +[[parachains]] +id = 2006 +addToGenesis = true +genesis_state_generator = "undying-collator export-genesis-state --pov-size=100000 --pvf-complexity=300" + + [parachains.collator] + name = "collator07" + image = "{{COL_IMAGE}}" + command = "undying-collator" + args = ["-lparachain=debug", "--pov-size=100000", "--pvf-complexity=300", "--parachain-id=2006"] + +[[parachains]] +id = 2007 +addToGenesis = true +genesis_state_generator = "undying-collator export-genesis-state --pov-size=100000 --pvf-complexity=300" + + [parachains.collator] + name = "collator08" + image = "{{COL_IMAGE}}" + command = "undying-collator" + args = ["-lparachain=debug", "--pov-size=100000", "--pvf-complexity=300", "--parachain-id=2007"] + +[types.Header] +number = "u64" +parent_hash = "Hash" +post_state = "Hash" \ No newline at end of file diff --git a/polkadot/zombienet_tests/functional/0016-approval-voting-parallel.zndsl b/polkadot/zombienet_tests/functional/0016-approval-voting-parallel.zndsl new file mode 100644 index 00000000000..d7070774747 --- /dev/null +++ b/polkadot/zombienet_tests/functional/0016-approval-voting-parallel.zndsl @@ -0,0 +1,35 @@ +Description: Check finality works with approval voting parallel enabled +Network: ./0016-approval-voting-parallel.toml +Creds: config + +# Check authority status. +alice: reports node_roles is 4 + +# Ensure parachains are registered. +alice: parachain 2000 is registered within 60 seconds +alice: parachain 2001 is registered within 60 seconds +alice: parachain 2002 is registered within 60 seconds +alice: parachain 2003 is registered within 60 seconds +alice: parachain 2004 is registered within 60 seconds +alice: parachain 2005 is registered within 60 seconds +alice: parachain 2006 is registered within 60 seconds +alice: parachain 2007 is registered within 60 seconds + +# Ensure parachains made progress. +alice: parachain 2000 block height is at least 10 within 300 seconds +alice: parachain 2001 block height is at least 10 within 300 seconds +alice: parachain 2002 block height is at least 10 within 300 seconds +alice: parachain 2003 block height is at least 10 within 300 seconds +alice: parachain 2004 block height is at least 10 within 300 seconds +alice: parachain 2005 block height is at least 10 within 300 seconds +alice: parachain 2006 block height is at least 10 within 300 seconds +alice: parachain 2007 block height is at least 10 within 300 seconds + +alice: reports substrate_block_height{status="finalized"} is at least 30 within 180 seconds +bob: reports substrate_block_height{status="finalized"} is at least 30 within 180 seconds + +alice: reports polkadot_parachain_approval_checking_finality_lag < 3 +bob: reports polkadot_parachain_approval_checking_finality_lag < 3 + +alice: reports polkadot_parachain_approvals_no_shows_total < 3 within 10 seconds +bob: reports polkadot_parachain_approvals_no_shows_total < 3 within 10 seconds diff --git a/prdoc/pr_4849.prdoc b/prdoc/pr_4849.prdoc new file mode 100644 index 00000000000..18529515106 --- /dev/null +++ b/prdoc/pr_4849.prdoc @@ -0,0 +1,47 @@ +title: Introduce approval-voting-parallel subsystem + +doc: + - audience: Node Dev + description: | + This introduces a new subsystem called approval-voting-parallel. It combines the tasks + previously handled by the approval-voting and approval-distribution subsystems. + + The new subsystem is enabled by default on all test networks. On production networks + like Polkadot and Kusama, the legacy system with two separate subsystems is still in use. + However, there is a CLI option --enable-approval-voting-parallel to gradually roll out + the new subsystem on specific nodes. Once we are confident that it works as expected, + it will be enabled by default on all networks. + + The approval-voting-parallel subsystem coordinates two groups of workers: + - Four approval-distribution workers that operate in parallel, each handling tasks based + on the validator_index of the message originator. + - One approval-voting worker that performs the tasks previously managed by the standalone + approval-voting subsystem. + +crates: + - name: polkadot-overseer + bump: major + - name: polkadot-node-primitives + bump: major + - name: polkadot-node-subsystem-types + bump: major + - name: polkadot-service + bump: major + - name: polkadot-approval-distribution + bump: major + - name: polkadot-node-core-approval-voting + bump: major + - name: polkadot-node-core-approval-voting-parallel + bump: major + - name: polkadot-network-bridge + bump: major + - name: polkadot-node-core-dispute-coordinator + bump: major + - name: cumulus-relay-chain-inprocess-interface + bump: major + - name: polkadot-cli + bump: major + - name: polkadot + bump: major + - name: polkadot-sdk + bump: minor diff --git a/umbrella/Cargo.toml b/umbrella/Cargo.toml index b7c1c375094..83cbebbc61c 100644 --- a/umbrella/Cargo.toml +++ b/umbrella/Cargo.toml @@ -600,7 +600,7 @@ runtime = [ "sp-wasm-interface", "sp-weights", ] -node = ["asset-test-utils", "bridge-hub-test-utils", "cumulus-client-cli", "cumulus-client-collator", "cumulus-client-consensus-aura", "cumulus-client-consensus-common", "cumulus-client-consensus-proposer", "cumulus-client-consensus-relay-chain", "cumulus-client-network", "cumulus-client-parachain-inherent", "cumulus-client-pov-recovery", "cumulus-client-service", "cumulus-relay-chain-inprocess-interface", "cumulus-relay-chain-interface", "cumulus-relay-chain-minimal-node", "cumulus-relay-chain-rpc-interface", "cumulus-test-relay-sproof-builder", "emulated-integration-tests-common", "fork-tree", "frame-benchmarking-cli", "frame-remote-externalities", "frame-support-procedural-tools", "generate-bags", "mmr-gadget", "mmr-rpc", "pallet-contracts-mock-network", "pallet-revive-mock-network", "pallet-transaction-payment-rpc", "parachains-runtimes-test-utils", "polkadot-approval-distribution", "polkadot-availability-bitfield-distribution", "polkadot-availability-distribution", "polkadot-availability-recovery", "polkadot-cli", "polkadot-collator-protocol", "polkadot-dispute-distribution", "polkadot-erasure-coding", "polkadot-gossip-support", "polkadot-network-bridge", "polkadot-node-collation-generation", "polkadot-node-core-approval-voting", "polkadot-node-core-av-store", "polkadot-node-core-backing", "polkadot-node-core-bitfield-signing", "polkadot-node-core-candidate-validation", "polkadot-node-core-chain-api", "polkadot-node-core-chain-selection", "polkadot-node-core-dispute-coordinator", "polkadot-node-core-parachains-inherent", "polkadot-node-core-prospective-parachains", "polkadot-node-core-provisioner", "polkadot-node-core-pvf", "polkadot-node-core-pvf-checker", "polkadot-node-core-pvf-common", "polkadot-node-core-pvf-execute-worker", "polkadot-node-core-pvf-prepare-worker", "polkadot-node-core-runtime-api", "polkadot-node-jaeger", "polkadot-node-metrics", "polkadot-node-network-protocol", "polkadot-node-primitives", "polkadot-node-subsystem", "polkadot-node-subsystem-types", "polkadot-node-subsystem-util", "polkadot-overseer", "polkadot-parachain-lib", "polkadot-rpc", "polkadot-service", "polkadot-statement-distribution", "polkadot-statement-table", "sc-allocator", "sc-authority-discovery", "sc-basic-authorship", "sc-block-builder", "sc-chain-spec", "sc-cli", "sc-client-api", "sc-client-db", "sc-consensus", "sc-consensus-aura", "sc-consensus-babe", "sc-consensus-babe-rpc", "sc-consensus-beefy", "sc-consensus-beefy-rpc", "sc-consensus-epochs", "sc-consensus-grandpa", "sc-consensus-grandpa-rpc", "sc-consensus-manual-seal", "sc-consensus-pow", "sc-consensus-slots", "sc-executor", "sc-executor-common", "sc-executor-polkavm", "sc-executor-wasmtime", "sc-informant", "sc-keystore", "sc-mixnet", "sc-network", "sc-network-common", "sc-network-gossip", "sc-network-light", "sc-network-statement", "sc-network-sync", "sc-network-transactions", "sc-network-types", "sc-offchain", "sc-proposer-metrics", "sc-rpc", "sc-rpc-api", "sc-rpc-server", "sc-rpc-spec-v2", "sc-service", "sc-state-db", "sc-statement-store", "sc-storage-monitor", "sc-sync-state-rpc", "sc-sysinfo", "sc-telemetry", "sc-tracing", "sc-transaction-pool", "sc-transaction-pool-api", "sc-utils", "snowbridge-runtime-test-common", "sp-blockchain", "sp-consensus", "sp-core-hashing", "sp-core-hashing-proc-macro", "sp-database", "sp-maybe-compressed-blob", "sp-panic-handler", "sp-rpc", "staging-chain-spec-builder", "staging-node-inspect", "staging-tracking-allocator", "std", "subkey", "substrate-build-script-utils", "substrate-frame-rpc-support", "substrate-frame-rpc-system", "substrate-prometheus-endpoint", "substrate-rpc-client", "substrate-state-trie-migration-rpc", "substrate-wasm-builder", "tracing-gum", "xcm-emulator", "xcm-simulator"] +node = ["asset-test-utils", "bridge-hub-test-utils", "cumulus-client-cli", "cumulus-client-collator", "cumulus-client-consensus-aura", "cumulus-client-consensus-common", "cumulus-client-consensus-proposer", "cumulus-client-consensus-relay-chain", "cumulus-client-network", "cumulus-client-parachain-inherent", "cumulus-client-pov-recovery", "cumulus-client-service", "cumulus-relay-chain-inprocess-interface", "cumulus-relay-chain-interface", "cumulus-relay-chain-minimal-node", "cumulus-relay-chain-rpc-interface", "cumulus-test-relay-sproof-builder", "emulated-integration-tests-common", "fork-tree", "frame-benchmarking-cli", "frame-remote-externalities", "frame-support-procedural-tools", "generate-bags", "mmr-gadget", "mmr-rpc", "pallet-contracts-mock-network", "pallet-revive-mock-network", "pallet-transaction-payment-rpc", "parachains-runtimes-test-utils", "polkadot-approval-distribution", "polkadot-availability-bitfield-distribution", "polkadot-availability-distribution", "polkadot-availability-recovery", "polkadot-cli", "polkadot-collator-protocol", "polkadot-dispute-distribution", "polkadot-erasure-coding", "polkadot-gossip-support", "polkadot-network-bridge", "polkadot-node-collation-generation", "polkadot-node-core-approval-voting", "polkadot-node-core-approval-voting-parallel", "polkadot-node-core-av-store", "polkadot-node-core-backing", "polkadot-node-core-bitfield-signing", "polkadot-node-core-candidate-validation", "polkadot-node-core-chain-api", "polkadot-node-core-chain-selection", "polkadot-node-core-dispute-coordinator", "polkadot-node-core-parachains-inherent", "polkadot-node-core-prospective-parachains", "polkadot-node-core-provisioner", "polkadot-node-core-pvf", "polkadot-node-core-pvf-checker", "polkadot-node-core-pvf-common", "polkadot-node-core-pvf-execute-worker", "polkadot-node-core-pvf-prepare-worker", "polkadot-node-core-runtime-api", "polkadot-node-jaeger", "polkadot-node-metrics", "polkadot-node-network-protocol", "polkadot-node-primitives", "polkadot-node-subsystem", "polkadot-node-subsystem-types", "polkadot-node-subsystem-util", "polkadot-overseer", "polkadot-parachain-lib", "polkadot-rpc", "polkadot-service", "polkadot-statement-distribution", "polkadot-statement-table", "sc-allocator", "sc-authority-discovery", "sc-basic-authorship", "sc-block-builder", "sc-chain-spec", "sc-cli", "sc-client-api", "sc-client-db", "sc-consensus", "sc-consensus-aura", "sc-consensus-babe", "sc-consensus-babe-rpc", "sc-consensus-beefy", "sc-consensus-beefy-rpc", "sc-consensus-epochs", "sc-consensus-grandpa", "sc-consensus-grandpa-rpc", "sc-consensus-manual-seal", "sc-consensus-pow", "sc-consensus-slots", "sc-executor", "sc-executor-common", "sc-executor-polkavm", "sc-executor-wasmtime", "sc-informant", "sc-keystore", "sc-mixnet", "sc-network", "sc-network-common", "sc-network-gossip", "sc-network-light", "sc-network-statement", "sc-network-sync", "sc-network-transactions", "sc-network-types", "sc-offchain", "sc-proposer-metrics", "sc-rpc", "sc-rpc-api", "sc-rpc-server", "sc-rpc-spec-v2", "sc-service", "sc-state-db", "sc-statement-store", "sc-storage-monitor", "sc-sync-state-rpc", "sc-sysinfo", "sc-telemetry", "sc-tracing", "sc-transaction-pool", "sc-transaction-pool-api", "sc-utils", "snowbridge-runtime-test-common", "sp-blockchain", "sp-consensus", "sp-core-hashing", "sp-core-hashing-proc-macro", "sp-database", "sp-maybe-compressed-blob", "sp-panic-handler", "sp-rpc", "staging-chain-spec-builder", "staging-node-inspect", "staging-tracking-allocator", "std", "subkey", "substrate-build-script-utils", "substrate-frame-rpc-support", "substrate-frame-rpc-system", "substrate-prometheus-endpoint", "substrate-rpc-client", "substrate-state-trie-migration-rpc", "substrate-wasm-builder", "tracing-gum", "xcm-emulator", "xcm-simulator"] tuples-96 = [ "frame-support-procedural?/tuples-96", "frame-support?/tuples-96", @@ -1967,6 +1967,11 @@ path = "../polkadot/node/core/approval-voting" default-features = false optional = true +[dependencies.polkadot-node-core-approval-voting-parallel] +path = "../polkadot/node/core/approval-voting-parallel" +default-features = false +optional = true + [dependencies.polkadot-node-core-av-store] path = "../polkadot/node/core/av-store" default-features = false diff --git a/umbrella/src/lib.rs b/umbrella/src/lib.rs index b7b9c15fe58..4a653dab99b 100644 --- a/umbrella/src/lib.rs +++ b/umbrella/src/lib.rs @@ -796,6 +796,10 @@ pub use polkadot_node_collation_generation; #[cfg(feature = "polkadot-node-core-approval-voting")] pub use polkadot_node_core_approval_voting; +/// Approval Voting Subsystem running approval work in parallel. +#[cfg(feature = "polkadot-node-core-approval-voting-parallel")] +pub use polkadot_node_core_approval_voting_parallel; + /// The Availability Store subsystem. Wrapper over the DB that stores availability data and /// chunks. #[cfg(feature = "polkadot-node-core-av-store")] -- GitLab From 7626a9d6c0208dff74943abab6f436b458b26c5a Mon Sep 17 00:00:00 2001 From: Alexander Samusev <41779041+alvicsam@users.noreply.github.com> Date: Thu, 26 Sep 2024 17:54:10 +0200 Subject: [PATCH 111/125] [ci] Disable cargo-hfuzz, disable cargo-doc (#5843) Changes in PR: - disabled cargo-hfuzz until [the issue](https://github.com/paritytech/polkadot-sdk/issues/5812) is fixed. - enabled condition to skip jobs when no rust files are changed --- .github/workflows/check-runtime-migration.yml | 2 +- .github/workflows/checks.yml | 7 +- .github/workflows/docs.yml | 11 ++- .github/workflows/tests-linux-stable.yml | 7 +- .github/workflows/tests-misc.yml | 88 +++++++++++-------- .github/workflows/tests.yml | 16 ++-- .gitlab/pipeline/test.yml | 1 + 7 files changed, 71 insertions(+), 61 deletions(-) diff --git a/.github/workflows/check-runtime-migration.yml b/.github/workflows/check-runtime-migration.yml index 8185cf171ae..8d30e752b11 100644 --- a/.github/workflows/check-runtime-migration.yml +++ b/.github/workflows/check-runtime-migration.yml @@ -17,13 +17,13 @@ concurrency: cancel-in-progress: true jobs: - preflight: uses: ./.github/workflows/reusable-preflight.yml # More info can be found here: https://github.com/paritytech/polkadot/pull/5865 check-runtime-migration: runs-on: ${{ needs.preflight.outputs.RUNNER }} + if: ${{ needs.preflight.outputs.changes_rust }} # We need to set this to rather long to allow the snapshot to be created, but the average time # should be much lower. timeout-minutes: 60 diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index f765d79254c..fa1d5f2bdd2 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -15,14 +15,13 @@ concurrency: permissions: {} jobs: - preflight: uses: ./.github/workflows/reusable-preflight.yml cargo-clippy: runs-on: ${{ needs.preflight.outputs.RUNNER }} needs: [preflight] - # if: ${{ needs.preflight.outputs.changes_rust }} + if: ${{ needs.preflight.outputs.changes_rust }} timeout-minutes: 40 container: image: ${{ needs.preflight.outputs.IMAGE }} @@ -38,7 +37,7 @@ jobs: check-try-runtime: runs-on: ${{ needs.preflight.outputs.RUNNER }} needs: [preflight] - # if: ${{ needs.preflight.outputs.changes_rust }} + if: ${{ needs.preflight.outputs.changes_rust }} timeout-minutes: 40 container: image: ${{ needs.preflight.outputs.IMAGE }} @@ -57,7 +56,7 @@ jobs: check-core-crypto-features: runs-on: ${{ needs.preflight.outputs.RUNNER }} needs: [preflight] - # if: ${{ needs.preflight.outputs.changes_rust }} + if: ${{ needs.preflight.outputs.changes_rust }} timeout-minutes: 30 container: image: ${{ needs.preflight.outputs.IMAGE }} diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 514bac3973b..44ad9613997 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -9,15 +9,17 @@ on: merge_group: concurrency: - group: ${{ github.ref }} - cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true jobs: preflight: + if: contains(github.event.label.name, 'GHA-migration') uses: ./.github/workflows/reusable-preflight.yml test-rustdoc: - runs-on: arc-runners-polkadot-sdk-beefy + runs-on: ${{ needs.preflight.outputs.RUNNER }} + if: ${{ needs.preflight.outputs.changes_rust }} needs: [preflight] container: image: ${{ needs.preflight.outputs.IMAGE }} @@ -38,7 +40,8 @@ jobs: RUSTFLAGS: "-Cdebug-assertions=y -Dwarnings" build-rustdoc: - runs-on: arc-runners-polkadot-sdk-beefy + runs-on: ${{ needs.preflight.outputs.RUNNER }} + if: ${{ needs.preflight.outputs.changes_rust }} needs: [preflight, test-rustdoc] container: image: ${{ needs.preflight.outputs.IMAGE }} diff --git a/.github/workflows/tests-linux-stable.yml b/.github/workflows/tests-linux-stable.yml index 994f1c9b78e..a23a7ad67de 100644 --- a/.github/workflows/tests-linux-stable.yml +++ b/.github/workflows/tests-linux-stable.yml @@ -13,13 +13,12 @@ concurrency: cancel-in-progress: true jobs: - preflight: uses: ./.github/workflows/reusable-preflight.yml test-linux-stable-int: needs: [preflight] - # if: ${{ needs.preflight.outputs.changes_rust }} + if: ${{ needs.preflight.outputs.changes_rust }} runs-on: ${{ needs.preflight.outputs.RUNNER }} timeout-minutes: 60 container: @@ -40,7 +39,7 @@ jobs: # https://github.com/paritytech/ci_cd/issues/864 test-linux-stable-runtime-benchmarks: needs: [preflight] - # if: ${{ needs.preflight.outputs.changes_rust }} + if: ${{ needs.preflight.outputs.changes_rust }} runs-on: ${{ needs.preflight.outputs.RUNNER }} timeout-minutes: 60 container: @@ -58,7 +57,7 @@ jobs: test-linux-stable: needs: [preflight] - # if: ${{ needs.preflight.outputs.changes_rust }} + if: ${{ needs.preflight.outputs.changes_rust }} runs-on: ${{ matrix.runners }} timeout-minutes: 60 strategy: diff --git a/.github/workflows/tests-misc.yml b/.github/workflows/tests-misc.yml index 90162adc225..7f3e01eb424 100644 --- a/.github/workflows/tests-misc.yml +++ b/.github/workflows/tests-misc.yml @@ -22,6 +22,7 @@ jobs: test-full-crypto-feature: needs: [preflight] runs-on: ${{ needs.preflight.outputs.RUNNER }} + if: ${{ needs.preflight.outputs.changes_rust }} timeout-minutes: 60 container: image: ${{ needs.preflight.outputs.IMAGE }} @@ -46,6 +47,7 @@ jobs: # into one job needs: [preflight, test-full-crypto-feature] runs-on: ${{ needs.preflight.outputs.RUNNER }} + if: ${{ needs.preflight.outputs.changes_rust }} container: image: ${{ needs.preflight.outputs.IMAGE }} env: @@ -67,6 +69,7 @@ jobs: timeout-minutes: 60 needs: [preflight] runs-on: ${{ needs.preflight.outputs.RUNNER }} + if: ${{ needs.preflight.outputs.changes_rust }} container: image: ${{ needs.preflight.outputs.IMAGE }} env: @@ -95,6 +98,7 @@ jobs: timeout-minutes: 20 needs: [preflight, test-frame-examples-compile-to-wasm] runs-on: ${{ needs.preflight.outputs.RUNNER }} + if: ${{ needs.preflight.outputs.changes_rust }} container: image: ${{ needs.preflight.outputs.IMAGE }} env: @@ -194,6 +198,7 @@ jobs: needs: [preflight] timeout-minutes: 30 runs-on: ${{ needs.preflight.outputs.RUNNER }} + if: ${{ needs.preflight.outputs.changes_rust }} container: image: ${{ needs.preflight.outputs.IMAGE }} steps: @@ -226,6 +231,7 @@ jobs: timeout-minutes: 20 needs: [preflight, test-node-metrics] runs-on: ${{ needs.preflight.outputs.RUNNER }} + if: ${{ needs.preflight.outputs.changes_rust }} container: image: ${{ needs.preflight.outputs.IMAGE }} steps: @@ -241,6 +247,7 @@ jobs: timeout-minutes: 20 needs: [preflight, check-tracing] runs-on: ${{ needs.preflight.outputs.RUNNER }} + if: ${{ needs.preflight.outputs.changes_rust }} container: image: ${{ needs.preflight.outputs.IMAGE }} steps: @@ -251,50 +258,52 @@ jobs: run: | forklift cargo build --locked -p westend-runtime --features metadata-hash - cargo-hfuzz: - timeout-minutes: 20 - needs: [preflight, check-metadata-hash] - runs-on: ${{ needs.preflight.outputs.RUNNER }} - container: - image: ${{ needs.preflight.outputs.IMAGE }} - env: - # max 10s per iteration, 60s per file - HFUZZ_RUN_ARGS: | - --exit_upon_crash - --exit_code_upon_crash 1 - --timeout 10 - --run_time 60 - - # use git version of honggfuzz-rs until v0.5.56 is out, we need a few recent changes: - # https://github.com/rust-fuzz/honggfuzz-rs/pull/75 to avoid breakage on debian - # https://github.com/rust-fuzz/honggfuzz-rs/pull/81 fix to the above pr - # https://github.com/rust-fuzz/honggfuzz-rs/pull/82 fix for handling absolute CARGO_TARGET_DIR - HFUZZ_BUILD_ARGS: | - --config=patch.crates-io.honggfuzz.git="https://github.com/altaua/honggfuzz-rs" - --config=patch.crates-io.honggfuzz.rev="205f7c8c059a0d98fe1cb912cdac84f324cb6981" - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Run honggfuzz - run: | - cd substrate/primitives/arithmetic/fuzzer - forklift cargo hfuzz build - for target in $(cargo read-manifest | jq -r '.targets | .[] | .name'); - do - forklift cargo hfuzz run "$target" || { printf "fuzzing failure for %s\n" "$target"; exit 1; }; - done - - - name: Upload artifacts - uses: actions/upload-artifact@v4.3.6 - with: - path: substrate/primitives/arithmetic/fuzzer/hfuzz_workspace/ - name: hfuzz-${{ github.sha }} + # disabled until https://github.com/paritytech/polkadot-sdk/issues/5812 is resolved + # cargo-hfuzz: + # timeout-minutes: 20 + # needs: [preflight, check-metadata-hash] + # runs-on: ${{ needs.preflight.outputs.RUNNER }} + # container: + # image: ${{ needs.preflight.outputs.IMAGE }} + # env: + # # max 10s per iteration, 60s per file + # HFUZZ_RUN_ARGS: | + # --exit_upon_crash + # --exit_code_upon_crash 1 + # --timeout 10 + # --run_time 60 + + # # use git version of honggfuzz-rs until v0.5.56 is out, we need a few recent changes: + # # https://github.com/rust-fuzz/honggfuzz-rs/pull/75 to avoid breakage on debian + # # https://github.com/rust-fuzz/honggfuzz-rs/pull/81 fix to the above pr + # # https://github.com/rust-fuzz/honggfuzz-rs/pull/82 fix for handling absolute CARGO_TARGET_DIR + # HFUZZ_BUILD_ARGS: | + # --config=patch.crates-io.honggfuzz.git="https://github.com/altaua/honggfuzz-rs" + # --config=patch.crates-io.honggfuzz.rev="205f7c8c059a0d98fe1cb912cdac84f324cb6981" + # steps: + # - name: Checkout + # uses: actions/checkout@v4 + + # - name: Run honggfuzz + # run: | + # cd substrate/primitives/arithmetic/fuzzer + # forklift cargo hfuzz build + # for target in $(cargo read-manifest | jq -r '.targets | .[] | .name'); + # do + # forklift cargo hfuzz run "$target" || { printf "fuzzing failure for %s\n" "$target"; exit 1; }; + # done + + # - name: Upload artifacts + # uses: actions/upload-artifact@v4.3.6 + # with: + # path: substrate/primitives/arithmetic/fuzzer/hfuzz_workspace/ + # name: hfuzz-${{ github.sha }} cargo-check-each-crate: timeout-minutes: 140 needs: [preflight] runs-on: ${{ needs.preflight.outputs.RUNNER }} + if: ${{ needs.preflight.outputs.changes_rust }} container: image: ${{ needs.preflight.outputs.IMAGE }} env: @@ -322,6 +331,7 @@ jobs: timeout-minutes: 30 needs: [preflight] runs-on: ${{ needs.preflight.outputs.RUNNER_MACOS }} + if: ${{ needs.preflight.outputs.changes_rust }} env: SKIP_WASM_BUILD: 1 steps: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1132c2ca4dd..e371132647a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -5,21 +5,20 @@ on: branches: - master pull_request: - types: [ opened, synchronize, reopened, ready_for_review ] + types: [opened, synchronize, reopened, ready_for_review] merge_group: concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true jobs: - preflight: uses: ./.github/workflows/reusable-preflight.yml # This job runs all benchmarks defined in the `/bin/node/runtime` once to check that there are no errors. quick-benchmarks: - needs: [ preflight ] - # if: ${{ needs.preflight.outputs.changes_rust }} + needs: [preflight] + if: ${{ needs.preflight.outputs.changes_rust }} runs-on: ${{ needs.preflight.outputs.RUNNER }} timeout-minutes: 60 container: @@ -37,8 +36,8 @@ jobs: # cf https://github.com/paritytech/polkadot-sdk/issues/1652 test-syscalls: - needs: [ preflight ] - # if: ${{ needs.preflight.outputs.changes_rust }} + needs: [preflight] + if: ${{ needs.preflight.outputs.changes_rust }} runs-on: ${{ needs.preflight.outputs.RUNNER }} timeout-minutes: 60 container: @@ -61,10 +60,9 @@ jobs: run: | echo "The x86_64 syscalls used by the worker binaries have changed. Please review if this is expected and update polkadot/scripts/list-syscalls/*-worker-syscalls as needed." >> $GITHUB_STEP_SUMMARY - cargo-check-all-benches: - needs: [ preflight ] - # if: ${{ needs.preflight.outputs.changes_rust }} + needs: [preflight] + if: ${{ needs.preflight.outputs.changes_rust }} runs-on: ${{ needs.preflight.outputs.RUNNER }} timeout-minutes: 60 container: diff --git a/.gitlab/pipeline/test.yml b/.gitlab/pipeline/test.yml index 6fb35c61e48..0252620cd0f 100644 --- a/.gitlab/pipeline/test.yml +++ b/.gitlab/pipeline/test.yml @@ -112,6 +112,7 @@ test-linux-stable-codecov: # some tests do not run with `try-runtime` feature enabled # https://github.com/paritytech/polkadot-sdk/pull/4251#discussion_r1624282143 +# Move to github after https://github.com/paritytech/ci_cd/issues/1056 is fixed test-linux-stable-no-try-runtime: stage: test extends: -- GitLab From 17243e039158b78f2b57a4205ef35a2ec223bf1e Mon Sep 17 00:00:00 2001 From: Javier Viola <363911+pepoviola@users.noreply.github.com> Date: Thu, 26 Sep 2024 13:02:49 -0300 Subject: [PATCH 112/125] bump zombienet version `v1.3.110` (#5834) Bump `zombienet` version to prevent report fails at teardown phase. --- .gitlab/pipeline/zombienet.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab/pipeline/zombienet.yml b/.gitlab/pipeline/zombienet.yml index c17366dbe4c..e43b5a54b04 100644 --- a/.gitlab/pipeline/zombienet.yml +++ b/.gitlab/pipeline/zombienet.yml @@ -1,7 +1,7 @@ .zombienet-refs: extends: .build-refs variables: - ZOMBIENET_IMAGE: "docker.io/paritytech/zombienet:v1.3.105" + ZOMBIENET_IMAGE: "docker.io/paritytech/zombienet:v1.3.110" PUSHGATEWAY_URL: "http://zombienet-prometheus-pushgateway.managed-monitoring:9091/metrics/job/zombie-metrics" DEBUG: "zombie,zombie::network-node,zombie::kube::client::logs" ZOMBIE_PROVIDER: "k8s" -- GitLab From 6c3219ebe9231a0305f53c7b33cb558d46058062 Mon Sep 17 00:00:00 2001 From: Alexander Samusev <41779041+alvicsam@users.noreply.github.com> Date: Thu, 26 Sep 2024 20:51:29 +0200 Subject: [PATCH 113/125] [ci] Update CI image with rust 1.81.0 and 2024-09-11 (#5676) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cc https://github.com/paritytech/ci_cd/issues/1035 cc https://github.com/paritytech/ci_cd/issues/1023 --------- Signed-off-by: Oliver Tale-Yazdi Co-authored-by: command-bot <> Co-authored-by: Maksym H <1177472+mordamax@users.noreply.github.com> Co-authored-by: gui Co-authored-by: Oliver Tale-Yazdi Co-authored-by: Bastian Köcher Co-authored-by: ggwpez --- .github/env | 2 +- .github/scripts/generate-prdoc.py | 6 +- .github/workflows/checks.yml | 8 +- .github/workflows/command-prdoc.yml | 2 +- .github/workflows/tests-linux-stable.yml | 5 +- .github/workflows/tests-misc.yml | 16 +- .github/workflows/tests.yml | 6 +- .gitlab-ci.yml | 3 +- Cargo.toml | 2 +- bridges/modules/grandpa/src/lib.rs | 4 +- bridges/modules/xcm-bridge-hub/src/lib.rs | 40 +- .../header-chain/src/justification/mod.rs | 15 +- bridges/primitives/messages/src/lane.rs | 6 +- .../src/cli/relay_headers_and_messages/mod.rs | 4 +- .../src/messages/metrics.rs | 3 +- .../src/on_demand/parachains.rs | 3 +- .../messages/src/message_race_delivery.rs | 44 +- .../messages/src/message_race_limits.rs | 49 +- .../relays/messages/src/message_race_loop.rs | 18 - .../messages/src/message_race_strategy.rs | 6 +- .../outbound-queue/merkle-tree/src/lib.rs | 20 +- .../primitives/router/src/inbound/mod.rs | 6 +- .../primitives/router/src/outbound/mod.rs | 3 +- .../runtime/runtime-common/src/lib.rs | 3 +- cumulus/pallets/parachain-system/src/lib.rs | 3 +- cumulus/pallets/xcmp-queue/src/mock.rs | 61 +- .../asset-hub-rococo/src/tests/teleport.rs | 4 +- .../asset-hub-westend/src/tests/teleport.rs | 4 +- .../people-rococo/src/tests/teleport.rs | 4 +- .../people-westend/src/tests/teleport.rs | 4 +- .../assets/asset-hub-rococo/src/lib.rs | 5 +- .../assets/asset-hub-westend/src/lib.rs | 5 +- .../assets/test-utils/src/test_cases.rs | 3 +- .../bridge-hub-westend/tests/tests.rs | 32 +- .../test-utils/src/test_cases/mod.rs | 11 +- .../runtimes/starters/shell/src/lib.rs | 6 +- .../src/fake_runtime_api/mod.rs | 2 + cumulus/primitives/utility/src/lib.rs | 6 +- polkadot/cli/src/command.rs | 2 - .../core/approval-voting-parallel/src/lib.rs | 3 +- .../node/core/approval-voting/src/import.rs | 11 +- polkadot/node/core/approval-voting/src/lib.rs | 7 +- .../node/core/approval-voting/src/tests.rs | 3 +- polkadot/node/core/pvf/build.rs | 2 +- .../common/src/worker/security/change_root.rs | 3 +- polkadot/node/core/pvf/src/artifacts.rs | 16 +- polkadot/node/core/pvf/src/host.rs | 25 +- polkadot/node/core/pvf/src/prepare/pool.rs | 15 +- .../core/pvf/src/prepare/worker_interface.rs | 2 - polkadot/node/core/pvf/src/testing.rs | 2 +- .../approval-distribution/src/tests.rs | 3 +- .../src/tests/mod.rs | 11 +- .../src/tests/state.rs | 1 - .../src/task/strategy/chunks.rs | 13 +- .../src/task/strategy/systematic.rs | 6 +- polkadot/node/overseer/src/lib.rs | 1 + polkadot/node/service/src/fake_runtime_api.rs | 1 + .../node/service/src/parachains_db/mod.rs | 9 +- polkadot/node/service/src/tests.rs | 3 - .../src/lib/approval/message_generator.rs | 5 +- .../subsystem-bench/src/lib/statement/mod.rs | 16 +- .../subsystem-types/src/runtime_client.rs | 3 +- polkadot/primitives/src/runtime_api.rs | 2 +- polkadot/primitives/src/v8/metrics.rs | 12 - polkadot/runtime/parachains/src/hrmp.rs | 2 +- .../runtime/parachains/src/inclusion/mod.rs | 3 +- .../parachains/src/paras_inherent/mod.rs | 25 +- polkadot/runtime/parachains/src/ump_tests.rs | 7 +- polkadot/runtime/westend/src/lib.rs | 49 +- polkadot/runtime/westend/src/weights/mod.rs | 1 - .../pallet_referenda_fellowship_referenda.rs | 525 --------------- .../list-syscalls/execute-worker-syscalls | 1 + .../list-syscalls/prepare-worker-syscalls | 1 + polkadot/statement-table/src/generic.rs | 6 +- .../parachain/xcm_config.rs | 2 +- .../relay_chain/xcm_config.rs | 2 +- polkadot/xcm/pallet-xcm-benchmarks/src/lib.rs | 2 +- polkadot/xcm/procedural/tests/ui.rs | 1 - .../xcm/xcm-builder/src/asset_conversion.rs | 8 +- .../xcm-builder/src/nonfungibles_adapter.rs | 9 +- .../xcm/xcm-executor/src/traits/weight.rs | 7 - polkadot/xcm/xcm-runtime-apis/src/dry_run.rs | 7 +- .../xcm-runtime-apis/tests/fee_estimation.rs | 2 +- prdoc/pr_4851.prdoc | 4 +- prdoc/pr_5676.prdoc | 174 +++++ prdoc/pr_5678.prdoc | 2 +- prdoc/pr_5684.prdoc | 2 +- scripts/update-ui-tests.sh | 12 +- substrate/bin/node/cli/tests/fees.rs | 132 ---- .../authority-discovery/src/worker/tests.rs | 33 +- .../cli/src/commands/import_blocks_cmd.rs | 7 +- substrate/client/cli/src/commands/vanity.rs | 14 - .../client/cli/src/params/node_key_params.rs | 4 +- .../client/consensus/beefy/src/fisherman.rs | 9 +- .../consensus/grandpa/src/aux_schema.rs | 4 +- .../consensus/grandpa/src/voting_rule.rs | 2 +- substrate/client/consensus/slots/build.rs | 2 +- substrate/client/consensus/slots/src/lib.rs | 2 +- substrate/client/executor/wasmtime/build.rs | 2 +- .../client/executor/wasmtime/src/tests.rs | 2 +- substrate/client/network-gossip/src/bridge.rs | 3 - substrate/client/network/src/behaviour.rs | 12 +- .../client/network/src/litep2p/discovery.rs | 15 +- .../src/protocol/notifications/behaviour.rs | 112 ++-- .../src/protocol/notifications/handler.rs | 29 - .../src/protocol/notifications/service/mod.rs | 6 +- .../notifications/upgrade/notifications.rs | 15 +- .../client/network/src/service/metrics.rs | 5 - substrate/client/network/sync/src/engine.rs | 3 +- substrate/client/network/sync/src/lib.rs | 1 - .../network/sync/src/request_metrics.rs | 25 - substrate/client/network/test/src/lib.rs | 3 +- substrate/client/network/test/src/sync.rs | 18 - .../rpc-spec-v2/src/chain_head/test_utils.rs | 3 +- .../client/service/test/src/client/mod.rs | 6 +- .../transaction-pool/src/graph/tracked_map.rs | 3 +- substrate/client/utils/src/mpsc.rs | 7 +- substrate/frame/authorship/src/lib.rs | 3 +- substrate/frame/bags-list/src/lib.rs | 2 +- substrate/frame/bags-list/src/list/tests.rs | 2 +- substrate/frame/bags-list/src/mock.rs | 2 +- substrate/frame/balances/src/lib.rs | 3 +- .../balances/src/tests/currency_tests.rs | 6 +- substrate/frame/beefy-mmr/src/benchmarking.rs | 12 +- .../frame/benchmarking/pov/src/weights.rs | 2 + substrate/frame/bounties/src/lib.rs | 12 +- substrate/frame/child-bounties/src/lib.rs | 13 +- .../frame/contracts/src/benchmarking/code.rs | 3 +- substrate/frame/contracts/src/exec.rs | 7 - substrate/frame/contracts/src/wasm/mod.rs | 4 - substrate/frame/delegated-staking/src/lib.rs | 4 +- .../election-provider-multi-phase/src/lib.rs | 3 +- .../election-provider-support/src/lib.rs | 4 +- substrate/frame/elections-phragmen/src/lib.rs | 3 +- .../examples/offchain-worker/src/tests.rs | 22 +- .../src/migrations/v1.rs | 2 +- substrate/frame/executive/src/lib.rs | 3 +- substrate/frame/nis/src/lib.rs | 16 +- substrate/frame/referenda/src/mock.rs | 9 - substrate/frame/referenda/src/types.rs | 3 +- substrate/frame/revive/proc-macro/src/lib.rs | 25 +- substrate/frame/society/src/lib.rs | 12 - substrate/frame/society/src/tests.rs | 2 +- substrate/frame/staking/src/tests.rs | 2 +- .../procedural/src/construct_runtime/parse.rs | 3 - .../support/procedural/src/derive_impl.rs | 1 + substrate/frame/support/procedural/src/lib.rs | 25 +- .../src/pallet/expand/genesis_build.rs | 3 +- .../procedural/src/pallet/expand/hooks.rs | 4 +- .../procedural/src/pallet/parse/call.rs | 23 +- .../procedural/src/runtime/parse/pallet.rs | 1 - substrate/frame/support/src/lib.rs | 2 + .../src/storage/generator/double_map.rs | 3 +- .../support/src/storage/generator/map.rs | 3 +- .../support/src/storage/generator/nmap.rs | 3 +- .../support/src/storage/types/double_map.rs | 3 +- substrate/frame/support/src/tests/mod.rs | 1 + substrate/frame/support/src/traits/misc.rs | 8 +- .../traits/try_runtime/decode_entire_state.rs | 6 +- ...bad_return_type_blank_with_question.stderr | 4 + .../deprecated_where_block.stderr | 616 ++++++++++++++---- .../undefined_event_part.stderr | 4 +- .../undefined_genesis_config_part.stderr | 2 +- .../undefined_inherent_part.stderr | 4 +- .../undefined_origin_part.stderr | 2 +- .../frame/support/test/tests/derive_impl.rs | 10 - .../support/test/tests/derive_no_bound.rs | 1 + .../test/tests/derive_no_bound_ui/ord.stderr | 10 + substrate/frame/support/test/tests/pallet.rs | 5 +- .../call_argument_invalid_bound.stderr | 9 + .../call_argument_invalid_bound_2.stderr | 13 +- .../call_argument_invalid_bound_3.stderr | 9 + .../compare_unset_storage_version.stderr | 6 + ...ev_mode_without_arg_max_encoded_len.stderr | 16 +- .../error_does_not_derive_pallet_error.stderr | 16 +- .../tests/pallet_ui/hooks_invalid_item.stderr | 6 + .../inherent_check_inner_span.stderr | 2 +- ...age_ensure_span_are_ok_on_wrong_gen.stderr | 100 +-- ...re_span_are_ok_on_wrong_gen_unnamed.stderr | 100 +-- .../pallet_ui/storage_info_unsatisfied.stderr | 16 +- .../storage_info_unsatisfied_nmap.stderr | 16 +- .../type_value_error_in_block.stderr | 6 + substrate/frame/system/src/tests.rs | 1 + .../frame/transaction-payment/src/tests.rs | 6 +- substrate/frame/utility/src/lib.rs | 4 +- substrate/frame/vesting/src/tests.rs | 10 +- substrate/primitives/api/test/Cargo.toml | 1 + .../primitives/api/test/tests/trybuild.rs | 10 +- .../api/test/tests/ui/deprecation_info.stderr | 15 + .../ui/impl_incorrect_method_signature.stderr | 18 +- .../tests/ui/mock_only_self_reference.stderr | 6 +- ...reference_in_impl_runtime_apis_call.stderr | 18 +- .../primitives/runtime-interface/tests/ui.rs | 6 +- .../tests/ui/no_feature_gated_method.stderr | 34 +- substrate/primitives/runtime/src/traits.rs | 12 - substrate/primitives/state-machine/src/ext.rs | 1 + substrate/test-utils/cli/build.rs | 2 +- substrate/test-utils/cli/src/lib.rs | 2 +- substrate/utils/fork-tree/src/lib.rs | 3 +- .../utils/frame/benchmarking-cli/build.rs | 4 + .../utils/wasm-builder/src/wasm_project.rs | 7 +- .../pallets/template/src/benchmarking.rs | 1 - .../pallets/template/src/benchmarking.rs | 2 +- 203 files changed, 1455 insertions(+), 1885 deletions(-) delete mode 100644 polkadot/runtime/westend/src/weights/pallet_referenda_fellowship_referenda.rs create mode 100644 prdoc/pr_5676.prdoc delete mode 100644 substrate/client/network/sync/src/request_metrics.rs diff --git a/.github/env b/.github/env index 2e4d5b48100..bb61e1f4cd9 100644 --- a/.github/env +++ b/.github/env @@ -1 +1 @@ -IMAGE="docker.io/paritytech/ci-unified:bullseye-1.77.0-2024-04-10-v202407161507" +IMAGE="docker.io/paritytech/ci-unified:bullseye-1.81.0-2024-09-11-v202409111034" diff --git a/.github/scripts/generate-prdoc.py b/.github/scripts/generate-prdoc.py index d3b6b523ecf..f05d517a580 100644 --- a/.github/scripts/generate-prdoc.py +++ b/.github/scripts/generate-prdoc.py @@ -67,7 +67,6 @@ def create_prdoc(pr, audience, title, description, patch, bump, force): # Go up until we find a Cargo.toml p = os.path.join(workspace.path, p) while not os.path.exists(os.path.join(p, "Cargo.toml")): - print(f"Could not find Cargo.toml in {p}") if p == '/': exit(1) p = os.path.dirname(p) @@ -76,7 +75,6 @@ def create_prdoc(pr, audience, title, description, patch, bump, force): manifest = toml.load(f) if not "package" in manifest: - print(f"File was not in any crate: {p}") continue crate_name = manifest["package"]["name"] @@ -85,8 +83,6 @@ def create_prdoc(pr, audience, title, description, patch, bump, force): else: print(f"Skipping unpublished crate: {crate_name}") - print(f"Modified crates: {modified_crates.keys()}") - for crate_name in modified_crates.keys(): entry = { "name": crate_name } @@ -137,4 +133,4 @@ def main(args): if __name__ == "__main__": args = setup_parser().parse_args() - main(args) \ No newline at end of file + main(args) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index fa1d5f2bdd2..0793c31dbb8 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -32,8 +32,8 @@ jobs: - uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.1.7 - name: script run: | - forklift cargo clippy --all-targets --locked --workspace - forklift cargo clippy --all-targets --all-features --locked --workspace + forklift cargo clippy --all-targets --locked --workspace --quiet + forklift cargo clippy --all-targets --all-features --locked --workspace --quiet check-try-runtime: runs-on: ${{ needs.preflight.outputs.RUNNER }} needs: [preflight] @@ -45,13 +45,13 @@ jobs: - uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.1.7 - name: script run: | - forklift cargo check --locked --all --features try-runtime + forklift cargo check --locked --all --features try-runtime --quiet # this is taken from cumulus # Check that parachain-template will compile with `try-runtime` feature flag. forklift cargo check --locked -p parachain-template-node --features try-runtime # add after https://github.com/paritytech/substrate/pull/14502 is merged # experimental code may rely on try-runtime and vice-versa - forklift cargo check --locked --all --features try-runtime,experimental + forklift cargo check --locked --all --features try-runtime,experimental --quiet # check-core-crypto-features works fast without forklift check-core-crypto-features: runs-on: ${{ needs.preflight.outputs.RUNNER }} diff --git a/.github/workflows/command-prdoc.yml b/.github/workflows/command-prdoc.yml index aa9de9474a7..2154c8a987f 100644 --- a/.github/workflows/command-prdoc.yml +++ b/.github/workflows/command-prdoc.yml @@ -28,7 +28,7 @@ on: - "Runtime Dev" - "Runtime User" - "Node Dev" - - "Node User" + - "Node Operator" overwrite: type: choice description: Overwrite existing PrDoc diff --git a/.github/workflows/tests-linux-stable.yml b/.github/workflows/tests-linux-stable.yml index a23a7ad67de..599edd8e69a 100644 --- a/.github/workflows/tests-linux-stable.yml +++ b/.github/workflows/tests-linux-stable.yml @@ -53,7 +53,7 @@ jobs: - name: Checkout uses: actions/checkout@v4 - name: script - run: forklift cargo nextest run --workspace --features runtime-benchmarks benchmark --locked --cargo-profile testnet + run: forklift cargo nextest run --workspace --features runtime-benchmarks benchmark --locked --cargo-profile testnet --cargo-quiet test-linux-stable: needs: [preflight] @@ -90,12 +90,13 @@ jobs: --locked \ --release \ --no-fail-fast \ + --cargo-quiet \ --features try-runtime,experimental,riscv,ci-only-tests \ --partition count:${{ matrix.partition }} # run runtime-api tests with `enable-staging-api` feature on the 1st node - name: runtime-api tests if: ${{ matrix.partition == '1/3' }} - run: forklift cargo nextest run -p sp-api-test --features enable-staging-api + run: forklift cargo nextest run -p sp-api-test --features enable-staging-api --cargo-quiet confirm-required-jobs-passed: runs-on: ubuntu-latest diff --git a/.github/workflows/tests-misc.yml b/.github/workflows/tests-misc.yml index 7f3e01eb424..45ed4ebdc6c 100644 --- a/.github/workflows/tests-misc.yml +++ b/.github/workflows/tests-misc.yml @@ -77,8 +77,7 @@ jobs: # but still want to have debug assertions. RUSTFLAGS: "-C debug-assertions -D warnings" RUST_BACKTRACE: 1 - WASM_BUILD_NO_COLOR: 1 - WASM_BUILD_RUSTFLAGS: "-C debug-assertions -D warnings" + SKIP_WASM_BUILD: 1 # Ensure we run the UI tests. RUN_UI_TESTS: 1 steps: @@ -86,13 +85,14 @@ jobs: uses: actions/checkout@v4 - name: script run: | - forklift cargo test --locked -q --profile testnet -p frame-support-test --features=frame-feature-testing,no-metadata-docs,try-runtime,experimental - forklift cargo test --locked -q --profile testnet -p frame-support-test --features=frame-feature-testing,frame-feature-testing-2,no-metadata-docs,try-runtime,experimental - forklift cargo test --locked -q --profile testnet -p xcm-procedural - forklift cargo test --locked -q --profile testnet -p frame-election-provider-solution-type - forklift cargo test --locked -q --profile testnet -p sp-api-test + cargo version + forklift cargo test --locked -q --profile testnet -p frame-support-test --features=frame-feature-testing,no-metadata-docs,try-runtime,experimental ui + forklift cargo test --locked -q --profile testnet -p frame-support-test --features=frame-feature-testing,frame-feature-testing-2,no-metadata-docs,try-runtime,experimental ui + forklift cargo test --locked -q --profile testnet -p xcm-procedural ui + forklift cargo test --locked -q --profile testnet -p frame-election-provider-solution-type ui + forklift cargo test --locked -q --profile testnet -p sp-api-test ui # There is multiple version of sp-runtime-interface in the repo. So we point to the manifest. - forklift cargo test --locked -q --profile testnet --manifest-path substrate/primitives/runtime-interface/Cargo.toml + forklift cargo test --locked -q --profile testnet --manifest-path substrate/primitives/runtime-interface/Cargo.toml ui test-deterministic-wasm: timeout-minutes: 20 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e371132647a..6d6e393b041 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -32,7 +32,7 @@ jobs: - name: Checkout uses: actions/checkout@v4 - name: script - run: forklift cargo run --locked --release -p staging-node-cli --bin substrate-node --features runtime-benchmarks -- benchmark pallet --chain dev --pallet "*" --extrinsic "*" --steps 2 --repeat 1 --quiet + run: forklift cargo run --locked --release -p staging-node-cli --bin substrate-node --features runtime-benchmarks --quiet -- benchmark pallet --chain dev --pallet "*" --extrinsic "*" --steps 2 --repeat 1 --quiet # cf https://github.com/paritytech/polkadot-sdk/issues/1652 test-syscalls: @@ -51,7 +51,7 @@ jobs: - name: script id: test run: | - forklift cargo build --locked --profile production --target x86_64-unknown-linux-musl --bin polkadot-execute-worker --bin polkadot-prepare-worker + forklift cargo build --locked --profile production --target x86_64-unknown-linux-musl --bin polkadot-execute-worker --bin polkadot-prepare-worker --quiet cd polkadot/scripts/list-syscalls ./list-syscalls.rb ../../../target/x86_64-unknown-linux-musl/production/polkadot-execute-worker --only-used-syscalls | diff -u execute-worker-syscalls - ./list-syscalls.rb ../../../target/x86_64-unknown-linux-musl/production/polkadot-prepare-worker --only-used-syscalls | diff -u prepare-worker-syscalls - @@ -73,4 +73,4 @@ jobs: - name: Checkout uses: actions/checkout@v4 - name: script - run: forklift cargo check --all --benches + run: forklift cargo check --all --benches --quiet diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5c6b3928b46..dbc5dafeb0a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -21,7 +21,8 @@ workflow: - if: $CI_COMMIT_BRANCH variables: - CI_IMAGE: !reference [.ci-unified, variables, CI_IMAGE] + # CI_IMAGE: !reference [ .ci-unified, variables, CI_IMAGE ] + CI_IMAGE: "docker.io/paritytech/ci-unified:bullseye-1.81.0-2024-09-11-v202409111034" # BUILDAH_IMAGE is defined in group variables BUILDAH_COMMAND: "buildah --storage-driver overlay2" RELENG_SCRIPTS_BRANCH: "master" diff --git a/Cargo.toml b/Cargo.toml index c92254242fc..c40d59fce3c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -552,7 +552,7 @@ default-members = [ [workspace.lints.rust] suspicious_double_ref_op = { level = "allow", priority = 2 } # `substrate_runtime` is a common `cfg` condition name used in the repo. -unexpected_cfgs = { level = "warn", check-cfg = ['cfg(substrate_runtime)'] } +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(build_opt_level, values("3"))', 'cfg(build_profile, values("debug", "release"))', 'cfg(enable_alloc_error_handler)', 'cfg(fuzzing)', 'cfg(substrate_runtime)'] } [workspace.lints.clippy] all = { level = "allow", priority = 0 } diff --git a/bridges/modules/grandpa/src/lib.rs b/bridges/modules/grandpa/src/lib.rs index dff4b98fd91..22a15ec4062 100644 --- a/bridges/modules/grandpa/src/lib.rs +++ b/bridges/modules/grandpa/src/lib.rs @@ -728,15 +728,13 @@ pub mod pallet { init_params; let authority_set_length = authority_list.len(); let authority_set = StoredAuthoritySet::::try_new(authority_list, set_id) - .map_err(|e| { + .inspect_err(|_| { log::error!( target: LOG_TARGET, "Failed to initialize bridge. Number of authorities in the set {} is larger than the configured value {}", authority_set_length, T::BridgedChain::MAX_AUTHORITIES_COUNT, ); - - e })?; let initial_hash = header.hash(); diff --git a/bridges/modules/xcm-bridge-hub/src/lib.rs b/bridges/modules/xcm-bridge-hub/src/lib.rs index 22c60fb4ad6..1b2536598a2 100644 --- a/bridges/modules/xcm-bridge-hub/src/lib.rs +++ b/bridges/modules/xcm-bridge-hub/src/lib.rs @@ -415,7 +415,7 @@ pub mod pallet { bridge.deposit, Precision::BestEffort, ) - .map_err(|e| { + .inspect_err(|e| { // we can't do anything here - looks like funds have been (partially) unreserved // before by someone else. Let's not fail, though - it'll be worse for the caller log::error!( @@ -423,7 +423,6 @@ pub mod pallet { "Failed to unreserve during the bridge {:?} closure with error: {e:?}", locations.bridge_id(), ); - e }) .ok() .unwrap_or(BalanceOf::>::zero()); @@ -1456,25 +1455,26 @@ mod tests { let lane_id = TestLaneIdType::try_new(1, 2).unwrap(); let lane_id_mismatch = TestLaneIdType::try_new(3, 4).unwrap(); - let test_bridge_state = |id, - bridge, - (lane_id, bridge_id), - (inbound_lane_id, outbound_lane_id), - expected_error: Option| { - Bridges::::insert(id, bridge); - LaneToBridge::::insert(lane_id, bridge_id); - - let lanes_manager = LanesManagerOf::::new(); - lanes_manager.create_inbound_lane(inbound_lane_id).unwrap(); - lanes_manager.create_outbound_lane(outbound_lane_id).unwrap(); + let test_bridge_state = + |id, + bridge, + (lane_id, bridge_id), + (inbound_lane_id, outbound_lane_id), + expected_error: Option| { + Bridges::::insert(id, bridge); + LaneToBridge::::insert(lane_id, bridge_id); - let result = XcmOverBridge::do_try_state(); - if let Some(e) = expected_error { - assert_err!(result, e); - } else { - assert_ok!(result); - } - }; + let lanes_manager = LanesManagerOf::::new(); + lanes_manager.create_inbound_lane(inbound_lane_id).unwrap(); + lanes_manager.create_outbound_lane(outbound_lane_id).unwrap(); + + let result = XcmOverBridge::do_try_state(); + if let Some(e) = expected_error { + assert_err!(result, e); + } else { + assert_ok!(result); + } + }; let cleanup = |bridge_id, lane_ids| { Bridges::::remove(bridge_id); for lane_id in lane_ids { diff --git a/bridges/primitives/header-chain/src/justification/mod.rs b/bridges/primitives/header-chain/src/justification/mod.rs index d7c2cbf429e..87f53dac646 100644 --- a/bridges/primitives/header-chain/src/justification/mod.rs +++ b/bridges/primitives/header-chain/src/justification/mod.rs @@ -32,7 +32,6 @@ pub use verification::{ use bp_runtime::{BlockNumberOf, Chain, HashOf, HeaderId}; use codec::{Decode, Encode, MaxEncodedLen}; -use frame_support::RuntimeDebugNoBound; use scale_info::TypeInfo; use sp_consensus_grandpa::{AuthorityId, AuthoritySignature}; use sp_runtime::{traits::Header as HeaderT, RuntimeDebug, SaturatedConversion}; @@ -43,7 +42,8 @@ use sp_std::prelude::*; /// /// This particular proof is used to prove that headers on a bridged chain /// (so not our chain) have been finalized correctly. -#[derive(Encode, Decode, Clone, PartialEq, Eq, TypeInfo, RuntimeDebugNoBound)] +#[derive(Encode, Decode, Clone, PartialEq, Eq, TypeInfo)] +#[cfg_attr(feature = "std", derive(Debug))] pub struct GrandpaJustification { /// The round (voting period) this justification is valid for. pub round: u64, @@ -54,6 +54,17 @@ pub struct GrandpaJustification { pub votes_ancestries: Vec

, } +// A proper Debug impl for no-std is not possible for the `GrandpaJustification` since the `Commit` +// type only implements Debug that for `std` here: +// https://github.com/paritytech/finality-grandpa/blob/8c45a664c05657f0c71057158d3ba555ba7d20de/src/lib.rs#L275 +// so we do a manual impl. +#[cfg(not(feature = "std"))] +impl core::fmt::Debug for GrandpaJustification { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!(f, "GrandpaJustification {{ round: {:?}, commit: , votes_ancestries: {:?} }}", self.round, self.votes_ancestries) + } +} + impl GrandpaJustification { /// Returns reasonable size of justification using constants from the provided chain. /// diff --git a/bridges/primitives/messages/src/lane.rs b/bridges/primitives/messages/src/lane.rs index c835449db27..0f14ce93e11 100644 --- a/bridges/primitives/messages/src/lane.rs +++ b/bridges/primitives/messages/src/lane.rs @@ -71,7 +71,7 @@ impl LaneIdType for LegacyLaneId { } } -#[cfg(any(feature = "std", feature = "runtime-benchmarks", test))] +#[cfg(feature = "std")] impl TryFrom> for LegacyLaneId { type Error = (); @@ -140,7 +140,7 @@ impl HashedLaneId { /// There's no `From` implementation for the `LaneId`, because using this conversion /// in a wrong way (i.e. computing hash of endpoints manually) may lead to issues. So we /// want the call to be explicit. - #[cfg(any(feature = "std", feature = "runtime-benchmarks", test))] + #[cfg(feature = "std")] pub const fn from_inner(inner: H256) -> Self { Self(inner) } @@ -184,7 +184,7 @@ impl LaneIdType for HashedLaneId { } } -#[cfg(any(feature = "std", feature = "runtime-benchmarks", test))] +#[cfg(feature = "std")] impl TryFrom> for HashedLaneId { type Error = (); diff --git a/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/mod.rs b/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/mod.rs index 9261dc43753..e875c53a2e7 100644 --- a/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/mod.rs +++ b/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/mod.rs @@ -325,7 +325,9 @@ where _, Self::Left, MessagesLaneIdOf, - >(common.right.client.clone(), &common.metrics_params, &common.right.accounts, &lanes) + >( + common.right.client.clone(), &common.metrics_params, &common.right.accounts, &lanes + ) .await?; } diff --git a/bridges/relays/lib-substrate-relay/src/messages/metrics.rs b/bridges/relays/lib-substrate-relay/src/messages/metrics.rs index 9d45a4b3d66..efe429701c4 100644 --- a/bridges/relays/lib-substrate-relay/src/messages/metrics.rs +++ b/bridges/relays/lib-substrate-relay/src/messages/metrics.rs @@ -53,9 +53,8 @@ where let token_decimals = client .token_decimals() .await? - .map(|token_decimals| { + .inspect(|token_decimals| { log::info!(target: "bridge", "Read `tokenDecimals` for {}: {}", C::NAME, token_decimals); - token_decimals }) .unwrap_or_else(|| { // turns out it is normal not to have this property - e.g. when polkadot binary is diff --git a/bridges/relays/lib-substrate-relay/src/on_demand/parachains.rs b/bridges/relays/lib-substrate-relay/src/on_demand/parachains.rs index 2ef86f48ecb..96eba0af988 100644 --- a/bridges/relays/lib-substrate-relay/src/on_demand/parachains.rs +++ b/bridges/relays/lib-substrate-relay/src/on_demand/parachains.rs @@ -664,7 +664,8 @@ impl<'a, P: SubstrateParachainsPipeline, SourceRelayClnt, TargetClnt> for ( &'a OnDemandParachainsRelay, &'a ParachainsSource, - ) where + ) +where SourceRelayClnt: Client, TargetClnt: Client, { diff --git a/bridges/relays/messages/src/message_race_delivery.rs b/bridges/relays/messages/src/message_race_delivery.rs index cbb89baabcc..b09533a4ddc 100644 --- a/bridges/relays/messages/src/message_race_delivery.rs +++ b/bridges/relays/messages/src/message_race_delivery.rs @@ -59,9 +59,7 @@ pub async fn run( _phantom: Default::default(), }, target_state_updates, - MessageDeliveryStrategy:: { - lane_source_client: source_client, - lane_target_client: target_client, + MessageDeliveryStrategy::

{ max_unrewarded_relayer_entries_at_target: params .max_unrewarded_relayer_entries_at_target, max_unconfirmed_nonces_at_target: params.max_unconfirmed_nonces_at_target, @@ -71,7 +69,6 @@ pub async fn run( latest_confirmed_nonces_at_source: VecDeque::new(), target_nonces: None, strategy: BasicStrategy::new(), - metrics_msg, }, ) .await @@ -300,11 +297,7 @@ struct DeliveryRaceTargetNoncesData { } /// Messages delivery strategy. -struct MessageDeliveryStrategy { - /// The client that is connected to the message lane source node. - lane_source_client: SC, - /// The client that is connected to the message lane target node. - lane_target_client: TC, +struct MessageDeliveryStrategy { /// Maximal unrewarded relayer entries at target client. max_unrewarded_relayer_entries_at_target: MessageNonce, /// Maximal unconfirmed nonces at target client. @@ -322,8 +315,6 @@ struct MessageDeliveryStrategy { target_nonces: Option>, /// Basic delivery strategy. strategy: MessageDeliveryStrategyBase

, - /// Message lane metrics. - metrics_msg: Option, } type MessageDeliveryStrategyBase

= BasicStrategy< @@ -335,7 +326,7 @@ type MessageDeliveryStrategyBase

= BasicStrategy<

::MessagesProof, >; -impl std::fmt::Debug for MessageDeliveryStrategy { +impl std::fmt::Debug for MessageDeliveryStrategy

{ fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { fmt.debug_struct("MessageDeliveryStrategy") .field( @@ -353,11 +344,9 @@ impl std::fmt::Debug for MessageDeliveryStrategy MessageDeliveryStrategy +impl MessageDeliveryStrategy

where P: MessageLane, - SC: MessageLaneSourceClient

, - TC: MessageLaneTargetClient

, { /// Returns true if some race action can be selected (with `select_race_action`) at given /// `best_finalized_source_header_id_at_best_target` source header at target. @@ -465,23 +454,18 @@ where let max_nonces = std::cmp::min(max_nonces, self.max_messages_in_single_batch); let max_messages_weight_in_single_batch = self.max_messages_weight_in_single_batch; let max_messages_size_in_single_batch = self.max_messages_size_in_single_batch; - let lane_source_client = self.lane_source_client.clone(); - let lane_target_client = self.lane_target_client.clone(); // select nonces from nonces, available for delivery let selected_nonces = match self.strategy.available_source_queue_indices(race_state) { Some(available_source_queue_indices) => { let source_queue = self.strategy.source_queue(); - let reference = RelayMessagesBatchReference { + let reference = RelayMessagesBatchReference::

{ max_messages_in_this_batch: max_nonces, max_messages_weight_in_single_batch, max_messages_size_in_single_batch, - lane_source_client: lane_source_client.clone(), - lane_target_client: lane_target_client.clone(), best_target_nonce, nonces_queue: source_queue.clone(), nonces_queue_range: available_source_queue_indices, - metrics: self.metrics_msg.clone(), }; MessageRaceLimits::decide(reference).await @@ -534,12 +518,10 @@ where } #[async_trait] -impl RaceStrategy, TargetHeaderIdOf

, P::MessagesProof> - for MessageDeliveryStrategy +impl

RaceStrategy, TargetHeaderIdOf

, P::MessagesProof> + for MessageDeliveryStrategy

where P: MessageLane, - SC: MessageLaneSourceClient

, - TC: MessageLaneTargetClient

, { type SourceNoncesRange = MessageDetailsMap; type ProofParameters = MessageProofParameters; @@ -707,8 +689,7 @@ mod tests { message_lane_loop::{ tests::{ header_id, TestMessageLane, TestMessagesBatchTransaction, TestMessagesProof, - TestSourceChainBalance, TestSourceClient, TestSourceHeaderId, TestTargetClient, - TestTargetHeaderId, + TestSourceChainBalance, TestSourceHeaderId, TestTargetHeaderId, }, MessageDetails, }, @@ -726,8 +707,7 @@ mod tests { TestMessagesProof, TestMessagesBatchTransaction, >; - type TestStrategy = - MessageDeliveryStrategy; + type TestStrategy = MessageDeliveryStrategy; fn source_nonces( new_nonces: RangeInclusive, @@ -770,9 +750,6 @@ mod tests { max_messages_weight_in_single_batch: Weight::from_parts(4, 0), max_messages_size_in_single_batch: 4, latest_confirmed_nonces_at_source: vec![(header_id(1), 19)].into_iter().collect(), - lane_source_client: TestSourceClient::default(), - lane_target_client: TestTargetClient::default(), - metrics_msg: None, target_nonces: Some(TargetClientNonces { latest_nonce: 19, nonces_data: DeliveryRaceTargetNoncesData { @@ -1167,9 +1144,6 @@ mod tests { max_messages_weight_in_single_batch: Weight::from_parts(4, 0), max_messages_size_in_single_batch: 4, latest_confirmed_nonces_at_source: VecDeque::new(), - lane_source_client: TestSourceClient::default(), - lane_target_client: TestTargetClient::default(), - metrics_msg: None, target_nonces: None, strategy: BasicStrategy::new(), }; diff --git a/bridges/relays/messages/src/message_race_limits.rs b/bridges/relays/messages/src/message_race_limits.rs index 873bb6aad04..8fcd1f911f6 100644 --- a/bridges/relays/messages/src/message_race_limits.rs +++ b/bridges/relays/messages/src/message_race_limits.rs @@ -23,33 +23,16 @@ use bp_messages::{MessageNonce, Weight}; use crate::{ message_lane::MessageLane, - message_lane_loop::{ - MessageDetails, MessageDetailsMap, SourceClient as MessageLaneSourceClient, - TargetClient as MessageLaneTargetClient, - }, + message_lane_loop::{MessageDetails, MessageDetailsMap}, message_race_loop::NoncesRange, message_race_strategy::SourceRangesQueue, - metrics::MessageLaneLoopMetrics, }; /// Reference data for participating in relay -pub struct RelayReference< - P: MessageLane, - SourceClient: MessageLaneSourceClient

, - TargetClient: MessageLaneTargetClient

, -> { - /// The client that is connected to the message lane source node. - pub lane_source_client: SourceClient, - /// The client that is connected to the message lane target node. - pub lane_target_client: TargetClient, - /// Metrics reference. - pub metrics: Option, +pub struct RelayReference { /// Messages size summary pub selected_size: u32, - /// Hard check begin nonce - pub hard_selected_begin_nonce: MessageNonce, - /// Index by all ready nonces pub index: usize, /// Current nonce @@ -59,23 +42,13 @@ pub struct RelayReference< } /// Relay reference data -pub struct RelayMessagesBatchReference< - P: MessageLane, - SourceClient: MessageLaneSourceClient

, - TargetClient: MessageLaneTargetClient

, -> { +pub struct RelayMessagesBatchReference { /// Maximal number of relayed messages in single delivery transaction. pub max_messages_in_this_batch: MessageNonce, /// Maximal cumulative dispatch weight of relayed messages in single delivery transaction. pub max_messages_weight_in_single_batch: Weight, /// Maximal cumulative size of relayed messages in single delivery transaction. pub max_messages_size_in_single_batch: u32, - /// The client that is connected to the message lane source node. - pub lane_source_client: SourceClient, - /// The client that is connected to the message lane target node. - pub lane_target_client: TargetClient, - /// Metrics reference. - pub metrics: Option, /// Best available nonce at the **best** target block. We do not want to deliver nonces /// less than this nonce, even though the block may be retracted. pub best_target_nonce: MessageNonce, @@ -94,12 +67,8 @@ pub struct RelayMessagesBatchReference< pub struct MessageRaceLimits; impl MessageRaceLimits { - pub async fn decide< - P: MessageLane, - SourceClient: MessageLaneSourceClient

, - TargetClient: MessageLaneTargetClient

, - >( - reference: RelayMessagesBatchReference, + pub async fn decide( + reference: RelayMessagesBatchReference

, ) -> Option> { let mut hard_selected_count = 0; @@ -112,15 +81,9 @@ impl MessageRaceLimits { ); // relay reference - let mut relay_reference = RelayReference { - lane_source_client: reference.lane_source_client.clone(), - lane_target_client: reference.lane_target_client.clone(), - metrics: reference.metrics.clone(), - + let mut relay_reference = RelayReference::

{ selected_size: 0, - hard_selected_begin_nonce, - index: 0, nonce: 0, details: MessageDetails { diff --git a/bridges/relays/messages/src/message_race_loop.rs b/bridges/relays/messages/src/message_race_loop.rs index 31341a9a0c0..ea6a2371dc9 100644 --- a/bridges/relays/messages/src/message_race_loop.rs +++ b/bridges/relays/messages/src/message_race_loop.rs @@ -225,15 +225,9 @@ pub trait RaceState: Clone + Send + Sync { /// client (at the `best_finalized_source_header_id_at_best_target`). fn set_best_finalized_source_header_id_at_best_target(&mut self, id: SourceHeaderId); - /// Best finalized source header id at the source client. - fn best_finalized_source_header_id_at_source(&self) -> Option; /// Best finalized source header id at the best block on the target /// client (at the `best_finalized_source_header_id_at_best_target`). fn best_finalized_source_header_id_at_best_target(&self) -> Option; - /// The best header id at the target client. - fn best_target_header_id(&self) -> Option; - /// Best finalized header id at the target client. - fn best_finalized_target_header_id(&self) -> Option; /// Returns `true` if we have selected nonces to submit to the target node. fn nonces_to_submit(&self) -> Option>; @@ -296,22 +290,10 @@ where self.best_finalized_source_header_id_at_best_target = Some(id); } - fn best_finalized_source_header_id_at_source(&self) -> Option { - self.best_finalized_source_header_id_at_source.clone() - } - fn best_finalized_source_header_id_at_best_target(&self) -> Option { self.best_finalized_source_header_id_at_best_target.clone() } - fn best_target_header_id(&self) -> Option { - self.best_target_header_id.clone() - } - - fn best_finalized_target_header_id(&self) -> Option { - self.best_finalized_target_header_id.clone() - } - fn nonces_to_submit(&self) -> Option> { self.nonces_to_submit.clone().map(|(_, nonces, _)| nonces) } diff --git a/bridges/relays/messages/src/message_race_strategy.rs b/bridges/relays/messages/src/message_race_strategy.rs index 3a532331d79..1303fcfedeb 100644 --- a/bridges/relays/messages/src/message_race_strategy.rs +++ b/bridges/relays/messages/src/message_race_strategy.rs @@ -67,7 +67,8 @@ impl< TargetHeaderHash, SourceNoncesRange, Proof, - > where + > +where SourceHeaderHash: Clone, SourceHeaderNumber: Clone + Ord, SourceNoncesRange: NoncesRange, @@ -189,7 +190,8 @@ impl< TargetHeaderHash, SourceNoncesRange, Proof, - > where + > +where SourceHeaderHash: Clone + Debug + Send + Sync, SourceHeaderNumber: Clone + Ord + Debug + Send + Sync, SourceNoncesRange: NoncesRange + Debug + Send + Sync, diff --git a/bridges/snowbridge/pallets/outbound-queue/merkle-tree/src/lib.rs b/bridges/snowbridge/pallets/outbound-queue/merkle-tree/src/lib.rs index d5c89b9c098..eeeaa6e68cf 100644 --- a/bridges/snowbridge/pallets/outbound-queue/merkle-tree/src/lib.rs +++ b/bridges/snowbridge/pallets/outbound-queue/merkle-tree/src/lib.rs @@ -182,12 +182,6 @@ where let root = merkelize::(hashes.into_iter(), &mut collect_proof); let leaf = leaf.expect("Requested `leaf_index` is greater than number of leaves."); - #[cfg(feature = "debug")] - log::debug!( - "[merkle_proof] Proof: {:?}", - collect_proof.proof.iter().map(hex::encode).collect::>() - ); - MerkleProof { root, proof: collect_proof.proof, number_of_leaves, leaf_index, leaf } } @@ -274,8 +268,6 @@ where V: Visitor, I: Iterator, { - #[cfg(feature = "debug")] - log::debug!("[merkelize_row]"); next.clear(); let hash_len = ::LENGTH; @@ -286,9 +278,6 @@ where let b = iter.next(); visitor.visit(index, &a, &b); - #[cfg(feature = "debug")] - log::debug!(" {:?}\n {:?}", a.as_ref().map(hex::encode), b.as_ref().map(hex::encode)); - index += 2; match (a, b) { (Some(a), Some(b)) => { @@ -309,14 +298,7 @@ where // Last item = root. (Some(a), None) => return Ok(a), // Finish up, no more items. - _ => { - #[cfg(feature = "debug")] - log::debug!( - "[merkelize_row] Next: {:?}", - next.iter().map(hex::encode).collect::>() - ); - return Err(next) - }, + _ => return Err(next), } } } diff --git a/bridges/snowbridge/primitives/router/src/inbound/mod.rs b/bridges/snowbridge/primitives/router/src/inbound/mod.rs index a10884b4553..fbfc52d01c8 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/mod.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/mod.rs @@ -168,7 +168,8 @@ impl< ConvertAssetId, EthereumUniversalLocation, GlobalAssetHubLocation, - > where + > +where CreateAssetCall: Get, CreateAssetDeposit: Get, InboundQueuePalletInstance: Get, @@ -226,7 +227,8 @@ impl< ConvertAssetId, EthereumUniversalLocation, GlobalAssetHubLocation, - > where + > +where CreateAssetCall: Get, CreateAssetDeposit: Get, InboundQueuePalletInstance: Get, diff --git a/bridges/snowbridge/primitives/router/src/outbound/mod.rs b/bridges/snowbridge/primitives/router/src/outbound/mod.rs index d3b6c116dd7..b23bce0e218 100644 --- a/bridges/snowbridge/primitives/router/src/outbound/mod.rs +++ b/bridges/snowbridge/primitives/router/src/outbound/mod.rs @@ -44,7 +44,8 @@ impl where + > +where UniversalLocation: Get, EthereumNetwork: Get, OutboundQueue: SendMessage, diff --git a/bridges/snowbridge/runtime/runtime-common/src/lib.rs b/bridges/snowbridge/runtime/runtime-common/src/lib.rs index aae45520ff4..0b1a74b232a 100644 --- a/bridges/snowbridge/runtime/runtime-common/src/lib.rs +++ b/bridges/snowbridge/runtime/runtime-common/src/lib.rs @@ -50,7 +50,8 @@ impl where + > +where Balance: BaseArithmetic + Unsigned + Copy + From + Into + Debug, AccountId: Clone + FullCodec, FeeAssetLocation: Get, diff --git a/cumulus/pallets/parachain-system/src/lib.rs b/cumulus/pallets/parachain-system/src/lib.rs index 9dc41aa03d9..21af35fe3de 100644 --- a/cumulus/pallets/parachain-system/src/lib.rs +++ b/cumulus/pallets/parachain-system/src/lib.rs @@ -390,7 +390,8 @@ pub mod pallet { let maximum_channels = host_config .hrmp_max_message_num_per_candidate - .min(>::take()) as usize; + .min(>::take()) + as usize; // Note: this internally calls the `GetChannelInfo` implementation for this // pallet, which draws on the `RelevantMessagingState`. That in turn has diff --git a/cumulus/pallets/xcmp-queue/src/mock.rs b/cumulus/pallets/xcmp-queue/src/mock.rs index 470e00fe94e..3964ecf28ca 100644 --- a/cumulus/pallets/xcmp-queue/src/mock.rs +++ b/cumulus/pallets/xcmp-queue/src/mock.rs @@ -20,7 +20,7 @@ use cumulus_pallet_parachain_system::AnyRelayNumber; use cumulus_primitives_core::{ChannelInfo, IsSystem, ParaId}; use frame_support::{ derive_impl, parameter_types, - traits::{ConstU32, Everything, Nothing, OriginTrait}, + traits::{ConstU32, Everything, OriginTrait}, BoundedSlice, }; use frame_system::EnsureRoot; @@ -30,10 +30,6 @@ use sp_runtime::{ BuildStorage, }; use xcm::prelude::*; -use xcm_builder::{ - FixedWeightBounds, FrameTransactionalProcessor, FungibleAdapter, IsConcrete, NativeAsset, - ParentIsPreset, -}; use xcm_executor::traits::ConvertOrigin; type Block = frame_system::mocking::MockBlock; @@ -119,61 +115,6 @@ parameter_types! { pub const MaxAssetsIntoHolding: u32 = 64; } -/// Means for transacting assets on this chain. -pub type LocalAssetTransactor = FungibleAdapter< - // Use this currency: - Balances, - // Use this currency when it is a fungible asset matching the given location or name: - IsConcrete, - // Do a simple punn to convert an AccountId32 Location into a native chain account ID: - LocationToAccountId, - // Our chain's account ID type (we can't get away without mentioning it explicitly): - AccountId, - // We don't track any teleports. - (), ->; - -pub type LocationToAccountId = (ParentIsPreset,); - -pub struct XcmConfig; -impl xcm_executor::Config for XcmConfig { - type RuntimeCall = RuntimeCall; - type XcmSender = XcmRouter; - // How to withdraw and deposit an asset. - type AssetTransactor = LocalAssetTransactor; - type OriginConverter = (); - type IsReserve = NativeAsset; - type IsTeleporter = NativeAsset; - type UniversalLocation = UniversalLocation; - type Barrier = (); - type Weigher = FixedWeightBounds; - type Trader = (); - type ResponseHandler = (); - type AssetTrap = (); - type AssetClaims = (); - type SubscriptionService = (); - type PalletInstancesInfo = AllPalletsWithSystem; - type MaxAssetsIntoHolding = MaxAssetsIntoHolding; - type AssetLocker = (); - type AssetExchanger = (); - type FeeManager = (); - type MessageExporter = (); - type UniversalAliases = Nothing; - type CallDispatcher = RuntimeCall; - type SafeCallFilter = Everything; - type Aliasers = Nothing; - type TransactionalProcessor = FrameTransactionalProcessor; - type HrmpNewChannelOpenRequestHandler = (); - type HrmpChannelAcceptedHandler = (); - type HrmpChannelClosingHandler = (); - type XcmRecorder = (); -} - -pub type XcmRouter = ( - // XCMP to communicate with the sibling chains. - XcmpQueue, -); - pub struct SystemParachainAsSuperuser(PhantomData); impl ConvertOrigin for SystemParachainAsSuperuser diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/teleport.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/teleport.rs index d6cf819118e..470b4d0f389 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/teleport.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/teleport.rs @@ -265,7 +265,9 @@ fn limited_teleport_native_assets_from_system_para_to_relay_fails() { let delivery_fees = AssetHubRococo::execute_with(|| { xcm_helpers::teleport_assets_delivery_fees::< ::XcmSender, - >(test.args.assets.clone(), 0, test.args.weight_limit, test.args.beneficiary, test.args.dest) + >( + test.args.assets.clone(), 0, test.args.weight_limit, test.args.beneficiary, test.args.dest + ) }); // Sender's balance is reduced diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/teleport.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/teleport.rs index ddb82a954a8..ee0f297792f 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/teleport.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/teleport.rs @@ -265,7 +265,9 @@ fn limited_teleport_native_assets_from_system_para_to_relay_fails() { let delivery_fees = AssetHubWestend::execute_with(|| { xcm_helpers::teleport_assets_delivery_fees::< ::XcmSender, - >(test.args.assets.clone(), 0, test.args.weight_limit, test.args.beneficiary, test.args.dest) + >( + test.args.assets.clone(), 0, test.args.weight_limit, test.args.beneficiary, test.args.dest + ) }); // Sender's balance is reduced diff --git a/cumulus/parachains/integration-tests/emulated/tests/people/people-rococo/src/tests/teleport.rs b/cumulus/parachains/integration-tests/emulated/tests/people/people-rococo/src/tests/teleport.rs index 44e6b3934f0..2619ca7591d 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/people/people-rococo/src/tests/teleport.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/people/people-rococo/src/tests/teleport.rs @@ -107,7 +107,9 @@ fn limited_teleport_native_assets_from_system_para_to_relay_fails() { let delivery_fees = PeopleRococo::execute_with(|| { xcm_helpers::teleport_assets_delivery_fees::< ::XcmSender, - >(test.args.assets.clone(), 0, test.args.weight_limit, test.args.beneficiary, test.args.dest) + >( + test.args.assets.clone(), 0, test.args.weight_limit, test.args.beneficiary, test.args.dest + ) }); // Sender's balance is reduced diff --git a/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/teleport.rs b/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/teleport.rs index 83888031723..d9a2c23ac0c 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/teleport.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/teleport.rs @@ -107,7 +107,9 @@ fn limited_teleport_native_assets_from_system_para_to_relay_fails() { let delivery_fees = PeopleWestend::execute_with(|| { xcm_helpers::teleport_assets_delivery_fees::< ::XcmSender, - >(test.args.assets.clone(), 0, test.args.weight_limit, test.args.beneficiary, test.args.dest) + >( + test.args.assets.clone(), 0, test.args.weight_limit, test.args.beneficiary, test.args.dest + ) }); // Sender's balance is reduced diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs index e46734dfa97..430c5bd1856 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs @@ -545,7 +545,8 @@ impl InstanceFilter for ProxyType { RuntimeCall::Utility { .. } | RuntimeCall::Multisig { .. } | RuntimeCall::NftFractionalization { .. } | - RuntimeCall::Nfts { .. } | RuntimeCall::Uniques { .. } + RuntimeCall::Nfts { .. } | + RuntimeCall::Uniques { .. } ) }, ProxyType::AssetOwner => matches!( @@ -866,7 +867,7 @@ impl pallet_nft_fractionalization::Config for Runtime { type Assets = Assets; type Nfts = Nfts; type PalletId = NftFractionalizationPalletId; - type WeightInfo = pallet_nft_fractionalization::weights::SubstrateWeight; + type WeightInfo = weights::pallet_nft_fractionalization::WeightInfo; type RuntimeHoldReason = RuntimeHoldReason; #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = (); diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs index 946b72e966e..8565cd30f43 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -543,7 +543,8 @@ impl InstanceFilter for ProxyType { RuntimeCall::Utility { .. } | RuntimeCall::Multisig { .. } | RuntimeCall::NftFractionalization { .. } | - RuntimeCall::Nfts { .. } | RuntimeCall::Uniques { .. } + RuntimeCall::Nfts { .. } | + RuntimeCall::Uniques { .. } ) }, ProxyType::AssetOwner => matches!( @@ -859,7 +860,7 @@ impl pallet_nft_fractionalization::Config for Runtime { type Assets = Assets; type Nfts = Nfts; type PalletId = NftFractionalizationPalletId; - type WeightInfo = pallet_nft_fractionalization::weights::SubstrateWeight; + type WeightInfo = weights::pallet_nft_fractionalization::WeightInfo; type RuntimeHoldReason = RuntimeHoldReason; #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = (); diff --git a/cumulus/parachains/runtimes/assets/test-utils/src/test_cases.rs b/cumulus/parachains/runtimes/assets/test-utils/src/test_cases.rs index 67b585ecfe8..c8022214230 100644 --- a/cumulus/parachains/runtimes/assets/test-utils/src/test_cases.rs +++ b/cumulus/parachains/runtimes/assets/test-utils/src/test_cases.rs @@ -1143,7 +1143,8 @@ pub fn create_and_manage_foreign_assets_for_local_consensus_parachain_assets_wor .with_balances(vec![( foreign_creator_as_account_id.clone(), existential_deposit + - asset_deposit + metadata_deposit_base + + asset_deposit + + metadata_deposit_base + metadata_deposit_per_byte_eta + buy_execution_fee_amount.into() + buy_execution_fee_amount.into(), diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs index 4ff388f4ba2..d168d1b7a13 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs @@ -301,12 +301,16 @@ fn relayed_incoming_message_works() { XcmOverBridgeHubRococoInstance, LocationToAccountId, WestendLocation, - >(SiblingParachainLocation::get(), BridgedUniversalLocation::get(), |locations, fee| { - bridge_hub_test_utils::open_bridge_with_storage::< - Runtime, - XcmOverBridgeHubRococoInstance, - >(locations, fee, LegacyLaneId([0, 0, 0, 1])) - }) + >( + SiblingParachainLocation::get(), + BridgedUniversalLocation::get(), + |locations, fee| { + bridge_hub_test_utils::open_bridge_with_storage::< + Runtime, + XcmOverBridgeHubRococoInstance, + >(locations, fee, LegacyLaneId([0, 0, 0, 1])) + }, + ) .1 }, construct_and_apply_extrinsic, @@ -331,12 +335,16 @@ fn free_relay_extrinsic_works() { XcmOverBridgeHubRococoInstance, LocationToAccountId, WestendLocation, - >(SiblingParachainLocation::get(), BridgedUniversalLocation::get(), |locations, fee| { - bridge_hub_test_utils::open_bridge_with_storage::< - Runtime, - XcmOverBridgeHubRococoInstance, - >(locations, fee, LegacyLaneId([0, 0, 0, 1])) - }) + >( + SiblingParachainLocation::get(), + BridgedUniversalLocation::get(), + |locations, fee| { + bridge_hub_test_utils::open_bridge_with_storage::< + Runtime, + XcmOverBridgeHubRococoInstance, + >(locations, fee, LegacyLaneId([0, 0, 0, 1])) + }, + ) .1 }, construct_and_apply_extrinsic, diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/mod.rs b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/mod.rs index 663558f5fd5..9c5d6269dc0 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/mod.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/mod.rs @@ -506,11 +506,12 @@ pub fn message_dispatch_routing_works< // 2. this message is sent from other global consensus with destination of this Runtime // sibling parachain (HRMP) - let bridging_message = test_data::simulate_message_exporter_on_bridged_chain::< - BridgedNetwork, - NetworkWithParentCount, - AlwaysLatest, - >((RuntimeNetwork::get(), [Parachain(sibling_parachain_id)].into())); + let bridging_message = + test_data::simulate_message_exporter_on_bridged_chain::< + BridgedNetwork, + NetworkWithParentCount, + AlwaysLatest, + >((RuntimeNetwork::get(), [Parachain(sibling_parachain_id)].into())); // 2.1. WITHOUT opened hrmp channel -> RoutingError let result = diff --git a/cumulus/parachains/runtimes/starters/shell/src/lib.rs b/cumulus/parachains/runtimes/starters/shell/src/lib.rs index a7c8bc23935..c7e81d67bba 100644 --- a/cumulus/parachains/runtimes/starters/shell/src/lib.rs +++ b/cumulus/parachains/runtimes/starters/shell/src/lib.rs @@ -218,11 +218,7 @@ parameter_types! { impl pallet_message_queue::Config for Runtime { type RuntimeEvent = RuntimeEvent; type WeightInfo = (); - #[cfg(feature = "runtime-benchmarks")] - type MessageProcessor = pallet_message_queue::mock_helpers::NoopMessageProcessor< - cumulus_primitives_core::AggregateMessageOrigin, - >; - #[cfg(not(feature = "runtime-benchmarks"))] + // NOTE: Changes are needed here if you add benchmarking to the runtime. type MessageProcessor = xcm_builder::ProcessXcmMessage< AggregateMessageOrigin, xcm_executor::XcmExecutor, diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/fake_runtime_api/mod.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/fake_runtime_api/mod.rs index 02aa867d70f..bd4ff167d8f 100644 --- a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/fake_runtime_api/mod.rs +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/fake_runtime_api/mod.rs @@ -24,12 +24,14 @@ use utils::{impl_node_runtime_apis, imports::*}; type CustomBlock = crate::common::types::Block; pub mod aura_sr25519 { use super::*; + #[allow(dead_code)] struct FakeRuntime; impl_node_runtime_apis!(FakeRuntime, CustomBlock, sp_consensus_aura::sr25519::AuthorityId); } pub mod aura_ed25519 { use super::*; + #[allow(dead_code)] struct FakeRuntime; impl_node_runtime_apis!(FakeRuntime, CustomBlock, sp_consensus_aura::ed25519::AuthorityId); } diff --git a/cumulus/primitives/utility/src/lib.rs b/cumulus/primitives/utility/src/lib.rs index 6bd14d136a6..8530f5b8748 100644 --- a/cumulus/primitives/utility/src/lib.rs +++ b/cumulus/primitives/utility/src/lib.rs @@ -385,7 +385,8 @@ impl< FungiblesAssetMatcher, OnUnbalanced, AccountId, - > where + > +where Fungibles::Balance: Into, { fn new() -> Self { @@ -545,7 +546,8 @@ impl< FungiblesAssetMatcher, OnUnbalanced, AccountId, - > where + > +where Fungibles::Balance: Into, { fn drop(&mut self) { diff --git a/polkadot/cli/src/command.rs b/polkadot/cli/src/command.rs index 16576e4b272..34ada58bbcd 100644 --- a/polkadot/cli/src/command.rs +++ b/polkadot/cli/src/command.rs @@ -29,8 +29,6 @@ use sp_keyring::Sr25519Keyring; use std::net::ToSocketAddrs; pub use crate::error::Error; -#[cfg(feature = "hostperfcheck")] -pub use polkadot_performance_test::PerfCheckError; #[cfg(feature = "pyroscope")] use pyroscope_pprofrs::{pprof_backend, PprofConfig}; diff --git a/polkadot/node/core/approval-voting-parallel/src/lib.rs b/polkadot/node/core/approval-voting-parallel/src/lib.rs index 18c73cfba1f..1a7ef756bdf 100644 --- a/polkadot/node/core/approval-voting-parallel/src/lib.rs +++ b/polkadot/node/core/approval-voting-parallel/src/lib.rs @@ -878,7 +878,8 @@ pub struct ApprovalVotingToApprovalDistribution> - overseer::SubsystemSender for ApprovalVotingToApprovalDistribution + overseer::SubsystemSender + for ApprovalVotingToApprovalDistribution { #[allow(clippy::type_complexity, clippy::type_repetition_in_bounds)] fn send_message<'life0, 'async_trait>( diff --git a/polkadot/node/core/approval-voting/src/import.rs b/polkadot/node/core/approval-voting/src/import.rs index bf6ea0c9814..5c456d22b21 100644 --- a/polkadot/node/core/approval-voting/src/import.rs +++ b/polkadot/node/core/approval-voting/src/import.rs @@ -320,7 +320,6 @@ pub struct BlockImportedCandidates { pub block_hash: Hash, pub block_number: BlockNumber, pub block_tick: Tick, - pub no_show_duration: Tick, pub imported_candidates: Vec<(CandidateHash, CandidateEntry)>, } @@ -469,14 +468,7 @@ pub(crate) async fn handle_new_head< None => return Ok(Vec::new()), }; - let (block_tick, no_show_duration) = { - let block_tick = slot_number_to_tick(state.slot_duration_millis, slot); - let no_show_duration = slot_number_to_tick( - state.slot_duration_millis, - Slot::from(u64::from(session_info.no_show_slots)), - ); - (block_tick, no_show_duration) - }; + let block_tick = slot_number_to_tick(state.slot_duration_millis, slot); let needed_approvals = session_info.needed_approvals; let validator_group_lens: Vec = @@ -595,7 +587,6 @@ pub(crate) async fn handle_new_head< block_hash, block_number: block_header.number, block_tick, - no_show_duration, imported_candidates: candidate_entries .into_iter() .map(|(h, e)| (h, e.into())) diff --git a/polkadot/node/core/approval-voting/src/lib.rs b/polkadot/node/core/approval-voting/src/lib.rs index 2149ce81fa8..23f052b95f6 100644 --- a/polkadot/node/core/approval-voting/src/lib.rs +++ b/polkadot/node/core/approval-voting/src/lib.rs @@ -2548,7 +2548,12 @@ fn schedule_wakeup_action( last_assignment_tick.map(|l| l + APPROVAL_DELAY).filter(|t| t > &tick_now), next_no_show, ) - .map(|tick| Action::ScheduleWakeup { block_hash, block_number, candidate_hash, tick }) + .map(|tick| Action::ScheduleWakeup { + block_hash, + block_number, + candidate_hash, + tick, + }) }, RequiredTranches::Pending { considered, next_no_show, clock_drift, .. } => { // select the minimum of `next_no_show`, or the tick of the next non-empty tranche diff --git a/polkadot/node/core/approval-voting/src/tests.rs b/polkadot/node/core/approval-voting/src/tests.rs index 8aa78df5495..ec825b800c7 100644 --- a/polkadot/node/core/approval-voting/src/tests.rs +++ b/polkadot/node/core/approval-voting/src/tests.rs @@ -253,7 +253,8 @@ where _relay_vrf_story: polkadot_node_primitives::approval::v1::RelayVRFStory, _assignment: &polkadot_node_primitives::approval::v2::AssignmentCertV2, _backing_groups: Vec, - ) -> Result { + ) -> Result + { self.1(validator_index) } } diff --git a/polkadot/node/core/pvf/build.rs b/polkadot/node/core/pvf/build.rs index e01cc6deecc..e46f2dc5f55 100644 --- a/polkadot/node/core/pvf/build.rs +++ b/polkadot/node/core/pvf/build.rs @@ -16,6 +16,6 @@ fn main() { if let Ok(profile) = std::env::var("PROFILE") { - println!(r#"cargo:rustc-cfg=build_type="{}""#, profile); + println!(r#"cargo:rustc-cfg=build_profile="{}""#, profile); } } diff --git a/polkadot/node/core/pvf/common/src/worker/security/change_root.rs b/polkadot/node/core/pvf/common/src/worker/security/change_root.rs index 9ec66906819..fcfaf6541c2 100644 --- a/polkadot/node/core/pvf/common/src/worker/security/change_root.rs +++ b/polkadot/node/core/pvf/common/src/worker/security/change_root.rs @@ -124,7 +124,8 @@ fn try_restrict(worker_info: &WorkerInfo) -> Result<()> { libc::MS_BIND | libc::MS_REC | libc::MS_NOEXEC | libc::MS_NODEV | libc::MS_NOSUID | - libc::MS_NOATIME | additional_flags, + libc::MS_NOATIME | + additional_flags, ptr::null(), // ignored when MS_BIND is used ) < 0 { diff --git a/polkadot/node/core/pvf/src/artifacts.rs b/polkadot/node/core/pvf/src/artifacts.rs index 119af34082a..1126a0c90c8 100644 --- a/polkadot/node/core/pvf/src/artifacts.rs +++ b/polkadot/node/core/pvf/src/artifacts.rs @@ -56,7 +56,7 @@ use crate::{host::PrecheckResultSender, worker_interface::WORKER_DIR_PREFIX}; use always_assert::always; -use polkadot_node_core_pvf_common::{error::PrepareError, prepare::PrepareStats, pvf::PvfPrepData}; +use polkadot_node_core_pvf_common::{error::PrepareError, pvf::PvfPrepData}; use polkadot_parachain_primitives::primitives::ValidationCodeHash; use polkadot_primitives::ExecutorParamsPrepHash; use std::{ @@ -144,8 +144,6 @@ pub enum ArtifactState { last_time_needed: SystemTime, /// Size in bytes size: u64, - /// Stats produced by successful preparation. - prepare_stats: PrepareStats, }, /// A task to prepare this artifact is scheduled. Preparing { @@ -269,15 +267,11 @@ impl Artifacts { path: PathBuf, last_time_needed: SystemTime, size: u64, - prepare_stats: PrepareStats, ) { // See the precondition. always!(self .inner - .insert( - artifact_id, - ArtifactState::Prepared { path, last_time_needed, size, prepare_stats } - ) + .insert(artifact_id, ArtifactState::Prepared { path, last_time_needed, size }) .is_none()); } @@ -384,21 +378,18 @@ mod tests { path1.clone(), mock_now - Duration::from_secs(5), 1024, - PrepareStats::default(), ); artifacts.insert_prepared( artifact_id2.clone(), path2.clone(), mock_now - Duration::from_secs(10), 1024, - PrepareStats::default(), ); artifacts.insert_prepared( artifact_id3.clone(), path3.clone(), mock_now - Duration::from_secs(15), 1024, - PrepareStats::default(), ); let pruned = artifacts.prune(&cleanup_config); @@ -432,21 +423,18 @@ mod tests { path1.clone(), mock_now - Duration::from_secs(5), 1024, - PrepareStats::default(), ); artifacts.insert_prepared( artifact_id2.clone(), path2.clone(), mock_now - Duration::from_secs(10), 1024, - PrepareStats::default(), ); artifacts.insert_prepared( artifact_id3.clone(), path3.clone(), mock_now - Duration::from_secs(15), 1024, - PrepareStats::default(), ); let pruned = artifacts.prune(&cleanup_config); diff --git a/polkadot/node/core/pvf/src/host.rs b/polkadot/node/core/pvf/src/host.rs index 44a4cba2fbf..22943a06c43 100644 --- a/polkadot/node/core/pvf/src/host.rs +++ b/polkadot/node/core/pvf/src/host.rs @@ -813,12 +813,8 @@ async fn handle_prepare_done( } *state = match result { - Ok(PrepareSuccess { path, stats: prepare_stats, size }) => ArtifactState::Prepared { - path, - last_time_needed: SystemTime::now(), - size, - prepare_stats, - }, + Ok(PrepareSuccess { path, size, .. }) => + ArtifactState::Prepared { path, last_time_needed: SystemTime::now(), size }, Err(error) => { let last_time_failed = SystemTime::now(); let num_failures = *num_failures + 1; @@ -976,7 +972,6 @@ pub(crate) mod tests { use crate::{artifacts::generate_artifact_path, testing::artifact_id, PossiblyInvalidError}; use assert_matches::assert_matches; use futures::future::BoxFuture; - use polkadot_node_core_pvf_common::prepare::PrepareStats; use polkadot_node_primitives::BlockData; use sp_core::H256; @@ -1196,20 +1191,8 @@ pub(crate) mod tests { builder.cleanup_config = ArtifactsCleanupConfig::new(1024, Duration::from_secs(0)); let path1 = generate_artifact_path(cache_path); let path2 = generate_artifact_path(cache_path); - builder.artifacts.insert_prepared( - artifact_id(1), - path1.clone(), - mock_now, - 1024, - PrepareStats::default(), - ); - builder.artifacts.insert_prepared( - artifact_id(2), - path2.clone(), - mock_now, - 1024, - PrepareStats::default(), - ); + builder.artifacts.insert_prepared(artifact_id(1), path1.clone(), mock_now, 1024); + builder.artifacts.insert_prepared(artifact_id(2), path2.clone(), mock_now, 1024); let mut test = builder.build(); let mut host = test.host_handle(); diff --git a/polkadot/node/core/pvf/src/prepare/pool.rs b/polkadot/node/core/pvf/src/prepare/pool.rs index 4e11f977c9e..67cd71812e5 100644 --- a/polkadot/node/core/pvf/src/prepare/pool.rs +++ b/polkadot/node/core/pvf/src/prepare/pool.rs @@ -343,14 +343,13 @@ fn handle_mux( ), // Return `Concluded`, but do not kill the worker since the error was on the host // side. - Outcome::RenameTmpFile { worker: idle, result: _, err, src, dest } => - handle_concluded_no_rip( - from_pool, - spawned, - worker, - idle, - Err(PrepareError::RenameTmpFile { err, src, dest }), - ), + Outcome::RenameTmpFile { worker: idle, err, src, dest } => handle_concluded_no_rip( + from_pool, + spawned, + worker, + idle, + Err(PrepareError::RenameTmpFile { err, src, dest }), + ), // Could not clear worker cache. Kill the worker so other jobs can't see the data. Outcome::ClearWorkerDir { err } => { if attempt_retire(metrics, spawned, worker) { diff --git a/polkadot/node/core/pvf/src/prepare/worker_interface.rs b/polkadot/node/core/pvf/src/prepare/worker_interface.rs index d29d2717c4b..718416e8be7 100644 --- a/polkadot/node/core/pvf/src/prepare/worker_interface.rs +++ b/polkadot/node/core/pvf/src/prepare/worker_interface.rs @@ -81,7 +81,6 @@ pub enum Outcome { /// final destination location. RenameTmpFile { worker: IdleWorker, - result: PrepareWorkerResult, err: String, // Unfortunately `PathBuf` doesn't implement `Encode`/`Decode`, so we do a fallible // conversion to `Option`. @@ -287,7 +286,6 @@ async fn handle_response( ); Outcome::RenameTmpFile { worker, - result, err: format!("{:?}", err), src: tmp_file.to_str().map(String::from), dest: artifact_path.to_str().map(String::from), diff --git a/polkadot/node/core/pvf/src/testing.rs b/polkadot/node/core/pvf/src/testing.rs index 8c75dafa69c..9a4004f3903 100644 --- a/polkadot/node/core/pvf/src/testing.rs +++ b/polkadot/node/core/pvf/src/testing.rs @@ -72,7 +72,7 @@ pub fn build_workers_and_get_paths() -> (PathBuf, PathBuf) { "--bin=polkadot-execute-worker", ]; - if cfg!(build_type = "release") { + if cfg!(build_profile = "release") { build_args.push("--release"); } diff --git a/polkadot/node/network/approval-distribution/src/tests.rs b/polkadot/node/network/approval-distribution/src/tests.rs index 068559dea76..063e71f2f52 100644 --- a/polkadot/node/network/approval-distribution/src/tests.rs +++ b/polkadot/node/network/approval-distribution/src/tests.rs @@ -535,7 +535,8 @@ impl AssignmentCriteria for MockAssignmentCriteria { _relay_vrf_story: polkadot_node_primitives::approval::v1::RelayVRFStory, _assignment: &polkadot_node_primitives::approval::v2::AssignmentCertV2, _backing_groups: Vec, - ) -> Result { + ) -> Result + { self.tranche } } diff --git a/polkadot/node/network/availability-distribution/src/tests/mod.rs b/polkadot/node/network/availability-distribution/src/tests/mod.rs index 3320871bceb..078220607c3 100644 --- a/polkadot/node/network/availability-distribution/src/tests/mod.rs +++ b/polkadot/node/network/availability-distribution/src/tests/mod.rs @@ -45,7 +45,7 @@ fn test_harness>( let (context, virtual_overseer) = polkadot_node_subsystem_test_helpers::make_subsystem_context(pool.clone()); - let (pov_req_receiver, pov_req_cfg) = IncomingRequest::get_config_receiver::< + let (pov_req_receiver, _pov_req_cfg) = IncomingRequest::get_config_receiver::< Block, sc_network::NetworkWorker, >(&req_protocol_names); @@ -65,13 +65,8 @@ fn test_harness>( ); let subsystem = subsystem.run(context); - let test_fut = test_fx(TestHarness { - virtual_overseer, - pov_req_cfg, - chunk_req_v1_cfg, - chunk_req_v2_cfg, - pool, - }); + let test_fut = + test_fx(TestHarness { virtual_overseer, chunk_req_v1_cfg, chunk_req_v2_cfg, pool }); futures::pin_mut!(test_fut); futures::pin_mut!(subsystem); diff --git a/polkadot/node/network/availability-distribution/src/tests/state.rs b/polkadot/node/network/availability-distribution/src/tests/state.rs index 97e616f79fb..53d6fd2c530 100644 --- a/polkadot/node/network/availability-distribution/src/tests/state.rs +++ b/polkadot/node/network/availability-distribution/src/tests/state.rs @@ -60,7 +60,6 @@ type VirtualOverseer = polkadot_node_subsystem_test_helpers::TestSubsystemContex >; pub struct TestHarness { pub virtual_overseer: VirtualOverseer, - pub pov_req_cfg: RequestResponseConfig, pub chunk_req_v1_cfg: RequestResponseConfig, pub chunk_req_v2_cfg: RequestResponseConfig, pub pool: TaskExecutor, diff --git a/polkadot/node/network/availability-recovery/src/task/strategy/chunks.rs b/polkadot/node/network/availability-recovery/src/task/strategy/chunks.rs index b6376a5b543..6b34538b626 100644 --- a/polkadot/node/network/availability-recovery/src/task/strategy/chunks.rs +++ b/polkadot/node/network/availability-recovery/src/task/strategy/chunks.rs @@ -107,9 +107,10 @@ impl FetchChunks { state: &mut State, common_params: &RecoveryParams, ) -> Result { - let recovery_duration = common_params - .metrics - .time_erasure_recovery(RecoveryStrategy::::strategy_type(self)); + let recovery_duration = + common_params + .metrics + .time_erasure_recovery(RecoveryStrategy::::strategy_type(self)); // Send request to reconstruct available data from chunks. let (avilable_data_tx, available_data_rx) = oneshot::channel(); @@ -136,18 +137,16 @@ impl FetchChunks { // Attempt post-recovery check. Ok(data) => do_post_recovery_check(common_params, data) .await - .map_err(|e| { + .inspect_err(|_| { recovery_duration.map(|rd| rd.stop_and_discard()); - e }) - .map(|data| { + .inspect(|_| { gum::trace!( target: LOG_TARGET, candidate_hash = ?common_params.candidate_hash, erasure_root = ?common_params.erasure_root, "Data recovery from chunks complete", ); - data }), Err(err) => { recovery_duration.map(|rd| rd.stop_and_discard()); diff --git a/polkadot/node/network/availability-recovery/src/task/strategy/systematic.rs b/polkadot/node/network/availability-recovery/src/task/strategy/systematic.rs index 677bc2d1375..8b8cff54991 100644 --- a/polkadot/node/network/availability-recovery/src/task/strategy/systematic.rs +++ b/polkadot/node/network/availability-recovery/src/task/strategy/systematic.rs @@ -125,18 +125,16 @@ impl FetchSystematicChunks { // Attempt post-recovery check. do_post_recovery_check(common_params, data) .await - .map_err(|e| { + .inspect_err(|_| { recovery_duration.map(|rd| rd.stop_and_discard()); - e }) - .map(|data| { + .inspect(|_| { gum::trace!( target: LOG_TARGET, candidate_hash = ?common_params.candidate_hash, erasure_root = ?common_params.erasure_root, "Data recovery from systematic chunks complete", ); - data }) }, Err(err) => { diff --git a/polkadot/node/overseer/src/lib.rs b/polkadot/node/overseer/src/lib.rs index 10a4320433a..e30ed69f9e3 100644 --- a/polkadot/node/overseer/src/lib.rs +++ b/polkadot/node/overseer/src/lib.rs @@ -60,6 +60,7 @@ // unused dependencies can not work for test and examples at the same time // yielding false positives #![warn(missing_docs)] +#![allow(dead_code)] // TODO https://github.com/paritytech/polkadot-sdk/issues/5793 use std::{ collections::{hash_map, HashMap}, diff --git a/polkadot/node/service/src/fake_runtime_api.rs b/polkadot/node/service/src/fake_runtime_api.rs index 1f2efdbbb5b..d8f147a9cf7 100644 --- a/polkadot/node/service/src/fake_runtime_api.rs +++ b/polkadot/node/service/src/fake_runtime_api.rs @@ -53,6 +53,7 @@ sp_api::decl_runtime_apis! { } } +#[allow(dead_code)] struct Runtime; sp_api::impl_runtime_apis! { diff --git a/polkadot/node/service/src/parachains_db/mod.rs b/polkadot/node/service/src/parachains_db/mod.rs index 59af30dceeb..887db80a303 100644 --- a/polkadot/node/service/src/parachains_db/mod.rs +++ b/polkadot/node/service/src/parachains_db/mod.rs @@ -100,18 +100,11 @@ pub struct CacheSizes { pub availability_meta: usize, /// Cache used by approval data. pub approval_data: usize, - /// Cache used by session window data - pub session_data: usize, } impl Default for CacheSizes { fn default() -> Self { - CacheSizes { - availability_data: 25, - availability_meta: 1, - approval_data: 5, - session_data: 1, - } + CacheSizes { availability_data: 25, availability_meta: 1, approval_data: 5 } } } diff --git a/polkadot/node/service/src/tests.rs b/polkadot/node/service/src/tests.rs index 85b750ddad6..78bbfcd5444 100644 --- a/polkadot/node/service/src/tests.rs +++ b/polkadot/node/service/src/tests.rs @@ -63,9 +63,6 @@ struct TestHarness { finality_target_rx: Receiver>, } -#[derive(Default)] -struct HarnessConfig; - fn test_harness>( case_vars: CaseVars, test: impl FnOnce(TestHarness) -> T, diff --git a/polkadot/node/subsystem-bench/src/lib/approval/message_generator.rs b/polkadot/node/subsystem-bench/src/lib/approval/message_generator.rs index da25a3bf3b7..807afb0438c 100644 --- a/polkadot/node/subsystem-bench/src/lib/approval/message_generator.rs +++ b/polkadot/node/subsystem-bench/src/lib/approval/message_generator.rs @@ -401,7 +401,7 @@ impl PeerMessagesGenerator { /// We can not sample every time for all the messages because that would be too expensive to /// perform, so pre-generate a list of samples for a given network size. /// - result[i] give us as a list of random nodes that would send a given message to the node under -/// test. +/// test. fn random_samplings_to_node( node_under_test: ValidatorIndex, num_validators: usize, @@ -474,8 +474,7 @@ fn issue_approvals( coalesce_approvals_len(options.coalesce_mean, options.coalesce_std_dev, rand_chacha); let result = assignments .iter() - .enumerate() - .map(|(_index, message)| match &message.msg { + .map(|message| match &message.msg { protocol_v3::ApprovalDistributionMessage::Assignments(assignments) => { let mut approvals_to_create = Vec::new(); diff --git a/polkadot/node/subsystem-bench/src/lib/statement/mod.rs b/polkadot/node/subsystem-bench/src/lib/statement/mod.rs index e2d50f28568..dd7095d3b00 100644 --- a/polkadot/node/subsystem-bench/src/lib/statement/mod.rs +++ b/polkadot/node/subsystem-bench/src/lib/statement/mod.rs @@ -114,14 +114,14 @@ fn build_overseer( state.pvd.clone(), state.own_backing_group.clone(), ); - let (statement_req_receiver, statement_req_cfg) = IncomingRequest::get_config_receiver::< - Block, - sc_network::NetworkWorker, - >(&ReqProtocolNames::new(GENESIS_HASH, None)); - let (candidate_req_receiver, candidate_req_cfg) = IncomingRequest::get_config_receiver::< - Block, - sc_network::NetworkWorker, - >(&ReqProtocolNames::new(GENESIS_HASH, None)); + let (statement_req_receiver, statement_req_cfg) = + IncomingRequest::get_config_receiver::>( + &ReqProtocolNames::new(GENESIS_HASH, None), + ); + let (candidate_req_receiver, candidate_req_cfg) = + IncomingRequest::get_config_receiver::>( + &ReqProtocolNames::new(GENESIS_HASH, None), + ); let keystore = make_keystore(); let subsystem = StatementDistributionSubsystem::new( keystore.clone(), diff --git a/polkadot/node/subsystem-types/src/runtime_client.rs b/polkadot/node/subsystem-types/src/runtime_client.rs index 7938223df23..a8af8b7996f 100644 --- a/polkadot/node/subsystem-types/src/runtime_client.rs +++ b/polkadot/node/subsystem-types/src/runtime_client.rs @@ -665,7 +665,8 @@ where fn number( &self, hash: Block::Hash, - ) -> sc_client_api::blockchain::Result::Header as HeaderT>::Number>> { + ) -> sc_client_api::blockchain::Result::Header as HeaderT>::Number>> + { self.client.number(hash) } diff --git a/polkadot/primitives/src/runtime_api.rs b/polkadot/primitives/src/runtime_api.rs index ddebe99e621..3c90c050bae 100644 --- a/polkadot/primitives/src/runtime_api.rs +++ b/polkadot/primitives/src/runtime_api.rs @@ -36,7 +36,7 @@ //! //! Let's see a quick example: //! -//! ```rust(ignore) +//! ```nocompile //! sp_api::decl_runtime_apis! { //! #[api_version(2)] //! pub trait MyApi { diff --git a/polkadot/primitives/src/v8/metrics.rs b/polkadot/primitives/src/v8/metrics.rs index 1d66c9848a7..409efc86bc9 100644 --- a/polkadot/primitives/src/v8/metrics.rs +++ b/polkadot/primitives/src/v8/metrics.rs @@ -91,18 +91,6 @@ pub type RuntimeMetricLabelValue = RuntimeMetricLabel; /// A set of metric label values. pub type RuntimeMetricLabelValues = RuntimeMetricLabels; -/// Trait for converting Vec to `&str`. -pub trait AsStr { - /// Return a str reference. - fn as_str(&self) -> Option<&str>; -} - -impl AsStr for RuntimeMetricLabel { - fn as_str(&self) -> Option<&str> { - alloc::str::from_utf8(&self.0).ok() - } -} - impl From<&'static str> for RuntimeMetricLabel { fn from(s: &'static str) -> Self { Self(s.as_bytes().to_vec()) diff --git a/polkadot/runtime/parachains/src/hrmp.rs b/polkadot/runtime/parachains/src/hrmp.rs index b149404b41b..220543f00ec 100644 --- a/polkadot/runtime/parachains/src/hrmp.rs +++ b/polkadot/runtime/parachains/src/hrmp.rs @@ -945,7 +945,7 @@ impl Pallet { outgoing_paras.len() as u32 )) .saturating_add(::WeightInfo::force_process_hrmp_close( - outgoing_paras.len() as u32 + outgoing_paras.len() as u32, )) } diff --git a/polkadot/runtime/parachains/src/inclusion/mod.rs b/polkadot/runtime/parachains/src/inclusion/mod.rs index e014529ea11..cc333e3b20f 100644 --- a/polkadot/runtime/parachains/src/inclusion/mod.rs +++ b/polkadot/runtime/parachains/src/inclusion/mod.rs @@ -991,9 +991,8 @@ impl Pallet { .iter() .filter_map(|d| { BoundedSlice::try_from(&d[..]) - .map_err(|e| { + .inspect_err(|_| { defensive!("Accepted candidate contains too long msg, len=", d.len()); - e }) .ok() }) diff --git a/polkadot/runtime/parachains/src/paras_inherent/mod.rs b/polkadot/runtime/parachains/src/paras_inherent/mod.rs index 84d8299cd29..70c4ba72d58 100644 --- a/polkadot/runtime/parachains/src/paras_inherent/mod.rs +++ b/polkadot/runtime/parachains/src/paras_inherent/mod.rs @@ -1216,18 +1216,19 @@ fn filter_backed_statements_from_disabled_validators< // Get relay parent block number of the candidate. We need this to get the group index // assigned to this core at this block number - let relay_parent_block_number = - match allowed_relay_parents.acquire_info(bc.descriptor().relay_parent(), None) { - Some((_, block_num)) => block_num, - None => { - log::debug!( - target: LOG_TARGET, - "Relay parent {:?} for candidate is not in the allowed relay parents. Dropping the candidate.", - bc.descriptor().relay_parent() - ); - return false - }, - }; + let relay_parent_block_number = match allowed_relay_parents + .acquire_info(bc.descriptor().relay_parent(), None) + { + Some((_, block_num)) => block_num, + None => { + log::debug!( + target: LOG_TARGET, + "Relay parent {:?} for candidate is not in the allowed relay parents. Dropping the candidate.", + bc.descriptor().relay_parent() + ); + return false + }, + }; // Get the group index for the core let group_idx = match scheduler::Pallet::::group_assigned_to_core( diff --git a/polkadot/runtime/parachains/src/ump_tests.rs b/polkadot/runtime/parachains/src/ump_tests.rs index d914bf8b666..91571859ecf 100644 --- a/polkadot/runtime/parachains/src/ump_tests.rs +++ b/polkadot/runtime/parachains/src/ump_tests.rs @@ -462,10 +462,11 @@ fn verify_relay_dispatch_queue_size_is_externally_accessible() { fn assert_queue_size(para: ParaId, count: u32, size: u32) { #[allow(deprecated)] - let raw_queue_size = sp_io::storage::get(&well_known_keys::relay_dispatch_queue_size(para)).expect( - "enqueuing a message should create the dispatch queue\ + let raw_queue_size = sp_io::storage::get(&well_known_keys::relay_dispatch_queue_size(para)) + .expect( + "enqueuing a message should create the dispatch queue\ and it should be accessible via the well known keys", - ); + ); let (c, s) = <(u32, u32)>::decode(&mut &raw_queue_size[..]) .expect("the dispatch queue size should be decodable into (u32, u32)"); assert_eq!((c, s), (count, size)); diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index d6ed11d0787..576e297df31 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -1110,7 +1110,8 @@ impl InstanceFilter for ProxyType { matches!( c, RuntimeCall::Staking(..) | - RuntimeCall::Session(..) | RuntimeCall::Utility(..) | + RuntimeCall::Session(..) | + RuntimeCall::Utility(..) | RuntimeCall::FastUnstake(..) | RuntimeCall::VoterList(..) | RuntimeCall::NominationPools(..) @@ -2761,49 +2762,3 @@ sp_api::impl_runtime_apis! { } } } - -mod clean_state_migration { - use super::Runtime; - #[cfg(feature = "try-runtime")] - use super::Vec; - use frame_support::{pallet_prelude::*, storage_alias, traits::OnRuntimeUpgrade}; - use pallet_state_trie_migration::MigrationLimits; - - #[storage_alias] - type AutoLimits = StorageValue, ValueQuery>; - - // Actual type of value is `MigrationTask`, putting a dummy - // one to avoid the trait constraint on T. - // Since we only use `kill` it is fine. - #[storage_alias] - type MigrationProcess = StorageValue; - - #[storage_alias] - type SignedMigrationMaxLimits = StorageValue; - - /// Initialize an automatic migration process. - pub struct CleanMigrate; - - impl OnRuntimeUpgrade for CleanMigrate { - #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result, sp_runtime::TryRuntimeError> { - Ok(Default::default()) - } - - fn on_runtime_upgrade() -> frame_support::weights::Weight { - MigrationProcess::kill(); - AutoLimits::kill(); - SignedMigrationMaxLimits::kill(); - ::DbWeight::get().writes(3) - } - - #[cfg(feature = "try-runtime")] - fn post_upgrade(_state: Vec) -> Result<(), sp_runtime::TryRuntimeError> { - frame_support::ensure!( - !AutoLimits::exists() && !SignedMigrationMaxLimits::exists(), - "State migration clean.", - ); - Ok(()) - } - } -} diff --git a/polkadot/runtime/westend/src/weights/mod.rs b/polkadot/runtime/westend/src/weights/mod.rs index 1e7b01bc472..ae11f6ccba4 100644 --- a/polkadot/runtime/westend/src/weights/mod.rs +++ b/polkadot/runtime/westend/src/weights/mod.rs @@ -33,7 +33,6 @@ pub mod pallet_nomination_pools; pub mod pallet_parameters; pub mod pallet_preimage; pub mod pallet_proxy; -pub mod pallet_referenda_fellowship_referenda; pub mod pallet_referenda_referenda; pub mod pallet_scheduler; pub mod pallet_session; diff --git a/polkadot/runtime/westend/src/weights/pallet_referenda_fellowship_referenda.rs b/polkadot/runtime/westend/src/weights/pallet_referenda_fellowship_referenda.rs deleted file mode 100644 index a4ac0667911..00000000000 --- a/polkadot/runtime/westend/src/weights/pallet_referenda_fellowship_referenda.rs +++ /dev/null @@ -1,525 +0,0 @@ -// Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Polkadot. - -// Polkadot is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Polkadot is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Polkadot. If not, see . - -//! Autogenerated weights for `pallet_referenda` -//! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-06-19, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-e8ezs4ez-project-163-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("kusama-dev"), DB CACHE: 1024 - -// Executed Command: -// ./target/production/polkadot -// benchmark -// pallet -// --chain=kusama-dev -// --steps=50 -// --repeat=20 -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --pallet=pallet_referenda -// --extrinsic=* -// --execution=wasm -// --wasm-execution=compiled -// --header=./file_header.txt -// --output=./runtime/kusama/src/weights/ - -#![cfg_attr(rustfmt, rustfmt_skip)] -#![allow(unused_parens)] -#![allow(unused_imports)] -#![allow(missing_docs)] - -use frame_support::{traits::Get, weights::Weight}; -use core::marker::PhantomData; - -/// Weight functions for `pallet_referenda`. -pub struct WeightInfo(PhantomData); -impl pallet_referenda::WeightInfo for WeightInfo { - /// Storage: FellowshipCollective Members (r:1 w:0) - /// Proof: FellowshipCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) - /// Storage: FellowshipReferenda ReferendumCount (r:1 w:1) - /// Proof: FellowshipReferenda ReferendumCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) - /// Storage: FellowshipReferenda ReferendumInfoFor (r:0 w:1) - /// Proof: FellowshipReferenda ReferendumInfoFor (max_values: None, max_size: Some(900), added: 3375, mode: MaxEncodedLen) - fn submit() -> Weight { - // Proof Size summary in bytes: - // Measured: `327` - // Estimated: `42428` - // Minimum execution time: 28_969_000 picoseconds. - Weight::from_parts(30_902_000, 0) - .saturating_add(Weight::from_parts(0, 42428)) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(3)) - } - /// Storage: FellowshipReferenda ReferendumInfoFor (r:1 w:1) - /// Proof: FellowshipReferenda ReferendumInfoFor (max_values: None, max_size: Some(900), added: 3375, mode: MaxEncodedLen) - /// Storage: Scheduler Agenda (r:2 w:2) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) - fn place_decision_deposit_preparing() -> Weight { - // Proof Size summary in bytes: - // Measured: `404` - // Estimated: `83866` - // Minimum execution time: 53_500_000 picoseconds. - Weight::from_parts(54_447_000, 0) - .saturating_add(Weight::from_parts(0, 83866)) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(3)) - } - /// Storage: FellowshipReferenda ReferendumInfoFor (r:1 w:1) - /// Proof: FellowshipReferenda ReferendumInfoFor (max_values: None, max_size: Some(900), added: 3375, mode: MaxEncodedLen) - /// Storage: FellowshipReferenda DecidingCount (r:1 w:0) - /// Proof: FellowshipReferenda DecidingCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) - /// Storage: FellowshipReferenda TrackQueue (r:1 w:1) - /// Proof: FellowshipReferenda TrackQueue (max_values: None, max_size: Some(812), added: 3287, mode: MaxEncodedLen) - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) - fn place_decision_deposit_queued() -> Weight { - // Proof Size summary in bytes: - // Measured: `2042` - // Estimated: `42428` - // Minimum execution time: 114_321_000 picoseconds. - Weight::from_parts(122_607_000, 0) - .saturating_add(Weight::from_parts(0, 42428)) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(3)) - } - /// Storage: FellowshipReferenda ReferendumInfoFor (r:1 w:1) - /// Proof: FellowshipReferenda ReferendumInfoFor (max_values: None, max_size: Some(900), added: 3375, mode: MaxEncodedLen) - /// Storage: FellowshipReferenda DecidingCount (r:1 w:0) - /// Proof: FellowshipReferenda DecidingCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) - /// Storage: FellowshipReferenda TrackQueue (r:1 w:1) - /// Proof: FellowshipReferenda TrackQueue (max_values: None, max_size: Some(812), added: 3287, mode: MaxEncodedLen) - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) - fn place_decision_deposit_not_queued() -> Weight { - // Proof Size summary in bytes: - // Measured: `2083` - // Estimated: `42428` - // Minimum execution time: 113_476_000 picoseconds. - Weight::from_parts(120_078_000, 0) - .saturating_add(Weight::from_parts(0, 42428)) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(3)) - } - /// Storage: FellowshipReferenda ReferendumInfoFor (r:1 w:1) - /// Proof: FellowshipReferenda ReferendumInfoFor (max_values: None, max_size: Some(900), added: 3375, mode: MaxEncodedLen) - /// Storage: FellowshipReferenda DecidingCount (r:1 w:1) - /// Proof: FellowshipReferenda DecidingCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) - /// Storage: FellowshipCollective MemberCount (r:1 w:0) - /// Proof: FellowshipCollective MemberCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) - /// Storage: Scheduler Agenda (r:2 w:2) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) - fn place_decision_deposit_passing() -> Weight { - // Proof Size summary in bytes: - // Measured: `774` - // Estimated: `83866` - // Minimum execution time: 194_798_000 picoseconds. - Weight::from_parts(208_378_000, 0) - .saturating_add(Weight::from_parts(0, 83866)) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(4)) - } - /// Storage: FellowshipReferenda ReferendumInfoFor (r:1 w:1) - /// Proof: FellowshipReferenda ReferendumInfoFor (max_values: None, max_size: Some(900), added: 3375, mode: MaxEncodedLen) - /// Storage: FellowshipReferenda DecidingCount (r:1 w:1) - /// Proof: FellowshipReferenda DecidingCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) - /// Storage: FellowshipCollective MemberCount (r:1 w:0) - /// Proof: FellowshipCollective MemberCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) - /// Storage: Scheduler Agenda (r:2 w:2) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) - fn place_decision_deposit_failing() -> Weight { - // Proof Size summary in bytes: - // Measured: `639` - // Estimated: `83866` - // Minimum execution time: 69_502_000 picoseconds. - Weight::from_parts(71_500_000, 0) - .saturating_add(Weight::from_parts(0, 83866)) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(4)) - } - /// Storage: FellowshipReferenda ReferendumInfoFor (r:1 w:1) - /// Proof: FellowshipReferenda ReferendumInfoFor (max_values: None, max_size: Some(900), added: 3375, mode: MaxEncodedLen) - fn refund_decision_deposit() -> Weight { - // Proof Size summary in bytes: - // Measured: `317` - // Estimated: `4365` - // Minimum execution time: 30_561_000 picoseconds. - Weight::from_parts(31_427_000, 0) - .saturating_add(Weight::from_parts(0, 4365)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: FellowshipReferenda ReferendumInfoFor (r:1 w:1) - /// Proof: FellowshipReferenda ReferendumInfoFor (max_values: None, max_size: Some(900), added: 3375, mode: MaxEncodedLen) - fn refund_submission_deposit() -> Weight { - // Proof Size summary in bytes: - // Measured: `167` - // Estimated: `4365` - // Minimum execution time: 14_535_000 picoseconds. - Weight::from_parts(14_999_000, 0) - .saturating_add(Weight::from_parts(0, 4365)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: FellowshipReferenda ReferendumInfoFor (r:1 w:1) - /// Proof: FellowshipReferenda ReferendumInfoFor (max_values: None, max_size: Some(900), added: 3375, mode: MaxEncodedLen) - /// Storage: Scheduler Agenda (r:2 w:2) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) - fn cancel() -> Weight { - // Proof Size summary in bytes: - // Measured: `349` - // Estimated: `83866` - // Minimum execution time: 38_532_000 picoseconds. - Weight::from_parts(39_361_000, 0) - .saturating_add(Weight::from_parts(0, 83866)) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(3)) - } - /// Storage: FellowshipReferenda ReferendumInfoFor (r:1 w:1) - /// Proof: FellowshipReferenda ReferendumInfoFor (max_values: None, max_size: Some(900), added: 3375, mode: MaxEncodedLen) - /// Storage: Scheduler Agenda (r:2 w:2) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) - /// Storage: FellowshipReferenda MetadataOf (r:1 w:0) - /// Proof: FellowshipReferenda MetadataOf (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen) - fn kill() -> Weight { - // Proof Size summary in bytes: - // Measured: `450` - // Estimated: `83866` - // Minimum execution time: 78_956_000 picoseconds. - Weight::from_parts(80_594_000, 0) - .saturating_add(Weight::from_parts(0, 83866)) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(3)) - } - /// Storage: FellowshipReferenda TrackQueue (r:1 w:0) - /// Proof: FellowshipReferenda TrackQueue (max_values: None, max_size: Some(812), added: 3287, mode: MaxEncodedLen) - /// Storage: FellowshipReferenda DecidingCount (r:1 w:1) - /// Proof: FellowshipReferenda DecidingCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) - fn one_fewer_deciding_queue_empty() -> Weight { - // Proof Size summary in bytes: - // Measured: `140` - // Estimated: `4277` - // Minimum execution time: 9_450_000 picoseconds. - Weight::from_parts(9_881_000, 0) - .saturating_add(Weight::from_parts(0, 4277)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: FellowshipReferenda TrackQueue (r:1 w:1) - /// Proof: FellowshipReferenda TrackQueue (max_values: None, max_size: Some(812), added: 3287, mode: MaxEncodedLen) - /// Storage: FellowshipReferenda ReferendumInfoFor (r:1 w:1) - /// Proof: FellowshipReferenda ReferendumInfoFor (max_values: None, max_size: Some(900), added: 3375, mode: MaxEncodedLen) - /// Storage: FellowshipCollective MemberCount (r:1 w:0) - /// Proof: FellowshipCollective MemberCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) - fn one_fewer_deciding_failing() -> Weight { - // Proof Size summary in bytes: - // Measured: `2376` - // Estimated: `42428` - // Minimum execution time: 98_126_000 picoseconds. - Weight::from_parts(102_511_000, 0) - .saturating_add(Weight::from_parts(0, 42428)) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(3)) - } - /// Storage: FellowshipReferenda TrackQueue (r:1 w:1) - /// Proof: FellowshipReferenda TrackQueue (max_values: None, max_size: Some(812), added: 3287, mode: MaxEncodedLen) - /// Storage: FellowshipReferenda ReferendumInfoFor (r:1 w:1) - /// Proof: FellowshipReferenda ReferendumInfoFor (max_values: None, max_size: Some(900), added: 3375, mode: MaxEncodedLen) - /// Storage: FellowshipCollective MemberCount (r:1 w:0) - /// Proof: FellowshipCollective MemberCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) - fn one_fewer_deciding_passing() -> Weight { - // Proof Size summary in bytes: - // Measured: `2362` - // Estimated: `42428` - // Minimum execution time: 99_398_000 picoseconds. - Weight::from_parts(104_045_000, 0) - .saturating_add(Weight::from_parts(0, 42428)) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(3)) - } - /// Storage: FellowshipReferenda ReferendumInfoFor (r:1 w:0) - /// Proof: FellowshipReferenda ReferendumInfoFor (max_values: None, max_size: Some(900), added: 3375, mode: MaxEncodedLen) - /// Storage: FellowshipReferenda TrackQueue (r:1 w:1) - /// Proof: FellowshipReferenda TrackQueue (max_values: None, max_size: Some(812), added: 3287, mode: MaxEncodedLen) - fn nudge_referendum_requeued_insertion() -> Weight { - // Proof Size summary in bytes: - // Measured: `1807` - // Estimated: `4365` - // Minimum execution time: 43_734_000 picoseconds. - Weight::from_parts(46_962_000, 0) - .saturating_add(Weight::from_parts(0, 4365)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: FellowshipReferenda ReferendumInfoFor (r:1 w:0) - /// Proof: FellowshipReferenda ReferendumInfoFor (max_values: None, max_size: Some(900), added: 3375, mode: MaxEncodedLen) - /// Storage: FellowshipReferenda TrackQueue (r:1 w:1) - /// Proof: FellowshipReferenda TrackQueue (max_values: None, max_size: Some(812), added: 3287, mode: MaxEncodedLen) - fn nudge_referendum_requeued_slide() -> Weight { - // Proof Size summary in bytes: - // Measured: `1774` - // Estimated: `4365` - // Minimum execution time: 42_863_000 picoseconds. - Weight::from_parts(46_241_000, 0) - .saturating_add(Weight::from_parts(0, 4365)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: FellowshipReferenda ReferendumInfoFor (r:1 w:1) - /// Proof: FellowshipReferenda ReferendumInfoFor (max_values: None, max_size: Some(900), added: 3375, mode: MaxEncodedLen) - /// Storage: FellowshipReferenda DecidingCount (r:1 w:0) - /// Proof: FellowshipReferenda DecidingCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) - /// Storage: FellowshipReferenda TrackQueue (r:1 w:1) - /// Proof: FellowshipReferenda TrackQueue (max_values: None, max_size: Some(812), added: 3287, mode: MaxEncodedLen) - fn nudge_referendum_queued() -> Weight { - // Proof Size summary in bytes: - // Measured: `1790` - // Estimated: `4365` - // Minimum execution time: 57_511_000 picoseconds. - Weight::from_parts(64_027_000, 0) - .saturating_add(Weight::from_parts(0, 4365)) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: FellowshipReferenda ReferendumInfoFor (r:1 w:1) - /// Proof: FellowshipReferenda ReferendumInfoFor (max_values: None, max_size: Some(900), added: 3375, mode: MaxEncodedLen) - /// Storage: FellowshipReferenda DecidingCount (r:1 w:0) - /// Proof: FellowshipReferenda DecidingCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) - /// Storage: FellowshipReferenda TrackQueue (r:1 w:1) - /// Proof: FellowshipReferenda TrackQueue (max_values: None, max_size: Some(812), added: 3287, mode: MaxEncodedLen) - fn nudge_referendum_not_queued() -> Weight { - // Proof Size summary in bytes: - // Measured: `1831` - // Estimated: `4365` - // Minimum execution time: 56_726_000 picoseconds. - Weight::from_parts(61_962_000, 0) - .saturating_add(Weight::from_parts(0, 4365)) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: FellowshipReferenda ReferendumInfoFor (r:1 w:1) - /// Proof: FellowshipReferenda ReferendumInfoFor (max_values: None, max_size: Some(900), added: 3375, mode: MaxEncodedLen) - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) - fn nudge_referendum_no_deposit() -> Weight { - // Proof Size summary in bytes: - // Measured: `301` - // Estimated: `42428` - // Minimum execution time: 24_870_000 picoseconds. - Weight::from_parts(25_837_000, 0) - .saturating_add(Weight::from_parts(0, 42428)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: FellowshipReferenda ReferendumInfoFor (r:1 w:1) - /// Proof: FellowshipReferenda ReferendumInfoFor (max_values: None, max_size: Some(900), added: 3375, mode: MaxEncodedLen) - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) - fn nudge_referendum_preparing() -> Weight { - // Proof Size summary in bytes: - // Measured: `349` - // Estimated: `42428` - // Minimum execution time: 25_297_000 picoseconds. - Weight::from_parts(26_086_000, 0) - .saturating_add(Weight::from_parts(0, 42428)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: FellowshipReferenda ReferendumInfoFor (r:1 w:1) - /// Proof: FellowshipReferenda ReferendumInfoFor (max_values: None, max_size: Some(900), added: 3375, mode: MaxEncodedLen) - fn nudge_referendum_timed_out() -> Weight { - // Proof Size summary in bytes: - // Measured: `208` - // Estimated: `4365` - // Minimum execution time: 16_776_000 picoseconds. - Weight::from_parts(17_396_000, 0) - .saturating_add(Weight::from_parts(0, 4365)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: FellowshipReferenda ReferendumInfoFor (r:1 w:1) - /// Proof: FellowshipReferenda ReferendumInfoFor (max_values: None, max_size: Some(900), added: 3375, mode: MaxEncodedLen) - /// Storage: FellowshipReferenda DecidingCount (r:1 w:1) - /// Proof: FellowshipReferenda DecidingCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) - /// Storage: FellowshipCollective MemberCount (r:1 w:0) - /// Proof: FellowshipCollective MemberCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) - fn nudge_referendum_begin_deciding_failing() -> Weight { - // Proof Size summary in bytes: - // Measured: `584` - // Estimated: `42428` - // Minimum execution time: 37_780_000 picoseconds. - Weight::from_parts(38_626_000, 0) - .saturating_add(Weight::from_parts(0, 42428)) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(3)) - } - /// Storage: FellowshipReferenda ReferendumInfoFor (r:1 w:1) - /// Proof: FellowshipReferenda ReferendumInfoFor (max_values: None, max_size: Some(900), added: 3375, mode: MaxEncodedLen) - /// Storage: FellowshipReferenda DecidingCount (r:1 w:1) - /// Proof: FellowshipReferenda DecidingCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) - /// Storage: FellowshipCollective MemberCount (r:1 w:0) - /// Proof: FellowshipCollective MemberCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) - fn nudge_referendum_begin_deciding_passing() -> Weight { - // Proof Size summary in bytes: - // Measured: `719` - // Estimated: `42428` - // Minimum execution time: 85_265_000 picoseconds. - Weight::from_parts(89_986_000, 0) - .saturating_add(Weight::from_parts(0, 42428)) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(3)) - } - /// Storage: FellowshipReferenda ReferendumInfoFor (r:1 w:1) - /// Proof: FellowshipReferenda ReferendumInfoFor (max_values: None, max_size: Some(900), added: 3375, mode: MaxEncodedLen) - /// Storage: FellowshipCollective MemberCount (r:1 w:0) - /// Proof: FellowshipCollective MemberCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) - fn nudge_referendum_begin_confirming() -> Weight { - // Proof Size summary in bytes: - // Measured: `770` - // Estimated: `42428` - // Minimum execution time: 143_283_000 picoseconds. - Weight::from_parts(158_540_000, 0) - .saturating_add(Weight::from_parts(0, 42428)) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: FellowshipReferenda ReferendumInfoFor (r:1 w:1) - /// Proof: FellowshipReferenda ReferendumInfoFor (max_values: None, max_size: Some(900), added: 3375, mode: MaxEncodedLen) - /// Storage: FellowshipCollective MemberCount (r:1 w:0) - /// Proof: FellowshipCollective MemberCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) - fn nudge_referendum_end_confirming() -> Weight { - // Proof Size summary in bytes: - // Measured: `755` - // Estimated: `42428` - // Minimum execution time: 143_736_000 picoseconds. - Weight::from_parts(162_755_000, 0) - .saturating_add(Weight::from_parts(0, 42428)) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: FellowshipReferenda ReferendumInfoFor (r:1 w:1) - /// Proof: FellowshipReferenda ReferendumInfoFor (max_values: None, max_size: Some(900), added: 3375, mode: MaxEncodedLen) - /// Storage: FellowshipCollective MemberCount (r:1 w:0) - /// Proof: FellowshipCollective MemberCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) - fn nudge_referendum_continue_not_confirming() -> Weight { - // Proof Size summary in bytes: - // Measured: `770` - // Estimated: `42428` - // Minimum execution time: 139_021_000 picoseconds. - Weight::from_parts(157_398_000, 0) - .saturating_add(Weight::from_parts(0, 42428)) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: FellowshipReferenda ReferendumInfoFor (r:1 w:1) - /// Proof: FellowshipReferenda ReferendumInfoFor (max_values: None, max_size: Some(900), added: 3375, mode: MaxEncodedLen) - /// Storage: FellowshipCollective MemberCount (r:1 w:0) - /// Proof: FellowshipCollective MemberCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) - fn nudge_referendum_continue_confirming() -> Weight { - // Proof Size summary in bytes: - // Measured: `776` - // Estimated: `42428` - // Minimum execution time: 78_530_000 picoseconds. - Weight::from_parts(83_556_000, 0) - .saturating_add(Weight::from_parts(0, 42428)) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: FellowshipReferenda ReferendumInfoFor (r:1 w:1) - /// Proof: FellowshipReferenda ReferendumInfoFor (max_values: None, max_size: Some(900), added: 3375, mode: MaxEncodedLen) - /// Storage: FellowshipCollective MemberCount (r:1 w:0) - /// Proof: FellowshipCollective MemberCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) - /// Storage: Scheduler Agenda (r:2 w:2) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) - /// Storage: Scheduler Lookup (r:1 w:1) - /// Proof: Scheduler Lookup (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) - fn nudge_referendum_approved() -> Weight { - // Proof Size summary in bytes: - // Measured: `776` - // Estimated: `83866` - // Minimum execution time: 174_165_000 picoseconds. - Weight::from_parts(188_496_000, 0) - .saturating_add(Weight::from_parts(0, 83866)) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(4)) - } - /// Storage: FellowshipReferenda ReferendumInfoFor (r:1 w:1) - /// Proof: FellowshipReferenda ReferendumInfoFor (max_values: None, max_size: Some(900), added: 3375, mode: MaxEncodedLen) - /// Storage: FellowshipCollective MemberCount (r:1 w:0) - /// Proof: FellowshipCollective MemberCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) - fn nudge_referendum_rejected() -> Weight { - // Proof Size summary in bytes: - // Measured: `772` - // Estimated: `42428` - // Minimum execution time: 142_964_000 picoseconds. - Weight::from_parts(157_257_000, 0) - .saturating_add(Weight::from_parts(0, 42428)) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: FellowshipReferenda ReferendumInfoFor (r:1 w:0) - /// Proof: FellowshipReferenda ReferendumInfoFor (max_values: None, max_size: Some(900), added: 3375, mode: MaxEncodedLen) - /// Storage: Preimage StatusFor (r:1 w:0) - /// Proof: Preimage StatusFor (max_values: None, max_size: Some(91), added: 2566, mode: MaxEncodedLen) - /// Storage: FellowshipReferenda MetadataOf (r:0 w:1) - /// Proof: FellowshipReferenda MetadataOf (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen) - fn set_some_metadata() -> Weight { - // Proof Size summary in bytes: - // Measured: `352` - // Estimated: `4365` - // Minimum execution time: 20_126_000 picoseconds. - Weight::from_parts(20_635_000, 0) - .saturating_add(Weight::from_parts(0, 4365)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: FellowshipReferenda ReferendumInfoFor (r:1 w:0) - /// Proof: FellowshipReferenda ReferendumInfoFor (max_values: None, max_size: Some(900), added: 3375, mode: MaxEncodedLen) - /// Storage: FellowshipReferenda MetadataOf (r:1 w:1) - /// Proof: FellowshipReferenda MetadataOf (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen) - fn clear_metadata() -> Weight { - // Proof Size summary in bytes: - // Measured: `285` - // Estimated: `4365` - // Minimum execution time: 17_716_000 picoseconds. - Weight::from_parts(18_324_000, 0) - .saturating_add(Weight::from_parts(0, 4365)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } -} diff --git a/polkadot/scripts/list-syscalls/execute-worker-syscalls b/polkadot/scripts/list-syscalls/execute-worker-syscalls index 349af783cf1..4b22f678786 100644 --- a/polkadot/scripts/list-syscalls/execute-worker-syscalls +++ b/polkadot/scripts/list-syscalls/execute-worker-syscalls @@ -20,6 +20,7 @@ 24 (sched_yield) 25 (mremap) 28 (madvise) +34 (pause) 39 (getpid) 41 (socket) 42 (connect) diff --git a/polkadot/scripts/list-syscalls/prepare-worker-syscalls b/polkadot/scripts/list-syscalls/prepare-worker-syscalls index 05281b61591..fd46a788537 100644 --- a/polkadot/scripts/list-syscalls/prepare-worker-syscalls +++ b/polkadot/scripts/list-syscalls/prepare-worker-syscalls @@ -20,6 +20,7 @@ 24 (sched_yield) 25 (mremap) 28 (madvise) +34 (pause) 39 (getpid) 41 (socket) 42 (connect) diff --git a/polkadot/statement-table/src/generic.rs b/polkadot/statement-table/src/generic.rs index 1e90338a0f1..e3c470fcdee 100644 --- a/polkadot/statement-table/src/generic.rs +++ b/polkadot/statement-table/src/generic.rs @@ -245,7 +245,8 @@ impl CandidateData { pub fn attested( &self, validity_threshold: usize, - ) -> Option> { + ) -> Option> + { let valid_votes = self.validity_votes.len(); if valid_votes < validity_threshold { return None @@ -321,7 +322,8 @@ impl Table { digest: &Ctx::Digest, context: &Ctx, minimum_backing_votes: u32, - ) -> Option> { + ) -> Option> + { self.candidate_votes.get(digest).and_then(|data| { let v_threshold = context.get_group_size(&data.group_id).map_or(usize::MAX, |len| { effective_minimum_backing_votes(len, minimum_backing_votes) diff --git a/polkadot/xcm/docs/src/cookbook/relay_token_transactor/parachain/xcm_config.rs b/polkadot/xcm/docs/src/cookbook/relay_token_transactor/parachain/xcm_config.rs index 99f17693093..7cb230f6e00 100644 --- a/polkadot/xcm/docs/src/cookbook/relay_token_transactor/parachain/xcm_config.rs +++ b/polkadot/xcm/docs/src/cookbook/relay_token_transactor/parachain/xcm_config.rs @@ -152,7 +152,7 @@ impl pallet_xcm::Config for Runtime { // We turn off sending for these tests type SendXcmOrigin = EnsureXcmOrigin; type XcmRouter = super::super::network::ParachainXcmRouter; // Provided by xcm-simulator - // Anyone can execute XCM programs + // Anyone can execute XCM programs type ExecuteXcmOrigin = EnsureXcmOrigin; // We execute any type of program type XcmExecuteFilter = Everything; diff --git a/polkadot/xcm/docs/src/cookbook/relay_token_transactor/relay_chain/xcm_config.rs b/polkadot/xcm/docs/src/cookbook/relay_token_transactor/relay_chain/xcm_config.rs index 987bb3f9ab6..a31e664d821 100644 --- a/polkadot/xcm/docs/src/cookbook/relay_token_transactor/relay_chain/xcm_config.rs +++ b/polkadot/xcm/docs/src/cookbook/relay_token_transactor/relay_chain/xcm_config.rs @@ -125,7 +125,7 @@ impl pallet_xcm::Config for Runtime { // No one can call `send` type SendXcmOrigin = EnsureXcmOrigin; type XcmRouter = super::super::network::RelayChainXcmRouter; // Provided by xcm-simulator - // Anyone can execute XCM programs + // Anyone can execute XCM programs type ExecuteXcmOrigin = EnsureXcmOrigin; // We execute any type of program type XcmExecuteFilter = Everything; diff --git a/polkadot/xcm/pallet-xcm-benchmarks/src/lib.rs b/polkadot/xcm/pallet-xcm-benchmarks/src/lib.rs index 4a12bb7f47c..210b8f65637 100644 --- a/polkadot/xcm/pallet-xcm-benchmarks/src/lib.rs +++ b/polkadot/xcm/pallet-xcm-benchmarks/src/lib.rs @@ -72,7 +72,7 @@ pub fn generate_holding_assets(max_assets: u32) -> Assets { let fungibles_amount: u128 = 100; let holding_fungibles = max_assets / 2; let holding_non_fungibles = max_assets - holding_fungibles - 1; // -1 because of adding `Here` asset - // add count of `holding_fungibles` + // add count of `holding_fungibles` (0..holding_fungibles) .map(|i| { Asset { diff --git a/polkadot/xcm/procedural/tests/ui.rs b/polkadot/xcm/procedural/tests/ui.rs index b3469b520eb..4d0c8af4500 100644 --- a/polkadot/xcm/procedural/tests/ui.rs +++ b/polkadot/xcm/procedural/tests/ui.rs @@ -16,7 +16,6 @@ //! UI tests for XCM procedural macros -#[cfg(not(feature = "disable-ui-tests"))] #[test] fn ui() { // Only run the ui tests when `RUN_UI_TESTS` is set. diff --git a/polkadot/xcm/xcm-builder/src/asset_conversion.rs b/polkadot/xcm/xcm-builder/src/asset_conversion.rs index 16ae05c2079..6d090b04886 100644 --- a/polkadot/xcm/xcm-builder/src/asset_conversion.rs +++ b/polkadot/xcm/xcm-builder/src/asset_conversion.rs @@ -137,7 +137,13 @@ impl< ConvertClassId: MaybeEquivalence, ConvertInstanceId: MaybeEquivalence, > MatchesNonFungibles - for MatchedConvertedConcreteId + for MatchedConvertedConcreteId< + ClassId, + InstanceId, + MatchClassId, + ConvertClassId, + ConvertInstanceId, + > { fn matches_nonfungibles(a: &Asset) -> result::Result<(ClassId, InstanceId), MatchError> { let (instance, class) = match (&a.fun, &a.id) { diff --git a/polkadot/xcm/xcm-builder/src/nonfungibles_adapter.rs b/polkadot/xcm/xcm-builder/src/nonfungibles_adapter.rs index b111a05a4f1..006c28954bc 100644 --- a/polkadot/xcm/xcm-builder/src/nonfungibles_adapter.rs +++ b/polkadot/xcm/xcm-builder/src/nonfungibles_adapter.rs @@ -270,7 +270,14 @@ impl< CheckAsset: AssetChecking, CheckingAccount: Get>, > TransactAsset - for NonFungiblesAdapter + for NonFungiblesAdapter< + Assets, + Matcher, + AccountIdConverter, + AccountId, + CheckAsset, + CheckingAccount, + > { fn can_check_in(origin: &Location, what: &Asset, context: &XcmContext) -> XcmResult { NonFungiblesMutateAdapter::< diff --git a/polkadot/xcm/xcm-executor/src/traits/weight.rs b/polkadot/xcm/xcm-executor/src/traits/weight.rs index 72de3e0f433..61545c33062 100644 --- a/polkadot/xcm/xcm-executor/src/traits/weight.rs +++ b/polkadot/xcm/xcm-executor/src/traits/weight.rs @@ -29,13 +29,6 @@ pub trait WeightBounds { fn instr_weight(instruction: &Instruction) -> Result; } -/// A means of getting approximate weight consumption for a given destination message executor and a -/// message. -pub trait UniversalWeigher { - /// Get the upper limit of weight required for `dest` to execute `message`. - fn weigh(dest: impl Into, message: Xcm<()>) -> Result; -} - /// Charge for weight in order to execute XCM. /// /// A `WeightTrader` may also be put into a tuple, in which case the default behavior of diff --git a/polkadot/xcm/xcm-runtime-apis/src/dry_run.rs b/polkadot/xcm/xcm-runtime-apis/src/dry_run.rs index c51a4a5376a..f0a70b0dacf 100644 --- a/polkadot/xcm/xcm-runtime-apis/src/dry_run.rs +++ b/polkadot/xcm/xcm-runtime-apis/src/dry_run.rs @@ -57,7 +57,12 @@ sp_api::decl_runtime_apis! { /// Calls or XCMs might fail when executed, this doesn't mean the result of these calls will be an `Err`. /// In those cases, there might still be a valid result, with the execution error inside it. /// The only reasons why these calls might return an error are listed in the [`Error`] enum. - pub trait DryRunApi { + pub trait DryRunApi + where + Call: Encode, + Event: Decode, + OriginCaller: Encode + { /// Dry run call. fn dry_run_call(origin: OriginCaller, call: Call) -> Result, Error>; diff --git a/polkadot/xcm/xcm-runtime-apis/tests/fee_estimation.rs b/polkadot/xcm/xcm-runtime-apis/tests/fee_estimation.rs index 889a50a2bab..2d14b4e571c 100644 --- a/polkadot/xcm/xcm-runtime-apis/tests/fee_estimation.rs +++ b/polkadot/xcm/xcm-runtime-apis/tests/fee_estimation.rs @@ -197,7 +197,7 @@ fn fee_estimation_for_teleport() { fn dry_run_reserve_asset_transfer() { sp_tracing::init_for_tests(); let who = 1; // AccountId = u64. - // Native token used for fees. + // Native token used for fees. let balances = vec![(who, DeliveryFees::get() + ExistentialDeposit::get())]; // Relay token is the one we want to transfer. let assets = vec![(1, who, 100)]; // id, account_id, balance. diff --git a/prdoc/pr_4851.prdoc b/prdoc/pr_4851.prdoc index 923ca4bfff5..2110a68d401 100644 --- a/prdoc/pr_4851.prdoc +++ b/prdoc/pr_4851.prdoc @@ -5,8 +5,8 @@ title: Add support for deprecation metadata in `RuntimeMetadataIr` entries. doc: - audience: - - Runtime dev - - Runtime user + - Runtime Dev + - Runtime User description: | Changes introduced are listed below. Adds `DeprecationStatusIR` enum to sp_metadata_ir. diff --git a/prdoc/pr_5676.prdoc b/prdoc/pr_5676.prdoc new file mode 100644 index 00000000000..dfe23e120b4 --- /dev/null +++ b/prdoc/pr_5676.prdoc @@ -0,0 +1,174 @@ +title: '[ci] Update CI image with rust 1.81.0 and 2024-09-11' +doc: +- audience: [Runtime Dev, Node Dev, Node Operator] + description: |- + cc https://github.com/paritytech/ci_cd/issues/1035 + + close https://github.com/paritytech/ci_cd/issues/1023 +crates: +- name: pallet-xcm-bridge-hub + bump: patch +- name: snowbridge-router-primitives + bump: patch +- name: snowbridge-runtime-common + bump: patch +- name: cumulus-pallet-parachain-system + bump: patch +- name: asset-hub-rococo-runtime + bump: patch +- name: asset-hub-westend-runtime + bump: patch +- name: asset-test-utils + bump: patch +- name: bridge-hub-test-utils + bump: patch +- name: cumulus-primitives-utility + bump: patch +- name: polkadot-node-core-approval-voting + bump: patch +- name: polkadot-node-core-pvf-common + bump: patch +- name: polkadot-approval-distribution + bump: patch +- name: polkadot-availability-recovery + bump: patch +- name: polkadot-node-subsystem-types + bump: patch +- name: polkadot-runtime-parachains + bump: patch +- name: westend-runtime + bump: patch +- name: polkadot-statement-table + bump: patch +- name: pallet-xcm-benchmarks + bump: patch +- name: staging-xcm-builder + bump: patch +- name: xcm-runtime-apis + bump: patch +- name: sc-cli + bump: patch +- name: sc-consensus-grandpa + bump: patch +- name: sc-network + bump: patch +- name: sc-network-sync + bump: patch +- name: sc-rpc-spec-v2 + bump: patch +- name: pallet-bags-list + bump: patch +- name: pallet-balances + bump: patch +- name: pallet-bounties + bump: patch +- name: pallet-child-bounties + bump: patch +- name: pallet-nis + bump: patch +- name: pallet-referenda + bump: patch +- name: pallet-revive-proc-macro + bump: patch +- name: pallet-society + bump: patch +- name: pallet-staking + bump: patch +- name: frame-support-procedural + bump: patch +- name: frame-support + bump: patch +- name: pallet-transaction-payment + bump: patch +- name: pallet-utility + bump: patch +- name: pallet-vesting + bump: patch +- name: substrate-wasm-builder + bump: patch +- name: snowbridge-outbound-queue-merkle-tree + bump: patch +- name: shell-runtime + bump: patch +- name: polkadot-parachain-lib + bump: patch +- name: polkadot-cli + bump: patch +- name: polkadot-node-core-pvf + bump: patch +- name: polkadot-service + bump: patch +- name: polkadot-primitives + bump: patch +- name: staging-xcm-executor + bump: patch +- name: sc-consensus-beefy + bump: patch +- name: sc-consensus-slots + bump: patch +- name: frame-benchmarking-pallet-pov + bump: patch +- name: pallet-contracts + bump: patch +- name: frame-election-provider-support + bump: patch +- name: pallet-revive-mock-network + bump: patch +- name: frame-benchmarking-cli + bump: patch +- name: sc-utils + bump: patch +- name: pallet-beefy-mmr + bump: patch +- name: sp-state-machine + bump: patch +- name: fork-tree + bump: patch +- name: sc-transaction-pool + bump: patch +- name: pallet-delegated-staking + bump: patch +- name: sc-executor-wasmtime + bump: patch +- name: cumulus-pallet-xcmp-queue + bump: patch +- name: xcm-procedural + bump: patch +- name: sp-application-crypto + bump: patch +- name: sp-core + bump: patch +- name: sp-keyring + bump: patch +- name: polkadot-availability-distribution + bump: patch +- name: sp-runtime + bump: patch +- name: sc-authority-discovery + bump: patch +- name: frame-system + bump: patch +- name: sc-network-gossip + bump: patch +- name: pallet-authorship + bump: patch +- name: pallet-election-provider-multi-phase + bump: patch +- name: sp-runtime-interface + bump: patch +- name: pallet-bridge-grandpa + bump: patch +- name: pallet-elections-phragmen + bump: patch +- name: frame-executive + bump: patch +- name: bp-header-chain + bump: patch +- name: polkadot-overseer + bump: patch +- name: polkadot + bump: patch +- name: bridge-hub-westend-runtime + bump: major +- name: bp-messages + bump: patch diff --git a/prdoc/pr_5678.prdoc b/prdoc/pr_5678.prdoc index af1fac31c56..ebb5e5a0d79 100644 --- a/prdoc/pr_5678.prdoc +++ b/prdoc/pr_5678.prdoc @@ -1,6 +1,6 @@ title: 'rpc server: fix deny unsafe on RpcMethods::Auto' doc: -- audience: Node User +- audience: Node Operator description: |- Close #5677 diff --git a/prdoc/pr_5684.prdoc b/prdoc/pr_5684.prdoc index a17bacd2fb9..9800c85de2a 100644 --- a/prdoc/pr_5684.prdoc +++ b/prdoc/pr_5684.prdoc @@ -4,7 +4,7 @@ title: "[pallet-revive]" doc: - - audience: Runtime Devs + - audience: Runtime Dev description: | Update xcm runtime api, and fix pallet-revive xcm tests diff --git a/scripts/update-ui-tests.sh b/scripts/update-ui-tests.sh index a1f380c4712..c25b22fa7f7 100755 --- a/scripts/update-ui-tests.sh +++ b/scripts/update-ui-tests.sh @@ -32,10 +32,12 @@ export RUN_UI_TESTS=1 export SKIP_WASM_BUILD=1 # Let trybuild overwrite the .stderr files export TRYBUILD=overwrite +# Warnings are part of our UI and the CI also sets this. +export RUSTFLAGS="-C debug-assertions -D warnings" # ./substrate -$RUSTUP_RUN cargo test --manifest-path substrate/primitives/runtime-interface/Cargo.toml ui -$RUSTUP_RUN cargo test -p sp-api-test ui -$RUSTUP_RUN cargo test -p frame-election-provider-solution-type ui -$RUSTUP_RUN cargo test -p frame-support-test --features=no-metadata-docs,try-runtime,experimental ui -$RUSTUP_RUN cargo test -p xcm-procedural ui \ No newline at end of file +$RUSTUP_RUN cargo test -q --locked --manifest-path substrate/primitives/runtime-interface/Cargo.toml ui +$RUSTUP_RUN cargo test -q --locked -p sp-api-test ui +$RUSTUP_RUN cargo test -q --locked -p frame-election-provider-solution-type ui +$RUSTUP_RUN cargo test -q --locked -p frame-support-test --features=no-metadata-docs,try-runtime,experimental ui +$RUSTUP_RUN cargo test -q --locked -p xcm-procedural ui diff --git a/substrate/bin/node/cli/tests/fees.rs b/substrate/bin/node/cli/tests/fees.rs index 9f82338b4fb..b49af4c1055 100644 --- a/substrate/bin/node/cli/tests/fees.rs +++ b/substrate/bin/node/cli/tests/fees.rs @@ -188,135 +188,3 @@ fn transaction_fee_is_correct() { assert_eq!(Balances::total_balance(&alice()), balance_alice); }); } - -#[test] -#[should_panic] -#[cfg(feature = "stress-test")] -fn block_weight_capacity_report() { - // Just report how many transfer calls you could fit into a block. The number should at least - // be a few hundred (250 at the time of writing but can change over time). Runs until panic. - use node_primitives::Nonce; - - // execution ext. - let mut t = new_test_ext(compact_code_unwrap()); - // setup ext. - let mut tt = new_test_ext(compact_code_unwrap()); - - let factor = 50; - let mut time = 10; - let mut nonce: Nonce = 0; - let mut block_number = 1; - let mut previous_hash: node_primitives::Hash = GENESIS_HASH.into(); - - loop { - let num_transfers = block_number * factor; - let mut xts = (0..num_transfers) - .map(|i| CheckedExtrinsic { - signed: Some((charlie(), signed_extra(nonce + i as Nonce, 0))), - function: RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death { - dest: bob().into(), - value: 0, - }), - }) - .collect::>(); - - xts.insert( - 0, - CheckedExtrinsic { - signed: None, - function: RuntimeCall::Timestamp(pallet_timestamp::Call::set { now: time * 1000 }), - }, - ); - - // NOTE: this is super slow. Can probably be improved. - let block = construct_block( - &mut tt, - block_number, - previous_hash, - xts, - (time * 1000 / SLOT_DURATION).into(), - ); - - let len = block.0.len(); - print!( - "++ Executing block with {} transfers. Block size = {} bytes / {} kb / {} mb", - num_transfers, - len, - len / 1024, - len / 1024 / 1024, - ); - - let r = executor_call(&mut t, "Core_execute_block", &block.0).0; - - println!(" || Result = {:?}", r); - assert!(r.is_ok()); - - previous_hash = block.1; - nonce += num_transfers; - time += 10; - block_number += 1; - } -} - -#[test] -#[should_panic] -#[cfg(feature = "stress-test")] -fn block_length_capacity_report() { - // Just report how big a block can get. Executes until panic. Should be ignored unless if - // manually inspected. The number should at least be a few megabytes (5 at the time of - // writing but can change over time). - use node_primitives::Nonce; - - // execution ext. - let mut t = new_test_ext(compact_code_unwrap()); - // setup ext. - let mut tt = new_test_ext(compact_code_unwrap()); - - let factor = 256 * 1024; - let mut time = 10; - let mut nonce: Nonce = 0; - let mut block_number = 1; - let mut previous_hash: node_primitives::Hash = GENESIS_HASH.into(); - - loop { - // NOTE: this is super slow. Can probably be improved. - let block = construct_block( - &mut tt, - block_number, - previous_hash, - vec![ - CheckedExtrinsic { - signed: None, - function: RuntimeCall::Timestamp(pallet_timestamp::Call::set { - now: time * 1000, - }), - }, - CheckedExtrinsic { - signed: Some((charlie(), signed_extra(nonce, 0))), - function: RuntimeCall::System(frame_system::Call::remark { - remark: vec![0u8; (block_number * factor) as usize], - }), - }, - ], - (time * 1000 / SLOT_DURATION).into(), - ); - - let len = block.0.len(); - print!( - "++ Executing block with big remark. Block size = {} bytes / {} kb / {} mb", - len, - len / 1024, - len / 1024 / 1024, - ); - - let r = executor_call(&mut t, "Core_execute_block", &block.0).0; - - println!(" || Result = {:?}", r); - assert!(r.is_ok()); - - previous_hash = block.1; - nonce += 1; - time += 10; - block_number += 1; - } -} diff --git a/substrate/client/authority-discovery/src/worker/tests.rs b/substrate/client/authority-discovery/src/worker/tests.rs index b49615382b8..d71a85db8b8 100644 --- a/substrate/client/authority-discovery/src/worker/tests.rs +++ b/substrate/client/authority-discovery/src/worker/tests.rs @@ -117,10 +117,10 @@ sp_api::mock_impl_runtime_apis! { #[derive(Debug)] pub enum TestNetworkEvent { - GetCalled(KademliaKey), - PutCalled(KademliaKey, Vec), - PutToCalled(Record, HashSet, bool), - StoreRecordCalled(KademliaKey, Vec, Option, Option), + GetCalled, + PutCalled, + PutToCalled, + StoreRecordCalled, } pub struct TestNetwork { @@ -190,17 +190,11 @@ impl NetworkSigner for TestNetwork { impl NetworkDHTProvider for TestNetwork { fn put_value(&self, key: KademliaKey, value: Vec) { self.put_value_call.lock().unwrap().push((key.clone(), value.clone())); - self.event_sender - .clone() - .unbounded_send(TestNetworkEvent::PutCalled(key, value)) - .unwrap(); + self.event_sender.clone().unbounded_send(TestNetworkEvent::PutCalled).unwrap(); } fn get_value(&self, key: &KademliaKey) { self.get_value_call.lock().unwrap().push(key.clone()); - self.event_sender - .clone() - .unbounded_send(TestNetworkEvent::GetCalled(key.clone())) - .unwrap(); + self.event_sender.clone().unbounded_send(TestNetworkEvent::GetCalled).unwrap(); } fn put_record_to( @@ -214,10 +208,7 @@ impl NetworkDHTProvider for TestNetwork { peers.clone(), update_local_storage, )); - self.event_sender - .clone() - .unbounded_send(TestNetworkEvent::PutToCalled(record, peers, update_local_storage)) - .unwrap(); + self.event_sender.clone().unbounded_send(TestNetworkEvent::PutToCalled).unwrap(); } fn store_record( @@ -235,7 +226,7 @@ impl NetworkDHTProvider for TestNetwork { )); self.event_sender .clone() - .unbounded_send(TestNetworkEvent::StoreRecordCalled(key, value, publisher, expires)) + .unbounded_send(TestNetworkEvent::StoreRecordCalled) .unwrap(); } } @@ -536,7 +527,7 @@ fn dont_stop_polling_dht_event_stream_after_bogus_event() { pool.run_until(async { // Assert worker to trigger a lookup for the one and only authority. - assert!(matches!(network_events.next().await, Some(TestNetworkEvent::GetCalled(_)))); + assert!(matches!(network_events.next().await, Some(TestNetworkEvent::GetCalled))); // Send an event that should generate an error dht_event_tx @@ -1137,7 +1128,7 @@ fn lookup_throttling() { async { // Assert worker to trigger MAX_IN_FLIGHT_LOOKUPS lookups. for _ in 0..MAX_IN_FLIGHT_LOOKUPS { - assert!(matches!(receiver.next().await, Some(TestNetworkEvent::GetCalled(_)))); + assert!(matches!(receiver.next().await, Some(TestNetworkEvent::GetCalled))); } assert_eq!( metrics.requests_pending.get(), @@ -1168,7 +1159,7 @@ fn lookup_throttling() { } // Assert worker to trigger another lookup. - assert!(matches!(receiver.next().await, Some(TestNetworkEvent::GetCalled(_)))); + assert!(matches!(receiver.next().await, Some(TestNetworkEvent::GetCalled))); assert_eq!( metrics.requests_pending.get(), (remote_public_keys.len() - MAX_IN_FLIGHT_LOOKUPS - 1) as u64 @@ -1181,7 +1172,7 @@ fn lookup_throttling() { dht_event_tx.send(dht_event).await.expect("Channel has capacity of 1."); // Assert worker to trigger another lookup. - assert!(matches!(receiver.next().await, Some(TestNetworkEvent::GetCalled(_)))); + assert!(matches!(receiver.next().await, Some(TestNetworkEvent::GetCalled))); assert_eq!( metrics.requests_pending.get(), (remote_public_keys.len() - MAX_IN_FLIGHT_LOOKUPS - 2) as u64 diff --git a/substrate/client/cli/src/commands/import_blocks_cmd.rs b/substrate/client/cli/src/commands/import_blocks_cmd.rs index 815c6ab18aa..6bd607901e3 100644 --- a/substrate/client/cli/src/commands/import_blocks_cmd.rs +++ b/substrate/client/cli/src/commands/import_blocks_cmd.rs @@ -28,7 +28,7 @@ use sp_runtime::traits::Block as BlockT; use std::{ fmt::Debug, fs, - io::{self, Read, Seek}, + io::{self, Read}, path::PathBuf, sync::Arc, }; @@ -58,11 +58,6 @@ pub struct ImportBlocksCmd { pub import_params: ImportParams, } -/// Internal trait used to cast to a dynamic type that implements Read and Seek. -trait ReadPlusSeek: Read + Seek {} - -impl ReadPlusSeek for T {} - impl ImportBlocksCmd { /// Run the import-blocks command pub async fn run(&self, client: Arc, import_queue: IQ) -> error::Result<()> diff --git a/substrate/client/cli/src/commands/vanity.rs b/substrate/client/cli/src/commands/vanity.rs index 330a59493ef..9acacb4b15b 100644 --- a/substrate/client/cli/src/commands/vanity.rs +++ b/substrate/client/cli/src/commands/vanity.rs @@ -166,8 +166,6 @@ mod tests { crypto::{default_ss58_version, Ss58AddressFormatRegistry, Ss58Codec}, sr25519, Pair, }; - #[cfg(feature = "bench")] - use test::Bencher; #[test] fn vanity() { @@ -225,16 +223,4 @@ mod tests { 0 ); } - - #[cfg(feature = "bench")] - #[bench] - fn bench_paranoiac(b: &mut Bencher) { - b.iter(|| generate_key("polk")); - } - - #[cfg(feature = "bench")] - #[bench] - fn bench_not_paranoiac(b: &mut Bencher) { - b.iter(|| generate_key("polk")); - } } diff --git a/substrate/client/cli/src/params/node_key_params.rs b/substrate/client/cli/src/params/node_key_params.rs index cdd63788811..70671bff8c0 100644 --- a/substrate/client/cli/src/params/node_key_params.rs +++ b/substrate/client/cli/src/params/node_key_params.rs @@ -116,8 +116,8 @@ impl NodeKeyParams { .clone() .unwrap_or_else(|| net_config_dir.join(NODE_KEY_ED25519_FILE)); if !self.unsafe_force_node_key_generation && - role.is_authority() && !is_dev && - !key_path.exists() + role.is_authority() && + !is_dev && !key_path.exists() { return Err(Error::NetworkKeyNotFound(key_path)) } diff --git a/substrate/client/consensus/beefy/src/fisherman.rs b/substrate/client/consensus/beefy/src/fisherman.rs index faa4d34eff5..2b2683b35f0 100644 --- a/substrate/client/consensus/beefy/src/fisherman.rs +++ b/substrate/client/consensus/beefy/src/fisherman.rs @@ -32,9 +32,8 @@ use sp_runtime::{ }; use std::{marker::PhantomData, sync::Arc}; -/// Helper struct containing the id and the key ownership proof for a validator. -pub struct ProvedValidator<'a, AuthorityId: AuthorityIdBound> { - pub id: &'a AuthorityId, +/// Helper struct containing the key ownership proof for a validator. +pub struct ProvedValidator { pub key_owner_proof: OpaqueKeyOwnershipProof, } @@ -66,7 +65,7 @@ where at: BlockId, offender_ids: impl Iterator, validator_set_id: ValidatorSetId, - ) -> Result>, Error> { + ) -> Result, Error> { let hash = match at { BlockId::Hash(hash) => hash, BlockId::Number(number) => self @@ -91,7 +90,7 @@ where offender_id.clone(), ) { Ok(Some(key_owner_proof)) => { - proved_offenders.push(ProvedValidator { id: offender_id, key_owner_proof }); + proved_offenders.push(ProvedValidator { key_owner_proof }); }, Ok(None) => { debug!( diff --git a/substrate/client/consensus/grandpa/src/aux_schema.rs b/substrate/client/consensus/grandpa/src/aux_schema.rs index 8ec882591be..c42310dcd72 100644 --- a/substrate/client/consensus/grandpa/src/aux_schema.rs +++ b/substrate/client/consensus/grandpa/src/aux_schema.rs @@ -743,7 +743,9 @@ mod test { substrate_test_runtime_client::runtime::Block, _, _, - >(&client, H256::random(), 0, || unreachable!()) + >( + &client, H256::random(), 0, || unreachable!() + ) .unwrap(); assert_eq!( diff --git a/substrate/client/consensus/grandpa/src/voting_rule.rs b/substrate/client/consensus/grandpa/src/voting_rule.rs index c1d3cd2fbd6..6072f1895fd 100644 --- a/substrate/client/consensus/grandpa/src/voting_rule.rs +++ b/substrate/client/consensus/grandpa/src/voting_rule.rs @@ -82,7 +82,7 @@ where /// /// In the best case our vote is exactly N blocks /// behind the best block, but if there is a scenario where either -/// >34% of validators run without this rule or the fork-choice rule +/// \>34% of validators run without this rule or the fork-choice rule /// can prioritize shorter chains over longer ones, the vote may be /// closer to the best block than N. #[derive(Clone)] diff --git a/substrate/client/consensus/slots/build.rs b/substrate/client/consensus/slots/build.rs index a68cb706e8f..c63f0b8b667 100644 --- a/substrate/client/consensus/slots/build.rs +++ b/substrate/client/consensus/slots/build.rs @@ -20,6 +20,6 @@ use std::env; fn main() { if let Ok(profile) = env::var("PROFILE") { - println!("cargo:rustc-cfg=build_type=\"{}\"", profile); + println!("cargo:rustc-cfg=build_profile=\"{}\"", profile); } } diff --git a/substrate/client/consensus/slots/src/lib.rs b/substrate/client/consensus/slots/src/lib.rs index 06e0756fc96..4f7e8554177 100644 --- a/substrate/client/consensus/slots/src/lib.rs +++ b/substrate/client/consensus/slots/src/lib.rs @@ -227,7 +227,7 @@ pub trait SimpleSlotWorker { "⌛️ Discarding proposal for slot {}; block production took too long", slot, ); // If the node was compiled with debug, tell the user to use release optimizations. - #[cfg(build_type = "debug")] + #[cfg(build_profile = "debug")] info!( target: log_target, "👉 Recompile your node in `--release` mode to mitigate this problem.", diff --git a/substrate/client/executor/wasmtime/build.rs b/substrate/client/executor/wasmtime/build.rs index a68cb706e8f..c63f0b8b667 100644 --- a/substrate/client/executor/wasmtime/build.rs +++ b/substrate/client/executor/wasmtime/build.rs @@ -20,6 +20,6 @@ use std::env; fn main() { if let Ok(profile) = env::var("PROFILE") { - println!("cargo:rustc-cfg=build_type=\"{}\"", profile); + println!("cargo:rustc-cfg=build_profile=\"{}\"", profile); } } diff --git a/substrate/client/executor/wasmtime/src/tests.rs b/substrate/client/executor/wasmtime/src/tests.rs index f86a4275769..abf2b9509c2 100644 --- a/substrate/client/executor/wasmtime/src/tests.rs +++ b/substrate/client/executor/wasmtime/src/tests.rs @@ -455,7 +455,7 @@ fn test_max_memory_pages( // This test takes quite a while to execute in a debug build (over 6 minutes on a TR 3970x) // so it's ignored by default unless it was compiled with `--release`. -#[cfg_attr(build_type = "debug", ignore)] +#[cfg_attr(build_profile = "debug", ignore)] #[test] fn test_instances_without_reuse_are_not_leaked() { let runtime = crate::create_runtime::( diff --git a/substrate/client/network-gossip/src/bridge.rs b/substrate/client/network-gossip/src/bridge.rs index 414da9b2a58..a4bd922a76d 100644 --- a/substrate/client/network-gossip/src/bridge.rs +++ b/substrate/client/network-gossip/src/bridge.rs @@ -377,9 +377,6 @@ mod tests { #[derive(Clone, Default)] struct TestNetwork {} - #[derive(Clone, Default)] - struct TestNetworkInner {} - #[async_trait::async_trait] impl NetworkPeers for TestNetwork { fn set_authorized_peers(&self, _peers: HashSet) { diff --git a/substrate/client/network/src/behaviour.rs b/substrate/client/network/src/behaviour.rs index 9a6324dafd3..5ecbec52d50 100644 --- a/substrate/client/network/src/behaviour.rs +++ b/substrate/client/network/src/behaviour.rs @@ -76,8 +76,6 @@ pub enum BehaviourOut { /// /// This event is generated for statistics purposes. InboundRequest { - /// Peer which sent us a request. - peer: PeerId, /// Protocol name of the request. protocol: ProtocolName, /// If `Ok`, contains the time elapsed between when we received the request and when we @@ -89,8 +87,6 @@ pub enum BehaviourOut { /// /// This event is generated for statistics purposes. RequestFinished { - /// Peer that we send a request to. - peer: PeerId, /// Name of the protocol in question. protocol: ProtocolName, /// Duration the request took. @@ -350,10 +346,10 @@ impl From for BehaviourOut { impl From for BehaviourOut { fn from(event: request_responses::Event) -> Self { match event { - request_responses::Event::InboundRequest { peer, protocol, result } => - BehaviourOut::InboundRequest { peer, protocol, result }, - request_responses::Event::RequestFinished { peer, protocol, duration, result } => - BehaviourOut::RequestFinished { peer, protocol, duration, result }, + request_responses::Event::InboundRequest { protocol, result, .. } => + BehaviourOut::InboundRequest { protocol, result }, + request_responses::Event::RequestFinished { protocol, duration, result, .. } => + BehaviourOut::RequestFinished { protocol, duration, result }, request_responses::Event::ReputationChanges { peer, changes } => BehaviourOut::ReputationChanges { peer, changes }, } diff --git a/substrate/client/network/src/litep2p/discovery.rs b/substrate/client/network/src/litep2p/discovery.rs index bf2005df34d..5fe944cadc0 100644 --- a/substrate/client/network/src/litep2p/discovery.rs +++ b/substrate/client/network/src/litep2p/discovery.rs @@ -95,15 +95,6 @@ pub enum DiscoveryEvent { /// Peer ID. peer: PeerId, - /// Identify protocol version. - protocol_version: Option, - - /// Identify user agent version. - user_agent: Option, - - /// Observed address. - observed_address: Multiaddr, - /// Listen addresses. listen_addresses: Vec, @@ -561,11 +552,10 @@ impl Stream for Discovery { Poll::Ready(None) => return Poll::Ready(None), Poll::Ready(Some(IdentifyEvent::PeerIdentified { peer, - protocol_version, - user_agent, listen_addresses, supported_protocols, observed_address, + .. })) => { if this.is_new_external_address(&observed_address, peer) { this.pending_events.push_back(DiscoveryEvent::ExternalAddressDiscovered { @@ -575,10 +565,7 @@ impl Stream for Discovery { return Poll::Ready(Some(DiscoveryEvent::Identified { peer, - protocol_version, - user_agent, listen_addresses, - observed_address, supported_protocols, })); }, diff --git a/substrate/client/network/src/protocol/notifications/behaviour.rs b/substrate/client/network/src/protocol/notifications/behaviour.rs index cb4f089995e..a562546145c 100644 --- a/substrate/client/network/src/protocol/notifications/behaviour.rs +++ b/substrate/client/network/src/protocol/notifications/behaviour.rs @@ -33,7 +33,7 @@ use bytes::BytesMut; use fnv::FnvHashMap; use futures::{future::BoxFuture, prelude::*, stream::FuturesUnordered}; use libp2p::{ - core::{ConnectedPoint, Endpoint, Multiaddr}, + core::{Endpoint, Multiaddr}, swarm::{ behaviour::{ConnectionClosed, ConnectionEstablished, DialFailure, FromSwarm}, ConnectionDenied, ConnectionId, DialError, NetworkBehaviour, NotifyHandler, PollParameters, @@ -362,8 +362,6 @@ pub enum NotificationsOut { received_handshake: Vec, /// Object that permits sending notifications to the peer. notifications_sink: NotificationsSink, - /// Is the connection inbound. - inbound: bool, }, /// The [`NotificationsSink`] object used to send notifications with the given peer must be @@ -1223,33 +1221,20 @@ impl NetworkBehaviour for Notifications { &mut self, _connection_id: ConnectionId, peer: PeerId, - local_addr: &Multiaddr, - remote_addr: &Multiaddr, + _local_addr: &Multiaddr, + _remote_addr: &Multiaddr, ) -> Result, ConnectionDenied> { - Ok(NotifsHandler::new( - peer, - ConnectedPoint::Listener { - local_addr: local_addr.clone(), - send_back_addr: remote_addr.clone(), - }, - self.notif_protocols.clone(), - Some(self.metrics.clone()), - )) + Ok(NotifsHandler::new(peer, self.notif_protocols.clone(), Some(self.metrics.clone()))) } fn handle_established_outbound_connection( &mut self, _connection_id: ConnectionId, peer: PeerId, - addr: &Multiaddr, - role_override: Endpoint, + _addr: &Multiaddr, + _role_override: Endpoint, ) -> Result, ConnectionDenied> { - Ok(NotifsHandler::new( - peer, - ConnectedPoint::Dialer { address: addr.clone(), role_override }, - self.notif_protocols.clone(), - Some(self.metrics.clone()), - )) + Ok(NotifsHandler::new(peer, self.notif_protocols.clone(), Some(self.metrics.clone()))) } fn on_swarm_event(&mut self, event: FromSwarm) { @@ -2061,7 +2046,6 @@ impl NetworkBehaviour for Notifications { let event = NotificationsOut::CustomProtocolOpen { peer_id, set_id, - inbound, direction: if inbound { Direction::Inbound } else { @@ -2383,6 +2367,7 @@ mod tests { protocol::notifications::handler::tests::*, protocol_controller::{IncomingIndex, ProtoSetConfig, ProtocolController}, }; + use libp2p::core::ConnectedPoint; use sc_utils::mpsc::tracing_unbounded; use std::{collections::HashSet, iter}; @@ -2413,7 +2398,8 @@ mod tests { } fn development_notifs( - ) -> (Notifications, ProtocolController, Box) { + ) -> (Notifications, ProtocolController, Box) + { let (protocol_handle_pair, notif_service) = crate::protocol::notifications::service::notification_service("/proto/1".into()); let (to_notifications, from_controller) = @@ -2668,7 +2654,7 @@ mod tests { peer_id: peer, connection_id: conn, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, connected, vec![], None), + handler: NotifsHandler::new(peer, vec![], None), remaining_established: 0usize, }, )); @@ -2868,7 +2854,7 @@ mod tests { peer_id: peer, connection_id: conn, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, connected, vec![], None), + handler: NotifsHandler::new(peer, vec![], None), remaining_established: 0usize, }, )); @@ -3021,7 +3007,7 @@ mod tests { peer_id: peer, connection_id: conn, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, connected, vec![], None), + handler: NotifsHandler::new(peer, vec![], None), remaining_established: 0usize, }, )); @@ -3065,7 +3051,7 @@ mod tests { peer_id: peer, connection_id: conn, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, connected, vec![], None), + handler: NotifsHandler::new(peer, vec![], None), remaining_established: 0usize, }, )); @@ -3135,7 +3121,7 @@ mod tests { peer_id: peer, connection_id: conn, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, connected, vec![], None), + handler: NotifsHandler::new(peer, vec![], None), remaining_established: 0usize, }, )); @@ -3188,7 +3174,7 @@ mod tests { assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Enabled { .. }))); // open new substream - let event = conn_yielder.open_substream(peer, 0, connected, vec![1, 2, 3, 4]); + let event = conn_yielder.open_substream(peer, 0, vec![1, 2, 3, 4]); notif.on_connection_handler_event(peer, conn, event); assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Enabled { .. }))); @@ -3261,7 +3247,7 @@ mod tests { notif.on_connection_handler_event( peer, *conn, - conn_yielder.open_substream(peer, 0, connected.clone(), vec![1, 2, 3, 4]), + conn_yielder.open_substream(peer, 0, vec![1, 2, 3, 4]), ); } @@ -3283,7 +3269,7 @@ mod tests { peer_id: peer, connection_id: conn1, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, connected, vec![], None), + handler: NotifsHandler::new(peer, vec![], None), remaining_established: 0usize, }, )); @@ -3354,7 +3340,7 @@ mod tests { notif.on_connection_handler_event( peer, conn, - conn_yielder.open_substream(peer, 0, connected, vec![1, 2, 3, 4]), + conn_yielder.open_substream(peer, 0, vec![1, 2, 3, 4]), ); if let Some(PeerState::Enabled { ref connections, .. }) = notif.peers.get(&(peer, set_id)) { @@ -3409,7 +3395,7 @@ mod tests { peer_id: peer, connection_id: conn, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, connected, vec![], None), + handler: NotifsHandler::new(peer, vec![], None), remaining_established: 0usize, }, )); @@ -3483,7 +3469,7 @@ mod tests { peer_id: peer, connection_id: conn, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, connected, vec![], None), + handler: NotifsHandler::new(peer, vec![], None), remaining_established: 0usize, }, )); @@ -3546,7 +3532,7 @@ mod tests { peer_id: peer, connection_id: conn1, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, connected.clone(), vec![], None), + handler: NotifsHandler::new(peer, vec![], None), remaining_established: 0usize, }, )); @@ -3560,7 +3546,7 @@ mod tests { peer_id: peer, connection_id: conn2, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, connected, vec![], None), + handler: NotifsHandler::new(peer, vec![], None), remaining_established: 0usize, }, )); @@ -3614,7 +3600,7 @@ mod tests { peer_id: peer, connection_id: conn, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, connected, vec![], None), + handler: NotifsHandler::new(peer, vec![], None), remaining_established: 0usize, }, )); @@ -3672,7 +3658,7 @@ mod tests { peer_id: peer, connection_id: conn2, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, connected, vec![], None), + handler: NotifsHandler::new(peer, vec![], None), remaining_established: 0usize, }, )); @@ -3733,7 +3719,7 @@ mod tests { peer_id: peer, connection_id: conn1, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, connected, vec![], None), + handler: NotifsHandler::new(peer, vec![], None), remaining_established: 0usize, }, )); @@ -3786,7 +3772,7 @@ mod tests { notif.on_connection_handler_event( peer, conn1, - conn_yielder.open_substream(peer, 0, connected.clone(), vec![1, 2, 3, 4]), + conn_yielder.open_substream(peer, 0, vec![1, 2, 3, 4]), ); if let Some(PeerState::Enabled { ref connections, .. }) = notif.peers.get(&(peer, set_id)) { @@ -3802,7 +3788,7 @@ mod tests { peer_id: peer, connection_id: conn1, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, connected, vec![], None), + handler: NotifsHandler::new(peer, vec![], None), remaining_established: 0usize, }, )); @@ -3843,7 +3829,7 @@ mod tests { peer_id: peer, connection_id: conn, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, connected, vec![], None), + handler: NotifsHandler::new(peer, vec![], None), remaining_established: 0usize, }, )); @@ -3966,7 +3952,7 @@ mod tests { peer_id: peer, connection_id: conn, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, connected, vec![], None), + handler: NotifsHandler::new(peer, vec![], None), remaining_established: 0usize, }, )); @@ -4015,10 +4001,6 @@ mod tests { let peer = PeerId::random(); let conn = ConnectionId::new_unchecked(0); let set_id = SetId::from(0); - let connected = ConnectedPoint::Listener { - local_addr: Multiaddr::empty(), - send_back_addr: Multiaddr::empty(), - }; let mut conn_yielder = ConnectionYielder::new(); // move the peer to `Enabled` state @@ -4052,7 +4034,7 @@ mod tests { notif.protocol_report_accept(IncomingIndex(0)); assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Enabled { .. }))); - let event = conn_yielder.open_substream(peer, 0, connected, vec![1, 2, 3, 4]); + let event = conn_yielder.open_substream(peer, 0, vec![1, 2, 3, 4]); notif.on_connection_handler_event(peer, conn, event); assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Enabled { .. }))); @@ -4167,7 +4149,7 @@ mod tests { notif.peerset_report_connect(peer, set_id); assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Enabled { .. }))); - let event = conn_yielder.open_substream(peer, 0, connected, vec![1, 2, 3, 4]); + let event = conn_yielder.open_substream(peer, 0, vec![1, 2, 3, 4]); notif.on_connection_handler_event(peer, conn, event); assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Enabled { .. }))); @@ -4280,7 +4262,7 @@ mod tests { peer_id: peer, connection_id: conn, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, connected, vec![], None), + handler: NotifsHandler::new(peer, vec![], None), remaining_established: 0usize, }, )); @@ -4521,7 +4503,7 @@ mod tests { peer_id: peer, connection_id: ConnectionId::new_unchecked(0), endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, connected, vec![], None), + handler: NotifsHandler::new(peer, vec![], None), remaining_established: 0usize, }, )); @@ -4623,7 +4605,7 @@ mod tests { peer_id: peer, connection_id: ConnectionId::new_unchecked(0), endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, connected, vec![], None), + handler: NotifsHandler::new(peer, vec![], None), remaining_established: 0usize, }, )); @@ -4681,7 +4663,7 @@ mod tests { notif.peerset_report_connect(peer, set_id); assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Enabled { .. }))); - let event = conn_yielder.open_substream(peer, 0, connected, vec![1, 2, 3, 4]); + let event = conn_yielder.open_substream(peer, 0, vec![1, 2, 3, 4]); notif.on_connection_handler_event(peer, conn, event); assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Enabled { .. }))); @@ -4705,7 +4687,7 @@ mod tests { peer_id: peer, connection_id: ConnectionId::new_unchecked(0), endpoint: &endpoint.clone(), - handler: NotifsHandler::new(peer, endpoint, vec![], None), + handler: NotifsHandler::new(peer, vec![], None), remaining_established: 0usize, }, )); @@ -4822,7 +4804,7 @@ mod tests { peer_id: peer, connection_id: ConnectionId::new_unchecked(1337), endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, connected, vec![], None), + handler: NotifsHandler::new(peer, vec![], None), remaining_established: 0usize, }, )); @@ -4857,7 +4839,7 @@ mod tests { peer_id: peer, connection_id: ConnectionId::new_unchecked(1337), endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, connected, vec![], None), + handler: NotifsHandler::new(peer, vec![], None), remaining_established: 0usize, }, )); @@ -4908,7 +4890,7 @@ mod tests { peer_id: peer, connection_id: ConnectionId::new_unchecked(1337), endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, connected, vec![], None), + handler: NotifsHandler::new(peer, vec![], None), remaining_established: 0usize, }, )); @@ -4955,7 +4937,7 @@ mod tests { peer_id: peer, connection_id: conn, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, connected, vec![], None), + handler: NotifsHandler::new(peer, vec![], None), remaining_established: 0usize, }, )); @@ -5005,7 +4987,7 @@ mod tests { peer_id: peer, connection_id: ConnectionId::new_unchecked(1337), endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, connected, vec![], None), + handler: NotifsHandler::new(peer, vec![], None), remaining_established: 0usize, }, )); @@ -5048,7 +5030,7 @@ mod tests { peer_id: peer, connection_id: conn, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, connected.clone(), vec![], None), + handler: NotifsHandler::new(peer, vec![], None), remaining_established: 0usize, }, )); @@ -5059,7 +5041,7 @@ mod tests { peer_id: peer, connection_id: conn, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, connected, vec![], None), + handler: NotifsHandler::new(peer, vec![], None), remaining_established: 0usize, }, )); @@ -5071,16 +5053,12 @@ mod tests { fn open_result_ok_non_existent_peer() { let (mut notif, _controller, _notif_service) = development_notifs(); let conn = ConnectionId::new_unchecked(0); - let connected = ConnectedPoint::Listener { - local_addr: Multiaddr::empty(), - send_back_addr: Multiaddr::empty(), - }; let mut conn_yielder = ConnectionYielder::new(); notif.on_connection_handler_event( PeerId::random(), conn, - conn_yielder.open_substream(PeerId::random(), 0, connected, vec![1, 2, 3, 4]), + conn_yielder.open_substream(PeerId::random(), 0, vec![1, 2, 3, 4]), ); } } diff --git a/substrate/client/network/src/protocol/notifications/handler.rs b/substrate/client/network/src/protocol/notifications/handler.rs index 967ef614c55..bff60ba1125 100644 --- a/substrate/client/network/src/protocol/notifications/handler.rs +++ b/substrate/client/network/src/protocol/notifications/handler.rs @@ -73,7 +73,6 @@ use futures::{ prelude::*, }; use libp2p::{ - core::ConnectedPoint, swarm::{ handler::ConnectionEvent, ConnectionHandler, ConnectionHandlerEvent, KeepAlive, Stream, SubstreamProtocol, @@ -117,9 +116,6 @@ pub struct NotifsHandler { /// When the connection with the remote has been successfully established. when_connection_open: Instant, - /// Whether we are the connection dialer or listener. - endpoint: ConnectedPoint, - /// Remote we are connected to. peer_id: PeerId, @@ -136,7 +132,6 @@ impl NotifsHandler { /// Creates new [`NotifsHandler`]. pub fn new( peer_id: PeerId, - endpoint: ConnectedPoint, protocols: Vec, metrics: Option, ) -> Self { @@ -154,7 +149,6 @@ impl NotifsHandler { }) .collect(), peer_id, - endpoint, when_connection_open: Instant::now(), events_queue: VecDeque::with_capacity(16), metrics: metrics.map_or(None, |metrics| Some(Arc::new(metrics))), @@ -281,8 +275,6 @@ pub enum NotifsHandlerOut { protocol_index: usize, /// Name of the protocol that was actually negotiated, if the default one wasn't available. negotiated_fallback: Option, - /// The endpoint of the connection that is open for custom protocols. - endpoint: ConnectedPoint, /// Handshake that was sent to us. /// This is normally a "Status" message, but this out of the concern of this code. received_handshake: Vec, @@ -590,7 +582,6 @@ impl ConnectionHandler for NotifsHandler { NotifsHandlerOut::OpenResultOk { protocol_index, negotiated_fallback: new_open.negotiated_fallback, - endpoint: self.endpoint.clone(), received_handshake: new_open.handshake, notifications_sink, inbound, @@ -889,7 +880,6 @@ pub mod tests { use libp2p::{ core::muxing::SubstreamBox, swarm::handler::{self, StreamUpgradeError}, - Multiaddr, Stream, }; use multistream_select::{dialer_select_proto, listener_select_proto, Negotiated, Version}; use std::{ @@ -925,7 +915,6 @@ pub mod tests { &mut self, peer: PeerId, protocol_index: usize, - endpoint: ConnectedPoint, received_handshake: Vec, ) -> NotifsHandlerOut { let (async_tx, async_rx) = @@ -954,7 +943,6 @@ pub mod tests { NotifsHandlerOut::OpenResultOk { protocol_index, negotiated_fallback: None, - endpoint, received_handshake, notifications_sink, inbound: false, @@ -1110,10 +1098,6 @@ pub mod tests { NotifsHandler { protocols: vec![proto], when_connection_open: Instant::now(), - endpoint: ConnectedPoint::Listener { - local_addr: Multiaddr::empty(), - send_back_addr: Multiaddr::empty(), - }, peer_id: PeerId::random(), events_queue: VecDeque::new(), metrics: None, @@ -1131,7 +1115,6 @@ pub mod tests { let notif_in = NotificationsInOpen { handshake: b"hello, world".to_vec(), - negotiated_fallback: None, substream: NotificationsInSubstream::new( Framed::new(io, codec), NotificationsInSubstreamHandshake::NotSent, @@ -1158,7 +1141,6 @@ pub mod tests { let notif_in = NotificationsInOpen { handshake: b"hello, world".to_vec(), - negotiated_fallback: None, substream: NotificationsInSubstream::new( Framed::new(io, codec), NotificationsInSubstreamHandshake::NotSent, @@ -1191,7 +1173,6 @@ pub mod tests { let notif_in = NotificationsInOpen { handshake: b"hello, world".to_vec(), - negotiated_fallback: None, substream: NotificationsInSubstream::new( Framed::new(io, codec), NotificationsInSubstreamHandshake::NotSent, @@ -1225,7 +1206,6 @@ pub mod tests { let notif_in = NotificationsInOpen { handshake: b"hello, world".to_vec(), - negotiated_fallback: None, substream: NotificationsInSubstream::new( Framed::new(io, codec), NotificationsInSubstreamHandshake::NotSent, @@ -1265,7 +1245,6 @@ pub mod tests { let notif_in = NotificationsInOpen { handshake: b"hello, world".to_vec(), - negotiated_fallback: None, substream: NotificationsInSubstream::new( Framed::new(io, codec), NotificationsInSubstreamHandshake::NotSent, @@ -1316,7 +1295,6 @@ pub mod tests { codec.set_max_len(usize::MAX); let notif_in = NotificationsInOpen { handshake: b"hello, world".to_vec(), - negotiated_fallback: None, substream: NotificationsInSubstream::new( Framed::new(io, codec), NotificationsInSubstreamHandshake::NotSent, @@ -1355,7 +1333,6 @@ pub mod tests { let notif_in = NotificationsInOpen { handshake: b"hello, world".to_vec(), - negotiated_fallback: None, substream: NotificationsInSubstream::new( Framed::new(io, codec), NotificationsInSubstreamHandshake::NotSent, @@ -1415,7 +1392,6 @@ pub mod tests { let notif_in = NotificationsInOpen { handshake: b"hello, world".to_vec(), - negotiated_fallback: None, substream: NotificationsInSubstream::new( Framed::new(io, codec), NotificationsInSubstreamHandshake::NotSent, @@ -1452,7 +1428,6 @@ pub mod tests { let notif_in = NotificationsInOpen { handshake: b"hello, world".to_vec(), - negotiated_fallback: None, substream: NotificationsInSubstream::new( Framed::new(io, codec), NotificationsInSubstreamHandshake::NotSent, @@ -1498,7 +1473,6 @@ pub mod tests { let notif_in = NotificationsInOpen { handshake: b"hello, world".to_vec(), - negotiated_fallback: None, substream: NotificationsInSubstream::new( Framed::new(io, codec), NotificationsInSubstreamHandshake::NotSent, @@ -1547,7 +1521,6 @@ pub mod tests { let notif_in = NotificationsInOpen { handshake: b"hello, world".to_vec(), - negotiated_fallback: None, substream: NotificationsInSubstream::new( Framed::new(io, codec), NotificationsInSubstreamHandshake::NotSent, @@ -1583,7 +1556,6 @@ pub mod tests { let notif_in = NotificationsInOpen { handshake: b"hello, world".to_vec(), - negotiated_fallback: None, substream: NotificationsInSubstream::new( Framed::new(io, codec), NotificationsInSubstreamHandshake::NotSent, @@ -1658,7 +1630,6 @@ pub mod tests { let notif_in = NotificationsInOpen { handshake: b"hello, world".to_vec(), - negotiated_fallback: None, substream: NotificationsInSubstream::new( Framed::new(io, codec), NotificationsInSubstreamHandshake::PendingSend(vec![1, 2, 3, 4]), diff --git a/substrate/client/network/src/protocol/notifications/service/mod.rs b/substrate/client/network/src/protocol/notifications/service/mod.rs index 4f6d32ae3b3..a7eb31fc579 100644 --- a/substrate/client/network/src/protocol/notifications/service/mod.rs +++ b/substrate/client/network/src/protocol/notifications/service/mod.rs @@ -89,9 +89,8 @@ impl MessageSink for NotificationSink { .await .map_err(|_| error::Error::ConnectionClosed)?; - permit.send(notification).map_err(|_| error::Error::ChannelClosed).map(|res| { + permit.send(notification).map_err(|_| error::Error::ChannelClosed).inspect(|_| { metrics::register_notification_sent(sink.0.metrics(), &sink.1, notification_len); - res }) } } @@ -263,13 +262,12 @@ impl NotificationService for NotificationHandle { .map_err(|_| error::Error::ConnectionClosed)? .send(notification) .map_err(|_| error::Error::ChannelClosed) - .map(|res| { + .inspect(|_| { metrics::register_notification_sent( sink.metrics(), &self.protocol, notification_len, ); - res }) } diff --git a/substrate/client/network/src/protocol/notifications/upgrade/notifications.rs b/substrate/client/network/src/protocol/notifications/upgrade/notifications.rs index a8a9e453a7b..e01bcbe0bad 100644 --- a/substrate/client/network/src/protocol/notifications/upgrade/notifications.rs +++ b/substrate/client/network/src/protocol/notifications/upgrade/notifications.rs @@ -151,7 +151,7 @@ where type Future = Pin> + Send>>; type Error = NotificationsHandshakeError; - fn upgrade_inbound(self, mut socket: TSubstream, negotiated_name: Self::Info) -> Self::Future { + fn upgrade_inbound(self, mut socket: TSubstream, _negotiated_name: Self::Info) -> Self::Future { Box::pin(async move { let handshake_len = unsigned_varint::aio::read_usize(&mut socket).await?; if handshake_len > MAX_HANDSHAKE_SIZE { @@ -174,15 +174,7 @@ where handshake: NotificationsInSubstreamHandshake::NotSent, }; - Ok(NotificationsInOpen { - handshake, - negotiated_fallback: if negotiated_name == self.protocol_names[0] { - None - } else { - Some(negotiated_name) - }, - substream, - }) + Ok(NotificationsInOpen { handshake, substream }) }) } } @@ -191,9 +183,6 @@ where pub struct NotificationsInOpen { /// Handshake sent by the remote. pub handshake: Vec, - /// If the negotiated name is not the "main" protocol name but a fallback, contains the - /// name of the negotiated fallback. - pub negotiated_fallback: Option, /// Implementation of `Stream` that allows receives messages from the substream. pub substream: NotificationsInSubstream, } diff --git a/substrate/client/network/src/service/metrics.rs b/substrate/client/network/src/service/metrics.rs index 202dc7b2ed6..e48c53953ff 100644 --- a/substrate/client/network/src/service/metrics.rs +++ b/substrate/client/network/src/service/metrics.rs @@ -72,7 +72,6 @@ pub struct Metrics { pub distinct_peers_connections_opened_total: Counter, pub incoming_connections_errors_total: CounterVec, pub incoming_connections_total: Counter, - pub issued_light_requests: Counter, pub kademlia_query_duration: HistogramVec, pub kademlia_random_queries_total: Counter, pub kademlia_records_count: Gauge, @@ -126,10 +125,6 @@ impl Metrics { "substrate_sub_libp2p_incoming_connections_total", "Total number of incoming connections on the listening sockets" )?, registry)?, - issued_light_requests: prometheus::register(Counter::new( - "substrate_issued_light_requests", - "Number of light client requests that our node has issued.", - )?, registry)?, kademlia_query_duration: prometheus::register(HistogramVec::new( HistogramOpts { common_opts: Opts::new( diff --git a/substrate/client/network/sync/src/engine.rs b/substrate/client/network/sync/src/engine.rs index 96c1750b311..dceea9954c6 100644 --- a/substrate/client/network/sync/src/engine.rs +++ b/substrate/client/network/sync/src/engine.rs @@ -812,7 +812,8 @@ where } if !self.default_peers_set_no_slot_connected_peers.remove(&peer_id) && - info.inbound && info.info.roles.is_full() + info.inbound && + info.info.roles.is_full() { match self.num_in_peers.checked_sub(1) { Some(value) => { diff --git a/substrate/client/network/sync/src/lib.rs b/substrate/client/network/sync/src/lib.rs index ca7280edba5..c458c7a5da4 100644 --- a/substrate/client/network/sync/src/lib.rs +++ b/substrate/client/network/sync/src/lib.rs @@ -26,7 +26,6 @@ mod block_announce_validator; mod futures_stream; mod justification_requests; mod pending_responses; -mod request_metrics; mod schema; pub mod types; diff --git a/substrate/client/network/sync/src/request_metrics.rs b/substrate/client/network/sync/src/request_metrics.rs deleted file mode 100644 index 455f57ec393..00000000000 --- a/substrate/client/network/sync/src/request_metrics.rs +++ /dev/null @@ -1,25 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// 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 . - -#[derive(Debug)] -pub struct Metrics { - pub pending_requests: u32, - pub active_requests: u32, - pub importing_requests: u32, - pub failed_requests: u32, -} diff --git a/substrate/client/network/test/src/lib.rs b/substrate/client/network/test/src/lib.rs index 0f73e3194ba..06e243342fb 100644 --- a/substrate/client/network/test/src/lib.rs +++ b/substrate/client/network/test/src/lib.rs @@ -628,9 +628,8 @@ struct VerifierAdapter { impl Verifier for VerifierAdapter { async fn verify(&self, block: BlockImportParams) -> Result, String> { let hash = block.header.hash(); - self.verifier.lock().await.verify(block).await.map_err(|e| { + self.verifier.lock().await.verify(block).await.inspect_err(|e| { self.failed_verifications.lock().insert(hash, e.clone()); - e }) } } diff --git a/substrate/client/network/test/src/sync.rs b/substrate/client/network/test/src/sync.rs index 4244c49bf7f..91307d86928 100644 --- a/substrate/client/network/test/src/sync.rs +++ b/substrate/client/network/test/src/sync.rs @@ -749,24 +749,6 @@ async fn sync_blocks_when_block_announce_validator_says_it_is_new_best() { } } -/// Waits for some time until the validation is successful. -struct DeferredBlockAnnounceValidator; - -impl BlockAnnounceValidator for DeferredBlockAnnounceValidator { - fn validate( - &mut self, - _: &Header, - _: &[u8], - ) -> Pin>> + Send>> - { - async { - futures_timer::Delay::new(std::time::Duration::from_millis(500)).await; - Ok(Validation::Success { is_new_best: false }) - } - .boxed() - } -} - #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn wait_until_deferred_block_announce_validation_is_ready() { sp_tracing::try_init_simple(); diff --git a/substrate/client/rpc-spec-v2/src/chain_head/test_utils.rs b/substrate/client/rpc-spec-v2/src/chain_head/test_utils.rs index 073ee34a79f..fa10fde388f 100644 --- a/substrate/client/rpc-spec-v2/src/chain_head/test_utils.rs +++ b/substrate/client/rpc-spec-v2/src/chain_head/test_utils.rs @@ -343,7 +343,8 @@ where fn number( &self, hash: Block::Hash, - ) -> sc_client_api::blockchain::Result::Header as HeaderT>::Number>> { + ) -> sc_client_api::blockchain::Result::Header as HeaderT>::Number>> + { self.client.number(hash) } diff --git a/substrate/client/service/test/src/client/mod.rs b/substrate/client/service/test/src/client/mod.rs index 13e63962fe8..55bbfcdd859 100644 --- a/substrate/client/service/test/src/client/mod.rs +++ b/substrate/client/service/test/src/client/mod.rs @@ -1748,11 +1748,9 @@ fn respects_block_rules() { } #[test] -#[cfg(disable_flaky)] -#[allow(dead_code)] -// FIXME: https://github.com/paritytech/substrate/issues/11321 +// FIXME: https://github.com/paritytech/polkadot-sdk/issues/48 fn returns_status_for_pruned_blocks() { - use sc_consensus::BlockStatus; + use sp_consensus::BlockStatus; sp_tracing::try_init_simple(); let tmp = tempfile::tempdir().unwrap(); diff --git a/substrate/client/transaction-pool/src/graph/tracked_map.rs b/substrate/client/transaction-pool/src/graph/tracked_map.rs index 47ad22603e4..44c2c738ab1 100644 --- a/substrate/client/transaction-pool/src/graph/tracked_map.rs +++ b/substrate/client/transaction-pool/src/graph/tracked_map.rs @@ -119,10 +119,9 @@ where let new_bytes = val.size(); self.bytes.fetch_add(new_bytes as isize, AtomicOrdering::Relaxed); self.length.fetch_add(1, AtomicOrdering::Relaxed); - self.inner_guard.insert(key, val).map(|old_val| { + self.inner_guard.insert(key, val).inspect(|old_val| { self.bytes.fetch_sub(old_val.size() as isize, AtomicOrdering::Relaxed); self.length.fetch_sub(1, AtomicOrdering::Relaxed); - old_val }) } diff --git a/substrate/client/utils/src/mpsc.rs b/substrate/client/utils/src/mpsc.rs index 91db7e1e7b0..051cb5b387c 100644 --- a/substrate/client/utils/src/mpsc.rs +++ b/substrate/client/utils/src/mpsc.rs @@ -103,7 +103,7 @@ impl TracingUnboundedSender { /// Proxy function to `async_channel::Sender::try_send`. pub fn unbounded_send(&self, msg: T) -> Result<(), TrySendError> { - self.inner.try_send(msg).map(|s| { + self.inner.try_send(msg).inspect(|_| { UNBOUNDED_CHANNELS_COUNTER.with_label_values(&[self.name, SENT_LABEL]).inc(); UNBOUNDED_CHANNELS_SIZE .with_label_values(&[self.name]) @@ -124,8 +124,6 @@ impl TracingUnboundedSender { Backtrace::force_capture(), ); } - - s }) } @@ -144,12 +142,11 @@ impl TracingUnboundedReceiver { /// Proxy function to [`async_channel::Receiver`] /// that discounts the messages taken out. pub fn try_recv(&mut self) -> Result { - self.inner.try_recv().map(|s| { + self.inner.try_recv().inspect(|_| { UNBOUNDED_CHANNELS_COUNTER.with_label_values(&[self.name, RECEIVED_LABEL]).inc(); UNBOUNDED_CHANNELS_SIZE .with_label_values(&[self.name]) .set(self.inner.len().saturated_into()); - s }) } diff --git a/substrate/frame/authorship/src/lib.rs b/substrate/frame/authorship/src/lib.rs index a0cca806e78..1de2262a201 100644 --- a/substrate/frame/authorship/src/lib.rs +++ b/substrate/frame/authorship/src/lib.rs @@ -84,9 +84,8 @@ impl Pallet { let digest = >::digest(); let pre_runtime_digests = digest.logs.iter().filter_map(|d| d.as_pre_runtime()); - T::FindAuthor::find_author(pre_runtime_digests).map(|a| { + T::FindAuthor::find_author(pre_runtime_digests).inspect(|a| { >::put(&a); - a }) } } diff --git a/substrate/frame/bags-list/src/lib.rs b/substrate/frame/bags-list/src/lib.rs index f6af1da5e7b..ee36a3a3ebd 100644 --- a/substrate/frame/bags-list/src/lib.rs +++ b/substrate/frame/bags-list/src/lib.rs @@ -491,7 +491,7 @@ impl, I: 'static> ScoreProvider for Pallet { Node::::get(id).map(|node| node.score()).unwrap_or_default() } - frame_election_provider_support::runtime_benchmarks_fuzz_or_std_enabled! { + frame_election_provider_support::runtime_benchmarks_or_std_enabled! { fn set_score_of(id: &T::AccountId, new_score: T::Score) { ListNodes::::mutate(id, |maybe_node| { if let Some(node) = maybe_node.as_mut() { diff --git a/substrate/frame/bags-list/src/list/tests.rs b/substrate/frame/bags-list/src/list/tests.rs index e5fff76d75c..fc4c4fbd088 100644 --- a/substrate/frame/bags-list/src/list/tests.rs +++ b/substrate/frame/bags-list/src/list/tests.rs @@ -778,7 +778,7 @@ mod bags { assert_eq!(bag_1000.iter().count(), 3); bag_1000.insert_node_unchecked(node(4, None, None, bag_1000.bag_upper)); // panics in debug assert_eq!(bag_1000.iter().count(), 3); // in release we expect it to silently ignore the - // request. + // request. }); } diff --git a/substrate/frame/bags-list/src/mock.rs b/substrate/frame/bags-list/src/mock.rs index ea677cb9e73..3690a876f62 100644 --- a/substrate/frame/bags-list/src/mock.rs +++ b/substrate/frame/bags-list/src/mock.rs @@ -41,7 +41,7 @@ impl frame_election_provider_support::ScoreProvider for StakingMock { *NextVoteWeightMap::get().get(id).unwrap_or(&NextVoteWeight::get()) } - frame_election_provider_support::runtime_benchmarks_fuzz_or_std_enabled! { + frame_election_provider_support::runtime_benchmarks_or_std_enabled! { fn set_score_of(id: &AccountId, weight: Self::Score) { NEXT_VOTE_WEIGHT_MAP.with(|m| m.borrow_mut().insert(*id, weight)); } diff --git a/substrate/frame/balances/src/lib.rs b/substrate/frame/balances/src/lib.rs index 87d2029d488..f6858347f24 100644 --- a/substrate/frame/balances/src/lib.rs +++ b/substrate/frame/balances/src/lib.rs @@ -1031,7 +1031,7 @@ pub mod pallet { } if did_provide && !does_provide { // This could reap the account so must go last. - frame_system::Pallet::::dec_providers(who).map_err(|r| { + frame_system::Pallet::::dec_providers(who).inspect_err(|_| { // best-effort revert consumer change. if did_consume && !does_consume { let _ = frame_system::Pallet::::inc_consumers(who).defensive(); @@ -1039,7 +1039,6 @@ pub mod pallet { if !did_consume && does_consume { let _ = frame_system::Pallet::::dec_consumers(who); } - r })?; } diff --git a/substrate/frame/balances/src/tests/currency_tests.rs b/substrate/frame/balances/src/tests/currency_tests.rs index 2243859458b..a4984b34f6d 100644 --- a/substrate/frame/balances/src/tests/currency_tests.rs +++ b/substrate/frame/balances/src/tests/currency_tests.rs @@ -1017,7 +1017,7 @@ fn slash_consumed_slash_full_works() { ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { Balances::make_free_balance_be(&1, 1_000); assert_ok!(System::inc_consumers(&1)); // <-- Reference counter added here is enough for all tests - // Slashed completed in full + // Slashed completed in full assert_eq!(Balances::slash(&1, 900), (NegativeImbalance::new(900), 0)); // Account is still alive assert!(System::account_exists(&1)); @@ -1029,7 +1029,7 @@ fn slash_consumed_slash_over_works() { ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { Balances::make_free_balance_be(&1, 1_000); assert_ok!(System::inc_consumers(&1)); // <-- Reference counter added here is enough for all tests - // Slashed completed in full + // Slashed completed in full assert_eq!(Balances::slash(&1, 1_000), (NegativeImbalance::new(900), 100)); // Account is still alive assert!(System::account_exists(&1)); @@ -1041,7 +1041,7 @@ fn slash_consumed_slash_partial_works() { ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { Balances::make_free_balance_be(&1, 1_000); assert_ok!(System::inc_consumers(&1)); // <-- Reference counter added here is enough for all tests - // Slashed completed in full + // Slashed completed in full assert_eq!(Balances::slash(&1, 800), (NegativeImbalance::new(800), 0)); // Account is still alive assert!(System::account_exists(&1)); diff --git a/substrate/frame/beefy-mmr/src/benchmarking.rs b/substrate/frame/beefy-mmr/src/benchmarking.rs index 135f95eabb9..fea6a2078f0 100644 --- a/substrate/frame/beefy-mmr/src/benchmarking.rs +++ b/substrate/frame/beefy-mmr/src/benchmarking.rs @@ -51,9 +51,7 @@ mod benchmarks { #[benchmark] fn extract_validation_context() { - if !cfg!(feature = "test") { - pallet_mmr::UseLocalStorage::::set(true); - } + pallet_mmr::UseLocalStorage::::set(true); init_block::(1); let header = System::::finalize(); @@ -71,9 +69,7 @@ mod benchmarks { #[benchmark] fn read_peak() { - if !cfg!(feature = "test") { - pallet_mmr::UseLocalStorage::::set(true); - } + pallet_mmr::UseLocalStorage::::set(true); init_block::(1); @@ -91,9 +87,7 @@ mod benchmarks { /// the verification. We need to account for the peaks separately. #[benchmark] fn n_items_proof_is_non_canonical(n: Linear<2, 512>) { - if !cfg!(feature = "test") { - pallet_mmr::UseLocalStorage::::set(true); - } + pallet_mmr::UseLocalStorage::::set(true); for block_num in 1..=n { init_block::(block_num); diff --git a/substrate/frame/benchmarking/pov/src/weights.rs b/substrate/frame/benchmarking/pov/src/weights.rs index c4fc03d1dd9..1f20d5f0b51 100644 --- a/substrate/frame/benchmarking/pov/src/weights.rs +++ b/substrate/frame/benchmarking/pov/src/weights.rs @@ -45,6 +45,7 @@ use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; use core::marker::PhantomData; /// Weight functions needed for `frame_benchmarking_pallet_pov`. +#[allow(dead_code)] pub trait WeightInfo { fn storage_single_value_read() -> Weight; fn storage_single_value_ignored_read() -> Weight; @@ -79,6 +80,7 @@ pub trait WeightInfo { } /// Weights for `frame_benchmarking_pallet_pov` using the Substrate node and recommended hardware. +#[allow(dead_code)] pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { /// Storage: `Pov::Value` (r:1 w:0) diff --git a/substrate/frame/bounties/src/lib.rs b/substrate/frame/bounties/src/lib.rs index 7b89a6e3e76..e30d6fa2d14 100644 --- a/substrate/frame/bounties/src/lib.rs +++ b/substrate/frame/bounties/src/lib.rs @@ -459,12 +459,12 @@ pub mod pallet { Bounties::::try_mutate_exists(bounty_id, |maybe_bounty| -> DispatchResult { let bounty = maybe_bounty.as_mut().ok_or(Error::::InvalidIndex)?; - let slash_curator = |curator: &T::AccountId, - curator_deposit: &mut BalanceOf| { - let imbalance = T::Currency::slash_reserved(curator, *curator_deposit).0; - T::OnSlash::on_unbalanced(imbalance); - *curator_deposit = Zero::zero(); - }; + let slash_curator = + |curator: &T::AccountId, curator_deposit: &mut BalanceOf| { + let imbalance = T::Currency::slash_reserved(curator, *curator_deposit).0; + T::OnSlash::on_unbalanced(imbalance); + *curator_deposit = Zero::zero(); + }; match bounty.status { BountyStatus::Proposed | BountyStatus::Approved | BountyStatus::Funded => { diff --git a/substrate/frame/child-bounties/src/lib.rs b/substrate/frame/child-bounties/src/lib.rs index 911fd4c4c49..660a30ca5d2 100644 --- a/substrate/frame/child-bounties/src/lib.rs +++ b/substrate/frame/child-bounties/src/lib.rs @@ -473,12 +473,13 @@ pub mod pallet { let child_bounty = maybe_child_bounty.as_mut().ok_or(BountiesError::::InvalidIndex)?; - let slash_curator = |curator: &T::AccountId, - curator_deposit: &mut BalanceOf| { - let imbalance = T::Currency::slash_reserved(curator, *curator_deposit).0; - T::OnSlash::on_unbalanced(imbalance); - *curator_deposit = Zero::zero(); - }; + let slash_curator = + |curator: &T::AccountId, curator_deposit: &mut BalanceOf| { + let imbalance = + T::Currency::slash_reserved(curator, *curator_deposit).0; + T::OnSlash::on_unbalanced(imbalance); + *curator_deposit = Zero::zero(); + }; match child_bounty.status { ChildBountyStatus::Added => { diff --git a/substrate/frame/contracts/src/benchmarking/code.rs b/substrate/frame/contracts/src/benchmarking/code.rs index 1473022b553..b5918a5e182 100644 --- a/substrate/frame/contracts/src/benchmarking/code.rs +++ b/substrate/frame/contracts/src/benchmarking/code.rs @@ -114,7 +114,6 @@ pub struct ImportedFunction { pub struct WasmModule { pub code: Vec, pub hash: ::Output, - pub memory: Option, } impl From for WasmModule { @@ -233,7 +232,7 @@ impl From for WasmModule { let code = contract.build().into_bytes().unwrap(); let hash = T::Hashing::hash(&code); - Self { code: code.into(), hash, memory: def.memory } + Self { code: code.into(), hash } } } diff --git a/substrate/frame/contracts/src/exec.rs b/substrate/frame/contracts/src/exec.rs index 31e0bf50b73..046affe32d9 100644 --- a/substrate/frame/contracts/src/exec.rs +++ b/substrate/frame/contracts/src/exec.rs @@ -454,9 +454,6 @@ pub trait Executable: Sized { /// The code hash of the executable. fn code_hash(&self) -> &CodeHash; - /// Size of the contract code in bytes. - fn code_len(&self) -> u32; - /// The code does not contain any instructions which could lead to indeterminism. fn is_deterministic(&self) -> bool; } @@ -1838,10 +1835,6 @@ mod tests { &self.code_info } - fn code_len(&self) -> u32 { - 0 - } - fn is_deterministic(&self) -> bool { true } diff --git a/substrate/frame/contracts/src/wasm/mod.rs b/substrate/frame/contracts/src/wasm/mod.rs index f4ee76459c4..c9786fa1516 100644 --- a/substrate/frame/contracts/src/wasm/mod.rs +++ b/substrate/frame/contracts/src/wasm/mod.rs @@ -488,10 +488,6 @@ impl Executable for WasmBlob { &self.code_info } - fn code_len(&self) -> u32 { - self.code.len() as u32 - } - fn is_deterministic(&self) -> bool { matches!(self.code_info.determinism, Determinism::Enforced) } diff --git a/substrate/frame/delegated-staking/src/lib.rs b/substrate/frame/delegated-staking/src/lib.rs index 7b8d14b0a61..1d181eb29ca 100644 --- a/substrate/frame/delegated-staking/src/lib.rs +++ b/substrate/frame/delegated-staking/src/lib.rs @@ -71,8 +71,8 @@ //! - Migrate a `Nominator` account to an `agent` account. See [`Pallet::migrate_to_agent`]. //! Explained in more detail in the `Migration` section. //! - Migrate unclaimed delegated funds from `agent` to delegator. When a nominator migrates to an -//! agent, the funds are held in a proxy account. This function allows the delegator to claim their -//! share of the funds from the proxy account. See [`Pallet::migrate_delegation`]. +//! agent, the funds are held in a proxy account. This function allows the delegator to claim +//! their share of the funds from the proxy account. See [`Pallet::migrate_delegation`]. //! //! ## Lazy Slashing //! One of the reasons why direct nominators on staking pallet cannot scale well is because all diff --git a/substrate/frame/election-provider-multi-phase/src/lib.rs b/substrate/frame/election-provider-multi-phase/src/lib.rs index 09248e77848..072cfe176b6 100644 --- a/substrate/frame/election-provider-multi-phase/src/lib.rs +++ b/substrate/frame/election-provider-multi-phase/src/lib.rs @@ -1208,9 +1208,8 @@ pub mod pallet { } let _ = Self::unsigned_pre_dispatch_checks(raw_solution) - .map_err(|err| { + .inspect_err(|err| { log!(debug, "unsigned transaction validation failed due to {:?}", err); - err }) .map_err(dispatch_error_to_invalid)?; diff --git a/substrate/frame/election-provider-support/src/lib.rs b/substrate/frame/election-provider-support/src/lib.rs index 394f58a3844..cb3249e388a 100644 --- a/substrate/frame/election-provider-support/src/lib.rs +++ b/substrate/frame/election-provider-support/src/lib.rs @@ -687,7 +687,7 @@ sp_core::generate_feature_enabled_macro!( ); sp_core::generate_feature_enabled_macro!( - runtime_benchmarks_fuzz_or_std_enabled, - any(feature = "runtime-benchmarks", feature = "fuzzing", feature = "std"), + runtime_benchmarks_or_std_enabled, + any(feature = "runtime-benchmarks", feature = "std"), $ ); diff --git a/substrate/frame/elections-phragmen/src/lib.rs b/substrate/frame/elections-phragmen/src/lib.rs index 6d91448fd18..a1c5f69e1b6 100644 --- a/substrate/frame/elections-phragmen/src/lib.rs +++ b/substrate/frame/elections-phragmen/src/lib.rs @@ -829,7 +829,7 @@ impl Pallet { T::Currency::unreserve(who, removed.deposit); } - let maybe_next_best = RunnersUp::::mutate(|r| r.pop()).map(|next_best| { + let maybe_next_best = RunnersUp::::mutate(|r| r.pop()).inspect(|next_best| { // defensive-only: Members and runners-up are disjoint. This will always be err and // give us an index to insert. if let Err(index) = members.binary_search_by(|m| m.who.cmp(&next_best.who)) { @@ -839,7 +839,6 @@ impl Pallet { // is already a member, so not much more to do. log::error!(target: LOG_TARGET, "A member seems to also be a runner-up."); } - next_best }); Ok(maybe_next_best) })?; diff --git a/substrate/frame/examples/offchain-worker/src/tests.rs b/substrate/frame/examples/offchain-worker/src/tests.rs index b665cbbb62a..741adbe6d26 100644 --- a/substrate/frame/examples/offchain-worker/src/tests.rs +++ b/substrate/frame/examples/offchain-worker/src/tests.rs @@ -266,11 +266,12 @@ fn should_submit_unsigned_transaction_on_chain_for_any_account() { { assert_eq!(body, price_payload); - let signature_valid = - ::Public, - frame_system::pallet_prelude::BlockNumberFor, - > as SignedPayload>::verify::(&price_payload, signature); + let signature_valid = ::Public, + frame_system::pallet_prelude::BlockNumberFor, + > as SignedPayload>::verify::( + &price_payload, signature + ); assert!(signature_valid); } @@ -320,11 +321,12 @@ fn should_submit_unsigned_transaction_on_chain_for_all_accounts() { { assert_eq!(body, price_payload); - let signature_valid = - ::Public, - frame_system::pallet_prelude::BlockNumberFor, - > as SignedPayload>::verify::(&price_payload, signature); + let signature_valid = ::Public, + frame_system::pallet_prelude::BlockNumberFor, + > as SignedPayload>::verify::( + &price_payload, signature + ); assert!(signature_valid); } diff --git a/substrate/frame/examples/single-block-migrations/src/migrations/v1.rs b/substrate/frame/examples/single-block-migrations/src/migrations/v1.rs index 55cf7cef9a7..922c03afdd1 100644 --- a/substrate/frame/examples/single-block-migrations/src/migrations/v1.rs +++ b/substrate/frame/examples/single-block-migrations/src/migrations/v1.rs @@ -60,7 +60,7 @@ impl UncheckedOnRuntimeUpgrade for InnerMigrateV0ToV1 { /// /// - If the value doesn't exist, there is nothing to do. /// - If the value exists, it is read and then written back to storage inside a - /// [`crate::CurrentAndPreviousValue`]. + /// [`crate::CurrentAndPreviousValue`]. fn on_runtime_upgrade() -> frame_support::weights::Weight { // Read the old value from storage if let Some(old_value) = v0::Value::::take() { diff --git a/substrate/frame/executive/src/lib.rs b/substrate/frame/executive/src/lib.rs index 1e7bac64e18..fe702e1fc39 100644 --- a/substrate/frame/executive/src/lib.rs +++ b/substrate/frame/executive/src/lib.rs @@ -382,9 +382,8 @@ where , >>::try_state(*header.number(), select.clone()) - .map_err(|e| { + .inspect_err(|e| { log::error!(target: LOG_TARGET, "failure: {:?}", e); - e })?; if select.any() { let res = AllPalletsWithSystem::try_decode_entire_state(); diff --git a/substrate/frame/nis/src/lib.rs b/substrate/frame/nis/src/lib.rs index 016daa4cb78..87e2276e768 100644 --- a/substrate/frame/nis/src/lib.rs +++ b/substrate/frame/nis/src/lib.rs @@ -756,15 +756,13 @@ pub mod pallet { .map(|_| ()) // We ignore this error as it just means the amount we're trying to deposit is // dust and the beneficiary account doesn't exist. - .or_else( - |e| { - if e == TokenError::CannotCreate.into() { - Ok(()) - } else { - Err(e) - } - }, - )?; + .or_else(|e| { + if e == TokenError::CannotCreate.into() { + Ok(()) + } else { + Err(e) + } + })?; summary.receipts_on_hold.saturating_reduce(on_hold); } T::Currency::release(&HoldReason::NftReceipt.into(), &who, amount, Exact)?; diff --git a/substrate/frame/referenda/src/mock.rs b/substrate/frame/referenda/src/mock.rs index bf0fa4e1a12..c96a50af865 100644 --- a/substrate/frame/referenda/src/mock.rs +++ b/substrate/frame/referenda/src/mock.rs @@ -24,7 +24,6 @@ use frame_support::{ assert_ok, derive_impl, ord_parameter_types, parameter_types, traits::{ ConstU32, ConstU64, Contains, EqualPrivilegeOnly, OnInitialize, OriginTrait, Polling, - SortedMembers, }, weights::Weight, }; @@ -98,14 +97,6 @@ ord_parameter_types! { pub const Five: u64 = 5; pub const Six: u64 = 6; } -pub struct OneToFive; -impl SortedMembers for OneToFive { - fn sorted_members() -> Vec { - vec![1, 2, 3, 4, 5] - } - #[cfg(feature = "runtime-benchmarks")] - fn add(_m: &u64) {} -} pub struct TestTracksInfo; impl TracksInfo for TestTracksInfo { diff --git a/substrate/frame/referenda/src/types.rs b/substrate/frame/referenda/src/types.rs index 1039b288b2a..e83f28b472c 100644 --- a/substrate/frame/referenda/src/types.rs +++ b/substrate/frame/referenda/src/types.rs @@ -258,7 +258,8 @@ impl< Tally: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone, AccountId: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone, ScheduleAddress: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone, - > ReferendumInfo + > + ReferendumInfo { /// Take the Decision Deposit from `self`, if there is one. Returns an `Err` if `self` is not /// in a valid state for the Decision Deposit to be refunded. diff --git a/substrate/frame/revive/proc-macro/src/lib.rs b/substrate/frame/revive/proc-macro/src/lib.rs index 95f4110a2d7..012b4bfab9a 100644 --- a/substrate/frame/revive/proc-macro/src/lib.rs +++ b/substrate/frame/revive/proc-macro/src/lib.rs @@ -349,18 +349,19 @@ where let Some(ident) = path.path.get_ident() else { panic!("Type needs to be ident"); }; - let size = - if ident == "i8" || - ident == "i16" || ident == "i32" || - ident == "u8" || ident == "u16" || - ident == "u32" - { - 1 - } else if ident == "i64" || ident == "u64" { - 2 - } else { - panic!("Pass by value only supports primitives"); - }; + let size = if ident == "i8" || + ident == "i16" || + ident == "i32" || + ident == "u8" || + ident == "u16" || + ident == "u32" + { + 1 + } else if ident == "i64" || ident == "u64" { + 2 + } else { + panic!("Pass by value only supports primitives"); + }; registers_used += size; if registers_used > ALLOWED_REGISTERS { return quote! { diff --git a/substrate/frame/society/src/lib.rs b/substrate/frame/society/src/lib.rs index b4c5c88af3d..04879cd8709 100644 --- a/substrate/frame/society/src/lib.rs +++ b/substrate/frame/society/src/lib.rs @@ -1387,18 +1387,6 @@ impl_ensure_origin_with_arg_ignoring_arg! { {} } -struct InputFromRng<'a, T>(&'a mut T); -impl<'a, T: RngCore> codec::Input for InputFromRng<'a, T> { - fn remaining_len(&mut self) -> Result, codec::Error> { - return Ok(None) - } - - fn read(&mut self, into: &mut [u8]) -> Result<(), codec::Error> { - self.0.fill_bytes(into); - Ok(()) - } -} - pub enum Period { Voting { elapsed: BlockNumber, more: BlockNumber }, Claim { elapsed: BlockNumber, more: BlockNumber }, diff --git a/substrate/frame/society/src/tests.rs b/substrate/frame/society/src/tests.rs index df8e844cdad..2a13f99855b 100644 --- a/substrate/frame/society/src/tests.rs +++ b/substrate/frame/society/src/tests.rs @@ -281,7 +281,7 @@ fn bidding_works() { // No more candidates satisfy the requirements assert_eq!(candidacies(), vec![]); assert_ok!(Society::defender_vote(Origin::signed(10), true)); // Keep defender around - // Next period + // Next period run_to_block(16); // Same members assert_eq!(members(), vec![10, 30, 40, 50]); diff --git a/substrate/frame/staking/src/tests.rs b/substrate/frame/staking/src/tests.rs index ab2c00ca9cc..dd178a95bec 100644 --- a/substrate/frame/staking/src/tests.rs +++ b/substrate/frame/staking/src/tests.rs @@ -7995,7 +7995,7 @@ mod ledger_recovery { assert_eq!(Balances::balance_locked(crate::STAKING_ID, &333), lock_333_before); // OK assert_eq!(Bonded::::get(&333), Some(444)); // OK assert!(Payee::::get(&333).is_some()); // OK - // however, ledger associated with its controller was killed. + // however, ledger associated with its controller was killed. assert!(Ledger::::get(&444).is_none()); // NOK // side effects on 444 - ledger, bonded, payee, lock should be completely removed. diff --git a/substrate/frame/support/procedural/src/construct_runtime/parse.rs b/substrate/frame/support/procedural/src/construct_runtime/parse.rs index 3e38adcc3c5..729a803a302 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/parse.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/parse.rs @@ -592,8 +592,6 @@ pub struct Pallet { pub cfg_pattern: Vec, /// The doc literals pub docs: Vec, - /// attributes - pub attrs: Vec, } impl Pallet { @@ -764,7 +762,6 @@ fn convert_pallets(pallets: Vec) -> syn::Result>>()?; diff --git a/substrate/frame/support/procedural/src/derive_impl.rs b/substrate/frame/support/procedural/src/derive_impl.rs index 69117c02681..5d39c2707de 100644 --- a/substrate/frame/support/procedural/src/derive_impl.rs +++ b/substrate/frame/support/procedural/src/derive_impl.rs @@ -304,6 +304,7 @@ fn test_derive_impl_attr_args_parsing() { #[test] fn test_runtime_type_with_doc() { + #[allow(dead_code)] trait TestTrait { type Test; } diff --git a/substrate/frame/support/procedural/src/lib.rs b/substrate/frame/support/procedural/src/lib.rs index d40a571c9ea..a2c1e6eec7f 100644 --- a/substrate/frame/support/procedural/src/lib.rs +++ b/substrate/frame/support/procedural/src/lib.rs @@ -321,9 +321,10 @@ pub fn derive_debug_no_bound(input: TokenStream) -> TokenStream { /// This behaviour is useful to prevent bloating the runtime WASM blob from unneeded code. #[proc_macro_derive(RuntimeDebugNoBound)] pub fn derive_runtime_debug_no_bound(input: TokenStream) -> TokenStream { - if cfg!(any(feature = "std", feature = "try-runtime")) { - no_bound::debug::derive_debug_no_bound(input) - } else { + let try_runtime_or_std_impl: proc_macro2::TokenStream = + no_bound::debug::derive_debug_no_bound(input.clone()).into(); + + let stripped_impl = { let input = syn::parse_macro_input!(input as syn::DeriveInput); let name = &input.ident; @@ -338,8 +339,22 @@ pub fn derive_runtime_debug_no_bound(input: TokenStream) -> TokenStream { } }; ) - .into() - } + }; + + let frame_support = match generate_access_from_frame_or_crate("frame-support") { + Ok(frame_support) => frame_support, + Err(e) => return e.to_compile_error().into(), + }; + + quote::quote!( + #frame_support::try_runtime_or_std_enabled! { + #try_runtime_or_std_impl + } + #frame_support::try_runtime_and_std_not_enabled! { + #stripped_impl + } + ) + .into() } /// Derive [`PartialEq`] but do not bound any generic. diff --git a/substrate/frame/support/procedural/src/pallet/expand/genesis_build.rs b/substrate/frame/support/procedural/src/pallet/expand/genesis_build.rs index 248e8346943..b71aed680dc 100644 --- a/substrate/frame/support/procedural/src/pallet/expand/genesis_build.rs +++ b/substrate/frame/support/procedural/src/pallet/expand/genesis_build.rs @@ -35,7 +35,7 @@ pub fn expand_genesis_build(def: &mut Def) -> proc_macro2::TokenStream { let where_clause = &genesis_build.where_clause; quote::quote_spanned!(genesis_build.attr_span => - #[cfg(feature = "std")] + #frame_support::std_enabled! { impl<#type_impl_gen> #frame_support::sp_runtime::BuildStorage for #gen_cfg_ident<#gen_cfg_use_gen> #where_clause { fn assimilate_storage(&self, storage: &mut #frame_support::sp_runtime::Storage) -> std::result::Result<(), std::string::String> { @@ -45,5 +45,6 @@ pub fn expand_genesis_build(def: &mut Def) -> proc_macro2::TokenStream { }) } } + } ) } diff --git a/substrate/frame/support/procedural/src/pallet/expand/hooks.rs b/substrate/frame/support/procedural/src/pallet/expand/hooks.rs index 1b0c09c4e36..c31ddd8a47b 100644 --- a/substrate/frame/support/procedural/src/pallet/expand/hooks.rs +++ b/substrate/frame/support/procedural/src/pallet/expand/hooks.rs @@ -324,15 +324,13 @@ pub fn expand_hooks(def: &mut Def) -> proc_macro2::TokenStream { Self as #frame_support::traits::Hooks< #frame_system::pallet_prelude::BlockNumberFor:: > - >::try_state(n).map_err(|err| { + >::try_state(n).inspect_err(|err| { #frame_support::__private::log::error!( target: #frame_support::LOG_TARGET, "❌ {:?} try_state checks failed: {:?}", #pallet_name, err ); - - err }) } } diff --git a/substrate/frame/support/procedural/src/pallet/parse/call.rs b/substrate/frame/support/procedural/src/pallet/parse/call.rs index 346dff46f12..68ced1bc0ed 100644 --- a/substrate/frame/support/procedural/src/pallet/parse/call.rs +++ b/substrate/frame/support/procedural/src/pallet/parse/call.rs @@ -400,18 +400,19 @@ impl CallDef { } for (feeless_arg, arg) in feeless_check.inputs.iter().skip(1).zip(args.iter()) { - let feeless_arg_type = - if let syn::Pat::Type(syn::PatType { ty, .. }) = feeless_arg.clone() { - if let syn::Type::Reference(pat) = *ty { - pat.elem.clone() - } else { - let msg = "Invalid pallet::call, feeless_if closure argument must be a reference"; - return Err(syn::Error::new(ty.span(), msg)); - } + let feeless_arg_type = if let syn::Pat::Type(syn::PatType { ty, .. }) = + feeless_arg.clone() + { + if let syn::Type::Reference(pat) = *ty { + pat.elem.clone() } else { - let msg = "Invalid pallet::call, feeless_if closure argument must be a type ascription pattern"; - return Err(syn::Error::new(feeless_arg.span(), msg)); - }; + let msg = "Invalid pallet::call, feeless_if closure argument must be a reference"; + return Err(syn::Error::new(ty.span(), msg)); + } + } else { + let msg = "Invalid pallet::call, feeless_if closure argument must be a type ascription pattern"; + return Err(syn::Error::new(feeless_arg.span(), msg)); + }; if feeless_arg_type != arg.2 { let msg = diff --git a/substrate/frame/support/procedural/src/runtime/parse/pallet.rs b/substrate/frame/support/procedural/src/runtime/parse/pallet.rs index de1efa267c8..52f57cd2cd8 100644 --- a/substrate/frame/support/procedural/src/runtime/parse/pallet.rs +++ b/substrate/frame/support/procedural/src/runtime/parse/pallet.rs @@ -91,7 +91,6 @@ impl Pallet { cfg_pattern, pallet_parts, docs, - attrs: item.attrs.clone(), }) } } diff --git a/substrate/frame/support/src/lib.rs b/substrate/frame/support/src/lib.rs index 28283f2a5a0..269867e38c5 100644 --- a/substrate/frame/support/src/lib.rs +++ b/substrate/frame/support/src/lib.rs @@ -2523,6 +2523,8 @@ pub use frame_support_procedural::register_default_impl; sp_core::generate_feature_enabled_macro!(std_enabled, feature = "std", $); // Generate a macro that will enable/disable code based on `try-runtime` feature being active. sp_core::generate_feature_enabled_macro!(try_runtime_enabled, feature = "try-runtime", $); +sp_core::generate_feature_enabled_macro!(try_runtime_or_std_enabled, any(feature = "try-runtime", feature = "std"), $); +sp_core::generate_feature_enabled_macro!(try_runtime_and_std_not_enabled, all(not(feature = "try-runtime"), not(feature = "std")), $); // Helper for implementing GenesisBuilder runtime API pub mod genesis_builder_helper; diff --git a/substrate/frame/support/src/storage/generator/double_map.rs b/substrate/frame/support/src/storage/generator/double_map.rs index b68f3fa495f..a9116f1f66b 100644 --- a/substrate/frame/support/src/storage/generator/double_map.rs +++ b/substrate/frame/support/src/storage/generator/double_map.rs @@ -346,9 +346,8 @@ where final_key }; - unhashed::take(old_key.as_ref()).map(|value| { + unhashed::take(old_key.as_ref()).inspect(|value| { unhashed::put(Self::storage_double_map_final_key(key1, key2).as_ref(), &value); - value }) } } diff --git a/substrate/frame/support/src/storage/generator/map.rs b/substrate/frame/support/src/storage/generator/map.rs index e905df41a5a..2d1f6c9f73a 100644 --- a/substrate/frame/support/src/storage/generator/map.rs +++ b/substrate/frame/support/src/storage/generator/map.rs @@ -311,9 +311,8 @@ impl> storage::StorageMap final_key }; - unhashed::take(old_key.as_ref()).map(|value| { + unhashed::take(old_key.as_ref()).inspect(|value| { unhashed::put(Self::storage_map_final_key(key).as_ref(), &value); - value }) } } diff --git a/substrate/frame/support/src/storage/generator/nmap.rs b/substrate/frame/support/src/storage/generator/nmap.rs index 0466583a279..9083aba9d32 100755 --- a/substrate/frame/support/src/storage/generator/nmap.rs +++ b/substrate/frame/support/src/storage/generator/nmap.rs @@ -305,9 +305,8 @@ where final_key }; - unhashed::take(old_key.as_ref()).map(|value| { + unhashed::take(old_key.as_ref()).inspect(|value| { unhashed::put(Self::storage_n_map_final_key::(key).as_ref(), &value); - value }) } } diff --git a/substrate/frame/support/src/storage/types/double_map.rs b/substrate/frame/support/src/storage/types/double_map.rs index c70d9de5446..24aad3de0b3 100644 --- a/substrate/frame/support/src/storage/types/double_map.rs +++ b/substrate/frame/support/src/storage/types/double_map.rs @@ -129,7 +129,8 @@ impl OnEmpty, MaxValues, >, - > where + > +where Prefix: StorageInstance, Hasher1: crate::hash::StorageHasher, Hasher2: crate::hash::StorageHasher, diff --git a/substrate/frame/support/src/tests/mod.rs b/substrate/frame/support/src/tests/mod.rs index 5e1bcc777df..7c90a12d416 100644 --- a/substrate/frame/support/src/tests/mod.rs +++ b/substrate/frame/support/src/tests/mod.rs @@ -769,5 +769,6 @@ fn derive_partial_eq_no_bound_core_mod() { crate::DefaultNoBound, crate::EqNoBound, )] + #[allow(dead_code)] struct Test; } diff --git a/substrate/frame/support/src/traits/misc.rs b/substrate/frame/support/src/traits/misc.rs index 492475d6f63..4d3b122daf6 100644 --- a/substrate/frame/support/src/traits/misc.rs +++ b/substrate/frame/support/src/traits/misc.rs @@ -488,7 +488,7 @@ pub trait DefensiveMin { /// assert_eq!(4, 4_u32.defensive_min(4_u32)); /// ``` /// - /// ```#[cfg_attr(debug_assertions, should_panic)] + /// ```should_panic /// use frame_support::traits::DefensiveMin; /// // min(4, 3) panics. /// 4_u32.defensive_min(3_u32); @@ -505,7 +505,7 @@ pub trait DefensiveMin { /// assert_eq!(3, 3_u32.defensive_strict_min(4_u32)); /// ``` /// - /// ```#[cfg_attr(debug_assertions, should_panic)] + /// ```should_panic /// use frame_support::traits::DefensiveMin; /// // min(4, 4) panics. /// 4_u32.defensive_strict_min(4_u32); @@ -552,7 +552,7 @@ pub trait DefensiveMax { /// assert_eq!(4, 4_u32.defensive_max(4_u32)); /// ``` /// - /// ```#[cfg_attr(debug_assertions, should_panic)] + /// ```should_panic /// use frame_support::traits::DefensiveMax; /// // max(4, 5) panics. /// 4_u32.defensive_max(5_u32); @@ -569,7 +569,7 @@ pub trait DefensiveMax { /// assert_eq!(4, 4_u32.defensive_strict_max(3_u32)); /// ``` /// - /// ```#[cfg_attr(debug_assertions, should_panic)] + /// ```should_panic /// use frame_support::traits::DefensiveMax; /// // max(4, 4) panics. /// 4_u32.defensive_strict_max(4_u32); diff --git a/substrate/frame/support/src/traits/try_runtime/decode_entire_state.rs b/substrate/frame/support/src/traits/try_runtime/decode_entire_state.rs index 8dbeecd8e86..a7465c87fb2 100644 --- a/substrate/frame/support/src/traits/try_runtime/decode_entire_state.rs +++ b/substrate/frame/support/src/traits/try_runtime/decode_entire_state.rs @@ -197,7 +197,8 @@ impl TryDecodeEntireS QueryKind, OnEmpty, MaxValues, - > where + > +where Prefix: CountedStorageMapInstance, Hasher: StorageHasher, Key: FullCodec, @@ -229,7 +230,8 @@ impl QueryKind, OnEmpty, MaxValues, - > where + > +where Prefix: StorageInstance, Hasher1: StorageHasher, Key1: FullCodec, diff --git a/substrate/frame/support/test/tests/benchmark_ui/bad_return_type_blank_with_question.stderr b/substrate/frame/support/test/tests/benchmark_ui/bad_return_type_blank_with_question.stderr index 7e0a02be649..04203e4b684 100644 --- a/substrate/frame/support/test/tests/benchmark_ui/bad_return_type_blank_with_question.stderr +++ b/substrate/frame/support/test/tests/benchmark_ui/bad_return_type_blank_with_question.stderr @@ -8,3 +8,7 @@ error[E0277]: the `?` operator can only be used in a function that returns `Resu | ^ cannot use the `?` operator in a function that returns `()` | = help: the trait `FromResidual>` is not implemented for `()` +help: consider adding return type + | +31 | fn bench() -> Result<(), Box> { + | +++++++++++++++++++++++++++++++++++++++++ diff --git a/substrate/frame/support/test/tests/construct_runtime_ui/deprecated_where_block.stderr b/substrate/frame/support/test/tests/construct_runtime_ui/deprecated_where_block.stderr index b28cae2ddef..59e36775d46 100644 --- a/substrate/frame/support/test/tests/construct_runtime_ui/deprecated_where_block.stderr +++ b/substrate/frame/support/test/tests/construct_runtime_ui/deprecated_where_block.stderr @@ -53,7 +53,15 @@ note: required by a bound in `frame_system::Event` | ^^^^^^ required by this bound in `Event` = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0277]: the trait bound `Runtime: Config` is not satisfied in `RuntimeEvent` +error[E0277]: the trait bound `Runtime: Config` is not satisfied + --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 + | +20 | construct_runtime! { + | ^ the trait `Config` is not implemented for `Runtime` + | + = note: this error originates in the macro `frame_support::construct_runtime` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `RawOrigin<_>: TryFrom` is not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | 20 | / construct_runtime! { @@ -63,9 +71,12 @@ error[E0277]: the trait bound `Runtime: Config` is not satisfied in `RuntimeEven ... | 27 | | } 28 | | } - | |_^ within `RuntimeEvent`, the trait `Config` is not implemented for `Runtime`, which is required by `RuntimeEvent: Sized` + | |_^ the trait `TryFrom` is not implemented for `RawOrigin<_>` | -note: required because it appears within the type `RuntimeEvent` + = help: the trait `TryFrom` is implemented for `RawOrigin<::AccountId>` + = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `Runtime: Config` is not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | 20 | / construct_runtime! { @@ -75,15 +86,25 @@ note: required because it appears within the type `RuntimeEvent` ... | 27 | | } 28 | | } - | |_^ -note: required by a bound in `Clone` - --> $RUST/core/src/clone.rs + | |_^ the trait `Config` is not implemented for `Runtime`, which is required by `Pallet: Callable` | - | pub trait Clone: Sized { - | ^^^^^ required by this bound in `Clone` - = note: this error originates in the derive macro `Clone` which comes from the expansion of the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + = help: the trait `Callable` is implemented for `Pallet` + = note: required for `Pallet` to implement `Callable` + = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `Runtime: Config` is not satisfied + --> tests/construct_runtime_ui/deprecated_where_block.rs:26:3 + | +26 | System: frame_system::{Pallet, Call, Storage, Config, Event}, + | ^^^^^^ the trait `Config` is not implemented for `Runtime` + | +note: required by a bound in `GenesisConfig` + --> $WORKSPACE/substrate/frame/system/src/lib.rs + | + | pub struct GenesisConfig { + | ^^^^^^ required by this bound in `GenesisConfig` -error[E0277]: the trait bound `Runtime: Config` is not satisfied in `RuntimeEvent` +error[E0277]: the trait bound `Runtime: Config` is not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | 20 | / construct_runtime! { @@ -93,9 +114,16 @@ error[E0277]: the trait bound `Runtime: Config` is not satisfied in `RuntimeEven ... | 27 | | } 28 | | } - | |_^ within `RuntimeEvent`, the trait `Config` is not implemented for `Runtime`, which is required by `RuntimeEvent: Sized` + | |_^ the trait `Config` is not implemented for `Runtime` | -note: required because it appears within the type `RuntimeEvent` +note: required by a bound in `frame_system::Event` + --> $WORKSPACE/substrate/frame/system/src/lib.rs + | + | pub enum Event { + | ^^^^^^ required by this bound in `Event` + = note: this error originates in the derive macro `Clone` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0369]: binary operation `==` cannot be applied to type `&frame_system::Event` --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | 20 | / construct_runtime! { @@ -106,14 +134,21 @@ note: required because it appears within the type `RuntimeEvent` 27 | | } 28 | | } | |_^ -note: required by a bound in `EncodeLike` - --> $CARGO/parity-scale-codec-3.6.12/src/encode_like.rs | - | pub trait EncodeLike: Sized + Encode {} - | ^^^^^ required by this bound in `EncodeLike` - = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) +note: an implementation of `Config` might be missing for `Runtime` + --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 + | +20 | / construct_runtime! { +21 | | pub struct Runtime where + | |______________________^ must implement `Config` +note: the trait `Config` must be implemented + --> $WORKSPACE/substrate/frame/system/src/lib.rs + | + | pub trait Config: 'static + Eq + Clone { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: this error originates in the derive macro `PartialEq` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0277]: the trait bound `Runtime: Config` is not satisfied in `RuntimeEvent` +error[E0277]: the trait bound `Runtime: Config` is not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | 20 | / construct_runtime! { @@ -123,9 +158,16 @@ error[E0277]: the trait bound `Runtime: Config` is not satisfied in `RuntimeEven ... | 27 | | } 28 | | } - | |_^ within `RuntimeEvent`, the trait `Config` is not implemented for `Runtime`, which is required by `RuntimeEvent: Sized` + | |_^ the trait `Config` is not implemented for `Runtime` | -note: required because it appears within the type `RuntimeEvent` +note: required by a bound in `frame_system::Event` + --> $WORKSPACE/substrate/frame/system/src/lib.rs + | + | pub enum Event { + | ^^^^^^ required by this bound in `Event` + = note: this error originates in the derive macro `Eq` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `frame_system::Event: Encode` is not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | 20 | / construct_runtime! { @@ -135,15 +177,12 @@ note: required because it appears within the type `RuntimeEvent` ... | 27 | | } 28 | | } - | |_^ -note: required by a bound in `Decode` - --> $CARGO/parity-scale-codec-3.6.12/src/codec.rs + | |_^ the trait `Encode` is not implemented for `frame_system::Event` | - | pub trait Decode: Sized { - | ^^^^^ required by this bound in `Decode` - = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + = help: the trait `Encode` is implemented for `frame_system::Event` + = note: this error originates in the derive macro `self::sp_api_hidden_includes_construct_runtime::hidden_include::__private::codec::Encode` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0277]: the trait bound `Runtime: Config` is not satisfied in `frame_system::Event` +error[E0277]: the trait bound `Runtime: Config` is not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | 20 | / construct_runtime! { @@ -153,21 +192,16 @@ error[E0277]: the trait bound `Runtime: Config` is not satisfied in `frame_syste ... | 27 | | } 28 | | } - | |_^ within `frame_system::Event`, the trait `Config` is not implemented for `Runtime`, which is required by `frame_system::Event: Sized` + | |_^ the trait `Config` is not implemented for `Runtime` | -note: required because it appears within the type `frame_system::Event` +note: required by a bound in `frame_system::Event` --> $WORKSPACE/substrate/frame/system/src/lib.rs | | pub enum Event { - | ^^^^^ -note: required by a bound in `From` - --> $RUST/core/src/convert/mod.rs - | - | pub trait From: Sized { - | ^ required by this bound in `From` - = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + | ^^^^^^ required by this bound in `Event` + = note: this error originates in the derive macro `self::sp_api_hidden_includes_construct_runtime::hidden_include::__private::codec::Encode` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0277]: the trait bound `Runtime: Config` is not satisfied in `frame_system::Event` +error[E0277]: the trait bound `frame_system::Event: Decode` is not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | 20 | / construct_runtime! { @@ -177,29 +211,40 @@ error[E0277]: the trait bound `Runtime: Config` is not satisfied in `frame_syste ... | 27 | | } 28 | | } - | |_^ within `frame_system::Event`, the trait `Config` is not implemented for `Runtime`, which is required by `frame_system::Event: Sized` + | |_^ the trait `Decode` is not implemented for `frame_system::Event` | -note: required because it appears within the type `frame_system::Event` + = help: the trait `Decode` is implemented for `frame_system::Event` + +error[E0277]: the trait bound `Runtime: Config` is not satisfied + --> tests/construct_runtime_ui/deprecated_where_block.rs:26:11 + | +26 | System: frame_system::{Pallet, Call, Storage, Config, Event}, + | ^^^^^^^^^^^^ the trait `Config` is not implemented for `Runtime` + | +note: required by a bound in `frame_system::Event` --> $WORKSPACE/substrate/frame/system/src/lib.rs | | pub enum Event { - | ^^^^^ -note: required by a bound in `TryInto` - --> $RUST/core/src/convert/mod.rs - | - | pub trait TryInto: Sized { - | ^ required by this bound in `TryInto` - = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + | ^^^^^^ required by this bound in `Event` error[E0277]: the trait bound `Runtime: Config` is not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | -20 | construct_runtime! { - | ^ the trait `Config` is not implemented for `Runtime` +20 | / construct_runtime! { +21 | | pub struct Runtime where +22 | | Block = Block, +23 | | NodeBlock = Block, +... | +27 | | } +28 | | } + | |_^ the trait `Config` is not implemented for `Runtime`, which is required by `frame_system::Event: std::fmt::Debug` | - = note: this error originates in the macro `frame_support::construct_runtime` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + = help: the trait `std::fmt::Debug` is implemented for `frame_system::Event` + = note: required for `frame_system::Event` to implement `std::fmt::Debug` + = note: required for the cast from `&frame_system::Event` to `&dyn std::fmt::Debug` + = note: this error originates in the derive macro `self::sp_api_hidden_includes_construct_runtime::hidden_include::__private::RuntimeDebug` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0277]: the trait bound `RawOrigin<_>: TryFrom` is not satisfied +error[E0277]: the trait bound `Runtime: Config` is not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | 20 | / construct_runtime! { @@ -209,10 +254,12 @@ error[E0277]: the trait bound `RawOrigin<_>: TryFrom` is not satis ... | 27 | | } 28 | | } - | |_^ the trait `TryFrom` is not implemented for `RawOrigin<_>` + | |_^ the trait `Config` is not implemented for `Runtime`, which is required by `frame_system::Error: std::fmt::Debug` | - = help: the trait `TryFrom` is implemented for `RawOrigin<::AccountId>` - = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + = help: the trait `std::fmt::Debug` is implemented for `frame_system::Error` + = note: required for `frame_system::Error` to implement `std::fmt::Debug` + = note: required for the cast from `&frame_system::Error` to `&dyn std::fmt::Debug` + = note: this error originates in the derive macro `self::sp_api_hidden_includes_construct_runtime::hidden_include::__private::RuntimeDebug` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `Runtime: Config` is not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 @@ -224,11 +271,15 @@ error[E0277]: the trait bound `Runtime: Config` is not satisfied ... | 27 | | } 28 | | } - | |_^ the trait `Config` is not implemented for `Runtime`, which is required by `Pallet: Callable` + | |_^ the trait `Config` is not implemented for `Runtime` | - = help: the trait `Callable` is implemented for `Pallet` - = note: required for `Pallet` to implement `Callable` - = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the derive macro `Clone` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `Runtime: Config` is not satisfied + --> tests/construct_runtime_ui/deprecated_where_block.rs:21:13 + | +21 | pub struct Runtime where + | ^^^^^^^ the trait `Config` is not implemented for `Runtime` error[E0277]: the trait bound `Runtime: Config` is not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 @@ -240,10 +291,12 @@ error[E0277]: the trait bound `Runtime: Config` is not satisfied ... | 27 | | } 28 | | } - | |_^ the trait `Config` is not implemented for `Runtime`, which is required by `RuntimeCall: Sized` + | |_^ the trait `Config` is not implemented for `Runtime`, which is required by `RawOrigin<_>: Into<_>` | - = note: required for `Pallet` to implement `Callable` -note: required because it appears within the type `RuntimeCall` + = note: required for `RawOrigin<_>` to implement `Into` + = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `Runtime: Config` is not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | 20 | / construct_runtime! { @@ -253,13 +306,9 @@ note: required because it appears within the type `RuntimeCall` ... | 27 | | } 28 | | } - | |_^ -note: required by a bound in `Clone` - --> $RUST/core/src/clone.rs + | |_^ the trait `Config` is not implemented for `Runtime` | - | pub trait Clone: Sized { - | ^^^^^ required by this bound in `Clone` - = note: this error originates in the derive macro `Clone` which comes from the expansion of the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the derive macro `PartialEq` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `Runtime: Config` is not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 @@ -271,10 +320,17 @@ error[E0277]: the trait bound `Runtime: Config` is not satisfied ... | 27 | | } 28 | | } - | |_^ the trait `Config` is not implemented for `Runtime`, which is required by `RuntimeCall: Sized` + | |_^ the trait `Config` is not implemented for `Runtime` | - = note: required for `Pallet` to implement `Callable` -note: required because it appears within the type `RuntimeCall` + = note: this error originates in the derive macro `self::sp_api_hidden_includes_construct_runtime::hidden_include::__private::RuntimeDebug` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `Runtime: Config` is not satisfied + --> tests/construct_runtime_ui/deprecated_where_block.rs:26:11 + | +26 | System: frame_system::{Pallet, Call, Storage, Config, Event}, + | ^^^^^^^^^^^^ the trait `Config` is not implemented for `Runtime` + +error[E0277]: the trait bound `Runtime: Config` is not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | 20 | / construct_runtime! { @@ -284,12 +340,10 @@ note: required because it appears within the type `RuntimeCall` ... | 27 | | } 28 | | } - | |_^ -note: required by a bound in `EncodeLike` - --> $CARGO/parity-scale-codec-3.6.12/src/encode_like.rs + | |_^ the trait `Config` is not implemented for `Runtime`, which is required by `Pallet: PalletInfoAccess` | - | pub trait EncodeLike: Sized + Encode {} - | ^^^^^ required by this bound in `EncodeLike` + = help: the trait `PalletInfoAccess` is implemented for `Pallet` + = note: required for `Pallet` to implement `PalletInfoAccess` = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `Runtime: Config` is not satisfied @@ -302,10 +356,13 @@ error[E0277]: the trait bound `Runtime: Config` is not satisfied ... | 27 | | } 28 | | } - | |_^ the trait `Config` is not implemented for `Runtime`, which is required by `RuntimeCall: Sized` + | |_^ the trait `Config` is not implemented for `Runtime`, which is required by `Pallet: Callable` | + = help: the trait `Callable` is implemented for `Pallet` = note: required for `Pallet` to implement `Callable` -note: required because it appears within the type `RuntimeCall` + = note: this error originates in the derive macro `Clone` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `Runtime: Config` is not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | 20 | / construct_runtime! { @@ -315,15 +372,13 @@ note: required because it appears within the type `RuntimeCall` ... | 27 | | } 28 | | } - | |_^ -note: required by a bound in `Decode` - --> $CARGO/parity-scale-codec-3.6.12/src/codec.rs + | |_^ the trait `Config` is not implemented for `Runtime`, which is required by `Pallet: Callable` | - | pub trait Decode: Sized { - | ^^^^^ required by this bound in `Decode` - = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + = help: the trait `Callable` is implemented for `Pallet` + = note: required for `Pallet` to implement `Callable` + = note: this error originates in the derive macro `PartialEq` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0277]: the trait bound `Runtime: Config` is not satisfied +error[E0369]: binary operation `==` cannot be applied to type `&frame_system::Call` --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | 20 | / construct_runtime! { @@ -333,10 +388,22 @@ error[E0277]: the trait bound `Runtime: Config` is not satisfied ... | 27 | | } 28 | | } - | |_^ the trait `Config` is not implemented for `Runtime`, which is required by `RuntimeCall: Sized` + | |_^ | - = note: required for `Pallet` to implement `Callable` -note: required because it appears within the type `RuntimeCall` +note: an implementation of `Config` might be missing for `Runtime` + --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 + | +20 | / construct_runtime! { +21 | | pub struct Runtime where + | |______________________^ must implement `Config` +note: the trait `Config` must be implemented + --> $WORKSPACE/substrate/frame/system/src/lib.rs + | + | pub trait Config: 'static + Eq + Clone { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: this error originates in the derive macro `PartialEq` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `frame_system::Call: Encode` is not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | 20 | / construct_runtime! { @@ -346,27 +413,31 @@ note: required because it appears within the type `RuntimeCall` ... | 27 | | } 28 | | } - | |_^ -note: required by a bound in `frame_support::sp_runtime::traits::Dispatchable::Config` - --> $WORKSPACE/substrate/primitives/runtime/src/traits.rs + | |_^ the trait `Encode` is not implemented for `frame_system::Call` | - | type Config; - | ^^^^^^^^^^^^ required by this bound in `Dispatchable::Config` - = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + = help: the trait `Encode` is implemented for `frame_system::Call` + = note: this error originates in the derive macro `self::sp_api_hidden_includes_construct_runtime::hidden_include::__private::codec::Encode` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `Runtime: Config` is not satisfied - --> tests/construct_runtime_ui/deprecated_where_block.rs:26:3 + --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | -26 | System: frame_system::{Pallet, Call, Storage, Config, Event}, - | ^^^^^^ the trait `Config` is not implemented for `Runtime` +20 | / construct_runtime! { +21 | | pub struct Runtime where +22 | | Block = Block, +23 | | NodeBlock = Block, +... | +27 | | } +28 | | } + | |_^ the trait `Config` is not implemented for `Runtime` | -note: required by a bound in `GenesisConfig` +note: required by a bound in `frame_system::Call` --> $WORKSPACE/substrate/frame/system/src/lib.rs | - | pub struct GenesisConfig { - | ^^^^^^ required by this bound in `GenesisConfig` + | #[pallet::call] + | ^^^^ required by this bound in `Call` + = note: this error originates in the derive macro `self::sp_api_hidden_includes_construct_runtime::hidden_include::__private::codec::Encode` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0277]: the trait bound `Runtime: Config` is not satisfied +error[E0277]: the trait bound `frame_system::Call: Decode` is not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | 20 | / construct_runtime! { @@ -376,10 +447,12 @@ error[E0277]: the trait bound `Runtime: Config` is not satisfied ... | 27 | | } 28 | | } - | |_^ the trait `Config` is not implemented for `Runtime`, which is required by `RuntimeCall: Sized` + | |_^ the trait `Decode` is not implemented for `frame_system::Call` | - = note: required for `Pallet` to implement `Callable` -note: required because it appears within the type `RuntimeCall` + = help: the trait `Decode` is implemented for `frame_system::Call` + = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `Runtime: Config` is not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | 20 | / construct_runtime! { @@ -389,15 +462,256 @@ note: required because it appears within the type `RuntimeCall` ... | 27 | | } 28 | | } - | |_^ -note: required by a bound in `frame_support::pallet_prelude::ValidateUnsigned::Call` - --> $WORKSPACE/substrate/primitives/runtime/src/traits.rs + | |_^ the trait `Config` is not implemented for `Runtime`, which is required by `Pallet: Callable` + | + = help: the trait `Callable` is implemented for `Pallet` + = note: required for `Pallet` to implement `Callable` + = note: this error originates in the derive macro `self::sp_api_hidden_includes_construct_runtime::hidden_include::__private::RuntimeDebug` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0599]: the method `get_dispatch_info` exists for reference `&Call`, but its trait bounds were not satisfied + --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 + | +20 | construct_runtime! { + | __^ + | | _| + | || +21 | || pub struct Runtime where + | ||______________________- doesn't satisfy `Runtime: Config` +22 | | Block = Block, +23 | | NodeBlock = Block, +... | +27 | | } +28 | | } + | |__^ method cannot be called on `&Call` due to unsatisfied trait bounds + | + ::: $WORKSPACE/substrate/frame/system/src/lib.rs + | + | #[pallet::call] + | ---- doesn't satisfy `frame_system::Call: GetDispatchInfo` + | + = note: the following trait bounds were not satisfied: + `Runtime: Config` + which is required by `frame_system::Call: GetDispatchInfo` +note: the trait `Config` must be implemented + --> $WORKSPACE/substrate/frame/system/src/lib.rs + | + | pub trait Config: 'static + Eq + Clone { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0599]: the method `is_feeless` exists for reference `&Call`, but its trait bounds were not satisfied + --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 + | +20 | construct_runtime! { + | __^ + | | _| + | || +21 | || pub struct Runtime where + | ||______________________- doesn't satisfy `Runtime: Config` +22 | | Block = Block, +23 | | NodeBlock = Block, +... | +27 | | } +28 | | } + | |__^ method cannot be called on `&Call` due to unsatisfied trait bounds + | + ::: $WORKSPACE/substrate/frame/system/src/lib.rs + | + | #[pallet::call] + | ---- doesn't satisfy `frame_system::Call: CheckIfFeeless` + | + = note: the following trait bounds were not satisfied: + `Runtime: Config` + which is required by `frame_system::Call: CheckIfFeeless` +note: the trait `Config` must be implemented + --> $WORKSPACE/substrate/frame/system/src/lib.rs | - | type Call; - | ^^^^^^^^^^ required by this bound in `ValidateUnsigned::Call` + | pub trait Config: 'static + Eq + Clone { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0277]: the trait bound `Runtime: Config` is not satisfied in `RuntimeEvent` +error[E0599]: the method `get_call_name` exists for reference `&Call`, but its trait bounds were not satisfied + --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 + | +20 | construct_runtime! { + | __^ + | | _| + | || +21 | || pub struct Runtime where + | ||______________________- doesn't satisfy `Runtime: Config` +22 | | Block = Block, +23 | | NodeBlock = Block, +... | +27 | | } +28 | | } + | |__^ method cannot be called on `&Call` due to unsatisfied trait bounds + | + ::: $WORKSPACE/substrate/frame/system/src/lib.rs + | + | #[pallet::call] + | ---- doesn't satisfy `frame_system::Call: GetCallName` + | + = note: the following trait bounds were not satisfied: + `Runtime: Config` + which is required by `frame_system::Call: GetCallName` +note: the trait `Config` must be implemented + --> $WORKSPACE/substrate/frame/system/src/lib.rs + | + | pub trait Config: 'static + Eq + Clone { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0599]: the function or associated item `storage_metadata` exists for struct `Pallet`, but its trait bounds were not satisfied + --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 + | +20 | construct_runtime! { + | __^ + | | _| + | || +21 | || pub struct Runtime where + | ||______________________- doesn't satisfy `Runtime: Config` +22 | | Block = Block, +23 | | NodeBlock = Block, +... | +27 | | } +28 | | } + | |__^ function or associated item cannot be called on `Pallet` due to unsatisfied trait bounds + | + = note: the following trait bounds were not satisfied: + `Runtime: Config` +note: the trait `Config` must be implemented + --> $WORKSPACE/substrate/frame/system/src/lib.rs + | + | pub trait Config: 'static + Eq + Clone { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0599]: the function or associated item `call_functions` exists for struct `Pallet`, but its trait bounds were not satisfied + --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 + | +20 | construct_runtime! { + | __^ + | | _| + | || +21 | || pub struct Runtime where + | ||______________________- doesn't satisfy `Runtime: Config` +22 | | Block = Block, +23 | | NodeBlock = Block, +... | +27 | | } +28 | | } + | |__^ function or associated item cannot be called on `Pallet` due to unsatisfied trait bounds + | + = note: the following trait bounds were not satisfied: + `Runtime: Config` +note: the trait `Config` must be implemented + --> $WORKSPACE/substrate/frame/system/src/lib.rs + | + | pub trait Config: 'static + Eq + Clone { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0599]: the variant or associated item `event_metadata` exists for enum `Event`, but its trait bounds were not satisfied + --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 + | +20 | construct_runtime! { + | __^ + | | _| + | || +21 | || pub struct Runtime where + | ||______________________- doesn't satisfy `Runtime: Config` +22 | | Block = Block, +23 | | NodeBlock = Block, +... | +27 | | } +28 | | } + | |__^ variant or associated item cannot be called on `Event` due to unsatisfied trait bounds + | + = note: the following trait bounds were not satisfied: + `Runtime: Config` +note: the trait `Config` must be implemented + --> $WORKSPACE/substrate/frame/system/src/lib.rs + | + | pub trait Config: 'static + Eq + Clone { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0599]: the function or associated item `pallet_constants_metadata` exists for struct `Pallet`, but its trait bounds were not satisfied + --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 + | +20 | construct_runtime! { + | __^ + | | _| + | || +21 | || pub struct Runtime where + | ||______________________- doesn't satisfy `Runtime: Config` +22 | | Block = Block, +23 | | NodeBlock = Block, +... | +27 | | } +28 | | } + | |__^ function or associated item cannot be called on `Pallet` due to unsatisfied trait bounds + | + = note: the following trait bounds were not satisfied: + `Runtime: Config` +note: the trait `Config` must be implemented + --> $WORKSPACE/substrate/frame/system/src/lib.rs + | + | pub trait Config: 'static + Eq + Clone { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0599]: the function or associated item `error_metadata` exists for struct `Pallet`, but its trait bounds were not satisfied + --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 + | +20 | construct_runtime! { + | __^ + | | _| + | || +21 | || pub struct Runtime where + | ||______________________- doesn't satisfy `Runtime: Config` +22 | | Block = Block, +23 | | NodeBlock = Block, +... | +27 | | } +28 | | } + | |__^ function or associated item cannot be called on `Pallet` due to unsatisfied trait bounds + | + = note: the following trait bounds were not satisfied: + `Runtime: Config` +note: the trait `Config` must be implemented + --> $WORKSPACE/substrate/frame/system/src/lib.rs + | + | pub trait Config: 'static + Eq + Clone { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0599]: the function or associated item `pallet_documentation_metadata` exists for struct `Pallet`, but its trait bounds were not satisfied + --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 + | +20 | construct_runtime! { + | __^ + | | _| + | || +21 | || pub struct Runtime where + | ||______________________- doesn't satisfy `Runtime: Config` +22 | | Block = Block, +23 | | NodeBlock = Block, +... | +27 | | } +28 | | } + | |__^ function or associated item cannot be called on `Pallet` due to unsatisfied trait bounds + | + = note: the following trait bounds were not satisfied: + `Runtime: Config` +note: the trait `Config` must be implemented + --> $WORKSPACE/substrate/frame/system/src/lib.rs + | + | pub trait Config: 'static + Eq + Clone { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `Runtime: Config` is not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | 20 | / construct_runtime! { @@ -407,9 +721,21 @@ error[E0277]: the trait bound `Runtime: Config` is not satisfied in `RuntimeEven ... | 27 | | } 28 | | } - | |_^ within `RuntimeEvent`, the trait `Config` is not implemented for `Runtime`, which is required by `RuntimeEvent: Sized` - | -note: required because it appears within the type `RuntimeEvent` + | |_^ the trait `Config` is not implemented for `Runtime`, which is required by `GenesisConfig: Serialize` + | + = help: the trait `Serialize` is implemented for `GenesisConfig` + = note: required for `GenesisConfig` to implement `Serialize` +note: required by a bound in `frame_support::sp_runtime::serde::ser::SerializeStruct::serialize_field` + --> $CARGO/serde-1.0.210/src/ser/mod.rs + | + | fn serialize_field(&mut self, key: &'static str, value: &T) -> Result<(), Self::Error> + | --------------- required by a bound in this associated function + | where + | T: ?Sized + Serialize; + | ^^^^^^^^^ required by this bound in `SerializeStruct::serialize_field` + = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `Runtime: Config` is not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | 20 | / construct_runtime! { @@ -419,15 +745,16 @@ note: required because it appears within the type `RuntimeEvent` ... | 27 | | } 28 | | } - | |_^ -note: required by a bound in `Result` - --> $RUST/core/src/result.rs + | |_^ the trait `Config` is not implemented for `Runtime` | - | pub enum Result { - | ^ required by this bound in `Result` - = note: this error originates in the derive macro `self::sp_api_hidden_includes_construct_runtime::hidden_include::__private::codec::Decode` which comes from the expansion of the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) +note: required by a bound in `GenesisConfig` + --> $WORKSPACE/substrate/frame/system/src/lib.rs + | + | pub struct GenesisConfig { + | ^^^^^^ required by this bound in `GenesisConfig` + = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0277]: the trait bound `Runtime: Config` is not satisfied in `RuntimeEvent` +error[E0277]: the trait bound `Runtime: Config` is not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | 20 | / construct_runtime! { @@ -437,9 +764,16 @@ error[E0277]: the trait bound `Runtime: Config` is not satisfied in `RuntimeEven ... | 27 | | } 28 | | } - | |_^ within `RuntimeEvent`, the trait `Config` is not implemented for `Runtime`, which is required by `RuntimeEvent: Sized` + | |_^ the trait `Config` is not implemented for `Runtime` | -note: required because it appears within the type `RuntimeEvent` +note: required by a bound in `GenesisConfig` + --> $WORKSPACE/substrate/frame/system/src/lib.rs + | + | pub struct GenesisConfig { + | ^^^^^^ required by this bound in `GenesisConfig` + = note: this error originates in the derive macro `self::sp_api_hidden_includes_construct_runtime::hidden_include::__private::serde::Deserialize` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `Runtime: Config` is not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | 20 | / construct_runtime! { @@ -449,13 +783,11 @@ note: required because it appears within the type `RuntimeEvent` ... | 27 | | } 28 | | } - | |_^ -note: required by a bound in `TryInto` - --> $RUST/core/src/convert/mod.rs + | |_^ the trait `Config` is not implemented for `Runtime`, which is required by `GenesisConfig: std::default::Default` | - | pub trait TryInto: Sized { - | ^^^^^ required by this bound in `TryInto` - = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + = help: the trait `std::default::Default` is implemented for `GenesisConfig` + = note: required for `GenesisConfig` to implement `std::default::Default` + = note: this error originates in the derive macro `Default` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `Runtime: Config` is not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 @@ -467,10 +799,24 @@ error[E0277]: the trait bound `Runtime: Config` is not satisfied ... | 27 | | } 28 | | } - | |_^ the trait `Config` is not implemented for `Runtime`, which is required by `RuntimeCall: Sized` - | - = note: required for `Pallet` to implement `Callable` -note: required because it appears within the type `RuntimeCall` + | |_^ the trait `Config` is not implemented for `Runtime`, which is required by `(Pallet,): OnGenesis` + | + = help: the following other types implement trait `OnGenesis`: + () + (TupleElement0, TupleElement1) + (TupleElement0, TupleElement1, TupleElement2) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6, TupleElement7) + and $N others + = note: required for `Pallet` to implement `OnGenesis` + = note: 1 redundant requirement hidden + = note: required for `(Pallet,)` to implement `OnGenesis` + = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0282]: type annotations needed --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | 20 | / construct_runtime! { @@ -480,10 +826,8 @@ note: required because it appears within the type `RuntimeCall` ... | 27 | | } 28 | | } - | |_^ -note: required by a bound in `Result` - --> $RUST/core/src/result.rs + | |_^ cannot infer type | - | pub enum Result { - | ^ required by this bound in `Result` - = note: this error originates in the derive macro `self::sp_api_hidden_includes_construct_runtime::hidden_include::__private::codec::Decode` which comes from the expansion of the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: internal compiler error: compiler/rustc_middle/src/ty/normalize_erasing_regions.rs:168:90: Failed to normalize std::rc::Rc::RuntimeCall,)>), bound_vars: [Region(BrAnon)] }, Binder { value: Projection(Output = bool), bound_vars: [Region(BrAnon)] }] + '{erased}, std::alloc::Global>, std::alloc::Global>, maybe try to call `try_normalize_erasing_regions` instead diff --git a/substrate/frame/support/test/tests/construct_runtime_ui/undefined_event_part.stderr b/substrate/frame/support/test/tests/construct_runtime_ui/undefined_event_part.stderr index 0f7afb2b990..c50cba71d4e 100644 --- a/substrate/frame/support/test/tests/construct_runtime_ui/undefined_event_part.stderr +++ b/substrate/frame/support/test/tests/construct_runtime_ui/undefined_event_part.stderr @@ -28,7 +28,7 @@ error[E0412]: cannot find type `Event` in module `pallet` | |_^ not found in `pallet` | = note: this error originates in the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) -help: consider importing one of these items +help: consider importing one of these enums | 18 + use frame_support_test::Event; | @@ -48,7 +48,7 @@ error[E0433]: failed to resolve: could not find `Event` in `pallet` | |_^ could not find `Event` in `pallet` | = note: this error originates in the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) -help: consider importing one of these items +help: consider importing one of these enums | 18 + use frame_support_test::Event; | diff --git a/substrate/frame/support/test/tests/construct_runtime_ui/undefined_genesis_config_part.stderr b/substrate/frame/support/test/tests/construct_runtime_ui/undefined_genesis_config_part.stderr index 10093b26f5a..2aa794edc3c 100644 --- a/substrate/frame/support/test/tests/construct_runtime_ui/undefined_genesis_config_part.stderr +++ b/substrate/frame/support/test/tests/construct_runtime_ui/undefined_genesis_config_part.stderr @@ -28,7 +28,7 @@ error[E0412]: cannot find type `GenesisConfig` in module `pallet` | |_^ not found in `pallet` | = note: this error originates in the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) -help: consider importing one of these items +help: consider importing one of these structs | 18 + use frame_system::GenesisConfig; | diff --git a/substrate/frame/support/test/tests/construct_runtime_ui/undefined_inherent_part.stderr b/substrate/frame/support/test/tests/construct_runtime_ui/undefined_inherent_part.stderr index 30005c07cb6..d8dc7bd45bc 100644 --- a/substrate/frame/support/test/tests/construct_runtime_ui/undefined_inherent_part.stderr +++ b/substrate/frame/support/test/tests/construct_runtime_ui/undefined_inherent_part.stderr @@ -54,8 +54,8 @@ error[E0599]: no function or associated item named `is_inherent` found for struc | = help: items from traits can only be used if the trait is implemented and in scope = note: the following traits define an item `is_inherent`, perhaps you need to implement one of them: - candidate #1: `ProvideInherent` - candidate #2: `IsInherent` + candidate #1: `IsInherent` + candidate #2: `ProvideInherent` = note: this error originates in the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0599]: no function or associated item named `check_inherent` found for struct `pallet::Pallet` in the current scope diff --git a/substrate/frame/support/test/tests/construct_runtime_ui/undefined_origin_part.stderr b/substrate/frame/support/test/tests/construct_runtime_ui/undefined_origin_part.stderr index d0f4b44ab0d..58c42311b87 100644 --- a/substrate/frame/support/test/tests/construct_runtime_ui/undefined_origin_part.stderr +++ b/substrate/frame/support/test/tests/construct_runtime_ui/undefined_origin_part.stderr @@ -28,7 +28,7 @@ error[E0412]: cannot find type `Origin` in module `pallet` | |_^ not found in `pallet` | = note: this error originates in the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) -help: consider importing one of these items +help: consider importing one of these type aliases | 18 + use frame_support_test::Origin; | diff --git a/substrate/frame/support/test/tests/derive_impl.rs b/substrate/frame/support/test/tests/derive_impl.rs index 675e85f4bfc..3514593c856 100644 --- a/substrate/frame/support/test/tests/derive_impl.rs +++ b/substrate/frame/support/test/tests/derive_impl.rs @@ -25,15 +25,9 @@ struct SomeRectangle {} #[frame_support::register_default_impl(SomeRectangle)] impl Shape for SomeRectangle { - #[cfg(not(feature = "feature-frame-testing"))] fn area(&self) -> u32 { 10 } - - #[cfg(feature = "feature-frame-testing")] - fn area(&self) -> u32 { - 0 - } } struct SomeSquare {} @@ -44,9 +38,5 @@ impl Shape for SomeSquare {} #[test] fn test_feature_parsing() { let square = SomeSquare {}; - #[cfg(not(feature = "feature-frame-testing"))] assert_eq!(square.area(), 10); - - #[cfg(feature = "feature-frame-testing")] - assert_eq!(square.area(), 0); } diff --git a/substrate/frame/support/test/tests/derive_no_bound.rs b/substrate/frame/support/test/tests/derive_no_bound.rs index b1914707805..6fc4ea12c51 100644 --- a/substrate/frame/support/test/tests/derive_no_bound.rs +++ b/substrate/frame/support/test/tests/derive_no_bound.rs @@ -159,6 +159,7 @@ fn test_struct_unnamed() { PartialOrdNoBound, OrdNoBound, )] +#[allow(dead_code)] struct StructNoGenerics { field1: u32, field2: u64, diff --git a/substrate/frame/support/test/tests/derive_no_bound_ui/ord.stderr b/substrate/frame/support/test/tests/derive_no_bound_ui/ord.stderr index db8a5079607..8bf82bff780 100644 --- a/substrate/frame/support/test/tests/derive_no_bound_ui/ord.stderr +++ b/substrate/frame/support/test/tests/derive_no_bound_ui/ord.stderr @@ -23,3 +23,13 @@ note: required by a bound in `std::cmp::Eq` | | pub trait Eq: PartialEq { | ^^^^^^^^^^^^^^^ required by this bound in `Eq` + +error[E0599]: `::C` is not an iterator + --> tests/derive_no_bound_ui/ord.rs:24:2 + | +24 | c: T::C, + | ^ `::C` is not an iterator + | + = note: the following trait bounds were not satisfied: + `::C: Iterator` + which is required by `&mut ::C: Iterator` diff --git a/substrate/frame/support/test/tests/pallet.rs b/substrate/frame/support/test/tests/pallet.rs index 7f1ce0556ea..3d6aa1d8374 100644 --- a/substrate/frame/support/test/tests/pallet.rs +++ b/substrate/frame/support/test/tests/pallet.rs @@ -2431,9 +2431,10 @@ fn post_runtime_upgrade_detects_storage_version_issues() { // any storage version "enabled". assert!( ExecutiveWithUpgradePallet4::try_runtime_upgrade(UpgradeCheckSelect::PreAndPost) - .unwrap_err() == "On chain storage version set, while the pallet \ + .unwrap_err() == + "On chain storage version set, while the pallet \ doesn't have the `#[pallet::storage_version(VERSION)]` attribute." - .into() + .into() ); }); } diff --git a/substrate/frame/support/test/tests/pallet_ui/call_argument_invalid_bound.stderr b/substrate/frame/support/test/tests/pallet_ui/call_argument_invalid_bound.stderr index 2a4ceecd8fa..1f91f774023 100644 --- a/substrate/frame/support/test/tests/pallet_ui/call_argument_invalid_bound.stderr +++ b/substrate/frame/support/test/tests/pallet_ui/call_argument_invalid_bound.stderr @@ -33,3 +33,12 @@ error[E0369]: binary operation `==` cannot be applied to type `&, _bar: T::Bar) -> DispatchResultWithPostInfo { | ^^^^ + +error: unused variable: `origin` + --> tests/pallet_ui/call_argument_invalid_bound.rs:38:14 + | +38 | pub fn foo(origin: OriginFor, _bar: T::Bar) -> DispatchResultWithPostInfo { + | ^^^^^^ help: if this is intentional, prefix it with an underscore: `_origin` + | + = note: `-D unused-variables` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(unused_variables)]` diff --git a/substrate/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_2.stderr b/substrate/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_2.stderr index fc993e9ff68..4657c0a0c60 100644 --- a/substrate/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_2.stderr +++ b/substrate/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_2.stderr @@ -34,7 +34,7 @@ error[E0369]: binary operation `==` cannot be applied to type `&, _bar: T::Bar) -> DispatchResultWithPostInfo { | ^^^^ -error[E0277]: the trait bound `::Bar: WrapperTypeEncode` is not satisfied +error[E0277]: the trait bound `::Bar: Encode` is not satisfied --> tests/pallet_ui/call_argument_invalid_bound_2.rs:38:36 | 18 | #[frame_support::pallet] @@ -45,10 +45,19 @@ error[E0277]: the trait bound `::Bar: WrapperTypeEncode` is | = note: required for `::Bar` to implement `Encode` -error[E0277]: the trait bound `::Bar: WrapperTypeDecode` is not satisfied +error[E0277]: the trait bound `::Bar: Decode` is not satisfied --> tests/pallet_ui/call_argument_invalid_bound_2.rs:38:42 | 38 | pub fn foo(origin: OriginFor, _bar: T::Bar) -> DispatchResultWithPostInfo { | ^^^^^^ the trait `WrapperTypeDecode` is not implemented for `::Bar`, which is required by `::Bar: Decode` | = note: required for `::Bar` to implement `Decode` + +error: unused variable: `origin` + --> tests/pallet_ui/call_argument_invalid_bound_2.rs:38:14 + | +38 | pub fn foo(origin: OriginFor, _bar: T::Bar) -> DispatchResultWithPostInfo { + | ^^^^^^ help: if this is intentional, prefix it with an underscore: `_origin` + | + = note: `-D unused-variables` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(unused_variables)]` diff --git a/substrate/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_3.stderr b/substrate/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_3.stderr index d6486a49079..f829baeb4c1 100644 --- a/substrate/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_3.stderr +++ b/substrate/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_3.stderr @@ -27,3 +27,12 @@ help: consider annotating `Bar` with `#[derive(Debug)]` 34 + #[derive(Debug)] 35 | struct Bar; | + +error: unused variable: `origin` + --> tests/pallet_ui/call_argument_invalid_bound_3.rs:40:14 + | +40 | pub fn foo(origin: OriginFor, _bar: Bar) -> DispatchResultWithPostInfo { + | ^^^^^^ help: if this is intentional, prefix it with an underscore: `_origin` + | + = note: `-D unused-variables` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(unused_variables)]` diff --git a/substrate/frame/support/test/tests/pallet_ui/compare_unset_storage_version.stderr b/substrate/frame/support/test/tests/pallet_ui/compare_unset_storage_version.stderr index 3256e69528a..8049c07648c 100644 --- a/substrate/frame/support/test/tests/pallet_ui/compare_unset_storage_version.stderr +++ b/substrate/frame/support/test/tests/pallet_ui/compare_unset_storage_version.stderr @@ -5,3 +5,9 @@ error[E0369]: binary operation `!=` cannot be applied to type `NoStorageVersionS | ------------------------------- ^^ -------------------------------- StorageVersion | | | NoStorageVersionSet + | +note: the foreign item type `NoStorageVersionSet` doesn't implement `PartialEq` + --> $WORKSPACE/substrate/frame/support/src/traits/metadata.rs + | + | pub struct NoStorageVersionSet; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not implement `PartialEq` diff --git a/substrate/frame/support/test/tests/pallet_ui/dev_mode_without_arg_max_encoded_len.stderr b/substrate/frame/support/test/tests/pallet_ui/dev_mode_without_arg_max_encoded_len.stderr index 629fefebbe2..2fcc3328214 100644 --- a/substrate/frame/support/test/tests/pallet_ui/dev_mode_without_arg_max_encoded_len.stderr +++ b/substrate/frame/support/test/tests/pallet_ui/dev_mode_without_arg_max_encoded_len.stderr @@ -38,13 +38,13 @@ error[E0277]: the trait bound `Vec: MaxEncodedLen` is not satisfied | |__________________^ the trait `MaxEncodedLen` is not implemented for `Vec`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageMyStorage, Vec>: StorageInfoTrait` | = help: the following other types implement trait `MaxEncodedLen`: - bool - i8 - i16 - i32 - i64 - i128 - u8 - u16 + () + (TupleElement0, TupleElement1) + (TupleElement0, TupleElement1, TupleElement2) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6, TupleElement7) and $N others = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageMyStorage, Vec>` to implement `StorageInfoTrait` diff --git a/substrate/frame/support/test/tests/pallet_ui/error_does_not_derive_pallet_error.stderr b/substrate/frame/support/test/tests/pallet_ui/error_does_not_derive_pallet_error.stderr index 44d8d3fcadb..92fb5b9cb38 100644 --- a/substrate/frame/support/test/tests/pallet_ui/error_does_not_derive_pallet_error.stderr +++ b/substrate/frame/support/test/tests/pallet_ui/error_does_not_derive_pallet_error.stderr @@ -5,12 +5,12 @@ error[E0277]: the trait bound `MyError: PalletError` is not satisfied | ^^^^^^^^^^^^^^ the trait `PalletError` is not implemented for `MyError` | = help: the following other types implement trait `PalletError`: - bool - i8 - i16 - i32 - i64 - i128 - u8 - u16 + () + (TupleElement0, TupleElement1) + (TupleElement0, TupleElement1, TupleElement2) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6, TupleElement7) and $N others diff --git a/substrate/frame/support/test/tests/pallet_ui/hooks_invalid_item.stderr b/substrate/frame/support/test/tests/pallet_ui/hooks_invalid_item.stderr index b7327943ee2..c04499dbbd1 100644 --- a/substrate/frame/support/test/tests/pallet_ui/hooks_invalid_item.stderr +++ b/substrate/frame/support/test/tests/pallet_ui/hooks_invalid_item.stderr @@ -13,3 +13,9 @@ help: add missing generic argument | 29 | impl Hooks for Pallet {} | +++++++++++++ + +error[E0277]: the trait bound `pallet::Pallet: Hooks<<<::Block as frame_support::sp_runtime::traits::Block>::Header as frame_support::sp_runtime::traits::Header>::Number>` is not satisfied + --> tests/pallet_ui/hooks_invalid_item.rs:28:12 + | +28 | #[pallet::hooks] + | ^^^^^ the trait `Hooks<<<::Block as frame_support::sp_runtime::traits::Block>::Header as frame_support::sp_runtime::traits::Header>::Number>` is not implemented for `pallet::Pallet` diff --git a/substrate/frame/support/test/tests/pallet_ui/inherent_check_inner_span.stderr b/substrate/frame/support/test/tests/pallet_ui/inherent_check_inner_span.stderr index 5ea3be470a0..516bddd2c61 100644 --- a/substrate/frame/support/test/tests/pallet_ui/inherent_check_inner_span.stderr +++ b/substrate/frame/support/test/tests/pallet_ui/inherent_check_inner_span.stderr @@ -6,6 +6,6 @@ error[E0046]: not all trait items implemented, missing: `Call`, `Error`, `INHERE | = help: implement the missing item: `type Call = /* Type */;` = help: implement the missing item: `type Error = /* Type */;` - = help: implement the missing item: `const INHERENT_IDENTIFIER: [u8; 8] = value;` + = help: implement the missing item: `const INHERENT_IDENTIFIER: [u8; 8] = [42; 8];` = help: implement the missing item: `fn create_inherent(_: &InherentData) -> std::option::Option<::Call> { todo!() }` = help: implement the missing item: `fn is_inherent(_: &::Call) -> bool { todo!() }` diff --git a/substrate/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr b/substrate/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr index c8c41e80501..fa6b7284d88 100644 --- a/substrate/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr +++ b/substrate/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr @@ -12,10 +12,10 @@ error[E0277]: the trait bound `Bar: WrapperTypeDecode` is not satisfied | |____________^ the trait `WrapperTypeDecode` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: PartialStorageInfoTrait` | = help: the following other types implement trait `WrapperTypeDecode`: + Arc Box - frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes Rc - Arc + frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes = note: required for `Bar` to implement `Decode` = note: required for `Bar` to implement `FullCodec` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `PartialStorageInfoTrait` @@ -34,14 +34,14 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied | |____________^ the trait `EncodeLike` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: PartialStorageInfoTrait` | = help: the following other types implement trait `EncodeLike`: - - - - - - - - + `&&T` implements `EncodeLike` + `&T` implements `EncodeLike` + `&T` implements `EncodeLike` + `&[(K, V)]` implements `EncodeLike>` + `&[(T,)]` implements `EncodeLike>` + `&[(T,)]` implements `EncodeLike>` + `&[(T,)]` implements `EncodeLike>` + `&[T]` implements `EncodeLike>` and $N others = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` @@ -61,14 +61,14 @@ error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied | |____________^ the trait `WrapperTypeEncode` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: PartialStorageInfoTrait` | = help: the following other types implement trait `WrapperTypeEncode`: + &T + &mut T + Arc Box - bytes::bytes::Bytes Cow<'a, T> - parity_scale_codec::Ref<'a, T, U> - frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes Rc - Arc Vec + bytes::bytes::Bytes and $N others = note: required for `Bar` to implement `Encode` = note: required for `Bar` to implement `FullEncode` @@ -84,14 +84,14 @@ error[E0277]: the trait bound `Bar: TypeInfo` is not satisfied | |____________^ the trait `TypeInfo` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: StorageEntryMetadataBuilder` | = help: the following other types implement trait `TypeInfo`: - bool - char - i8 - i16 - i32 - i64 - i128 - u8 + &T + &mut T + () + (A, B) + (A, B, C) + (A, B, C, D) + (A, B, C, D, E) + (A, B, C, D, E, F) and $N others = note: required for `Bar` to implement `StaticTypeInfo` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` @@ -105,10 +105,10 @@ error[E0277]: the trait bound `Bar: WrapperTypeDecode` is not satisfied | |____________^ the trait `WrapperTypeDecode` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: StorageEntryMetadataBuilder` | = help: the following other types implement trait `WrapperTypeDecode`: + Arc Box - frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes Rc - Arc + frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes = note: required for `Bar` to implement `Decode` = note: required for `Bar` to implement `FullCodec` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` @@ -122,14 +122,14 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied | |____________^ the trait `EncodeLike` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: StorageEntryMetadataBuilder` | = help: the following other types implement trait `EncodeLike`: - - - - - - - - + `&&T` implements `EncodeLike` + `&T` implements `EncodeLike` + `&T` implements `EncodeLike` + `&[(K, V)]` implements `EncodeLike>` + `&[(T,)]` implements `EncodeLike>` + `&[(T,)]` implements `EncodeLike>` + `&[(T,)]` implements `EncodeLike>` + `&[T]` implements `EncodeLike>` and $N others = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` @@ -144,14 +144,14 @@ error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied | |____________^ the trait `WrapperTypeEncode` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: StorageEntryMetadataBuilder` | = help: the following other types implement trait `WrapperTypeEncode`: + &T + &mut T + Arc Box - bytes::bytes::Bytes Cow<'a, T> - parity_scale_codec::Ref<'a, T, U> - frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes Rc - Arc Vec + bytes::bytes::Bytes and $N others = note: required for `Bar` to implement `Encode` = note: required for `Bar` to implement `FullEncode` @@ -167,10 +167,10 @@ error[E0277]: the trait bound `Bar: WrapperTypeDecode` is not satisfied | |____________^ the trait `WrapperTypeDecode` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: TryDecodeEntireStorage` | = help: the following other types implement trait `WrapperTypeDecode`: + Arc Box - frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes Rc - Arc + frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes = note: required for `Bar` to implement `Decode` = note: required for `Bar` to implement `FullCodec` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `TryDecodeEntireStorage` @@ -184,14 +184,14 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied | |____________^ the trait `EncodeLike` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: TryDecodeEntireStorage` | = help: the following other types implement trait `EncodeLike`: - - - - - - - - + `&&T` implements `EncodeLike` + `&T` implements `EncodeLike` + `&T` implements `EncodeLike` + `&[(K, V)]` implements `EncodeLike>` + `&[(T,)]` implements `EncodeLike>` + `&[(T,)]` implements `EncodeLike>` + `&[(T,)]` implements `EncodeLike>` + `&[T]` implements `EncodeLike>` and $N others = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` @@ -206,14 +206,14 @@ error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied | |____________^ the trait `WrapperTypeEncode` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: TryDecodeEntireStorage` | = help: the following other types implement trait `WrapperTypeEncode`: + &T + &mut T + Arc Box - bytes::bytes::Bytes Cow<'a, T> - parity_scale_codec::Ref<'a, T, U> - frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes Rc - Arc Vec + bytes::bytes::Bytes and $N others = note: required for `Bar` to implement `Encode` = note: required for `Bar` to implement `FullEncode` diff --git a/substrate/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr b/substrate/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr index 08b35eb8ed1..944b194b7bc 100644 --- a/substrate/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr +++ b/substrate/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr @@ -12,10 +12,10 @@ error[E0277]: the trait bound `Bar: WrapperTypeDecode` is not satisfied | |____________^ the trait `WrapperTypeDecode` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: PartialStorageInfoTrait` | = help: the following other types implement trait `WrapperTypeDecode`: + Arc Box - frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes Rc - Arc + frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes = note: required for `Bar` to implement `Decode` = note: required for `Bar` to implement `FullCodec` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `PartialStorageInfoTrait` @@ -34,14 +34,14 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied | |____________^ the trait `EncodeLike` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: PartialStorageInfoTrait` | = help: the following other types implement trait `EncodeLike`: - - - - - - - - + `&&T` implements `EncodeLike` + `&T` implements `EncodeLike` + `&T` implements `EncodeLike` + `&[(K, V)]` implements `EncodeLike>` + `&[(T,)]` implements `EncodeLike>` + `&[(T,)]` implements `EncodeLike>` + `&[(T,)]` implements `EncodeLike>` + `&[T]` implements `EncodeLike>` and $N others = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` @@ -61,14 +61,14 @@ error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied | |____________^ the trait `WrapperTypeEncode` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: PartialStorageInfoTrait` | = help: the following other types implement trait `WrapperTypeEncode`: + &T + &mut T + Arc Box - bytes::bytes::Bytes Cow<'a, T> - parity_scale_codec::Ref<'a, T, U> - frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes Rc - Arc Vec + bytes::bytes::Bytes and $N others = note: required for `Bar` to implement `Encode` = note: required for `Bar` to implement `FullEncode` @@ -84,14 +84,14 @@ error[E0277]: the trait bound `Bar: TypeInfo` is not satisfied | |____________^ the trait `TypeInfo` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: StorageEntryMetadataBuilder` | = help: the following other types implement trait `TypeInfo`: - bool - char - i8 - i16 - i32 - i64 - i128 - u8 + &T + &mut T + () + (A, B) + (A, B, C) + (A, B, C, D) + (A, B, C, D, E) + (A, B, C, D, E, F) and $N others = note: required for `Bar` to implement `StaticTypeInfo` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` @@ -105,10 +105,10 @@ error[E0277]: the trait bound `Bar: WrapperTypeDecode` is not satisfied | |____________^ the trait `WrapperTypeDecode` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: StorageEntryMetadataBuilder` | = help: the following other types implement trait `WrapperTypeDecode`: + Arc Box - frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes Rc - Arc + frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes = note: required for `Bar` to implement `Decode` = note: required for `Bar` to implement `FullCodec` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` @@ -122,14 +122,14 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied | |____________^ the trait `EncodeLike` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: StorageEntryMetadataBuilder` | = help: the following other types implement trait `EncodeLike`: - - - - - - - - + `&&T` implements `EncodeLike` + `&T` implements `EncodeLike` + `&T` implements `EncodeLike` + `&[(K, V)]` implements `EncodeLike>` + `&[(T,)]` implements `EncodeLike>` + `&[(T,)]` implements `EncodeLike>` + `&[(T,)]` implements `EncodeLike>` + `&[T]` implements `EncodeLike>` and $N others = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` @@ -144,14 +144,14 @@ error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied | |____________^ the trait `WrapperTypeEncode` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: StorageEntryMetadataBuilder` | = help: the following other types implement trait `WrapperTypeEncode`: + &T + &mut T + Arc Box - bytes::bytes::Bytes Cow<'a, T> - parity_scale_codec::Ref<'a, T, U> - frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes Rc - Arc Vec + bytes::bytes::Bytes and $N others = note: required for `Bar` to implement `Encode` = note: required for `Bar` to implement `FullEncode` @@ -167,10 +167,10 @@ error[E0277]: the trait bound `Bar: WrapperTypeDecode` is not satisfied | |____________^ the trait `WrapperTypeDecode` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: TryDecodeEntireStorage` | = help: the following other types implement trait `WrapperTypeDecode`: + Arc Box - frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes Rc - Arc + frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes = note: required for `Bar` to implement `Decode` = note: required for `Bar` to implement `FullCodec` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `TryDecodeEntireStorage` @@ -184,14 +184,14 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied | |____________^ the trait `EncodeLike` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: TryDecodeEntireStorage` | = help: the following other types implement trait `EncodeLike`: - - - - - - - - + `&&T` implements `EncodeLike` + `&T` implements `EncodeLike` + `&T` implements `EncodeLike` + `&[(K, V)]` implements `EncodeLike>` + `&[(T,)]` implements `EncodeLike>` + `&[(T,)]` implements `EncodeLike>` + `&[(T,)]` implements `EncodeLike>` + `&[T]` implements `EncodeLike>` and $N others = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` @@ -206,14 +206,14 @@ error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied | |____________^ the trait `WrapperTypeEncode` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: TryDecodeEntireStorage` | = help: the following other types implement trait `WrapperTypeEncode`: + &T + &mut T + Arc Box - bytes::bytes::Bytes Cow<'a, T> - parity_scale_codec::Ref<'a, T, U> - frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes Rc - Arc Vec + bytes::bytes::Bytes and $N others = note: required for `Bar` to implement `Encode` = note: required for `Bar` to implement `FullEncode` diff --git a/substrate/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr b/substrate/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr index 042a6f67fd3..95ec76e29c0 100644 --- a/substrate/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr +++ b/substrate/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr @@ -12,13 +12,13 @@ error[E0277]: the trait bound `Bar: MaxEncodedLen` is not satisfied | |____________^ the trait `MaxEncodedLen` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: StorageInfoTrait` | = help: the following other types implement trait `MaxEncodedLen`: - bool - i8 - i16 - i32 - i64 - i128 - u8 - u16 + () + (TupleElement0, TupleElement1) + (TupleElement0, TupleElement1, TupleElement2) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6, TupleElement7) and $N others = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageInfoTrait` diff --git a/substrate/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr b/substrate/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr index 9f57b85f3a8..8351dd92d59 100644 --- a/substrate/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr +++ b/substrate/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr @@ -12,14 +12,14 @@ error[E0277]: the trait bound `Bar: MaxEncodedLen` is not satisfied | |____________^ the trait `MaxEncodedLen` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageNMap<_GeneratedPrefixForStorageFoo, NMapKey, u32>: StorageInfoTrait` | = help: the following other types implement trait `MaxEncodedLen`: - bool - i8 - i16 - i32 - i64 - i128 - u8 - u16 + () + (TupleElement0, TupleElement1) + (TupleElement0, TupleElement1, TupleElement2) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6, TupleElement7) and $N others = note: required for `NMapKey` to implement `KeyGeneratorMaxEncodedLen` = note: required for `frame_support::pallet_prelude::StorageNMap<_GeneratedPrefixForStorageFoo, NMapKey, u32>` to implement `StorageInfoTrait` diff --git a/substrate/frame/support/test/tests/pallet_ui/type_value_error_in_block.stderr b/substrate/frame/support/test/tests/pallet_ui/type_value_error_in_block.stderr index 41dcd273d96..0b13dcff90c 100644 --- a/substrate/frame/support/test/tests/pallet_ui/type_value_error_in_block.stderr +++ b/substrate/frame/support/test/tests/pallet_ui/type_value_error_in_block.stderr @@ -3,3 +3,9 @@ error[E0599]: no function or associated item named `new` found for type `u32` in | 37 | u32::new() | ^^^ function or associated item not found in `u32` + | +help: there is a method `ne` with a similar name, but with different arguments + --> $RUST/core/src/cmp.rs + | + | fn ne(&self, other: &Rhs) -> bool { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/substrate/frame/system/src/tests.rs b/substrate/frame/system/src/tests.rs index 534ba1e863f..aa1094e3fe4 100644 --- a/substrate/frame/system/src/tests.rs +++ b/substrate/frame/system/src/tests.rs @@ -848,6 +848,7 @@ pub fn from_post_weight_info(ref_time: Option, pays_fee: Pays) -> PostDispa #[docify::export] #[test] fn last_runtime_upgrade_spec_version_usage() { + #[allow(dead_code)] struct Migration; impl OnRuntimeUpgrade for Migration { diff --git a/substrate/frame/transaction-payment/src/tests.rs b/substrate/frame/transaction-payment/src/tests.rs index 35d5322a6f3..bac89967d6a 100644 --- a/substrate/frame/transaction-payment/src/tests.rs +++ b/substrate/frame/transaction-payment/src/tests.rs @@ -273,8 +273,10 @@ fn signed_ext_length_fee_is_also_updated_per_congestion() { NextFeeMultiplier::::put(Multiplier::saturating_from_rational(3, 2)); let len = 10; - assert_ok!(ChargeTransactionPayment::::from(10) // tipped - .pre_dispatch(&1, CALL, &info_from_weight(Weight::from_parts(3, 0)), len)); + assert_ok!( + ChargeTransactionPayment::::from(10) // tipped + .pre_dispatch(&1, CALL, &info_from_weight(Weight::from_parts(3, 0)), len) + ); assert_eq!( Balances::free_balance(1), 100 // original diff --git a/substrate/frame/utility/src/lib.rs b/substrate/frame/utility/src/lib.rs index ed5544fe55c..a4f66298f3f 100644 --- a/substrate/frame/utility/src/lib.rs +++ b/substrate/frame/utility/src/lib.rs @@ -134,8 +134,8 @@ pub mod pallet { fn batched_calls_limit() -> u32 { let allocator_limit = sp_core::MAX_POSSIBLE_ALLOCATION; let call_size = ((core::mem::size_of::<::RuntimeCall>() as u32 + - CALL_ALIGN - 1) / CALL_ALIGN) * - CALL_ALIGN; + CALL_ALIGN - 1) / + CALL_ALIGN) * CALL_ALIGN; // The margin to take into account vec doubling capacity. let margin_factor = 3; diff --git a/substrate/frame/vesting/src/tests.rs b/substrate/frame/vesting/src/tests.rs index 004da0dfbfa..57cb59f27a4 100644 --- a/substrate/frame/vesting/src/tests.rs +++ b/substrate/frame/vesting/src/tests.rs @@ -182,7 +182,7 @@ fn unvested_balance_should_not_transfer() { ExtBuilder::default().existential_deposit(10).build().execute_with(|| { let user1_free_balance = Balances::free_balance(&1); assert_eq!(user1_free_balance, 100); // Account 1 has free balance - // Account 1 has only 5 units vested at block 1 (plus 50 unvested) + // Account 1 has only 5 units vested at block 1 (plus 50 unvested) assert_eq!(Vesting::vesting_balance(&1), Some(45)); // Account 1 cannot send more than vested amount... assert_noop!(Balances::transfer_allow_death(Some(1).into(), 2, 56), TokenError::Frozen); @@ -194,7 +194,7 @@ fn vested_balance_should_transfer() { ExtBuilder::default().existential_deposit(10).build().execute_with(|| { let user1_free_balance = Balances::free_balance(&1); assert_eq!(user1_free_balance, 100); // Account 1 has free balance - // Account 1 has only 5 units vested at block 1 (plus 50 unvested) + // Account 1 has only 5 units vested at block 1 (plus 50 unvested) assert_eq!(Vesting::vesting_balance(&1), Some(45)); assert_ok!(Vesting::vest(Some(1).into())); assert_ok!(Balances::transfer_allow_death(Some(1).into(), 2, 55)); @@ -232,7 +232,7 @@ fn vested_balance_should_transfer_using_vest_other() { ExtBuilder::default().existential_deposit(10).build().execute_with(|| { let user1_free_balance = Balances::free_balance(&1); assert_eq!(user1_free_balance, 100); // Account 1 has free balance - // Account 1 has only 5 units vested at block 1 (plus 50 unvested) + // Account 1 has only 5 units vested at block 1 (plus 50 unvested) assert_eq!(Vesting::vesting_balance(&1), Some(45)); assert_ok!(Vesting::vest_other(Some(2).into(), 1)); assert_ok!(Balances::transfer_allow_death(Some(1).into(), 2, 55)); @@ -286,7 +286,7 @@ fn extra_balance_should_transfer() { assert_eq!(Vesting::vesting_balance(&2), Some(200)); assert_ok!(Vesting::vest(Some(2).into())); assert_ok!(Balances::transfer_allow_death(Some(2).into(), 3, 100)); // Account 2 can send extra - // units gained + // units gained }); } @@ -296,7 +296,7 @@ fn liquid_funds_should_transfer_with_delayed_vesting() { let user12_free_balance = Balances::free_balance(&12); assert_eq!(user12_free_balance, 2560); // Account 12 has free balance - // Account 12 has liquid funds + // Account 12 has liquid funds assert_eq!(Vesting::vesting_balance(&12), Some(user12_free_balance - 256 * 5)); // Account 12 has delayed vesting diff --git a/substrate/primitives/api/test/Cargo.toml b/substrate/primitives/api/test/Cargo.toml index 121ce6b9993..1d21f23eb80 100644 --- a/substrate/primitives/api/test/Cargo.toml +++ b/substrate/primitives/api/test/Cargo.toml @@ -41,3 +41,4 @@ harness = false [features] "enable-staging-api" = [] +disable-ui-tests = [] diff --git a/substrate/primitives/api/test/tests/trybuild.rs b/substrate/primitives/api/test/tests/trybuild.rs index b0a334eb7a2..b13e5df9d6f 100644 --- a/substrate/primitives/api/test/tests/trybuild.rs +++ b/substrate/primitives/api/test/tests/trybuild.rs @@ -15,18 +15,20 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::env; - #[rustversion::attr(not(stable), ignore)] +#[cfg(not(feature = "disable-ui-tests"))] #[test] fn ui() { // Only run the ui tests when `RUN_UI_TESTS` is set. - if env::var("RUN_UI_TESTS").is_err() { + if std::env::var("RUN_UI_TESTS").is_err() { return } // As trybuild is using `cargo check`, we don't need the real WASM binaries. - env::set_var("SKIP_WASM_BUILD", "1"); + std::env::set_var("SKIP_WASM_BUILD", "1"); + + // Warnings are part of our UI. + std::env::set_var("RUSTFLAGS", "--deny warnings"); let t = trybuild::TestCases::new(); t.compile_fail("tests/ui/*.rs"); diff --git a/substrate/primitives/api/test/tests/ui/deprecation_info.stderr b/substrate/primitives/api/test/tests/ui/deprecation_info.stderr index 2466c3ea5d5..78c687e876d 100644 --- a/substrate/primitives/api/test/tests/ui/deprecation_info.stderr +++ b/substrate/primitives/api/test/tests/ui/deprecation_info.stderr @@ -12,6 +12,21 @@ error: Invalid deprecation attribute: missing `note` 20 | #[deprecated(unknown_kw = "test")] | ^ +error: malformed `deprecated` attribute input + --> tests/ui/deprecation_info.rs:24:3 + | +24 | #[deprecated = 5] + | ^^^^^^^^^^^^^^^^^ + | +help: the following are the possible correct uses + | +24 | #[deprecated = "reason"] + | +24 | #[deprecated(/*opt*/ since = "version", /*opt*/ note = "reason")] + | +24 | #[deprecated] + | + error[E0541]: unknown meta item 'unknown_kw' --> tests/ui/deprecation_info.rs:20:16 | diff --git a/substrate/primitives/api/test/tests/ui/impl_incorrect_method_signature.stderr b/substrate/primitives/api/test/tests/ui/impl_incorrect_method_signature.stderr index 535bbb178d5..d625020fe4d 100644 --- a/substrate/primitives/api/test/tests/ui/impl_incorrect_method_signature.stderr +++ b/substrate/primitives/api/test/tests/ui/impl_incorrect_method_signature.stderr @@ -22,10 +22,7 @@ error[E0053]: method `test` has an incompatible type for trait --> tests/ui/impl_incorrect_method_signature.rs:33:17 | 33 | fn test(data: String) {} - | ^^^^^^ - | | - | expected `u64`, found `std::string::String` - | help: change the parameter type to match the trait: `u64` + | ^^^^^^ expected `u64`, found `std::string::String` | note: type in trait --> tests/ui/impl_incorrect_method_signature.rs:27:17 @@ -34,6 +31,10 @@ note: type in trait | ^^^ = note: expected signature `fn(u64)` found signature `fn(std::string::String)` +help: change the parameter type to match the trait + | +33 | fn test(data: u64) {} + | ~~~ error[E0308]: mismatched types --> tests/ui/impl_incorrect_method_signature.rs:33:11 @@ -53,3 +54,12 @@ note: associated function defined here | 27 | fn test(data: u64); | ^^^^ + +error: unused variable: `data` + --> tests/ui/impl_incorrect_method_signature.rs:33:11 + | +33 | fn test(data: String) {} + | ^^^^ help: if this is intentional, prefix it with an underscore: `_data` + | + = note: `-D unused-variables` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(unused_variables)]` diff --git a/substrate/primitives/api/test/tests/ui/mock_only_self_reference.stderr b/substrate/primitives/api/test/tests/ui/mock_only_self_reference.stderr index 84575577187..764a0bafaa4 100644 --- a/substrate/primitives/api/test/tests/ui/mock_only_self_reference.stderr +++ b/substrate/primitives/api/test/tests/ui/mock_only_self_reference.stderr @@ -21,8 +21,7 @@ error[E0050]: method `test` has 2 parameters but the declaration in trait `Api:: 29 | / sp_api::mock_impl_runtime_apis! { 30 | | impl Api for MockApi { 31 | | fn test(self, data: u64) {} -32 | | -33 | | fn test2(&mut self, data: u64) {} +... | 34 | | } 35 | | } | |_^ expected 3 parameters, found 2 @@ -41,8 +40,7 @@ error[E0050]: method `test2` has 2 parameters but the declaration in trait `Api: 29 | / sp_api::mock_impl_runtime_apis! { 30 | | impl Api for MockApi { 31 | | fn test(self, data: u64) {} -32 | | -33 | | fn test2(&mut self, data: u64) {} +... | 34 | | } 35 | | } | |_^ expected 3 parameters, found 2 diff --git a/substrate/primitives/api/test/tests/ui/type_reference_in_impl_runtime_apis_call.stderr b/substrate/primitives/api/test/tests/ui/type_reference_in_impl_runtime_apis_call.stderr index f4e0f3b0afb..26be311c02f 100644 --- a/substrate/primitives/api/test/tests/ui/type_reference_in_impl_runtime_apis_call.stderr +++ b/substrate/primitives/api/test/tests/ui/type_reference_in_impl_runtime_apis_call.stderr @@ -22,10 +22,7 @@ error[E0053]: method `test` has an incompatible type for trait --> tests/ui/type_reference_in_impl_runtime_apis_call.rs:33:17 | 33 | fn test(data: &u64) { - | ^^^^ - | | - | expected `u64`, found `&u64` - | help: change the parameter type to match the trait: `u64` + | ^^^^ expected `u64`, found `&u64` | note: type in trait --> tests/ui/type_reference_in_impl_runtime_apis_call.rs:27:17 @@ -34,6 +31,10 @@ note: type in trait | ^^^ = note: expected signature `fn(_)` found signature `fn(&_)` +help: change the parameter type to match the trait + | +33 | fn test(data: u64) { + | ~~~ error[E0308]: mismatched types --> tests/ui/type_reference_in_impl_runtime_apis_call.rs:33:11 @@ -57,3 +58,12 @@ help: consider removing the borrow | 33 | fn test(data: &u64) { | + +error: unused variable: `data` + --> tests/ui/type_reference_in_impl_runtime_apis_call.rs:33:11 + | +33 | fn test(data: &u64) { + | ^^^^ help: if this is intentional, prefix it with an underscore: `_data` + | + = note: `-D unused-variables` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(unused_variables)]` diff --git a/substrate/primitives/runtime-interface/tests/ui.rs b/substrate/primitives/runtime-interface/tests/ui.rs index 821d0b73f26..408ddbc981e 100644 --- a/substrate/primitives/runtime-interface/tests/ui.rs +++ b/substrate/primitives/runtime-interface/tests/ui.rs @@ -15,18 +15,16 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::env; - #[rustversion::attr(not(stable), ignore)] #[test] fn ui() { // Only run the ui tests when `RUN_UI_TESTS` is set. - if env::var("RUN_UI_TESTS").is_err() { + if std::env::var("RUN_UI_TESTS").is_err() { return } // As trybuild is using `cargo check`, we don't need the real WASM binaries. - env::set_var("SKIP_WASM_BUILD", "1"); + std::env::set_var("SKIP_WASM_BUILD", "1"); let t = trybuild::TestCases::new(); t.compile_fail("tests/ui/*.rs"); diff --git a/substrate/primitives/runtime-interface/tests/ui/no_feature_gated_method.stderr b/substrate/primitives/runtime-interface/tests/ui/no_feature_gated_method.stderr index 10012ede793..1c1649d011e 100644 --- a/substrate/primitives/runtime-interface/tests/ui/no_feature_gated_method.stderr +++ b/substrate/primitives/runtime-interface/tests/ui/no_feature_gated_method.stderr @@ -9,9 +9,41 @@ note: found an item that was configured out | 25 | fn bar() {} | ^^^ - = note: the item is gated behind the `bar-feature` feature +note: the item is gated behind the `bar-feature` feature + --> tests/ui/no_feature_gated_method.rs:24:8 + | +24 | #[cfg(feature = "bar-feature")] + | ^^^^^^^^^^^^^^^^^^^^^^^ note: found an item that was configured out --> tests/ui/no_feature_gated_method.rs:25:5 | 25 | fn bar() {} | ^^^ +note: the item is gated here + --> tests/ui/no_feature_gated_method.rs:20:1 + | +20 | #[runtime_interface] + | ^^^^^^^^^^^^^^^^^^^^ + = note: this error originates in the attribute macro `runtime_interface` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: unexpected `cfg` condition value: `bar-feature` + --> tests/ui/no_feature_gated_method.rs:24:8 + | +24 | #[cfg(feature = "bar-feature")] + | ^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: expected values for `feature` are: `default`, `disable_target_static_assertions`, and `std` + = help: consider adding `bar-feature` as a feature in `Cargo.toml` + = note: see for more information about checking conditional configuration + = note: `-D unexpected-cfgs` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(unexpected_cfgs)]` + +error: unexpected `cfg` condition value: `bar-feature` + --> tests/ui/no_feature_gated_method.rs:27:12 + | +27 | #[cfg(not(feature = "bar-feature"))] + | ^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: expected values for `feature` are: `default`, `disable_target_static_assertions`, and `std` + = help: consider adding `bar-feature` as a feature in `Cargo.toml` + = note: see for more information about checking conditional configuration diff --git a/substrate/primitives/runtime/src/traits.rs b/substrate/primitives/runtime/src/traits.rs index 25ef15eaf56..fc63bc76dec 100644 --- a/substrate/primitives/runtime/src/traits.rs +++ b/substrate/primitives/runtime/src/traits.rs @@ -2342,8 +2342,6 @@ impl BlockNumberProvider for () { mod tests { use super::*; use crate::codec::{Decode, Encode, Input}; - #[cfg(feature = "bls-experimental")] - use sp_core::{bls377, bls381}; use sp_core::{ crypto::{Pair, UncheckedFrom}, ecdsa, ed25519, sr25519, @@ -2486,14 +2484,4 @@ mod tests { fn ecdsa_verify_works() { signature_verify_test!(ecdsa); } - - #[cfg(feature = "bls-experimental")] - fn bls377_verify_works() { - signature_verify_test!(bls377) - } - - #[cfg(feature = "bls-experimental")] - fn bls381_verify_works() { - signature_verify_test!(bls381) - } } diff --git a/substrate/primitives/state-machine/src/ext.rs b/substrate/primitives/state-machine/src/ext.rs index 7a79c4e8a1f..baad7e621be 100644 --- a/substrate/primitives/state-machine/src/ext.rs +++ b/substrate/primitives/state-machine/src/ext.rs @@ -713,6 +713,7 @@ where } /// Implement `Encode` by forwarding the stored raw vec. +#[allow(dead_code)] struct EncodeOpaqueValue(Vec); impl Encode for EncodeOpaqueValue { diff --git a/substrate/test-utils/cli/build.rs b/substrate/test-utils/cli/build.rs index a68cb706e8f..c63f0b8b667 100644 --- a/substrate/test-utils/cli/build.rs +++ b/substrate/test-utils/cli/build.rs @@ -20,6 +20,6 @@ use std::env; fn main() { if let Ok(profile) = env::var("PROFILE") { - println!("cargo:rustc-cfg=build_type=\"{}\"", profile); + println!("cargo:rustc-cfg=build_profile=\"{}\"", profile); } } diff --git a/substrate/test-utils/cli/src/lib.rs b/substrate/test-utils/cli/src/lib.rs index d77a89b4dbf..70d68f6f183 100644 --- a/substrate/test-utils/cli/src/lib.rs +++ b/substrate/test-utils/cli/src/lib.rs @@ -130,7 +130,7 @@ pub fn start_node() -> Child { /// build_substrate(&["--features=try-runtime"]); /// ``` pub fn build_substrate(args: &[&str]) { - let is_release_build = !cfg!(build_type = "debug"); + let is_release_build = !cfg!(build_profile = "debug"); // Get the root workspace directory from the CARGO_MANIFEST_DIR environment variable let mut cmd = Command::new("cargo"); diff --git a/substrate/utils/fork-tree/src/lib.rs b/substrate/utils/fork-tree/src/lib.rs index ff86467c85d..fe349b6c29a 100644 --- a/substrate/utils/fork-tree/src/lib.rs +++ b/substrate/utils/fork-tree/src/lib.rs @@ -810,12 +810,11 @@ impl<'a, H, N, V> Iterator for ForkTreeIterator<'a, H, N, V> { type Item = &'a Node; fn next(&mut self) -> Option { - self.stack.pop().map(|node| { + self.stack.pop().inspect(|node| { // child nodes are stored ordered by max branch height (decreasing), // we want to keep this ordering while iterating but since we're // using a stack for iterator state we need to reverse it. self.stack.extend(node.children.iter().rev()); - node }) } } diff --git a/substrate/utils/frame/benchmarking-cli/build.rs b/substrate/utils/frame/benchmarking-cli/build.rs index 1545d1e0c21..06cdb7973ab 100644 --- a/substrate/utils/frame/benchmarking-cli/build.rs +++ b/substrate/utils/frame/benchmarking-cli/build.rs @@ -24,8 +24,12 @@ use std::env; pub fn main() { if let Ok(opt_level) = env::var("OPT_LEVEL") { println!("cargo:rustc-cfg=build_opt_level={:?}", opt_level); + } else { + println!("cargo:rustc-cfg=build_opt_level={:?}", "unknown"); } if let Ok(profile) = env::var("PROFILE") { println!("cargo:rustc-cfg=build_profile={:?}", profile); + } else { + println!("cargo:rustc-cfg=build_profile={:?}", "unknown"); } } diff --git a/substrate/utils/wasm-builder/src/wasm_project.rs b/substrate/utils/wasm-builder/src/wasm_project.rs index a6eda078fde..dcd5e6f1ade 100644 --- a/substrate/utils/wasm-builder/src/wasm_project.rs +++ b/substrate/utils/wasm-builder/src/wasm_project.rs @@ -601,9 +601,10 @@ fn project_enabled_features( // We don't want to enable the `std`/`default` feature for the wasm build and // we need to check if the feature is enabled by checking the env variable. *f != "std" && - *f != "default" && env::var(format!("CARGO_FEATURE_{}", feature_env)) - .map(|v| v == "1") - .unwrap_or_default() + *f != "default" && + env::var(format!("CARGO_FEATURE_{feature_env}")) + .map(|v| v == "1") + .unwrap_or_default() }) .map(|d| d.0.clone()) .collect::>(); diff --git a/templates/parachain/pallets/template/src/benchmarking.rs b/templates/parachain/pallets/template/src/benchmarking.rs index 5acad6e60de..29572c3ff60 100644 --- a/templates/parachain/pallets/template/src/benchmarking.rs +++ b/templates/parachain/pallets/template/src/benchmarking.rs @@ -1,5 +1,4 @@ //! Benchmarking setup for pallet-template -#![cfg(feature = "runtime-benchmarks")] use super::*; use frame_benchmarking::v2::*; diff --git a/templates/solochain/pallets/template/src/benchmarking.rs b/templates/solochain/pallets/template/src/benchmarking.rs index d1a9554aed6..8af5d246f76 100644 --- a/templates/solochain/pallets/template/src/benchmarking.rs +++ b/templates/solochain/pallets/template/src/benchmarking.rs @@ -1,5 +1,5 @@ //! Benchmarking setup for pallet-template -#![cfg(feature = "runtime-benchmarks")] + use super::*; #[allow(unused)] -- GitLab From 8edc2cadedb6a6b4508682731b983ac25837f105 Mon Sep 17 00:00:00 2001 From: Alexandru Gheorghe <49718502+alexggh@users.noreply.github.com> Date: Fri, 27 Sep 2024 11:37:14 +0300 Subject: [PATCH 114/125] Remove jaeger from approval-voting and approval-distribution (#5830) Jaeger spans were not usable for debugging, see https://github.com/paritytech/polkadot-sdk/issues/4995, but we pay a price in CPU cost, subsystem-benchmarks show this brings a reduction of about 10-15% in CPU usage per subsystem, so remove it. --------- Signed-off-by: Alexandru Gheorghe --- Cargo.lock | 2 - polkadot/node/core/approval-voting/Cargo.toml | 1 - .../approval-voting-regression-bench.rs | 5 +- .../node/core/approval-voting/src/import.rs | 9 - polkadot/node/core/approval-voting/src/lib.rs | 173 +----------------- .../node/core/approval-voting/src/tests.rs | 3 +- .../network/approval-distribution/Cargo.toml | 1 - .../network/approval-distribution/src/lib.rs | 68 +------ prdoc/pr_5830.prdoc | 13 ++ 9 files changed, 23 insertions(+), 252 deletions(-) create mode 100644 prdoc/pr_5830.prdoc diff --git a/Cargo.lock b/Cargo.lock index c20c8f71c80..2e8eda3ca9e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13902,7 +13902,6 @@ dependencies = [ "futures-timer", "itertools 0.11.0", "log", - "polkadot-node-jaeger", "polkadot-node-metrics", "polkadot-node-network-protocol", "polkadot-node-primitives", @@ -14240,7 +14239,6 @@ dependencies = [ "merlin", "parity-scale-codec", "parking_lot 0.12.3", - "polkadot-node-jaeger", "polkadot-node-primitives", "polkadot-node-subsystem", "polkadot-node-subsystem-test-helpers", diff --git a/polkadot/node/core/approval-voting/Cargo.toml b/polkadot/node/core/approval-voting/Cargo.toml index e678118440f..2c3db866566 100644 --- a/polkadot/node/core/approval-voting/Cargo.toml +++ b/polkadot/node/core/approval-voting/Cargo.toml @@ -29,7 +29,6 @@ polkadot-node-subsystem-util = { workspace = true, default-features = true } polkadot-overseer = { workspace = true, default-features = true } polkadot-primitives = { workspace = true, default-features = true } polkadot-node-primitives = { workspace = true, default-features = true } -polkadot-node-jaeger = { workspace = true, default-features = true } sc-keystore = { workspace = true } sp-consensus = { workspace = true } diff --git a/polkadot/node/core/approval-voting/benches/approval-voting-regression-bench.rs b/polkadot/node/core/approval-voting/benches/approval-voting-regression-bench.rs index db0396a8319..e202d1ee229 100644 --- a/polkadot/node/core/approval-voting/benches/approval-voting-regression-bench.rs +++ b/polkadot/node/core/approval-voting/benches/approval-voting-regression-bench.rs @@ -83,8 +83,9 @@ fn main() -> Result<(), String> { ("Sent to peers", 63995.2200, 0.01), ])); messages.extend(average_usage.check_cpu_usage(&[ - ("approval-distribution", 12.2736, 0.1), - ("approval-voting", 2.7174, 0.1), + ("approval-distribution", 0.1, 0.1), + ("approval-voting", 0.1, 0.1), + ("approval-voting-parallel", 18.0758, 0.1), ])); if messages.is_empty() { diff --git a/polkadot/node/core/approval-voting/src/import.rs b/polkadot/node/core/approval-voting/src/import.rs index 5c456d22b21..e50a2f91148 100644 --- a/polkadot/node/core/approval-voting/src/import.rs +++ b/polkadot/node/core/approval-voting/src/import.rs @@ -28,7 +28,6 @@ //! //! We maintain a rolling window of session indices. This starts as empty -use polkadot_node_jaeger as jaeger; use polkadot_node_primitives::{ approval::{ self as approval_types, @@ -348,13 +347,6 @@ pub(crate) async fn handle_new_head< finalized_number: &Option, ) -> SubsystemResult> { const MAX_HEADS_LOOK_BACK: BlockNumber = MAX_FINALITY_LAG; - let _handle_new_head_span = state - .spans - .get(&head) - .map(|span| span.child("handle-new-head")) - .unwrap_or_else(|| jaeger::Span::new(head, "handle-new-head")) - .with_string_tag("head", format!("{:?}", head)) - .with_stage(jaeger::Stage::ApprovalChecking); let header = { let (h_tx, h_rx) = oneshot::channel(); @@ -666,7 +658,6 @@ pub(crate) mod tests { slot_duration_millis: 6_000, clock: Arc::new(MockClock::default()), assignment_criteria: Box::new(MockAssignmentCriteria::default()), - spans: HashMap::new(), per_block_assignments_gathering_times: LruMap::new(ByLength::new( MAX_BLOCKS_WITH_ASSIGNMENT_TIMESTAMPS, )), diff --git a/polkadot/node/core/approval-voting/src/lib.rs b/polkadot/node/core/approval-voting/src/lib.rs index 23f052b95f6..83509829305 100644 --- a/polkadot/node/core/approval-voting/src/lib.rs +++ b/polkadot/node/core/approval-voting/src/lib.rs @@ -21,9 +21,6 @@ //! of others. It uses this information to determine when candidates and blocks have //! been sufficiently approved to finalize. -use itertools::Itertools; -use jaeger::{hash_to_trace_identifier, PerLeafSpan}; -use polkadot_node_jaeger as jaeger; use polkadot_node_primitives::{ approval::{ v1::{BlockApprovalMeta, DelayTranche}, @@ -634,11 +631,7 @@ impl Wakeups { self.wakeups.entry(tick).or_default().push((block_hash, candidate_hash)); } - fn prune_finalized_wakeups( - &mut self, - finalized_number: BlockNumber, - spans: &mut HashMap, - ) { + fn prune_finalized_wakeups(&mut self, finalized_number: BlockNumber) { let after = self.block_numbers.split_off(&(finalized_number + 1)); let pruned_blocks: HashSet<_> = std::mem::replace(&mut self.block_numbers, after) .into_iter() @@ -662,9 +655,6 @@ impl Wakeups { } } } - - // Remove all spans that are associated with pruned blocks. - spans.retain(|h, _| !pruned_blocks.contains(h)); } // Get the wakeup for a particular block/candidate combo, if any. @@ -841,7 +831,6 @@ struct State { slot_duration_millis: u64, clock: Arc, assignment_criteria: Box, - spans: HashMap, // Per block, candidate records about how long we take until we gather enough // assignments, this is relevant because it gives us a good idea about how many // tranches we trigger and why. @@ -1203,7 +1192,6 @@ where slot_duration_millis: subsystem.slot_duration_millis, clock: subsystem.clock, assignment_criteria, - spans: HashMap::new(), per_block_assignments_gathering_times: LruMap::new(ByLength::new( MAX_BLOCKS_WITH_ASSIGNMENT_TIMESTAMPS, )), @@ -1525,18 +1513,8 @@ async fn handle_actions< continue } - let mut launch_approval_span = state - .spans - .get(&relay_block_hash) - .map(|span| span.child("launch-approval")) - .unwrap_or_else(|| jaeger::Span::new(candidate_hash, "launch-approval")) - .with_trace_id(candidate_hash) - .with_candidate(candidate_hash) - .with_stage(jaeger::Stage::ApprovalChecking); - metrics.on_assignment_produced(assignment_tranche); let block_hash = indirect_cert.block_hash; - launch_approval_span.add_string_tag("block-hash", format!("{:?}", block_hash)); let validator_index = indirect_cert.validator; if distribute_assignment { @@ -1580,7 +1558,6 @@ async fn handle_actions< backing_group, executor_params, core_index, - &launch_approval_span, ) .await }, @@ -1591,15 +1568,6 @@ async fn handle_actions< } }, Action::NoteApprovedInChainSelection(block_hash) => { - let _span = state - .spans - .get(&block_hash) - .map(|span| span.child("note-approved-in-chain-selection")) - .unwrap_or_else(|| { - jaeger::Span::new(block_hash, "note-approved-in-chain-selection") - }) - .with_string_tag("block-hash", format!("{:?}", block_hash)) - .with_stage(jaeger::Stage::ApprovalChecking); sender.send_message(ChainSelectionMessage::Approved(block_hash)).await; }, Action::BecomeActive => { @@ -1704,15 +1672,6 @@ async fn distribution_messages_for_activation b, None => { @@ -1722,9 +1681,6 @@ async fn distribution_messages_for_activation c, None => { @@ -1936,9 +1890,6 @@ async fn handle_from_overseer< let mut actions = Vec::new(); if let Some(activated) = update.activated { let head = activated.hash; - let approval_voting_span = - jaeger::PerLeafSpan::new(activated.span, "approval-voting"); - state.spans.insert(head, approval_voting_span); match import::handle_new_head( sender, approval_voting_sender, @@ -2009,7 +1960,7 @@ async fn handle_from_overseer< // `prune_finalized_wakeups` prunes all finalized block hashes. We prune spans // accordingly. - wakeups.prune_finalized_wakeups(block_number, &mut state.spans); + wakeups.prune_finalized_wakeups(block_number); state.cleanup_assignments_gathering_timestamp(block_number); // // `prune_finalized_wakeups` prunes all finalized block hashes. We prune spans @@ -2051,23 +2002,8 @@ async fn handle_from_overseer< result.0 }, ApprovalVotingMessage::ApprovedAncestor(target, lower_bound, res) => { - let mut approved_ancestor_span = state - .spans - .get(&target) - .map(|span| span.child("approved-ancestor")) - .unwrap_or_else(|| jaeger::Span::new(target, "approved-ancestor")) - .with_stage(jaeger::Stage::ApprovalChecking) - .with_string_tag("leaf", format!("{:?}", target)); - match handle_approved_ancestor( - sender, - db, - target, - lower_bound, - wakeups, - &mut approved_ancestor_span, - &metrics, - ) - .await + match handle_approved_ancestor(sender, db, target, lower_bound, wakeups, &metrics) + .await { Ok(v) => { let _ = res.send(v); @@ -2260,15 +2196,11 @@ async fn handle_approved_ancestor>( target: Hash, lower_bound: BlockNumber, wakeups: &Wakeups, - span: &mut jaeger::Span, metrics: &Metrics, ) -> SubsystemResult> { const MAX_TRACING_WINDOW: usize = 200; const ABNORMAL_DEPTH_THRESHOLD: usize = 5; const LOGGING_DEPTH_THRESHOLD: usize = 10; - let mut span = span - .child("handle-approved-ancestor") - .with_stage(jaeger::Stage::ApprovalChecking); let mut all_approved_max = None; @@ -2284,8 +2216,6 @@ async fn handle_approved_ancestor>( } }; - span.add_uint_tag("leaf-number", target_number as u64); - span.add_uint_tag("lower-bound", lower_bound as u64); if target_number <= lower_bound { return Ok(None) } @@ -2317,9 +2247,6 @@ async fn handle_approved_ancestor>( let mut bits: BitVec = Default::default(); for (i, block_hash) in std::iter::once(target).chain(ancestry).enumerate() { - let mut entry_span = - span.child("load-block-entry").with_stage(jaeger::Stage::ApprovalChecking); - entry_span.add_string_tag("block-hash", format!("{:?}", block_hash)); // Block entries should be present as the assumption is that // nothing here is finalized. If we encounter any missing block // entries we can fail. @@ -2386,7 +2313,6 @@ async fn handle_approved_ancestor>( ) } metrics.on_unapproved_candidates_in_unfinalized_chain(unapproved.len()); - entry_span.add_uint_tag("unapproved-candidates", unapproved.len() as u64); for candidate_hash in unapproved { match db.load_candidate_entry(&candidate_hash)? { None => { @@ -2507,15 +2433,6 @@ async fn handle_approved_ancestor>( number: block_number, descriptions: block_descriptions, }); - match all_approved_max { - Some(HighestApprovedAncestorBlock { ref hash, ref number, .. }) => { - span.add_uint_tag("highest-approved-number", *number as u64); - span.add_string_fmt_debug_tag("highest-approved-hash", hash); - }, - None => { - span.add_string_tag("reached-lower-bound", "true"); - }, - } Ok(all_approved_max) } @@ -2622,17 +2539,6 @@ where let assignment = checked_assignment.assignment(); let candidate_indices = checked_assignment.candidate_indices(); let tranche = checked_assignment.tranche(); - let mut import_assignment_span = state - .spans - .get(&assignment.block_hash) - .map(|span| span.child("import-assignment")) - .unwrap_or_else(|| jaeger::Span::new(assignment.block_hash, "import-assignment")) - .with_relay_parent(assignment.block_hash) - .with_stage(jaeger::Stage::ApprovalChecking); - - for candidate_index in candidate_indices.iter_ones() { - import_assignment_span.add_uint_tag("candidate-index", candidate_index as u64); - } let block_entry = match db.load_block_entry(&assignment.block_hash)? { Some(b) => b, @@ -2712,13 +2618,6 @@ where )), // no candidate at core. }; - import_assignment_span - .add_string_tag("candidate-hash", format!("{:?}", assigned_candidate_hash)); - import_assignment_span.add_string_tag( - "traceID", - format!("{:?}", jaeger::hash_to_trace_identifier(assigned_candidate_hash.0)), - ); - if candidate_entry.approval_entry_mut(&assignment.block_hash).is_none() { return Ok(( AssignmentCheckResult::Bad(AssignmentCheckError::Internal( @@ -2776,7 +2675,6 @@ where }; is_duplicate &= approval_entry.is_assigned(assignment.validator); approval_entry.import_assignment(tranche, assignment.validator, tick_now); - import_assignment_span.add_uint_tag("tranche", tranche as u64); // We've imported a new assignment, so we need to schedule a wake-up for when that might // no-show. @@ -2850,14 +2748,6 @@ where return Ok((Vec::new(), $e)) }}; } - let mut span = state - .spans - .get(&approval.block_hash) - .map(|span| span.child("import-approval")) - .unwrap_or_else(|| jaeger::Span::new(approval.block_hash, "import-approval")) - .with_string_fmt_debug_tag("candidate-index", approval.candidate_indices.clone()) - .with_relay_parent(approval.block_hash) - .with_stage(jaeger::Stage::ApprovalChecking); let block_entry = match db.load_block_entry(&approval.block_hash)? { Some(b) => b, @@ -2887,20 +2777,6 @@ where }, }; - span.add_string_tag("candidate-hashes", format!("{:?}", approved_candidates_info)); - span.add_string_tag( - "traceIDs", - format!( - "{:?}", - approved_candidates_info - .iter() - .map(|(_, approved_candidate_hash)| hash_to_trace_identifier( - approved_candidate_hash.0 - )) - .collect_vec() - ), - ); - gum::trace!( target: LOG_TARGET, "Received approval for num_candidates {:}", @@ -3255,16 +3131,6 @@ async fn process_wakeup>( metrics: &Metrics, wakeups: &Wakeups, ) -> SubsystemResult> { - let mut span = state - .spans - .get(&relay_block) - .map(|span| span.child("process-wakeup")) - .unwrap_or_else(|| jaeger::Span::new(candidate_hash, "process-wakeup")) - .with_trace_id(candidate_hash) - .with_relay_parent(relay_block) - .with_candidate(candidate_hash) - .with_stage(jaeger::Stage::ApprovalChecking); - let block_entry = db.load_block_entry(&relay_block)?; let candidate_entry = db.load_candidate_entry(&candidate_hash)?; @@ -3293,7 +3159,7 @@ async fn process_wakeup>( Slot::from(u64::from(session_info.no_show_slots)), ); let tranche_now = state.clock.tranche_now(state.slot_duration_millis, block_entry.slot()); - span.add_uint_tag("tranche", tranche_now as u64); + gum::trace!( target: LOG_TARGET, tranche = tranche_now, @@ -3456,7 +3322,6 @@ async fn launch_approval< backing_group: GroupIndex, executor_params: ExecutorParams, core_index: Option, - span: &jaeger::Span, ) -> SubsystemResult> { let (a_tx, a_rx) = oneshot::channel(); let (code_tx, code_rx) = oneshot::channel(); @@ -3490,13 +3355,6 @@ async fn launch_approval< let para_id = candidate.descriptor.para_id; gum::trace!(target: LOG_TARGET, ?candidate_hash, ?para_id, "Recovering data."); - let request_validation_data_span = span - .child("request-validation-data") - .with_trace_id(candidate_hash) - .with_candidate(candidate_hash) - .with_string_tag("block-hash", format!("{:?}", block_hash)) - .with_stage(jaeger::Stage::ApprovalChecking); - let timer = metrics.time_recover_and_approve(); sender .send_message(AvailabilityRecoveryMessage::RecoverAvailableData( @@ -3508,13 +3366,6 @@ async fn launch_approval< )) .await; - let request_validation_result_span = span - .child("request-validation-result") - .with_trace_id(candidate_hash) - .with_candidate(candidate_hash) - .with_string_tag("block-hash", format!("{:?}", block_hash)) - .with_stage(jaeger::Stage::ApprovalChecking); - sender .send_message(RuntimeApiMessage::Request( block_hash, @@ -3578,7 +3429,6 @@ async fn launch_approval< return ApprovalState::failed(validator_index, candidate_hash) }, }; - drop(request_validation_data_span); let validation_code = match code_rx.await { Err(_) => return ApprovalState::failed(validator_index, candidate_hash), @@ -3650,7 +3500,6 @@ async fn launch_approval< "Failed to validate candidate due to internal error", ); metrics_guard.take().on_approval_error(); - drop(request_validation_result_span); return ApprovalState::failed(validator_index, candidate_hash) }, } @@ -3678,17 +3527,6 @@ async fn issue_approval< ApprovalVoteRequest { validator_index, block_hash }: ApprovalVoteRequest, wakeups: &Wakeups, ) -> SubsystemResult> { - let mut issue_approval_span = state - .spans - .get(&block_hash) - .map(|span| span.child("issue-approval")) - .unwrap_or_else(|| jaeger::Span::new(block_hash, "issue-approval")) - .with_trace_id(candidate_hash) - .with_string_tag("block-hash", format!("{:?}", block_hash)) - .with_candidate(candidate_hash) - .with_validator_index(validator_index) - .with_stage(jaeger::Stage::ApprovalChecking); - let mut block_entry = match db.load_block_entry(&block_hash)? { Some(b) => b, None => { @@ -3713,7 +3551,6 @@ async fn issue_approval< }, Some(idx) => idx, }; - issue_approval_span.add_int_tag("candidate_index", candidate_index as i64); let candidate_hash = match block_entry.candidate(candidate_index as usize) { Some((_, h)) => *h, diff --git a/polkadot/node/core/approval-voting/src/tests.rs b/polkadot/node/core/approval-voting/src/tests.rs index ec825b800c7..db5ffd441c0 100644 --- a/polkadot/node/core/approval-voting/src/tests.rs +++ b/polkadot/node/core/approval-voting/src/tests.rs @@ -17,6 +17,7 @@ use self::test_helpers::mock::new_leaf; use super::*; use crate::backend::V1ReadBackend; +use itertools::Itertools; use overseer::prometheus::{ prometheus::{IntCounter, IntCounterVec}, Histogram, HistogramOpts, HistogramVec, Opts, @@ -4922,7 +4923,6 @@ fn test_gathering_assignments_statements() { slot_duration_millis: 6_000, clock: Arc::new(MockClock::default()), assignment_criteria: Box::new(MockAssignmentCriteria::check_only(|_| Ok(0))), - spans: HashMap::new(), per_block_assignments_gathering_times: LruMap::new(ByLength::new( MAX_BLOCKS_WITH_ASSIGNMENT_TIMESTAMPS, )), @@ -5017,7 +5017,6 @@ fn test_observe_assignment_gathering_status() { slot_duration_millis: 6_000, clock: Arc::new(MockClock::default()), assignment_criteria: Box::new(MockAssignmentCriteria::check_only(|_| Ok(0))), - spans: HashMap::new(), per_block_assignments_gathering_times: LruMap::new(ByLength::new( MAX_BLOCKS_WITH_ASSIGNMENT_TIMESTAMPS, )), diff --git a/polkadot/node/network/approval-distribution/Cargo.toml b/polkadot/node/network/approval-distribution/Cargo.toml index 51478dfa4a4..8d674a73347 100644 --- a/polkadot/node/network/approval-distribution/Cargo.toml +++ b/polkadot/node/network/approval-distribution/Cargo.toml @@ -16,7 +16,6 @@ polkadot-node-primitives = { workspace = true, default-features = true } polkadot-node-subsystem = { workspace = true, default-features = true } polkadot-node-subsystem-util = { workspace = true, default-features = true } polkadot-primitives = { workspace = true, default-features = true } -polkadot-node-jaeger = { workspace = true, default-features = true } rand = { workspace = true, default-features = true } itertools = { workspace = true } diff --git a/polkadot/node/network/approval-distribution/src/lib.rs b/polkadot/node/network/approval-distribution/src/lib.rs index 2fcb639338e..876cc59b9c2 100644 --- a/polkadot/node/network/approval-distribution/src/lib.rs +++ b/polkadot/node/network/approval-distribution/src/lib.rs @@ -27,7 +27,6 @@ use self::metrics::Metrics; use futures::{select, FutureExt as _}; use itertools::Itertools; use net_protocol::peer_set::{ProtocolVersion, ValidationVersion}; -use polkadot_node_jaeger as jaeger; use polkadot_node_network_protocol::{ self as net_protocol, filter_by_peer_version, grid_topology::{RandomRouting, RequiredRouting, SessionGridTopologies, SessionGridTopology}, @@ -359,9 +358,6 @@ pub struct State { /// Tracks recently finalized blocks. recent_outdated_blocks: RecentlyOutdated, - /// HashMap from active leaves to spans - spans: HashMap, - /// Aggression configuration. aggression_config: AggressionConfig, @@ -872,18 +868,9 @@ impl State { ); for meta in metas { - let mut span = self - .spans - .get(&meta.hash) - .map(|span| span.child(&"handle-new-blocks")) - .unwrap_or_else(|| jaeger::Span::new(meta.hash, &"handle-new-blocks")) - .with_string_tag("block-hash", format!("{:?}", meta.hash)) - .with_stage(jaeger::Stage::ApprovalDistribution); - match self.blocks.entry(meta.hash) { hash_map::Entry::Vacant(entry) => { let candidates_count = meta.candidates.len(); - span.add_uint_tag("candidates-count", candidates_count as u64); let mut candidates = Vec::with_capacity(candidates_count); candidates.resize_with(candidates_count, Default::default); @@ -1330,7 +1317,6 @@ impl State { if let Some(block_entry) = self.blocks.remove(relay_block) { self.topologies.dec_session_refs(block_entry.session); } - self.spans.remove(&relay_block); }); // If a block was finalized, this means we may need to move our aggression @@ -1357,21 +1343,6 @@ impl State { RA: overseer::SubsystemSender, R: CryptoRng + Rng, { - let _span = self - .spans - .get(&assignment.block_hash) - .map(|span| { - span.child(if source.peer_id().is_some() { - "peer-import-and-distribute-assignment" - } else { - "local-import-and-distribute-assignment" - }) - }) - .unwrap_or_else(|| jaeger::Span::new(&assignment.block_hash, "distribute-assignment")) - .with_string_tag("block-hash", format!("{:?}", assignment.block_hash)) - .with_optional_peer_id(source.peer_id().as_ref()) - .with_stage(jaeger::Stage::ApprovalDistribution); - let block_hash = assignment.block_hash; let validator_index = assignment.validator; @@ -1837,21 +1808,6 @@ impl State { vote: IndirectSignedApprovalVoteV2, session_info_provider: &mut RuntimeInfo, ) { - let _span = self - .spans - .get(&vote.block_hash) - .map(|span| { - span.child(if source.peer_id().is_some() { - "peer-import-and-distribute-approval" - } else { - "local-import-and-distribute-approval" - }) - }) - .unwrap_or_else(|| jaeger::Span::new(&vote.block_hash, "distribute-approval")) - .with_string_tag("block-hash", format!("{:?}", vote.block_hash)) - .with_optional_peer_id(source.peer_id().as_ref()) - .with_stage(jaeger::Stage::ApprovalDistribution); - let block_hash = vote.block_hash; let validator_index = vote.validator; let candidate_indices = &vote.candidate_indices; @@ -2091,14 +2047,6 @@ impl State { ) -> HashMap, ValidatorSignature)> { let mut all_sigs = HashMap::new(); for (hash, index) in indices { - let _span = self - .spans - .get(&hash) - .map(|span| span.child("get-approval-signatures")) - .unwrap_or_else(|| jaeger::Span::new(&hash, "get-approval-signatures")) - .with_string_tag("block-hash", format!("{:?}", hash)) - .with_stage(jaeger::Stage::ApprovalDistribution); - let block_entry = match self.blocks.get(&hash) { None => { gum::debug!( @@ -2776,18 +2724,12 @@ impl ApprovalDistribution { session_info_provider, ) .await, - FromOrchestra::Signal(OverseerSignal::ActiveLeaves(update)) => { + FromOrchestra::Signal(OverseerSignal::ActiveLeaves(_update)) => { gum::trace!(target: LOG_TARGET, "active leaves signal (ignored)"); // the relay chain blocks relevant to the approval subsystems // are those that are available, but not finalized yet // activated and deactivated heads hence are irrelevant to this subsystem, other // than for tracing purposes. - if let Some(activated) = update.activated { - let head = activated.hash; - let approval_distribution_span = - jaeger::PerLeafSpan::new(activated.span, "approval-distribution"); - state.spans.insert(head, approval_distribution_span); - } }, FromOrchestra::Signal(OverseerSignal::BlockFinalized(_hash, number)) => { gum::trace!(target: LOG_TARGET, number = %number, "finalized signal"); @@ -2846,14 +2788,6 @@ impl ApprovalDistribution { .await; }, ApprovalDistributionMessage::DistributeAssignment(cert, candidate_indices) => { - let _span = state - .spans - .get(&cert.block_hash) - .map(|span| span.child("import-and-distribute-assignment")) - .unwrap_or_else(|| jaeger::Span::new(&cert.block_hash, "distribute-assignment")) - .with_string_tag("block-hash", format!("{:?}", cert.block_hash)) - .with_stage(jaeger::Stage::ApprovalDistribution); - gum::debug!( target: LOG_TARGET, ?candidate_indices, diff --git a/prdoc/pr_5830.prdoc b/prdoc/pr_5830.prdoc new file mode 100644 index 00000000000..10b586e4a4a --- /dev/null +++ b/prdoc/pr_5830.prdoc @@ -0,0 +1,13 @@ +title: "Remove jaeger from approval-voting and approval-distribution" + +doc: + - audience: Node Dev + description: | + Jaeger was remove from approval-voting and approval-distribution because + it did not prove to improve the debugging and it wasted precious cpu cycles. + +crates: + - name: polkadot-approval-distribution + bump: none + - name: polkadot-node-core-approval-voting + bump: none -- GitLab From e85099e23c35a4614640b2c2c576f4385055033a Mon Sep 17 00:00:00 2001 From: Javier Viola <363911+pepoviola@users.noreply.github.com> Date: Fri, 27 Sep 2024 06:07:40 -0300 Subject: [PATCH 115/125] Disable flaky tests reported in 5848/5844 (#5851) Reported in: #5844 #5848 --- .gitlab/pipeline/zombienet/polkadot.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab/pipeline/zombienet/polkadot.yml b/.gitlab/pipeline/zombienet/polkadot.yml index e25bc4ca229..9a3bed24cfa 100644 --- a/.gitlab/pipeline/zombienet/polkadot.yml +++ b/.gitlab/pipeline/zombienet/polkadot.yml @@ -108,7 +108,7 @@ zombienet-polkadot-functional-0004-parachains-disputes-garbage-candidate: --local-dir="${LOCAL_DIR}/functional" --test="0004-parachains-garbage-candidate.zndsl" -zombienet-polkadot-functional-0005-parachains-disputes-past-session: +.zombienet-polkadot-functional-0005-parachains-disputes-past-session: extends: - .zombienet-polkadot-common script: @@ -351,7 +351,7 @@ zombienet-polkadot-malus-0001-dispute-valid: --local-dir="${LOCAL_DIR}/integrationtests" --test="0001-dispute-valid-block.zndsl" -zombienet-polkadot-coretime-revenue: +.zombienet-polkadot-coretime-revenue: extends: - .zombienet-polkadot-common needs: -- GitLab From 6d1943be7630b693397ed8e5db9d061afe0dd898 Mon Sep 17 00:00:00 2001 From: Maksym H <1177472+mordamax@users.noreply.github.com> Date: Fri, 27 Sep 2024 12:33:37 +0200 Subject: [PATCH 116/125] make frame omni bencher to install from path (#5853) - install frame-omni-bencher from the path - fix bench_features config --- .github/workflows/cmd.yml | 4 +++- .github/workflows/runtimes-matrix.json | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/cmd.yml b/.github/workflows/cmd.yml index 5498beb50cc..e416b120269 100644 --- a/.github/workflows/cmd.yml +++ b/.github/workflows/cmd.yml @@ -336,7 +336,9 @@ jobs: - name: Install dependencies for bench if: startsWith(steps.get-pr-comment.outputs.group2, 'bench') - run: cargo install subweight frame-omni-bencher --locked + run: | + cargo install subweight --locked + cargo install --path substrate/utils/frame/omni-bencher --locked - name: Run cmd id: cmd diff --git a/.github/workflows/runtimes-matrix.json b/.github/workflows/runtimes-matrix.json index b868a410a16..b0a307ab315 100644 --- a/.github/workflows/runtimes-matrix.json +++ b/.github/workflows/runtimes-matrix.json @@ -44,6 +44,7 @@ "package": "asset-hub-rococo-runtime", "path": "cumulus/parachains/runtimes/assets/asset-hub-rococo", "header": "cumulus/file_header.txt", + "bench_features": "runtime-benchmarks", "template": "cumulus/templates/xcm-bench-template.hbs", "uri": "wss://rococo-asset-hub-rpc.polkadot.io:443", "is_relay": false -- GitLab From a5e40d0cd0a0d941d6fe58aa278fedfcb9102710 Mon Sep 17 00:00:00 2001 From: Sebastian Kunert Date: Fri, 27 Sep 2024 13:24:25 +0200 Subject: [PATCH 117/125] Remove fixed workaround in `impl_runtime_apis` (#5839) This PR removes a workaround which had a reference comment to a rust compiler issue. The issue has been fixed and we should be able to remove that trait bound. --- .../polkadot-parachain-lib/src/command.rs | 4 +-- prdoc/pr_5839.prdoc | 21 +++++++++++ .../procedural/src/runtime/expand/mod.rs | 2 +- .../api/proc-macro/src/impl_runtime_apis.rs | 22 ++---------- .../primitives/api/proc-macro/src/utils.rs | 35 ++----------------- 5 files changed, 28 insertions(+), 56 deletions(-) create mode 100644 prdoc/pr_5839.prdoc diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/command.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/command.rs index 43fb551f80d..63562f192ae 100644 --- a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/command.rs +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/command.rs @@ -39,7 +39,6 @@ use sc_cli::{Result, SubstrateCli}; use sp_runtime::traits::AccountIdConversion; #[cfg(feature = "runtime-benchmarks")] use sp_runtime::traits::HashingFor; -use std::panic::{RefUnwindSafe, UnwindSafe}; /// Structure that can be used in order to provide customizers for different functionalities of the /// node binary that is being built using this library. @@ -55,8 +54,7 @@ pub fn new_aura_node_spec( extra_args: &NodeExtraArgs, ) -> Box where - Block: NodeBlock + UnwindSafe + RefUnwindSafe, - Block::BoundedHeader: UnwindSafe + RefUnwindSafe, + Block: NodeBlock, { match aura_id { AuraConsensusId::Sr25519 => crate::service::new_aura_node_spec::< diff --git a/prdoc/pr_5839.prdoc b/prdoc/pr_5839.prdoc new file mode 100644 index 00000000000..1dc95fe5c33 --- /dev/null +++ b/prdoc/pr_5839.prdoc @@ -0,0 +1,21 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Remove internal workaround for compiler bug + +doc: + - audience: + - Runtime Dev + - Node Dev + description: | + Remove a workaround we had in the `impl_runtime_apis` macro for a compiler bug that has been long fixed. + No impact on downstream users is expected, except relaxed trait bounds in a few places where the compiler + is now able to deduce more type info itself. + +crates: + - name: sp-api-proc-macro + bump: patch + - name: frame-support-procedural + bump: patch + - name: polkadot-parachain-lib + bump: patch diff --git a/substrate/frame/support/procedural/src/runtime/expand/mod.rs b/substrate/frame/support/procedural/src/runtime/expand/mod.rs index f34ab1cef54..666bc03aa41 100644 --- a/substrate/frame/support/procedural/src/runtime/expand/mod.rs +++ b/substrate/frame/support/procedural/src/runtime/expand/mod.rs @@ -77,7 +77,7 @@ pub fn expand(def: Def, legacy_ordering: bool) -> TokenStream2 { }; let res = expander::Expander::new("construct_runtime") - .dry(std::env::var("FRAME_EXPAND").is_err()) + .dry(std::env::var("EXPAND_MACROS").is_err()) .verbose(true) .write_to_out_dir(res) .expect("Does not fail because of IO in OUT_DIR; qed"); diff --git a/substrate/primitives/api/proc-macro/src/impl_runtime_apis.rs b/substrate/primitives/api/proc-macro/src/impl_runtime_apis.rs index 21397abc8fc..de922e3253e 100644 --- a/substrate/primitives/api/proc-macro/src/impl_runtime_apis.rs +++ b/substrate/primitives/api/proc-macro/src/impl_runtime_apis.rs @@ -18,7 +18,7 @@ use crate::{ common::API_VERSION_ATTRIBUTE, utils::{ - extract_all_signature_types, extract_block_type_from_trait_path, extract_impl_trait, + extract_block_type_from_trait_path, extract_impl_trait, extract_parameter_names_types_and_borrows, generate_crate_access, generate_runtime_mod_name_for_trait, parse_runtime_api_version, prefix_function_with_trait, versioned_trait_name, AllowSelfRefInParameters, RequireQualifiedTraitPath, @@ -632,11 +632,6 @@ impl<'a> Fold for ApiRuntimeImplToApiRuntimeApiImpl<'a> { } fn fold_item_impl(&mut self, mut input: ItemImpl) -> ItemImpl { - // All this `UnwindSafe` magic below here is required for this rust bug: - // https://github.com/rust-lang/rust/issues/24159 - // Before we directly had the final block type and rust could determine that it is unwind - // safe, but now we just have a generic parameter `Block`. - let crate_ = generate_crate_access(); // Implement the trait for the `RuntimeApiImpl` @@ -644,9 +639,9 @@ impl<'a> Fold for ApiRuntimeImplToApiRuntimeApiImpl<'a> { Box::new(parse_quote!( RuntimeApiImpl<__SrApiBlock__, RuntimeApiImplCall> )); input.generics.params.push(parse_quote!( - __SrApiBlock__: #crate_::BlockT + std::panic::UnwindSafe + - std::panic::RefUnwindSafe + __SrApiBlock__: #crate_::BlockT )); + input .generics .params @@ -661,17 +656,6 @@ impl<'a> Fold for ApiRuntimeImplToApiRuntimeApiImpl<'a> { where_clause.predicates.push(parse_quote! { &'static RuntimeApiImplCall: Send }); - // Require that all types used in the function signatures are unwind safe. - extract_all_signature_types(&input.items).iter().for_each(|i| { - where_clause.predicates.push(parse_quote! { - #i: std::panic::UnwindSafe + std::panic::RefUnwindSafe - }); - }); - - where_clause.predicates.push(parse_quote! { - __SrApiBlock__::Header: std::panic::UnwindSafe + std::panic::RefUnwindSafe - }); - input.attrs = filter_cfg_attrs(&input.attrs); fold::fold_item_impl(self, input) diff --git a/substrate/primitives/api/proc-macro/src/utils.rs b/substrate/primitives/api/proc-macro/src/utils.rs index 94da6748cbd..dc993c2ac42 100644 --- a/substrate/primitives/api/proc-macro/src/utils.rs +++ b/substrate/primitives/api/proc-macro/src/utils.rs @@ -22,8 +22,8 @@ use proc_macro_crate::{crate_name, FoundCrate}; use quote::{format_ident, quote}; use syn::{ parse_quote, punctuated::Punctuated, spanned::Spanned, token::And, Attribute, Error, Expr, - ExprLit, FnArg, GenericArgument, Ident, ImplItem, ItemImpl, Lit, Meta, MetaNameValue, Pat, - Path, PathArguments, Result, ReturnType, Signature, Token, Type, TypePath, + ExprLit, FnArg, GenericArgument, Ident, ItemImpl, Lit, Meta, MetaNameValue, Pat, Path, + PathArguments, Result, ReturnType, Signature, Token, Type, TypePath, }; /// Generates the access to the `sc_client` crate. @@ -159,37 +159,6 @@ pub fn prefix_function_with_trait(trait_: &Ident, function: &F) -> format!("{}_{}", trait_, function.to_string()) } -/// Extract all types that appear in signatures in the given `ImplItem`'s. -/// -/// If a type is a reference, the inner type is extracted (without the reference). -pub fn extract_all_signature_types(items: &[ImplItem]) -> Vec { - items - .iter() - .filter_map(|i| match i { - ImplItem::Fn(method) => Some(&method.sig), - _ => None, - }) - .flat_map(|sig| { - let ret_ty = match &sig.output { - ReturnType::Default => None, - ReturnType::Type(_, ty) => Some((**ty).clone()), - }; - - sig.inputs - .iter() - .filter_map(|i| match i { - FnArg::Typed(arg) => Some(&arg.ty), - _ => None, - }) - .map(|ty| match &**ty { - Type::Reference(t) => (*t.elem).clone(), - _ => (**ty).clone(), - }) - .chain(ret_ty) - }) - .collect() -} - /// Extracts the block type from a trait path. /// /// It is expected that the block type is the first type in the generic arguments. -- GitLab From 58ade7a62bc18f1453c9abb037dcd9fdf95f5c25 Mon Sep 17 00:00:00 2001 From: Iulian Barbu <14218860+iulianbarbu@users.noreply.github.com> Date: Sat, 28 Sep 2024 12:47:13 +0300 Subject: [PATCH 118/125] substrate/utils: enable wasm builder diagnostics propagation (#5838) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description `substrate-wasm-builder` can be a build dependency for crates which develop FRAME runtimes. I had a tough time seeing errors happening in such crates (e.g. runtimes from the `templates` directory) in my IDE. I use a combination of rust-analyzer + nvim-lsp + nvim-lspconfig + rustacean.vim and all of this stack is not able to correctly parse errors emitted during the `build` phase. As a matter of fact there is also a cargo issue tracking specifically this issue where cargo doesn't propagate the `--message-format` type to the build phase: [here](https://github.com/rust-lang/cargo/issues/14246) initially and then [here](https://github.com/rust-lang/cargo/issues/8283). It feels like a solution for this use case isn't very close, so if it comes to runtimes development (both as an SDK user and developer), enabling wasm builder to emit diagnostics messages friendly to IDEs would be useful for regular workflows where IDEs are used for finding errors instead of manually running `cargo` commands. ## Integration It can be an issue if Substrate/FRAME SDKs users and developers rely on the runtimes' crates build phase output in certain ways. Emitting compilation messages as json will pollute the regular compilation output so people that would manually run `cargo build` or `cargo check` on their crates will have a tougher time extracting the non JSON output. ## Review Notes Rust IDEs based on rust-analyzer rely on cargo check/clippy to extract diagnostic information. The information is generated by passing flags like `--messages-format=json` to the `cargo` commands. The messages are extracted by rust-analyzer and published to LSP clients that will populate UIs accordingly. We need to build against the wasm target by using `message-format=json` too so that IDEs can show the errors for crates that have a build dependency on `substrate-wasm-builder`. --------- Signed-off-by: Iulian Barbu Co-authored-by: Bastian Köcher --- Cargo.lock | 1 + Cargo.toml | 1 + prdoc/pr_5838.prdoc | 20 +++++++++++++++++++ substrate/utils/wasm-builder/Cargo.toml | 1 + substrate/utils/wasm-builder/src/lib.rs | 7 +++++++ .../utils/wasm-builder/src/wasm_project.rs | 13 ++++++++++++ 6 files changed, 43 insertions(+) create mode 100644 prdoc/pr_5838.prdoc diff --git a/Cargo.lock b/Cargo.lock index 2e8eda3ca9e..c10d1e93907 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24090,6 +24090,7 @@ dependencies = [ "parity-wasm", "polkavm-linker 0.9.2", "sc-executor 0.32.0", + "shlex", "sp-core 28.0.0", "sp-io 30.0.0", "sp-maybe-compressed-blob 11.0.0", diff --git a/Cargo.toml b/Cargo.toml index c40d59fce3c..a30b5b57d36 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1200,6 +1200,7 @@ sha1 = { version = "0.10.6" } sha2 = { version = "0.10.7", default-features = false } sha3 = { version = "0.10.0", default-features = false } shell-runtime = { path = "cumulus/parachains/runtimes/starters/shell" } +shlex = { version = "1.3.0" } slot-range-helper = { path = "polkadot/runtime/common/slot_range_helper", default-features = false } slotmap = { version = "1.0" } smallvec = { version = "1.11.0", default-features = false } diff --git a/prdoc/pr_5838.prdoc b/prdoc/pr_5838.prdoc new file mode 100644 index 00000000000..f6ce091a12d --- /dev/null +++ b/prdoc/pr_5838.prdoc @@ -0,0 +1,20 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: enable wasm builder diagnostics propagation + +doc: + - audience: Runtime Dev + description: | + `substrate-wasm-builder` is used as a build dependency by crates that implement FRAME runtimes. + Errors that occur in these crates can not be detected by IDEs that use rust-analyzer as a language + server because rust-analyzer needs the errors to be reported as diagnostic message in json format to + be able to publish them to language server clients. This PR adds `WASM_BUILD_CARGO_ARGS` environment + variable, which can hold a space separated list of args that will be parsed and passed to the `cargo` + command that it is used for building against wasm target. It can be used for the stated initial case, + but it is also flexible enough to allow passing other arguments or formatting the messages using another + available type. +crates: + - name: substrate-wasm-builder + bump: patch + diff --git a/substrate/utils/wasm-builder/Cargo.toml b/substrate/utils/wasm-builder/Cargo.toml index 15a1fd007ca..8f0e8a23e54 100644 --- a/substrate/utils/wasm-builder/Cargo.toml +++ b/substrate/utils/wasm-builder/Cargo.toml @@ -39,6 +39,7 @@ frame-metadata = { features = ["current"], optional = true, workspace = true, de codec = { optional = true, workspace = true, default-features = true } array-bytes = { optional = true, workspace = true, default-features = true } sp-tracing = { optional = true, workspace = true, default-features = true } +shlex = { workspace = true } [features] # Enable support for generating the metadata hash. diff --git a/substrate/utils/wasm-builder/src/lib.rs b/substrate/utils/wasm-builder/src/lib.rs index 07de4c15831..e3f2ff5cd73 100644 --- a/substrate/utils/wasm-builder/src/lib.rs +++ b/substrate/utils/wasm-builder/src/lib.rs @@ -84,6 +84,9 @@ //! - `WASM_BUILD_STD` - Sets whether the Rust's standard library crates will also be built. This is //! necessary to make sure the standard library crates only use the exact WASM feature set that //! our executor supports. Enabled by default. +//! - `WASM_BUILD_CARGO_ARGS` - This can take a string as space separated list of `cargo` arguments. +//! It was added specifically for the use case of enabling JSON diagnostic messages during the +//! build phase, to be used by IDEs that parse them, but it might be useful for other cases too. //! - `CARGO_NET_OFFLINE` - If `true`, `--offline` will be passed to all processes launched to //! prevent network access. Useful in offline environments. //! @@ -161,6 +164,10 @@ const WASM_BUILD_WORKSPACE_HINT: &str = "WASM_BUILD_WORKSPACE_HINT"; /// Environment variable to set whether we'll build `core`/`std`. const WASM_BUILD_STD: &str = "WASM_BUILD_STD"; +/// Environment variable to set additional cargo arguments that might be useful +/// during the build phase. +const WASM_BUILD_CARGO_ARGS: &str = "WASM_BUILD_CARGO_ARGS"; + /// The target to use for the runtime. Valid values are `wasm` (default) or `riscv`. const RUNTIME_TARGET: &str = "SUBSTRATE_RUNTIME_TARGET"; diff --git a/substrate/utils/wasm-builder/src/wasm_project.rs b/substrate/utils/wasm-builder/src/wasm_project.rs index dcd5e6f1ade..26edd2ea1f2 100644 --- a/substrate/utils/wasm-builder/src/wasm_project.rs +++ b/substrate/utils/wasm-builder/src/wasm_project.rs @@ -869,6 +869,18 @@ fn build_bloaty_blob( // We don't want to call ourselves recursively .env(crate::SKIP_BUILD_ENV, ""); + let cargo_args = env::var(crate::WASM_BUILD_CARGO_ARGS).unwrap_or_default(); + if !cargo_args.is_empty() { + let Some(args) = shlex::split(&cargo_args) else { + build_helper::warning(format!( + "the {} environment variable is not a valid shell string", + crate::WASM_BUILD_CARGO_ARGS + )); + std::process::exit(1); + }; + build_cmd.args(args); + } + #[cfg(feature = "metadata-hash")] if let Some(hash) = metadata_hash { build_cmd.env("RUNTIME_METADATA_HASH", array_bytes::bytes2hex("0x", &hash)); @@ -1140,6 +1152,7 @@ fn generate_rerun_if_changed_instructions( println!("cargo:rerun-if-env-changed={}", crate::WASM_BUILD_TOOLCHAIN); println!("cargo:rerun-if-env-changed={}", crate::WASM_BUILD_STD); println!("cargo:rerun-if-env-changed={}", crate::RUNTIME_TARGET); + println!("cargo:rerun-if-env-changed={}", crate::WASM_BUILD_CARGO_ARGS); } /// Track files and paths related to the given package to rerun `build.rs` on any relevant change. -- GitLab From 0a569963042a205d886acaee8a7f65a53c57bf1d Mon Sep 17 00:00:00 2001 From: Maksym H <1177472+mordamax@users.noreply.github.com> Date: Sat, 28 Sep 2024 11:53:55 +0200 Subject: [PATCH 119/125] Update runtimes-matrix.json (#5829) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Just a tiny config fix Co-authored-by: Bastian Köcher --- .github/workflows/runtimes-matrix.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/runtimes-matrix.json b/.github/workflows/runtimes-matrix.json index b0a307ab315..50fa44b1b07 100644 --- a/.github/workflows/runtimes-matrix.json +++ b/.github/workflows/runtimes-matrix.json @@ -52,7 +52,7 @@ { "name": "bridge-hub-rococo", "package": "bridge-hub-rococo-runtime", - "path": "cumulus/parachains/runtimes/bridges/bridge-hub-rococo", + "path": "cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo", "header": "cumulus/file_header.txt", "bench_features": "runtime-benchmarks", "template": "cumulus/templates/xcm-bench-template.hbs", @@ -62,7 +62,7 @@ { "name": "bridge-hub-westend", "package": "bridge-hub-rococo-runtime", - "path": "cumulus/parachains/runtimes/bridges/bridge-hub-westend", + "path": "cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend", "header": "cumulus/file_header.txt", "bench_features": "runtime-benchmarks", "template": "cumulus/templates/xcm-bench-template.hbs", -- GitLab From df12fd34e36848a535892b1e88281faa59bf34b6 Mon Sep 17 00:00:00 2001 From: Facundo Farall <37149322+ffarall@users.noreply.github.com> Date: Sat, 28 Sep 2024 12:43:38 -0400 Subject: [PATCH 120/125] Clarify firing of `import_notification_stream` in doc comment (#5811) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description Updates the doc comment on the `import_notification_stream` to make its behaviour clearer. Closes [Unexpected behaviour of block `import_notification_stream`](https://github.com/paritytech/polkadot-sdk/issues/5596). ## Integration Doesn't apply. ## Review Notes The old comment docs caused some confusion to myself and some members of my team, on when this notification stream is triggered. This is reflected in the linked [issue](https://github.com/paritytech/polkadot-sdk/issues/5596), and as discussed there, this PR aims to prevent this confusion in future devs looking to make use of this functionality. # Checklist * [x] My PR includes a detailed description as outlined in the "Description" and its two subsections above. * [ ] My PR follows the [labeling requirements]( https://github.com/paritytech/polkadot-sdk/blob/master/docs/contributor/CONTRIBUTING.md#Process ) of this project (at minimum one label for `T` required) * External contributors: ask maintainers to put the right label on your PR. * [x] I have made corresponding changes to the documentation (if applicable) * [x] I have added tests that prove my fix is effective or that my feature works (if applicable) You can remove the "Checklist" section once all have been checked. Thank you for your contribution! --------- Co-authored-by: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Co-authored-by: Bastian Köcher --- prdoc/pr_5811.prdoc | 13 +++++++++++++ substrate/client/api/src/client.rs | 13 ++++++++++--- 2 files changed, 23 insertions(+), 3 deletions(-) create mode 100644 prdoc/pr_5811.prdoc diff --git a/prdoc/pr_5811.prdoc b/prdoc/pr_5811.prdoc new file mode 100644 index 00000000000..103fef4bb8b --- /dev/null +++ b/prdoc/pr_5811.prdoc @@ -0,0 +1,13 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "Improve `import_notification_stream` documentation" + +doc: + - audience: Node Dev + description: | + "Updates the doc comment on the `import_notification_stream` to make its behaviour clearer. Now it specifically states that this notification stream is fired on every import notification after the initial sync, and only when there are re-orgs in the initial sync." + +crates: + - name: sc-client-api + bump: patch diff --git a/substrate/client/api/src/client.rs b/substrate/client/api/src/client.rs index 45cfafb2584..764930984ed 100644 --- a/substrate/client/api/src/client.rs +++ b/substrate/client/api/src/client.rs @@ -65,9 +65,16 @@ pub trait BlockOf { pub trait BlockchainEvents { /// Get block import event stream. /// - /// Not guaranteed to be fired for every imported block, only fired when the node - /// has synced to the tip or there is a re-org. Use `every_import_notification_stream()` - /// if you want a notification of every imported block regardless. + /// Not guaranteed to be fired for every imported block. Use + /// `every_import_notification_stream()` if you want a notification of every imported block + /// regardless. + /// + /// The events for this notification stream are emitted: + /// - During initial sync process: if there is a re-org while importing blocks. See + /// [here](https://github.com/paritytech/substrate/pull/7118#issuecomment-694091901) for the + /// rationale behind this. + /// - After initial sync process: on every imported block, regardless of whether it is + /// the new best block or not, causes a re-org or not. fn import_notification_stream(&self) -> ImportNotifications; /// Get a stream of every imported block. -- GitLab From 05b5fb2bd4ba587f5c478f39eb82906691d8ab06 Mon Sep 17 00:00:00 2001 From: Shawn Tabrizi Date: Sun, 29 Sep 2024 19:20:56 -0400 Subject: [PATCH 121/125] Improve APIs for Tries in Runtime (#5756) This is a refactor and improvement from: https://github.com/paritytech/polkadot-sdk/pull/3881 - `sp_runtime::proving_trie` now exposes a `BasicProvingTrie` for both `base2` and `base16`. - APIs for `base16` are more focused on single value proofs, also aligning their APIs with the `base2` trie - A `ProvingTrie` trait is included which wraps both the `base2` and `base16` trie, and exposes all APIs needed for an end to end scenario. - A `ProofToHashes` trait is exposed which can allow us to write proper benchmarks for the merkle trie. --------- Co-authored-by: Ankan <10196091+Ank4n@users.noreply.github.com> Co-authored-by: Adrian Catangiu --- Cargo.lock | 1 + prdoc/pr_5756.prdoc | 16 + substrate/frame/support/src/traits/proving.rs | 124 +++++- substrate/primitives/runtime/Cargo.toml | 2 + .../primitives/runtime/src/proving_trie.rs | 391 ------------------ .../runtime/src/proving_trie/base16.rs | 327 +++++++++++++++ .../runtime/src/proving_trie/base2.rs | 288 +++++++++++++ .../runtime/src/proving_trie/mod.rs | 187 +++++++++ 8 files changed, 930 insertions(+), 406 deletions(-) create mode 100644 prdoc/pr_5756.prdoc delete mode 100644 substrate/primitives/runtime/src/proving_trie.rs create mode 100644 substrate/primitives/runtime/src/proving_trie/base16.rs create mode 100644 substrate/primitives/runtime/src/proving_trie/base2.rs create mode 100644 substrate/primitives/runtime/src/proving_trie/mod.rs diff --git a/Cargo.lock b/Cargo.lock index c10d1e93907..97bdc935135 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -22523,6 +22523,7 @@ dependencies = [ name = "sp-runtime" version = "31.0.1" dependencies = [ + "binary-merkle-tree", "docify", "either", "hash256-std-hasher", diff --git a/prdoc/pr_5756.prdoc b/prdoc/pr_5756.prdoc new file mode 100644 index 00000000000..525f955d3ac --- /dev/null +++ b/prdoc/pr_5756.prdoc @@ -0,0 +1,16 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Improve APIs for Tries in Runtime + +doc: + - audience: Runtime Dev + description: | + This PR introduces a trait `ProvingTrie` which has all the function you need to use tries in the runtime. + This trait includes the ability to create, query, and prove data in a trie. Another trait `ProofToHashes` + allows developers to express the computational complexity of proof verification using the proof data. +crates: + - name: sp-runtime + bump: major + - name: frame-support + bump: major diff --git a/substrate/frame/support/src/traits/proving.rs b/substrate/frame/support/src/traits/proving.rs index dc44f4cd68e..84e37bde38d 100644 --- a/substrate/frame/support/src/traits/proving.rs +++ b/substrate/frame/support/src/traits/proving.rs @@ -20,6 +20,10 @@ use alloc::vec::Vec; use codec::{Decode, Encode}; use sp_core::Hasher; +use sp_runtime::DispatchError; + +// Re-export the `proving_trie` types and traits. +pub use sp_runtime::proving_trie::*; /// Something that can verify the existence of some data in a given proof. pub trait VerifyExistenceProof { @@ -31,7 +35,7 @@ pub trait VerifyExistenceProof { /// Verify the given `proof`. /// /// Ensures that the `proof` was build for `root` and returns the proved data. - fn verify_proof(proof: Self::Proof, root: &Self::Hash) -> Result, ()>; + fn verify_proof(proof: Self::Proof, root: &Self::Hash) -> Result, DispatchError>; } /// Implements [`VerifyExistenceProof`] using a binary merkle tree. @@ -44,9 +48,9 @@ where type Proof = binary_merkle_tree::MerkleProof>; type Hash = H::Out; - fn verify_proof(proof: Self::Proof, root: &Self::Hash) -> Result, ()> { + fn verify_proof(proof: Self::Proof, root: &Self::Hash) -> Result, DispatchError> { if proof.root != *root { - return Err(()); + return Err(TrieError::RootMismatch.into()); } if binary_merkle_tree::verify_proof::( @@ -58,13 +62,25 @@ where ) { Ok(proof.leaf) } else { - Err(()) + Err(TrieError::IncompleteProof.into()) } } } +impl ProofToHashes for BinaryMerkleTreeProver { + type Proof = binary_merkle_tree::MerkleProof>; + + // This base 2 merkle trie includes a `proof` field which is a `Vec`. + // The length of this vector tells us the depth of the proof, and how many + // hashes we need to calculate. + fn proof_to_hashes(proof: &Self::Proof) -> Result { + let depth = proof.proof.len(); + Ok(depth as u32) + } +} + /// Proof used by [`SixteenPatriciaMerkleTreeProver`] for [`VerifyExistenceProof`]. -#[derive(Encode, Decode)] +#[derive(Encode, Decode, Clone)] pub struct SixteenPatriciaMerkleTreeExistenceProof { /// The key of the value to prove. pub key: Vec, @@ -81,21 +97,35 @@ impl VerifyExistenceProof for SixteenPatriciaMerkleTreeProver { type Proof = SixteenPatriciaMerkleTreeExistenceProof; type Hash = H::Out; - fn verify_proof(proof: Self::Proof, root: &Self::Hash) -> Result, ()> { + fn verify_proof(proof: Self::Proof, root: &Self::Hash) -> Result, DispatchError> { sp_trie::verify_trie_proof::, _, _, _>( &root, &proof.proof, [&(&proof.key, Some(&proof.value))], ) - .map_err(drop) + .map_err(|err| TrieError::from(err).into()) .map(|_| proof.value) } } +impl ProofToHashes for SixteenPatriciaMerkleTreeProver { + type Proof = SixteenPatriciaMerkleTreeExistenceProof; + + // This base 16 trie uses a raw proof of `Vec`, where the length of the first `Vec` + // is the depth of the trie. We can use this to predict the number of hashes. + fn proof_to_hashes(proof: &Self::Proof) -> Result { + let depth = proof.proof.len(); + Ok(depth as u32) + } +} + #[cfg(test)] mod tests { use super::*; - use sp_runtime::{proving_trie::BasicProvingTrie, traits::BlakeTwo256}; + use sp_runtime::{ + proving_trie::{base16::BasicProvingTrie, ProvingTrie}, + traits::BlakeTwo256, + }; #[test] fn verify_binary_merkle_tree_prover_works() { @@ -113,23 +143,87 @@ mod tests { #[test] fn verify_sixteen_patricia_merkle_tree_prover_works() { - let trie = BasicProvingTrie::::generate_for(vec![ - (0u32, &b"hey"[..]), - (1u32, &b"yes"[..]), + let trie = BasicProvingTrie::::generate_for(vec![ + (0u32, String::from("hey")), + (1u32, String::from("yes")), ]) .unwrap(); - let proof = trie.create_single_value_proof(1u32).unwrap(); + let proof = trie.create_proof(&1u32).unwrap(); + let structured_proof: Vec> = Decode::decode(&mut &proof[..]).unwrap(); let root = *trie.root(); let proof = SixteenPatriciaMerkleTreeExistenceProof { key: 1u32.encode(), - value: b"yes"[..].encode(), - proof, + value: String::from("yes").encode(), + proof: structured_proof, }; assert_eq!( SixteenPatriciaMerkleTreeProver::::verify_proof(proof, &root).unwrap(), - b"yes"[..].encode() + String::from("yes").encode() ); } + + #[test] + fn proof_to_hashes_sixteen() { + let mut i: u32 = 1; + + // Compute log base 16 and round up + let log16 = |x: u32| -> u32 { + let x_f64 = x as f64; + let log16_x = (x_f64.ln() / 16_f64.ln()).ceil(); + log16_x as u32 + }; + + while i < 10_000_000 { + let trie = BasicProvingTrie::::generate_for( + (0..i).map(|i| (i, u128::from(i))), + ) + .unwrap(); + let proof = trie.create_proof(&0).unwrap(); + let structured_proof: Vec> = Decode::decode(&mut &proof[..]).unwrap(); + let root = *trie.root(); + + let proof = SixteenPatriciaMerkleTreeExistenceProof { + key: 0u32.encode(), + value: 0u128.encode(), + proof: structured_proof, + }; + let hashes = + SixteenPatriciaMerkleTreeProver::::proof_to_hashes(&proof).unwrap(); + let log16 = log16(i).max(1); + assert_eq!(hashes, log16); + + assert_eq!( + SixteenPatriciaMerkleTreeProver::::verify_proof(proof.clone(), &root) + .unwrap(), + proof.value + ); + + i = i * 10; + } + } + + #[test] + fn proof_to_hashes_binary() { + let mut i: u32 = 1; + while i < 10_000_000 { + let proof = binary_merkle_tree::merkle_proof::( + (0..i).map(|i| u128::from(i).encode()), + 0, + ); + let root = proof.root; + + let hashes = BinaryMerkleTreeProver::::proof_to_hashes(&proof).unwrap(); + let log2 = (i as f64).log2().ceil() as u32; + assert_eq!(hashes, log2); + + assert_eq!( + BinaryMerkleTreeProver::::verify_proof(proof, &root).unwrap(), + 0u128.encode() + ); + + i = i * 10; + } + } } diff --git a/substrate/primitives/runtime/Cargo.toml b/substrate/primitives/runtime/Cargo.toml index 800bf4bd073..c3413775b1c 100644 --- a/substrate/primitives/runtime/Cargo.toml +++ b/substrate/primitives/runtime/Cargo.toml @@ -36,6 +36,7 @@ sp-trie = { workspace = true } sp-weights = { workspace = true } docify = { workspace = true } tracing = { workspace = true, features = ["log"], default-features = false } +binary-merkle-tree = { workspace = true } simple-mermaid = { version = "0.1.1", optional = true } @@ -53,6 +54,7 @@ runtime-benchmarks = [] try-runtime = [] default = ["std"] std = [ + "binary-merkle-tree/std", "codec/std", "either/use_std", "hash256-std-hasher/std", diff --git a/substrate/primitives/runtime/src/proving_trie.rs b/substrate/primitives/runtime/src/proving_trie.rs deleted file mode 100644 index 9a423f18284..00000000000 --- a/substrate/primitives/runtime/src/proving_trie.rs +++ /dev/null @@ -1,391 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Types for a compact base-16 merkle trie used for checking and generating proofs within the -//! runtime. The `sp-trie` crate exposes all of these same functionality (and more), but this -//! library is designed to work more easily with runtime native types, which simply need to -//! implement `Encode`/`Decode`. It also exposes a runtime friendly `TrieError` type which can be -//! use inside of a FRAME Pallet. -//! -//! Proofs are created with latest substrate trie format (`LayoutV1`), and are not compatible with -//! proofs using `LayoutV0`. - -use crate::{Decode, DispatchError, Encode, MaxEncodedLen, TypeInfo}; -#[cfg(feature = "serde")] -use crate::{Deserialize, Serialize}; - -use sp_std::vec::Vec; -use sp_trie::{ - trie_types::{TrieDBBuilder, TrieDBMutBuilderV1, TrieError as SpTrieError}, - LayoutV1, MemoryDB, Trie, TrieMut, VerifyError, -}; - -type HashOf = ::Out; - -/// A runtime friendly error type for tries. -#[derive(Eq, PartialEq, Clone, Copy, Encode, Decode, Debug, TypeInfo, MaxEncodedLen)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub enum TrieError { - /* From TrieError */ - /// Attempted to create a trie with a state root not in the DB. - InvalidStateRoot, - /// Trie item not found in the database, - IncompleteDatabase, - /// A value was found in the trie with a nibble key that was not byte-aligned. - ValueAtIncompleteKey, - /// Corrupt Trie item. - DecoderError, - /// Hash is not value. - InvalidHash, - /* From VerifyError */ - /// The statement being verified contains multiple key-value pairs with the same key. - DuplicateKey, - /// The proof contains at least one extraneous node. - ExtraneousNode, - /// The proof contains at least one extraneous value which should have been omitted from the - /// proof. - ExtraneousValue, - /// The proof contains at least one extraneous hash reference the should have been omitted. - ExtraneousHashReference, - /// The proof contains an invalid child reference that exceeds the hash length. - InvalidChildReference, - /// The proof indicates that an expected value was not found in the trie. - ValueMismatch, - /// The proof is missing trie nodes required to verify. - IncompleteProof, - /// The root hash computed from the proof is incorrect. - RootMismatch, - /// One of the proof nodes could not be decoded. - DecodeError, -} - -impl From> for TrieError { - fn from(error: SpTrieError) -> Self { - match error { - SpTrieError::InvalidStateRoot(..) => Self::InvalidStateRoot, - SpTrieError::IncompleteDatabase(..) => Self::IncompleteDatabase, - SpTrieError::ValueAtIncompleteKey(..) => Self::ValueAtIncompleteKey, - SpTrieError::DecoderError(..) => Self::DecoderError, - SpTrieError::InvalidHash(..) => Self::InvalidHash, - } - } -} - -impl From> for TrieError { - fn from(error: VerifyError) -> Self { - match error { - VerifyError::DuplicateKey(..) => Self::DuplicateKey, - VerifyError::ExtraneousNode => Self::ExtraneousNode, - VerifyError::ExtraneousValue(..) => Self::ExtraneousValue, - VerifyError::ExtraneousHashReference(..) => Self::ExtraneousHashReference, - VerifyError::InvalidChildReference(..) => Self::InvalidChildReference, - VerifyError::ValueMismatch(..) => Self::ValueMismatch, - VerifyError::IncompleteProof => Self::IncompleteProof, - VerifyError::RootMismatch(..) => Self::RootMismatch, - VerifyError::DecodeError(..) => Self::DecodeError, - } - } -} - -impl From for &'static str { - fn from(e: TrieError) -> &'static str { - match e { - TrieError::InvalidStateRoot => "The state root is not in the database.", - TrieError::IncompleteDatabase => "A trie item was not found in the database.", - TrieError::ValueAtIncompleteKey => - "A value was found with a key that is not byte-aligned.", - TrieError::DecoderError => "A corrupt trie item was encountered.", - TrieError::InvalidHash => "The hash does not match the expected value.", - TrieError::DuplicateKey => "The proof contains duplicate keys.", - TrieError::ExtraneousNode => "The proof contains extraneous nodes.", - TrieError::ExtraneousValue => "The proof contains extraneous values.", - TrieError::ExtraneousHashReference => "The proof contains extraneous hash references.", - TrieError::InvalidChildReference => "The proof contains an invalid child reference.", - TrieError::ValueMismatch => "The proof indicates a value mismatch.", - TrieError::IncompleteProof => "The proof is incomplete.", - TrieError::RootMismatch => "The root hash computed from the proof is incorrect.", - TrieError::DecodeError => "One of the proof nodes could not be decoded.", - } - } -} - -/// A helper structure for building a basic base-16 merkle trie and creating compact proofs for that -/// trie. Proofs are created with latest substrate trie format (`LayoutV1`), and are not compatible -/// with proofs using `LayoutV0`. -pub struct BasicProvingTrie -where - Hashing: sp_core::Hasher, -{ - db: MemoryDB, - root: HashOf, - _phantom: core::marker::PhantomData<(Key, Value)>, -} - -impl BasicProvingTrie -where - Hashing: sp_core::Hasher, - Key: Encode, - Value: Encode, -{ - /// Create a new instance of a `ProvingTrie` using an iterator of key/value pairs. - pub fn generate_for(items: I) -> Result - where - I: IntoIterator, - { - let mut db = MemoryDB::default(); - let mut root = Default::default(); - - { - let mut trie = TrieDBMutBuilderV1::new(&mut db, &mut root).build(); - for (key, value) in items.into_iter() { - key.using_encoded(|k| value.using_encoded(|v| trie.insert(k, v))) - .map_err(|_| "failed to insert into trie")?; - } - } - - Ok(Self { db, root, _phantom: Default::default() }) - } - - /// Access the underlying trie root. - pub fn root(&self) -> &HashOf { - &self.root - } - - /// Query a value contained within the current trie. Returns `None` if the - /// nodes within the current `MemoryDB` are insufficient to query the item. - pub fn query(&self, key: Key) -> Option - where - Value: Decode, - { - let trie = TrieDBBuilder::new(&self.db, &self.root).build(); - key.using_encoded(|s| trie.get(s)) - .ok()? - .and_then(|raw| Value::decode(&mut &*raw).ok()) - } - - /// Create a compact merkle proof needed to prove all `keys` and their values are in the trie. - /// Returns `None` if the nodes within the current `MemoryDB` are insufficient to create a - /// proof. - /// - /// This function makes a proof with latest substrate trie format (`LayoutV1`), and is not - /// compatible with `LayoutV0`. - /// - /// When verifying the proof created by this function, you must include all of the keys and - /// values of the proof, else the verifier will complain that extra nodes are provided in the - /// proof that are not needed. - pub fn create_proof(&self, keys: &[Key]) -> Result>, DispatchError> { - sp_trie::generate_trie_proof::, _, _, _>( - &self.db, - self.root, - &keys.into_iter().map(|k| k.encode()).collect::>>(), - ) - .map_err(|err| TrieError::from(*err).into()) - } - - /// Create a compact merkle proof needed to prove a single key and its value are in the trie. - /// Returns `None` if the nodes within the current `MemoryDB` are insufficient to create a - /// proof. - /// - /// This function makes a proof with latest substrate trie format (`LayoutV1`), and is not - /// compatible with `LayoutV0`. - pub fn create_single_value_proof(&self, key: Key) -> Result>, DispatchError> { - self.create_proof(&[key]) - } -} - -/// Verify the existence or non-existence of `key` and `value` in a given trie root and proof. -/// -/// Proofs must be created with latest substrate trie format (`LayoutV1`). -pub fn verify_single_value_proof( - root: HashOf, - proof: &[Vec], - key: Key, - maybe_value: Option, -) -> Result<(), DispatchError> -where - Hashing: sp_core::Hasher, - Key: Encode, - Value: Encode, -{ - sp_trie::verify_trie_proof::, _, _, _>( - &root, - proof, - &[(key.encode(), maybe_value.map(|value| value.encode()))], - ) - .map_err(|err| TrieError::from(err).into()) -} - -/// Verify the existence or non-existence of multiple `items` in a given trie root and proof. -/// -/// Proofs must be created with latest substrate trie format (`LayoutV1`). -pub fn verify_proof( - root: HashOf, - proof: &[Vec], - items: &[(Key, Option)], -) -> Result<(), DispatchError> -where - Hashing: sp_core::Hasher, - Key: Encode, - Value: Encode, -{ - let items_encoded = items - .into_iter() - .map(|(key, maybe_value)| (key.encode(), maybe_value.as_ref().map(|value| value.encode()))) - .collect::, Option>)>>(); - - sp_trie::verify_trie_proof::, _, _, _>(&root, proof, &items_encoded) - .map_err(|err| TrieError::from(err).into()) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::traits::BlakeTwo256; - use sp_core::H256; - use sp_std::collections::btree_map::BTreeMap; - - // A trie which simulates a trie of accounts (u32) and balances (u128). - type BalanceTrie = BasicProvingTrie; - - // The expected root hash for an empty trie. - fn empty_root() -> H256 { - sp_trie::empty_trie_root::>() - } - - fn create_balance_trie() -> BalanceTrie { - // Create a map of users and their balances. - let mut map = BTreeMap::::new(); - for i in 0..100u32 { - map.insert(i, i.into()); - } - - // Put items into the trie. - let balance_trie = BalanceTrie::generate_for(map).unwrap(); - - // Root is changed. - let root = *balance_trie.root(); - assert!(root != empty_root()); - - // Assert valid keys are queryable. - assert_eq!(balance_trie.query(6u32), Some(6u128)); - assert_eq!(balance_trie.query(9u32), Some(9u128)); - assert_eq!(balance_trie.query(69u32), Some(69u128)); - // Invalid key returns none. - assert_eq!(balance_trie.query(6969u32), None); - - balance_trie - } - - #[test] - fn empty_trie_works() { - let empty_trie = BalanceTrie::generate_for(Vec::new()).unwrap(); - assert_eq!(*empty_trie.root(), empty_root()); - } - - #[test] - fn basic_end_to_end_single_value() { - let balance_trie = create_balance_trie(); - let root = *balance_trie.root(); - - // Create a proof for a valid key. - let proof = balance_trie.create_single_value_proof(6u32).unwrap(); - - // Assert key is provable, all other keys are invalid. - for i in 0..200u32 { - if i == 6 { - assert_eq!( - verify_single_value_proof::( - root, - &proof, - i, - Some(u128::from(i)) - ), - Ok(()) - ); - // Wrong value is invalid. - assert_eq!( - verify_single_value_proof::( - root, - &proof, - i, - Some(u128::from(i + 1)) - ), - Err(TrieError::RootMismatch.into()) - ); - } else { - assert!(verify_single_value_proof::( - root, - &proof, - i, - Some(u128::from(i)) - ) - .is_err()); - assert!(verify_single_value_proof::( - root, - &proof, - i, - None:: - ) - .is_err()); - } - } - } - - #[test] - fn basic_end_to_end_multi_value() { - let balance_trie = create_balance_trie(); - let root = *balance_trie.root(); - - // Create a proof for a valid and invalid key. - let proof = balance_trie.create_proof(&[6u32, 69u32, 6969u32]).unwrap(); - let items = [(6u32, Some(6u128)), (69u32, Some(69u128)), (6969u32, None)]; - - assert_eq!(verify_proof::(root, &proof, &items), Ok(())); - } - - #[test] - fn proof_fails_with_bad_data() { - let balance_trie = create_balance_trie(); - let root = *balance_trie.root(); - - // Create a proof for a valid key. - let proof = balance_trie.create_single_value_proof(6u32).unwrap(); - - // Correct data verifies successfully - assert_eq!( - verify_single_value_proof::(root, &proof, 6u32, Some(6u128)), - Ok(()) - ); - - // Fail to verify proof with wrong root - assert_eq!( - verify_single_value_proof::( - Default::default(), - &proof, - 6u32, - Some(6u128) - ), - Err(TrieError::RootMismatch.into()) - ); - - // Fail to verify proof with wrong data - assert_eq!( - verify_single_value_proof::(root, &[], 6u32, Some(6u128)), - Err(TrieError::IncompleteProof.into()) - ); - } -} diff --git a/substrate/primitives/runtime/src/proving_trie/base16.rs b/substrate/primitives/runtime/src/proving_trie/base16.rs new file mode 100644 index 00000000000..da05c551c6d --- /dev/null +++ b/substrate/primitives/runtime/src/proving_trie/base16.rs @@ -0,0 +1,327 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Types for a compact base-16 merkle trie used for checking and generating proofs within the +//! runtime. The `sp-trie` crate exposes all of these same functionality (and more), but this +//! library is designed to work more easily with runtime native types, which simply need to +//! implement `Encode`/`Decode`. It also exposes a runtime friendly `TrieError` type which can be +//! use inside of a FRAME Pallet. +//! +//! Proofs are created with latest substrate trie format (`LayoutV1`), and are not compatible with +//! proofs using `LayoutV0`. + +use super::{ProofToHashes, ProvingTrie, TrieError}; +use crate::{Decode, DispatchError, Encode}; +use codec::MaxEncodedLen; +use sp_std::vec::Vec; +use sp_trie::{ + trie_types::{TrieDBBuilder, TrieDBMutBuilderV1}, + LayoutV1, MemoryDB, Trie, TrieMut, +}; + +/// A helper structure for building a basic base-16 merkle trie and creating compact proofs for that +/// trie. Proofs are created with latest substrate trie format (`LayoutV1`), and are not compatible +/// with proofs using `LayoutV0`. +pub struct BasicProvingTrie +where + Hashing: sp_core::Hasher, +{ + db: MemoryDB, + root: Hashing::Out, + _phantom: core::marker::PhantomData<(Key, Value)>, +} + +impl BasicProvingTrie +where + Hashing: sp_core::Hasher, + Key: Encode, +{ + /// Create a compact merkle proof needed to prove all `keys` and their values are in the trie. + /// + /// When verifying the proof created by this function, you must include all of the keys and + /// values of the proof, else the verifier will complain that extra nodes are provided in the + /// proof that are not needed. + pub fn create_multi_proof(&self, keys: &[Key]) -> Result, DispatchError> { + sp_trie::generate_trie_proof::, _, _, _>( + &self.db, + self.root, + &keys.into_iter().map(|k| k.encode()).collect::>>(), + ) + .map_err(|err| TrieError::from(*err).into()) + .map(|structured_proof| structured_proof.encode()) + } +} + +impl ProvingTrie for BasicProvingTrie +where + Hashing: sp_core::Hasher, + Key: Encode, + Value: Encode + Decode, +{ + /// Create a new instance of a `ProvingTrie` using an iterator of key/value pairs. + fn generate_for(items: I) -> Result + where + I: IntoIterator, + { + let mut db = MemoryDB::default(); + let mut root = Default::default(); + + { + let mut trie = TrieDBMutBuilderV1::new(&mut db, &mut root).build(); + for (key, value) in items.into_iter() { + key.using_encoded(|k| value.using_encoded(|v| trie.insert(k, v))) + .map_err(|_| "failed to insert into trie")?; + } + } + + Ok(Self { db, root, _phantom: Default::default() }) + } + + /// Access the underlying trie root. + fn root(&self) -> &Hashing::Out { + &self.root + } + + /// Query a value contained within the current trie. Returns `None` if the + /// nodes within the current `MemoryDB` are insufficient to query the item. + fn query(&self, key: &Key) -> Option { + let trie = TrieDBBuilder::new(&self.db, &self.root).build(); + key.using_encoded(|s| trie.get(s)) + .ok()? + .and_then(|raw| Value::decode(&mut &*raw).ok()) + } + + /// Create a compact merkle proof needed to prove a single key and its value are in the trie. + fn create_proof(&self, key: &Key) -> Result, DispatchError> { + sp_trie::generate_trie_proof::, _, _, _>( + &self.db, + self.root, + &[key.encode()], + ) + .map_err(|err| TrieError::from(*err).into()) + .map(|structured_proof| structured_proof.encode()) + } + + /// Verify the existence of `key` and `value` in a given trie root and proof. + fn verify_proof( + root: &Hashing::Out, + proof: &[u8], + key: &Key, + value: &Value, + ) -> Result<(), DispatchError> { + verify_proof::(root, proof, key, value) + } +} + +impl ProofToHashes for BasicProvingTrie +where + Hashing: sp_core::Hasher, + Hashing::Out: MaxEncodedLen, +{ + // Our proof is just raw bytes. + type Proof = [u8]; + // This base 16 trie uses a raw proof of `Vec`, where the length of the first `Vec` + // is the depth of the trie. We can use this to predict the number of hashes. + fn proof_to_hashes(proof: &[u8]) -> Result { + use codec::DecodeLength; + let depth = + > as DecodeLength>::len(proof).map_err(|_| TrieError::DecodeError)?; + Ok(depth as u32) + } +} + +/// Verify the existence of `key` and `value` in a given trie root and proof. +pub fn verify_proof( + root: &Hashing::Out, + proof: &[u8], + key: &Key, + value: &Value, +) -> Result<(), DispatchError> +where + Hashing: sp_core::Hasher, + Key: Encode, + Value: Encode, +{ + let structured_proof: Vec> = + Decode::decode(&mut &proof[..]).map_err(|_| TrieError::DecodeError)?; + sp_trie::verify_trie_proof::, _, _, _>( + &root, + &structured_proof, + &[(key.encode(), Some(value.encode()))], + ) + .map_err(|err| TrieError::from(err).into()) +} + +/// Verify the existence of multiple `items` in a given trie root and proof. +pub fn verify_multi_proof( + root: &Hashing::Out, + proof: &[u8], + items: &[(Key, Value)], +) -> Result<(), DispatchError> +where + Hashing: sp_core::Hasher, + Key: Encode, + Value: Encode, +{ + let structured_proof: Vec> = + Decode::decode(&mut &proof[..]).map_err(|_| TrieError::DecodeError)?; + let items_encoded = items + .into_iter() + .map(|(key, value)| (key.encode(), Some(value.encode()))) + .collect::, Option>)>>(); + + sp_trie::verify_trie_proof::, _, _, _>( + &root, + &structured_proof, + &items_encoded, + ) + .map_err(|err| TrieError::from(err).into()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::traits::BlakeTwo256; + use sp_core::H256; + use sp_std::collections::btree_map::BTreeMap; + + // A trie which simulates a trie of accounts (u32) and balances (u128). + type BalanceTrie = BasicProvingTrie; + + // The expected root hash for an empty trie. + fn empty_root() -> H256 { + sp_trie::empty_trie_root::>() + } + + fn create_balance_trie() -> BalanceTrie { + // Create a map of users and their balances. + let mut map = BTreeMap::::new(); + for i in 0..100u32 { + map.insert(i, i.into()); + } + + // Put items into the trie. + let balance_trie = BalanceTrie::generate_for(map).unwrap(); + + // Root is changed. + let root = *balance_trie.root(); + assert!(root != empty_root()); + + // Assert valid keys are queryable. + assert_eq!(balance_trie.query(&6u32), Some(6u128)); + assert_eq!(balance_trie.query(&9u32), Some(9u128)); + assert_eq!(balance_trie.query(&69u32), Some(69u128)); + // Invalid key returns none. + assert_eq!(balance_trie.query(&6969u32), None); + + balance_trie + } + + #[test] + fn empty_trie_works() { + let empty_trie = BalanceTrie::generate_for(Vec::new()).unwrap(); + assert_eq!(*empty_trie.root(), empty_root()); + } + + #[test] + fn basic_end_to_end_single_value() { + let balance_trie = create_balance_trie(); + let root = *balance_trie.root(); + + // Create a proof for a valid key. + let proof = balance_trie.create_proof(&6u32).unwrap(); + + // Assert key is provable, all other keys are invalid. + for i in 0..200u32 { + if i == 6 { + assert_eq!( + verify_proof::(&root, &proof, &i, &u128::from(i)), + Ok(()) + ); + // Wrong value is invalid. + assert_eq!( + verify_proof::(&root, &proof, &i, &u128::from(i + 1)), + Err(TrieError::RootMismatch.into()) + ); + } else { + assert!( + verify_proof::(&root, &proof, &i, &u128::from(i)).is_err() + ); + } + } + } + + #[test] + fn basic_end_to_end_multi() { + let balance_trie = create_balance_trie(); + let root = *balance_trie.root(); + + // Create a proof for a valid and invalid key. + let proof = balance_trie.create_multi_proof(&[6u32, 9u32, 69u32]).unwrap(); + let items = [(6u32, 6u128), (9u32, 9u128), (69u32, 69u128)]; + + assert_eq!(verify_multi_proof::(&root, &proof, &items), Ok(())); + } + + #[test] + fn proof_fails_with_bad_data() { + let balance_trie = create_balance_trie(); + let root = *balance_trie.root(); + + // Create a proof for a valid key. + let proof = balance_trie.create_proof(&6u32).unwrap(); + + // Correct data verifies successfully + assert_eq!(verify_proof::(&root, &proof, &6u32, &6u128), Ok(())); + + // Fail to verify proof with wrong root + assert_eq!( + verify_proof::(&Default::default(), &proof, &6u32, &6u128), + Err(TrieError::RootMismatch.into()) + ); + + // Crete a bad proof. + let bad_proof = balance_trie.create_proof(&99u32).unwrap(); + + // Fail to verify data with the wrong proof + assert_eq!( + verify_proof::(&root, &bad_proof, &6u32, &6u128), + Err(TrieError::ExtraneousHashReference.into()) + ); + } + + #[test] + fn proof_to_hashes() { + let mut i: u32 = 1; + // Compute log base 16 and round up + let log16 = |x: u32| -> u32 { + let x_f64 = x as f64; + let log16_x = (x_f64.ln() / 16_f64.ln()).ceil(); + log16_x as u32 + }; + + while i < 10_000_000 { + let trie = BalanceTrie::generate_for((0..i).map(|i| (i, u128::from(i)))).unwrap(); + let proof = trie.create_proof(&0).unwrap(); + let hashes = BalanceTrie::proof_to_hashes(&proof).unwrap(); + let log16 = log16(i).max(1); + + assert_eq!(hashes, log16); + i = i * 10; + } + } +} diff --git a/substrate/primitives/runtime/src/proving_trie/base2.rs b/substrate/primitives/runtime/src/proving_trie/base2.rs new file mode 100644 index 00000000000..2b14a59ab05 --- /dev/null +++ b/substrate/primitives/runtime/src/proving_trie/base2.rs @@ -0,0 +1,288 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Types for a base-2 merkle tree used for checking and generating proofs within the +//! runtime. The `binary-merkle-tree` crate exposes all of these same functionality (and more), but +//! this library is designed to work more easily with runtime native types, which simply need to +//! implement `Encode`/`Decode`. + +use super::{ProofToHashes, ProvingTrie, TrieError}; +use crate::{Decode, DispatchError, Encode}; +use binary_merkle_tree::{merkle_proof, merkle_root, MerkleProof}; +use codec::MaxEncodedLen; +use sp_std::{collections::btree_map::BTreeMap, vec::Vec}; + +/// A helper structure for building a basic base-2 merkle trie and creating compact proofs for that +/// trie. +pub struct BasicProvingTrie +where + Hashing: sp_core::Hasher, +{ + db: BTreeMap, + root: Hashing::Out, + _phantom: core::marker::PhantomData<(Key, Value)>, +} + +impl ProvingTrie for BasicProvingTrie +where + Hashing: sp_core::Hasher, + Hashing::Out: Encode + Decode, + Key: Encode + Decode + Ord, + Value: Encode + Decode + Clone, +{ + /// Create a new instance of a `ProvingTrie` using an iterator of key/value pairs. + fn generate_for(items: I) -> Result + where + I: IntoIterator, + { + let mut db = BTreeMap::default(); + for (key, value) in items.into_iter() { + db.insert(key, value); + } + let root = merkle_root::(db.iter().map(|item| item.encode())); + Ok(Self { db, root, _phantom: Default::default() }) + } + + /// Access the underlying trie root. + fn root(&self) -> &Hashing::Out { + &self.root + } + + /// Query a value contained within the current trie. Returns `None` if the + /// nodes within the current `db` are insufficient to query the item. + fn query(&self, key: &Key) -> Option { + self.db.get(&key).cloned() + } + + /// Create a compact merkle proof needed to prove a single key and its value are in the trie. + /// Returns an error if the nodes within the current `db` are insufficient to create a proof. + fn create_proof(&self, key: &Key) -> Result, DispatchError> { + let mut encoded = Vec::with_capacity(self.db.len()); + let mut found_index = None; + + // Find the index of our key, and encode the (key, value) pair. + for (i, (k, v)) in self.db.iter().enumerate() { + // If we found the key we are looking for, save it. + if k == key { + found_index = Some(i); + } + + encoded.push((k, v).encode()); + } + + let index = found_index.ok_or(TrieError::IncompleteDatabase)?; + let proof = merkle_proof::>, Vec>(encoded, index as u32); + Ok(proof.encode()) + } + + /// Verify the existence of `key` and `value` in a given trie root and proof. + fn verify_proof( + root: &Hashing::Out, + proof: &[u8], + key: &Key, + value: &Value, + ) -> Result<(), DispatchError> { + verify_proof::(root, proof, key, value) + } +} + +impl ProofToHashes for BasicProvingTrie +where + Hashing: sp_core::Hasher, + Hashing::Out: MaxEncodedLen + Decode, + Key: Decode, + Value: Decode, +{ + // Our proof is just raw bytes. + type Proof = [u8]; + // This base 2 merkle trie includes a `proof` field which is a `Vec`. + // The length of this vector tells us the depth of the proof, and how many + // hashes we need to calculate. + fn proof_to_hashes(proof: &[u8]) -> Result { + let decoded_proof: MerkleProof> = + Decode::decode(&mut &proof[..]).map_err(|_| TrieError::IncompleteProof)?; + let depth = decoded_proof.proof.len(); + Ok(depth as u32) + } +} + +/// Verify the existence of `key` and `value` in a given trie root and proof. +pub fn verify_proof( + root: &Hashing::Out, + proof: &[u8], + key: &Key, + value: &Value, +) -> Result<(), DispatchError> +where + Hashing: sp_core::Hasher, + Hashing::Out: Decode, + Key: Encode + Decode, + Value: Encode + Decode, +{ + let decoded_proof: MerkleProof> = + Decode::decode(&mut &proof[..]).map_err(|_| TrieError::IncompleteProof)?; + if *root != decoded_proof.root { + return Err(TrieError::RootMismatch.into()); + } + + if (key, value).encode() != decoded_proof.leaf { + return Err(TrieError::ValueMismatch.into()); + } + + if binary_merkle_tree::verify_proof::( + &decoded_proof.root, + decoded_proof.proof, + decoded_proof.number_of_leaves, + decoded_proof.leaf_index, + &decoded_proof.leaf, + ) { + Ok(()) + } else { + Err(TrieError::IncompleteProof.into()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::traits::BlakeTwo256; + use sp_core::H256; + use sp_std::collections::btree_map::BTreeMap; + + // A trie which simulates a trie of accounts (u32) and balances (u128). + type BalanceTrie = BasicProvingTrie; + + // The expected root hash for an empty trie. + fn empty_root() -> H256 { + let tree = BalanceTrie::generate_for(Vec::new()).unwrap(); + *tree.root() + } + + fn create_balance_trie() -> BalanceTrie { + // Create a map of users and their balances. + let mut map = BTreeMap::::new(); + for i in 0..100u32 { + map.insert(i, i.into()); + } + + // Put items into the trie. + let balance_trie = BalanceTrie::generate_for(map).unwrap(); + + // Root is changed. + let root = *balance_trie.root(); + assert!(root != empty_root()); + + // Assert valid keys are queryable. + assert_eq!(balance_trie.query(&6u32), Some(6u128)); + assert_eq!(balance_trie.query(&9u32), Some(9u128)); + assert_eq!(balance_trie.query(&69u32), Some(69u128)); + + balance_trie + } + + #[test] + fn empty_trie_works() { + let empty_trie = BalanceTrie::generate_for(Vec::new()).unwrap(); + assert_eq!(*empty_trie.root(), empty_root()); + } + + #[test] + fn basic_end_to_end_single_value() { + let balance_trie = create_balance_trie(); + let root = *balance_trie.root(); + + // Create a proof for a valid key. + let proof = balance_trie.create_proof(&6u32).unwrap(); + + // Assert key is provable, all other keys are invalid. + for i in 0..200u32 { + if i == 6 { + assert_eq!( + verify_proof::(&root, &proof, &i, &u128::from(i)), + Ok(()) + ); + // Wrong value is invalid. + assert_eq!( + verify_proof::(&root, &proof, &i, &u128::from(i + 1)), + Err(TrieError::ValueMismatch.into()) + ); + } else { + assert!( + verify_proof::(&root, &proof, &i, &u128::from(i)).is_err() + ); + } + } + } + + #[test] + fn proof_fails_with_bad_data() { + let balance_trie = create_balance_trie(); + let root = *balance_trie.root(); + + // Create a proof for a valid key. + let proof = balance_trie.create_proof(&6u32).unwrap(); + + // Correct data verifies successfully + assert_eq!(verify_proof::(&root, &proof, &6u32, &6u128), Ok(())); + + // Fail to verify proof with wrong root + assert_eq!( + verify_proof::(&Default::default(), &proof, &6u32, &6u128), + Err(TrieError::RootMismatch.into()) + ); + + // Fail to verify proof with wrong data + assert_eq!( + verify_proof::(&root, &[], &6u32, &6u128), + Err(TrieError::IncompleteProof.into()) + ); + } + + // We make assumptions about the structure of the merkle proof in order to provide the + // `proof_to_hashes` function. This test keeps those assumptions checked. + #[test] + fn assert_structure_of_merkle_proof() { + let balance_trie = create_balance_trie(); + let root = *balance_trie.root(); + // Create a proof for a valid key. + let proof = balance_trie.create_proof(&6u32).unwrap(); + let decoded_proof: MerkleProof> = Decode::decode(&mut &proof[..]).unwrap(); + + let constructed_proof = MerkleProof::> { + root, + proof: decoded_proof.proof.clone(), + number_of_leaves: 100, + leaf_index: 6, + leaf: (6u32, 6u128).encode(), + }; + assert_eq!(constructed_proof, decoded_proof); + } + + #[test] + fn proof_to_hashes() { + let mut i: u32 = 1; + while i < 10_000_000 { + let trie = BalanceTrie::generate_for((0..i).map(|i| (i, u128::from(i)))).unwrap(); + let proof = trie.create_proof(&0).unwrap(); + let hashes = BalanceTrie::proof_to_hashes(&proof).unwrap(); + let log2 = (i as f64).log2().ceil() as u32; + + assert_eq!(hashes, log2); + i = i * 10; + } + } +} diff --git a/substrate/primitives/runtime/src/proving_trie/mod.rs b/substrate/primitives/runtime/src/proving_trie/mod.rs new file mode 100644 index 00000000000..009aa6d4935 --- /dev/null +++ b/substrate/primitives/runtime/src/proving_trie/mod.rs @@ -0,0 +1,187 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Types for merkle tries compatible with the runtime. + +pub mod base16; +pub mod base2; + +use crate::{Decode, DispatchError, Encode, MaxEncodedLen, TypeInfo}; +#[cfg(feature = "serde")] +use crate::{Deserialize, Serialize}; +use sp_std::vec::Vec; +use sp_trie::{trie_types::TrieError as SpTrieError, VerifyError}; + +/// A runtime friendly error type for tries. +#[derive(Eq, PartialEq, Clone, Copy, Encode, Decode, Debug, TypeInfo, MaxEncodedLen)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum TrieError { + /* From TrieError */ + /// Attempted to create a trie with a state root not in the DB. + InvalidStateRoot, + /// Trie item not found in the database, + IncompleteDatabase, + /// A value was found in the trie with a nibble key that was not byte-aligned. + ValueAtIncompleteKey, + /// Corrupt Trie item. + DecoderError, + /// Hash is not value. + InvalidHash, + /* From VerifyError */ + /// The statement being verified contains multiple key-value pairs with the same key. + DuplicateKey, + /// The proof contains at least one extraneous node. + ExtraneousNode, + /// The proof contains at least one extraneous value which should have been omitted from the + /// proof. + ExtraneousValue, + /// The proof contains at least one extraneous hash reference the should have been omitted. + ExtraneousHashReference, + /// The proof contains an invalid child reference that exceeds the hash length. + InvalidChildReference, + /// The proof indicates that an expected value was not found in the trie. + ValueMismatch, + /// The proof is missing trie nodes required to verify. + IncompleteProof, + /// The root hash computed from the proof is incorrect. + RootMismatch, + /// One of the proof nodes could not be decoded. + DecodeError, +} + +impl From> for TrieError { + fn from(error: SpTrieError) -> Self { + match error { + SpTrieError::InvalidStateRoot(..) => Self::InvalidStateRoot, + SpTrieError::IncompleteDatabase(..) => Self::IncompleteDatabase, + SpTrieError::ValueAtIncompleteKey(..) => Self::ValueAtIncompleteKey, + SpTrieError::DecoderError(..) => Self::DecoderError, + SpTrieError::InvalidHash(..) => Self::InvalidHash, + } + } +} + +impl From> for TrieError { + fn from(error: VerifyError) -> Self { + match error { + VerifyError::DuplicateKey(..) => Self::DuplicateKey, + VerifyError::ExtraneousNode => Self::ExtraneousNode, + VerifyError::ExtraneousValue(..) => Self::ExtraneousValue, + VerifyError::ExtraneousHashReference(..) => Self::ExtraneousHashReference, + VerifyError::InvalidChildReference(..) => Self::InvalidChildReference, + VerifyError::ValueMismatch(..) => Self::ValueMismatch, + VerifyError::IncompleteProof => Self::IncompleteProof, + VerifyError::RootMismatch(..) => Self::RootMismatch, + VerifyError::DecodeError(..) => Self::DecodeError, + } + } +} + +impl From for &'static str { + fn from(e: TrieError) -> &'static str { + match e { + TrieError::InvalidStateRoot => "The state root is not in the database.", + TrieError::IncompleteDatabase => "A trie item was not found in the database.", + TrieError::ValueAtIncompleteKey => + "A value was found with a key that is not byte-aligned.", + TrieError::DecoderError => "A corrupt trie item was encountered.", + TrieError::InvalidHash => "The hash does not match the expected value.", + TrieError::DuplicateKey => "The proof contains duplicate keys.", + TrieError::ExtraneousNode => "The proof contains extraneous nodes.", + TrieError::ExtraneousValue => "The proof contains extraneous values.", + TrieError::ExtraneousHashReference => "The proof contains extraneous hash references.", + TrieError::InvalidChildReference => "The proof contains an invalid child reference.", + TrieError::ValueMismatch => "The proof indicates a value mismatch.", + TrieError::IncompleteProof => "The proof is incomplete.", + TrieError::RootMismatch => "The root hash computed from the proof is incorrect.", + TrieError::DecodeError => "One of the proof nodes could not be decoded.", + } + } +} + +/// An interface for creating, interacting with, and creating proofs in a merkle trie. +pub trait ProvingTrie +where + Self: Sized, + Hashing: sp_core::Hasher, +{ + /// Create a new instance of a `ProvingTrie` using an iterator of key/value pairs. + fn generate_for(items: I) -> Result + where + I: IntoIterator; + /// Access the underlying trie root. + fn root(&self) -> &Hashing::Out; + /// Query a value contained within the current trie. Returns `None` if the + /// the value does not exist in the trie. + fn query(&self, key: &Key) -> Option; + /// Create a proof that can be used to verify a key and its value are in the trie. + fn create_proof(&self, key: &Key) -> Result, DispatchError>; + /// Verify the existence of `key` and `value` in a given trie root and proof. + fn verify_proof( + root: &Hashing::Out, + proof: &[u8], + key: &Key, + value: &Value, + ) -> Result<(), DispatchError>; +} + +/// This trait is one strategy that can be used to benchmark a trie proof verification for the +/// runtime. This strategy assumes that the majority complexity of verifying a merkle proof comes +/// from computing hashes to recreate the merkle root. This trait converts the the proof, some +/// bytes, to the number of hashes we expect to execute to verify that proof. +pub trait ProofToHashes { + /// The Proof type we will use to determine the number of hashes. + type Proof: ?Sized; + /// This function returns the number of hashes we expect to calculate based on the + /// size of the proof. This is used for benchmarking, so for worst case scenario, we should + /// round up. + /// + /// The major complexity of doing a `verify_proof` is computing the hashes needed + /// to calculate the merkle root. For tries, it should be easy to predict the depth + /// of the trie (which is equivalent to the hashes), by looking at the length of the proof. + fn proof_to_hashes(proof: &Self::Proof) -> Result; +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::traits::BlakeTwo256; + + // A trie which simulates a trie of accounts (u32) and balances (u128). + type BalanceTrie2 = base2::BasicProvingTrie; + type BalanceTrie16 = base16::BasicProvingTrie; + + #[test] + fn basic_api_usage_base_2() { + let balance_trie = BalanceTrie2::generate_for((0..100u32).map(|i| (i, i.into()))).unwrap(); + let root = *balance_trie.root(); + assert_eq!(balance_trie.query(&69), Some(69)); + assert_eq!(balance_trie.query(&6969), None); + let proof = balance_trie.create_proof(&69u32).unwrap(); + assert_eq!(BalanceTrie2::verify_proof(&root, &proof, &69u32, &69u128), Ok(())); + } + + #[test] + fn basic_api_usage_base_16() { + let balance_trie = BalanceTrie16::generate_for((0..100u32).map(|i| (i, i.into()))).unwrap(); + let root = *balance_trie.root(); + assert_eq!(balance_trie.query(&69), Some(69)); + assert_eq!(balance_trie.query(&6969), None); + let proof = balance_trie.create_proof(&69u32).unwrap(); + assert_eq!(BalanceTrie16::verify_proof(&root, &proof, &69u32, &69u128), Ok(())); + } +} -- GitLab From 184af677481744b44f86f5881de79587ef854142 Mon Sep 17 00:00:00 2001 From: Alexander Samusev <41779041+alvicsam@users.noreply.github.com> Date: Mon, 30 Sep 2024 13:07:50 +0200 Subject: [PATCH 122/125] [ci] Run subsystem-benchmarks on large runners (#5860) cc @AndreiEres --- .github/workflows/subsystem-benchmarks.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/subsystem-benchmarks.yml b/.github/workflows/subsystem-benchmarks.yml index 3c0eaf30e45..210714d847f 100644 --- a/.github/workflows/subsystem-benchmarks.yml +++ b/.github/workflows/subsystem-benchmarks.yml @@ -22,7 +22,7 @@ jobs: build: timeout-minutes: 80 needs: [preflight] - runs-on: ${{ needs.preflight.outputs.RUNNER_BENCHMARK }} + runs-on: ${{ needs.preflight.outputs.RUNNER }} container: image: ${{ needs.preflight.outputs.IMAGE }} strategy: -- GitLab From 9283dc1db4bbc199b0fe3fb7da81eee98013785e Mon Sep 17 00:00:00 2001 From: Egor_P Date: Mon, 30 Sep 2024 15:14:49 +0200 Subject: [PATCH 123/125] [Backport] Version bumps and prdocs reordering from stable2409 (#5849) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR backports regular version bumps and prdocs reordering from the `stable2409` release branch to `master` --------- Co-authored-by: Morgan Adamiec Co-authored-by: Bastian Köcher Co-authored-by: Nazar Mokrynskyi --- .../parachains/runtimes/assets/asset-hub-rococo/src/lib.rs | 2 +- .../parachains/runtimes/assets/asset-hub-westend/src/lib.rs | 2 +- .../runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs | 4 ++-- .../runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs | 2 +- .../runtimes/collectives/collectives-westend/src/lib.rs | 2 +- .../parachains/runtimes/contracts/contracts-rococo/src/lib.rs | 2 +- .../parachains/runtimes/coretime/coretime-rococo/src/lib.rs | 2 +- .../parachains/runtimes/coretime/coretime-westend/src/lib.rs | 2 +- .../parachains/runtimes/glutton/glutton-westend/src/lib.rs | 2 +- cumulus/parachains/runtimes/people/people-rococo/src/lib.rs | 2 +- cumulus/parachains/runtimes/people/people-westend/src/lib.rs | 2 +- polkadot/node/primitives/src/lib.rs | 2 +- polkadot/runtime/rococo/src/lib.rs | 2 +- polkadot/runtime/westend/src/lib.rs | 2 +- prdoc/{ => 1.16.0}/pr_2923.prdoc | 0 prdoc/{ => 1.16.0}/pr_3049.prdoc | 0 prdoc/{ => 1.16.0}/pr_3786.prdoc | 0 prdoc/{ => 1.16.0}/pr_3996.prdoc | 0 prdoc/{ => 1.16.0}/pr_4129.prdoc | 0 prdoc/{ => 1.16.0}/pr_4424.prdoc | 0 prdoc/{ => 1.16.0}/pr_4460.prdoc | 0 prdoc/{ => 1.16.0}/pr_4487.prdoc | 0 prdoc/{ => 1.16.0}/pr_4488.prdoc | 0 prdoc/{ => 1.16.0}/pr_4527.prdoc | 0 prdoc/{ => 1.16.0}/pr_4564.prdoc | 0 prdoc/{ => 1.16.0}/pr_4586.prdoc | 0 prdoc/{ => 1.16.0}/pr_4613.prdoc | 0 prdoc/{ => 1.16.0}/pr_4640.prdoc | 0 prdoc/{ => 1.16.0}/pr_4665.prdoc | 0 prdoc/{ => 1.16.0}/pr_4706.prdoc | 0 prdoc/{ => 1.16.0}/pr_4739.prdoc | 0 prdoc/{ => 1.16.0}/pr_4751.prdoc | 0 prdoc/{ => 1.16.0}/pr_4792.prdoc | 0 prdoc/{ => 1.16.0}/pr_4822.prdoc | 0 prdoc/{ => 1.16.0}/pr_4845.prdoc | 0 prdoc/{ => 1.16.0}/pr_4928.prdoc | 0 prdoc/{ => 1.16.0}/pr_4930.prdoc | 0 prdoc/{ => 1.16.0}/pr_4936.prdoc | 0 prdoc/{ => 1.16.0}/pr_4938.prdoc | 0 prdoc/{ => 1.16.0}/pr_4949.prdoc | 0 prdoc/{ => 1.16.0}/pr_4956.prdoc | 0 prdoc/{ => 1.16.0}/pr_4959.prdoc | 0 prdoc/{ => 1.16.0}/pr_4962.prdoc | 0 prdoc/{ => 1.16.0}/pr_4963.prdoc | 0 prdoc/{ => 1.16.0}/pr_4967.prdoc | 0 prdoc/{ => 1.16.0}/pr_4970.prdoc | 0 prdoc/{ => 1.16.0}/pr_4973.prdoc | 0 prdoc/{ => 1.16.0}/pr_4976.prdoc | 0 prdoc/{ => 1.16.0}/pr_4993.prdoc | 0 prdoc/{ => 1.16.0}/pr_4998.prdoc | 0 prdoc/{ => 1.16.0}/pr_4999.prdoc | 0 prdoc/{ => 1.16.0}/pr_5029.prdoc | 0 prdoc/{ => 1.16.0}/pr_5036.prdoc | 0 prdoc/{ => 1.16.0}/pr_5055.prdoc | 0 prdoc/{ => 1.16.0}/pr_5065.prdoc | 0 prdoc/{ => 1.16.0}/pr_5067.prdoc | 0 prdoc/{ => 1.16.0}/pr_5074.prdoc | 0 prdoc/{ => 1.16.0}/pr_5078.prdoc | 0 prdoc/{ => 1.16.0}/pr_5082.prdoc | 0 prdoc/{ => 1.16.0}/pr_5113.prdoc | 0 prdoc/{ => 1.16.0}/pr_5114.prdoc | 0 prdoc/{ => 1.16.0}/pr_5124.prdoc | 0 prdoc/{ => 1.16.0}/pr_5127.prdoc | 0 prdoc/{ => 1.16.0}/pr_5129.prdoc | 0 prdoc/{ => 1.16.0}/pr_5130.prdoc | 0 prdoc/{ => 1.16.0}/pr_5131.prdoc | 0 prdoc/{ => 1.16.0}/pr_5132.prdoc | 0 prdoc/{ => 1.16.0}/pr_5142.prdoc | 0 prdoc/{ => 1.16.0}/pr_5155.prdoc | 0 prdoc/{ => 1.16.0}/pr_5173.prdoc | 0 prdoc/{ => 1.16.0}/pr_5174.prdoc | 0 prdoc/{ => 1.16.0}/pr_5188.prdoc | 0 prdoc/{ => 1.16.0}/pr_5195.prdoc | 0 prdoc/{ => 1.16.0}/pr_5196.prdoc | 0 prdoc/{ => 1.16.0}/pr_5197.prdoc | 0 prdoc/{ => 1.16.0}/pr_5204.prdoc | 0 prdoc/{ => 1.16.0}/pr_5205.prdoc | 0 prdoc/{ => 1.16.0}/pr_5214.prdoc | 0 prdoc/{ => 1.16.0}/pr_5240.prdoc | 0 prdoc/{ => 1.16.0}/pr_5250.prdoc | 0 prdoc/{ => 1.16.0}/pr_5252.prdoc | 0 prdoc/{ => 1.16.0}/pr_5257.prdoc | 0 prdoc/{ => 1.16.0}/pr_5262.prdoc | 0 prdoc/{ => 1.16.0}/pr_5269.prdoc | 0 prdoc/{ => 1.16.0}/pr_5270.prdoc | 0 prdoc/{ => 1.16.0}/pr_5284.prdoc | 0 prdoc/{ => 1.16.0}/pr_5288.prdoc | 0 prdoc/{ => 1.16.0}/pr_5293.prdoc | 0 prdoc/{ => 1.16.0}/pr_5316.prdoc | 0 prdoc/{ => 1.16.0}/pr_5326.prdoc | 0 prdoc/{ => 1.16.0}/pr_5327.prdoc | 0 prdoc/{ => 1.16.0}/pr_5339.prdoc | 0 prdoc/{ => 1.16.0}/pr_5344.prdoc | 0 prdoc/{ => 1.16.0}/pr_5348.prdoc | 0 prdoc/{ => 1.16.0}/pr_5352.prdoc | 0 prdoc/{ => 1.16.0}/pr_5354.prdoc | 0 prdoc/{ => 1.16.0}/pr_5356.prdoc | 0 prdoc/{ => 1.16.0}/pr_5359.prdoc | 0 prdoc/{ => 1.16.0}/pr_5360.prdoc | 0 prdoc/{ => 1.16.0}/pr_5364.prdoc | 0 prdoc/{ => 1.16.0}/pr_5369.prdoc | 0 prdoc/{ => 1.16.0}/pr_5376.prdoc | 0 prdoc/{ => 1.16.0}/pr_5380.prdoc | 0 prdoc/{ => 1.16.0}/pr_5384.prdoc | 0 prdoc/{ => 1.16.0}/pr_5392.prdoc | 0 prdoc/{ => 1.16.0}/pr_5393.prdoc | 0 prdoc/{ => 1.16.0}/pr_5396.prdoc | 0 prdoc/{ => 1.16.0}/pr_5407.prdoc | 0 prdoc/{ => 1.16.0}/pr_5410.prdoc | 0 prdoc/{ => 1.16.0}/pr_5411.prdoc | 0 prdoc/{ => 1.16.0}/pr_5424.prdoc | 0 prdoc/{ => 1.16.0}/pr_5430.prdoc | 0 prdoc/{ => 1.16.0}/pr_5431.prdoc | 0 prdoc/{ => 1.16.0}/pr_5436.prdoc | 0 prdoc/{ => 1.16.0}/pr_5439.prdoc | 0 prdoc/{ => 1.16.0}/pr_5442.prdoc | 0 prdoc/{ => 1.16.0}/pr_5443.prdoc | 0 prdoc/{ => 1.16.0}/pr_5450.prdoc | 0 prdoc/{ => 1.16.0}/pr_5465.prdoc | 0 prdoc/{ => 1.16.0}/pr_5466.prdoc | 0 prdoc/{ => 1.16.0}/pr_5467.prdoc | 0 prdoc/{ => 1.16.0}/pr_5509.prdoc | 0 prdoc/{ => 1.16.0}/pr_5513.prdoc | 0 prdoc/{ => 1.16.0}/pr_5527.prdoc | 0 prdoc/{ => 1.16.0}/pr_5538.prdoc | 0 prdoc/{ => 1.16.0}/pr_5546.prdoc | 0 prdoc/{ => 1.16.0}/pr_5563.prdoc | 0 prdoc/{ => 1.16.0}/pr_5580.prdoc | 0 prdoc/{ => 1.16.0}/pr_5581.prdoc | 0 prdoc/{ => 1.16.0}/pr_5594.prdoc | 0 prdoc/{ => 1.16.0}/pr_5632.prdoc | 0 prdoc/{ => 1.16.0}/pr_5644.prdoc | 0 prdoc/{ => 1.16.0}/pr_5649.prdoc | 0 prdoc/{ => 1.16.0}/pr_5655.prdoc | 0 prdoc/{ => 1.16.0}/pr_5660.prdoc | 0 prdoc/{ => 1.16.0}/pr_5671.prdoc | 0 prdoc/{ => 1.16.0}/pr_5678.prdoc | 0 prdoc/{ => 1.16.0}/pr_5688.prdoc | 0 prdoc/{ => 1.16.0}/pr_5695.prdoc | 0 prdoc/{ => 1.16.0}/pr_5712.prdoc | 0 prdoc/{ => 1.16.0}/pr_5713.prdoc | 0 prdoc/{ => 1.16.0}/pr_5747.prdoc | 0 142 files changed, 15 insertions(+), 15 deletions(-) rename prdoc/{ => 1.16.0}/pr_2923.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_3049.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_3786.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_3996.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4129.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4424.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4460.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4487.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4488.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4527.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4564.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4586.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4613.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4640.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4665.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4706.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4739.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4751.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4792.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4822.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4845.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4928.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4930.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4936.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4938.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4949.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4956.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4959.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4962.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4963.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4967.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4970.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4973.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4976.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4993.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4998.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4999.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5029.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5036.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5055.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5065.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5067.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5074.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5078.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5082.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5113.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5114.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5124.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5127.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5129.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5130.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5131.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5132.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5142.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5155.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5173.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5174.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5188.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5195.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5196.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5197.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5204.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5205.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5214.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5240.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5250.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5252.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5257.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5262.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5269.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5270.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5284.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5288.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5293.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5316.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5326.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5327.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5339.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5344.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5348.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5352.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5354.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5356.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5359.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5360.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5364.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5369.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5376.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5380.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5384.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5392.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5393.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5396.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5407.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5410.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5411.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5424.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5430.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5431.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5436.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5439.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5442.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5443.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5450.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5465.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5466.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5467.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5509.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5513.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5527.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5538.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5546.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5563.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5580.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5581.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5594.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5632.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5644.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5649.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5655.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5660.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5671.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5678.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5688.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5695.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5712.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5713.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5747.prdoc (100%) diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs index 430c5bd1856..8163a445220 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs @@ -123,7 +123,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("statemine"), impl_name: create_runtime_str!("statemine"), authoring_version: 1, - spec_version: 1_015_000, + spec_version: 1_016_001, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 16, diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs index 8565cd30f43..75aea0eb2c2 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -123,7 +123,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("westmint"), impl_name: create_runtime_str!("westmint"), authoring_version: 1, - spec_version: 1_015_000, + spec_version: 1_016_001, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 16, diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs index bf9bd58669b..13c1ab6b625 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs @@ -238,10 +238,10 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("bridge-hub-rococo"), impl_name: create_runtime_str!("bridge-hub-rococo"), authoring_version: 1, - spec_version: 1_016_000, + spec_version: 1_016_001, impl_version: 0, apis: RUNTIME_API_VERSIONS, - transaction_version: 5, + transaction_version: 6, system_version: 1, }; diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs index 87152d30977..03b69a21802 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs @@ -226,7 +226,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_version: 1_016_001, impl_version: 0, apis: RUNTIME_API_VERSIONS, - transaction_version: 5, + transaction_version: 6, system_version: 1, }; diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs index 7174595f12a..a60d3ed66ac 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs @@ -126,7 +126,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("collectives-westend"), impl_name: create_runtime_str!("collectives-westend"), authoring_version: 1, - spec_version: 1_015_000, + spec_version: 1_016_001, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 6, diff --git a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs index 5f03b6ab7e2..853c8b48dc8 100644 --- a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs @@ -144,7 +144,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("contracts-rococo"), impl_name: create_runtime_str!("contracts-rococo"), authoring_version: 1, - spec_version: 1_015_000, + spec_version: 1_016_001, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 7, diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs index be332f67c9e..74569af9ec7 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs @@ -149,7 +149,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("coretime-rococo"), impl_name: create_runtime_str!("coretime-rococo"), authoring_version: 1, - spec_version: 1_015_000, + spec_version: 1_016_001, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 2, diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs index cc43c95f005..5e37faf6181 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs @@ -148,7 +148,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("coretime-westend"), impl_name: create_runtime_str!("coretime-westend"), authoring_version: 1, - spec_version: 1_015_000, + spec_version: 1_016_001, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 2, diff --git a/cumulus/parachains/runtimes/glutton/glutton-westend/src/lib.rs b/cumulus/parachains/runtimes/glutton/glutton-westend/src/lib.rs index 1b213785cf9..bc76f174b50 100644 --- a/cumulus/parachains/runtimes/glutton/glutton-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/glutton/glutton-westend/src/lib.rs @@ -102,7 +102,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("glutton-westend"), impl_name: create_runtime_str!("glutton-westend"), authoring_version: 1, - spec_version: 1_015_000, + spec_version: 1_016_001, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1, diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs b/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs index 16e023ad3dc..bfd31e2b484 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs @@ -137,7 +137,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("people-rococo"), impl_name: create_runtime_str!("people-rococo"), authoring_version: 1, - spec_version: 1_015_000, + spec_version: 1_016_001, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1, diff --git a/cumulus/parachains/runtimes/people/people-westend/src/lib.rs b/cumulus/parachains/runtimes/people/people-westend/src/lib.rs index 60b861678c5..8258ba0564f 100644 --- a/cumulus/parachains/runtimes/people/people-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/people/people-westend/src/lib.rs @@ -136,7 +136,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("people-westend"), impl_name: create_runtime_str!("people-westend"), authoring_version: 1, - spec_version: 1_015_000, + spec_version: 1_016_001, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1, diff --git a/polkadot/node/primitives/src/lib.rs b/polkadot/node/primitives/src/lib.rs index 685a9fd337d..5b6bcdaa9d7 100644 --- a/polkadot/node/primitives/src/lib.rs +++ b/polkadot/node/primitives/src/lib.rs @@ -59,7 +59,7 @@ pub use disputes::{ /// relatively rare. /// /// The associated worker binaries should use the same version as the node that spawns them. -pub const NODE_VERSION: &'static str = "1.15.1"; +pub const NODE_VERSION: &'static str = "1.16.0"; // For a 16-ary Merkle Prefix Trie, we can expect at most 16 32-byte hashes per node // plus some overhead: diff --git a/polkadot/runtime/rococo/src/lib.rs b/polkadot/runtime/rococo/src/lib.rs index 5fd219c193c..e1826e4100b 100644 --- a/polkadot/runtime/rococo/src/lib.rs +++ b/polkadot/runtime/rococo/src/lib.rs @@ -168,7 +168,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("rococo"), impl_name: create_runtime_str!("parity-rococo-v2.0"), authoring_version: 0, - spec_version: 1_015_000, + spec_version: 1_016_001, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 26, diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index 576e297df31..7324485256a 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -168,7 +168,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("westend"), impl_name: create_runtime_str!("parity-westend"), authoring_version: 2, - spec_version: 1_015_000, + spec_version: 1_016_001, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 26, diff --git a/prdoc/pr_2923.prdoc b/prdoc/1.16.0/pr_2923.prdoc similarity index 100% rename from prdoc/pr_2923.prdoc rename to prdoc/1.16.0/pr_2923.prdoc diff --git a/prdoc/pr_3049.prdoc b/prdoc/1.16.0/pr_3049.prdoc similarity index 100% rename from prdoc/pr_3049.prdoc rename to prdoc/1.16.0/pr_3049.prdoc diff --git a/prdoc/pr_3786.prdoc b/prdoc/1.16.0/pr_3786.prdoc similarity index 100% rename from prdoc/pr_3786.prdoc rename to prdoc/1.16.0/pr_3786.prdoc diff --git a/prdoc/pr_3996.prdoc b/prdoc/1.16.0/pr_3996.prdoc similarity index 100% rename from prdoc/pr_3996.prdoc rename to prdoc/1.16.0/pr_3996.prdoc diff --git a/prdoc/pr_4129.prdoc b/prdoc/1.16.0/pr_4129.prdoc similarity index 100% rename from prdoc/pr_4129.prdoc rename to prdoc/1.16.0/pr_4129.prdoc diff --git a/prdoc/pr_4424.prdoc b/prdoc/1.16.0/pr_4424.prdoc similarity index 100% rename from prdoc/pr_4424.prdoc rename to prdoc/1.16.0/pr_4424.prdoc diff --git a/prdoc/pr_4460.prdoc b/prdoc/1.16.0/pr_4460.prdoc similarity index 100% rename from prdoc/pr_4460.prdoc rename to prdoc/1.16.0/pr_4460.prdoc diff --git a/prdoc/pr_4487.prdoc b/prdoc/1.16.0/pr_4487.prdoc similarity index 100% rename from prdoc/pr_4487.prdoc rename to prdoc/1.16.0/pr_4487.prdoc diff --git a/prdoc/pr_4488.prdoc b/prdoc/1.16.0/pr_4488.prdoc similarity index 100% rename from prdoc/pr_4488.prdoc rename to prdoc/1.16.0/pr_4488.prdoc diff --git a/prdoc/pr_4527.prdoc b/prdoc/1.16.0/pr_4527.prdoc similarity index 100% rename from prdoc/pr_4527.prdoc rename to prdoc/1.16.0/pr_4527.prdoc diff --git a/prdoc/pr_4564.prdoc b/prdoc/1.16.0/pr_4564.prdoc similarity index 100% rename from prdoc/pr_4564.prdoc rename to prdoc/1.16.0/pr_4564.prdoc diff --git a/prdoc/pr_4586.prdoc b/prdoc/1.16.0/pr_4586.prdoc similarity index 100% rename from prdoc/pr_4586.prdoc rename to prdoc/1.16.0/pr_4586.prdoc diff --git a/prdoc/pr_4613.prdoc b/prdoc/1.16.0/pr_4613.prdoc similarity index 100% rename from prdoc/pr_4613.prdoc rename to prdoc/1.16.0/pr_4613.prdoc diff --git a/prdoc/pr_4640.prdoc b/prdoc/1.16.0/pr_4640.prdoc similarity index 100% rename from prdoc/pr_4640.prdoc rename to prdoc/1.16.0/pr_4640.prdoc diff --git a/prdoc/pr_4665.prdoc b/prdoc/1.16.0/pr_4665.prdoc similarity index 100% rename from prdoc/pr_4665.prdoc rename to prdoc/1.16.0/pr_4665.prdoc diff --git a/prdoc/pr_4706.prdoc b/prdoc/1.16.0/pr_4706.prdoc similarity index 100% rename from prdoc/pr_4706.prdoc rename to prdoc/1.16.0/pr_4706.prdoc diff --git a/prdoc/pr_4739.prdoc b/prdoc/1.16.0/pr_4739.prdoc similarity index 100% rename from prdoc/pr_4739.prdoc rename to prdoc/1.16.0/pr_4739.prdoc diff --git a/prdoc/pr_4751.prdoc b/prdoc/1.16.0/pr_4751.prdoc similarity index 100% rename from prdoc/pr_4751.prdoc rename to prdoc/1.16.0/pr_4751.prdoc diff --git a/prdoc/pr_4792.prdoc b/prdoc/1.16.0/pr_4792.prdoc similarity index 100% rename from prdoc/pr_4792.prdoc rename to prdoc/1.16.0/pr_4792.prdoc diff --git a/prdoc/pr_4822.prdoc b/prdoc/1.16.0/pr_4822.prdoc similarity index 100% rename from prdoc/pr_4822.prdoc rename to prdoc/1.16.0/pr_4822.prdoc diff --git a/prdoc/pr_4845.prdoc b/prdoc/1.16.0/pr_4845.prdoc similarity index 100% rename from prdoc/pr_4845.prdoc rename to prdoc/1.16.0/pr_4845.prdoc diff --git a/prdoc/pr_4928.prdoc b/prdoc/1.16.0/pr_4928.prdoc similarity index 100% rename from prdoc/pr_4928.prdoc rename to prdoc/1.16.0/pr_4928.prdoc diff --git a/prdoc/pr_4930.prdoc b/prdoc/1.16.0/pr_4930.prdoc similarity index 100% rename from prdoc/pr_4930.prdoc rename to prdoc/1.16.0/pr_4930.prdoc diff --git a/prdoc/pr_4936.prdoc b/prdoc/1.16.0/pr_4936.prdoc similarity index 100% rename from prdoc/pr_4936.prdoc rename to prdoc/1.16.0/pr_4936.prdoc diff --git a/prdoc/pr_4938.prdoc b/prdoc/1.16.0/pr_4938.prdoc similarity index 100% rename from prdoc/pr_4938.prdoc rename to prdoc/1.16.0/pr_4938.prdoc diff --git a/prdoc/pr_4949.prdoc b/prdoc/1.16.0/pr_4949.prdoc similarity index 100% rename from prdoc/pr_4949.prdoc rename to prdoc/1.16.0/pr_4949.prdoc diff --git a/prdoc/pr_4956.prdoc b/prdoc/1.16.0/pr_4956.prdoc similarity index 100% rename from prdoc/pr_4956.prdoc rename to prdoc/1.16.0/pr_4956.prdoc diff --git a/prdoc/pr_4959.prdoc b/prdoc/1.16.0/pr_4959.prdoc similarity index 100% rename from prdoc/pr_4959.prdoc rename to prdoc/1.16.0/pr_4959.prdoc diff --git a/prdoc/pr_4962.prdoc b/prdoc/1.16.0/pr_4962.prdoc similarity index 100% rename from prdoc/pr_4962.prdoc rename to prdoc/1.16.0/pr_4962.prdoc diff --git a/prdoc/pr_4963.prdoc b/prdoc/1.16.0/pr_4963.prdoc similarity index 100% rename from prdoc/pr_4963.prdoc rename to prdoc/1.16.0/pr_4963.prdoc diff --git a/prdoc/pr_4967.prdoc b/prdoc/1.16.0/pr_4967.prdoc similarity index 100% rename from prdoc/pr_4967.prdoc rename to prdoc/1.16.0/pr_4967.prdoc diff --git a/prdoc/pr_4970.prdoc b/prdoc/1.16.0/pr_4970.prdoc similarity index 100% rename from prdoc/pr_4970.prdoc rename to prdoc/1.16.0/pr_4970.prdoc diff --git a/prdoc/pr_4973.prdoc b/prdoc/1.16.0/pr_4973.prdoc similarity index 100% rename from prdoc/pr_4973.prdoc rename to prdoc/1.16.0/pr_4973.prdoc diff --git a/prdoc/pr_4976.prdoc b/prdoc/1.16.0/pr_4976.prdoc similarity index 100% rename from prdoc/pr_4976.prdoc rename to prdoc/1.16.0/pr_4976.prdoc diff --git a/prdoc/pr_4993.prdoc b/prdoc/1.16.0/pr_4993.prdoc similarity index 100% rename from prdoc/pr_4993.prdoc rename to prdoc/1.16.0/pr_4993.prdoc diff --git a/prdoc/pr_4998.prdoc b/prdoc/1.16.0/pr_4998.prdoc similarity index 100% rename from prdoc/pr_4998.prdoc rename to prdoc/1.16.0/pr_4998.prdoc diff --git a/prdoc/pr_4999.prdoc b/prdoc/1.16.0/pr_4999.prdoc similarity index 100% rename from prdoc/pr_4999.prdoc rename to prdoc/1.16.0/pr_4999.prdoc diff --git a/prdoc/pr_5029.prdoc b/prdoc/1.16.0/pr_5029.prdoc similarity index 100% rename from prdoc/pr_5029.prdoc rename to prdoc/1.16.0/pr_5029.prdoc diff --git a/prdoc/pr_5036.prdoc b/prdoc/1.16.0/pr_5036.prdoc similarity index 100% rename from prdoc/pr_5036.prdoc rename to prdoc/1.16.0/pr_5036.prdoc diff --git a/prdoc/pr_5055.prdoc b/prdoc/1.16.0/pr_5055.prdoc similarity index 100% rename from prdoc/pr_5055.prdoc rename to prdoc/1.16.0/pr_5055.prdoc diff --git a/prdoc/pr_5065.prdoc b/prdoc/1.16.0/pr_5065.prdoc similarity index 100% rename from prdoc/pr_5065.prdoc rename to prdoc/1.16.0/pr_5065.prdoc diff --git a/prdoc/pr_5067.prdoc b/prdoc/1.16.0/pr_5067.prdoc similarity index 100% rename from prdoc/pr_5067.prdoc rename to prdoc/1.16.0/pr_5067.prdoc diff --git a/prdoc/pr_5074.prdoc b/prdoc/1.16.0/pr_5074.prdoc similarity index 100% rename from prdoc/pr_5074.prdoc rename to prdoc/1.16.0/pr_5074.prdoc diff --git a/prdoc/pr_5078.prdoc b/prdoc/1.16.0/pr_5078.prdoc similarity index 100% rename from prdoc/pr_5078.prdoc rename to prdoc/1.16.0/pr_5078.prdoc diff --git a/prdoc/pr_5082.prdoc b/prdoc/1.16.0/pr_5082.prdoc similarity index 100% rename from prdoc/pr_5082.prdoc rename to prdoc/1.16.0/pr_5082.prdoc diff --git a/prdoc/pr_5113.prdoc b/prdoc/1.16.0/pr_5113.prdoc similarity index 100% rename from prdoc/pr_5113.prdoc rename to prdoc/1.16.0/pr_5113.prdoc diff --git a/prdoc/pr_5114.prdoc b/prdoc/1.16.0/pr_5114.prdoc similarity index 100% rename from prdoc/pr_5114.prdoc rename to prdoc/1.16.0/pr_5114.prdoc diff --git a/prdoc/pr_5124.prdoc b/prdoc/1.16.0/pr_5124.prdoc similarity index 100% rename from prdoc/pr_5124.prdoc rename to prdoc/1.16.0/pr_5124.prdoc diff --git a/prdoc/pr_5127.prdoc b/prdoc/1.16.0/pr_5127.prdoc similarity index 100% rename from prdoc/pr_5127.prdoc rename to prdoc/1.16.0/pr_5127.prdoc diff --git a/prdoc/pr_5129.prdoc b/prdoc/1.16.0/pr_5129.prdoc similarity index 100% rename from prdoc/pr_5129.prdoc rename to prdoc/1.16.0/pr_5129.prdoc diff --git a/prdoc/pr_5130.prdoc b/prdoc/1.16.0/pr_5130.prdoc similarity index 100% rename from prdoc/pr_5130.prdoc rename to prdoc/1.16.0/pr_5130.prdoc diff --git a/prdoc/pr_5131.prdoc b/prdoc/1.16.0/pr_5131.prdoc similarity index 100% rename from prdoc/pr_5131.prdoc rename to prdoc/1.16.0/pr_5131.prdoc diff --git a/prdoc/pr_5132.prdoc b/prdoc/1.16.0/pr_5132.prdoc similarity index 100% rename from prdoc/pr_5132.prdoc rename to prdoc/1.16.0/pr_5132.prdoc diff --git a/prdoc/pr_5142.prdoc b/prdoc/1.16.0/pr_5142.prdoc similarity index 100% rename from prdoc/pr_5142.prdoc rename to prdoc/1.16.0/pr_5142.prdoc diff --git a/prdoc/pr_5155.prdoc b/prdoc/1.16.0/pr_5155.prdoc similarity index 100% rename from prdoc/pr_5155.prdoc rename to prdoc/1.16.0/pr_5155.prdoc diff --git a/prdoc/pr_5173.prdoc b/prdoc/1.16.0/pr_5173.prdoc similarity index 100% rename from prdoc/pr_5173.prdoc rename to prdoc/1.16.0/pr_5173.prdoc diff --git a/prdoc/pr_5174.prdoc b/prdoc/1.16.0/pr_5174.prdoc similarity index 100% rename from prdoc/pr_5174.prdoc rename to prdoc/1.16.0/pr_5174.prdoc diff --git a/prdoc/pr_5188.prdoc b/prdoc/1.16.0/pr_5188.prdoc similarity index 100% rename from prdoc/pr_5188.prdoc rename to prdoc/1.16.0/pr_5188.prdoc diff --git a/prdoc/pr_5195.prdoc b/prdoc/1.16.0/pr_5195.prdoc similarity index 100% rename from prdoc/pr_5195.prdoc rename to prdoc/1.16.0/pr_5195.prdoc diff --git a/prdoc/pr_5196.prdoc b/prdoc/1.16.0/pr_5196.prdoc similarity index 100% rename from prdoc/pr_5196.prdoc rename to prdoc/1.16.0/pr_5196.prdoc diff --git a/prdoc/pr_5197.prdoc b/prdoc/1.16.0/pr_5197.prdoc similarity index 100% rename from prdoc/pr_5197.prdoc rename to prdoc/1.16.0/pr_5197.prdoc diff --git a/prdoc/pr_5204.prdoc b/prdoc/1.16.0/pr_5204.prdoc similarity index 100% rename from prdoc/pr_5204.prdoc rename to prdoc/1.16.0/pr_5204.prdoc diff --git a/prdoc/pr_5205.prdoc b/prdoc/1.16.0/pr_5205.prdoc similarity index 100% rename from prdoc/pr_5205.prdoc rename to prdoc/1.16.0/pr_5205.prdoc diff --git a/prdoc/pr_5214.prdoc b/prdoc/1.16.0/pr_5214.prdoc similarity index 100% rename from prdoc/pr_5214.prdoc rename to prdoc/1.16.0/pr_5214.prdoc diff --git a/prdoc/pr_5240.prdoc b/prdoc/1.16.0/pr_5240.prdoc similarity index 100% rename from prdoc/pr_5240.prdoc rename to prdoc/1.16.0/pr_5240.prdoc diff --git a/prdoc/pr_5250.prdoc b/prdoc/1.16.0/pr_5250.prdoc similarity index 100% rename from prdoc/pr_5250.prdoc rename to prdoc/1.16.0/pr_5250.prdoc diff --git a/prdoc/pr_5252.prdoc b/prdoc/1.16.0/pr_5252.prdoc similarity index 100% rename from prdoc/pr_5252.prdoc rename to prdoc/1.16.0/pr_5252.prdoc diff --git a/prdoc/pr_5257.prdoc b/prdoc/1.16.0/pr_5257.prdoc similarity index 100% rename from prdoc/pr_5257.prdoc rename to prdoc/1.16.0/pr_5257.prdoc diff --git a/prdoc/pr_5262.prdoc b/prdoc/1.16.0/pr_5262.prdoc similarity index 100% rename from prdoc/pr_5262.prdoc rename to prdoc/1.16.0/pr_5262.prdoc diff --git a/prdoc/pr_5269.prdoc b/prdoc/1.16.0/pr_5269.prdoc similarity index 100% rename from prdoc/pr_5269.prdoc rename to prdoc/1.16.0/pr_5269.prdoc diff --git a/prdoc/pr_5270.prdoc b/prdoc/1.16.0/pr_5270.prdoc similarity index 100% rename from prdoc/pr_5270.prdoc rename to prdoc/1.16.0/pr_5270.prdoc diff --git a/prdoc/pr_5284.prdoc b/prdoc/1.16.0/pr_5284.prdoc similarity index 100% rename from prdoc/pr_5284.prdoc rename to prdoc/1.16.0/pr_5284.prdoc diff --git a/prdoc/pr_5288.prdoc b/prdoc/1.16.0/pr_5288.prdoc similarity index 100% rename from prdoc/pr_5288.prdoc rename to prdoc/1.16.0/pr_5288.prdoc diff --git a/prdoc/pr_5293.prdoc b/prdoc/1.16.0/pr_5293.prdoc similarity index 100% rename from prdoc/pr_5293.prdoc rename to prdoc/1.16.0/pr_5293.prdoc diff --git a/prdoc/pr_5316.prdoc b/prdoc/1.16.0/pr_5316.prdoc similarity index 100% rename from prdoc/pr_5316.prdoc rename to prdoc/1.16.0/pr_5316.prdoc diff --git a/prdoc/pr_5326.prdoc b/prdoc/1.16.0/pr_5326.prdoc similarity index 100% rename from prdoc/pr_5326.prdoc rename to prdoc/1.16.0/pr_5326.prdoc diff --git a/prdoc/pr_5327.prdoc b/prdoc/1.16.0/pr_5327.prdoc similarity index 100% rename from prdoc/pr_5327.prdoc rename to prdoc/1.16.0/pr_5327.prdoc diff --git a/prdoc/pr_5339.prdoc b/prdoc/1.16.0/pr_5339.prdoc similarity index 100% rename from prdoc/pr_5339.prdoc rename to prdoc/1.16.0/pr_5339.prdoc diff --git a/prdoc/pr_5344.prdoc b/prdoc/1.16.0/pr_5344.prdoc similarity index 100% rename from prdoc/pr_5344.prdoc rename to prdoc/1.16.0/pr_5344.prdoc diff --git a/prdoc/pr_5348.prdoc b/prdoc/1.16.0/pr_5348.prdoc similarity index 100% rename from prdoc/pr_5348.prdoc rename to prdoc/1.16.0/pr_5348.prdoc diff --git a/prdoc/pr_5352.prdoc b/prdoc/1.16.0/pr_5352.prdoc similarity index 100% rename from prdoc/pr_5352.prdoc rename to prdoc/1.16.0/pr_5352.prdoc diff --git a/prdoc/pr_5354.prdoc b/prdoc/1.16.0/pr_5354.prdoc similarity index 100% rename from prdoc/pr_5354.prdoc rename to prdoc/1.16.0/pr_5354.prdoc diff --git a/prdoc/pr_5356.prdoc b/prdoc/1.16.0/pr_5356.prdoc similarity index 100% rename from prdoc/pr_5356.prdoc rename to prdoc/1.16.0/pr_5356.prdoc diff --git a/prdoc/pr_5359.prdoc b/prdoc/1.16.0/pr_5359.prdoc similarity index 100% rename from prdoc/pr_5359.prdoc rename to prdoc/1.16.0/pr_5359.prdoc diff --git a/prdoc/pr_5360.prdoc b/prdoc/1.16.0/pr_5360.prdoc similarity index 100% rename from prdoc/pr_5360.prdoc rename to prdoc/1.16.0/pr_5360.prdoc diff --git a/prdoc/pr_5364.prdoc b/prdoc/1.16.0/pr_5364.prdoc similarity index 100% rename from prdoc/pr_5364.prdoc rename to prdoc/1.16.0/pr_5364.prdoc diff --git a/prdoc/pr_5369.prdoc b/prdoc/1.16.0/pr_5369.prdoc similarity index 100% rename from prdoc/pr_5369.prdoc rename to prdoc/1.16.0/pr_5369.prdoc diff --git a/prdoc/pr_5376.prdoc b/prdoc/1.16.0/pr_5376.prdoc similarity index 100% rename from prdoc/pr_5376.prdoc rename to prdoc/1.16.0/pr_5376.prdoc diff --git a/prdoc/pr_5380.prdoc b/prdoc/1.16.0/pr_5380.prdoc similarity index 100% rename from prdoc/pr_5380.prdoc rename to prdoc/1.16.0/pr_5380.prdoc diff --git a/prdoc/pr_5384.prdoc b/prdoc/1.16.0/pr_5384.prdoc similarity index 100% rename from prdoc/pr_5384.prdoc rename to prdoc/1.16.0/pr_5384.prdoc diff --git a/prdoc/pr_5392.prdoc b/prdoc/1.16.0/pr_5392.prdoc similarity index 100% rename from prdoc/pr_5392.prdoc rename to prdoc/1.16.0/pr_5392.prdoc diff --git a/prdoc/pr_5393.prdoc b/prdoc/1.16.0/pr_5393.prdoc similarity index 100% rename from prdoc/pr_5393.prdoc rename to prdoc/1.16.0/pr_5393.prdoc diff --git a/prdoc/pr_5396.prdoc b/prdoc/1.16.0/pr_5396.prdoc similarity index 100% rename from prdoc/pr_5396.prdoc rename to prdoc/1.16.0/pr_5396.prdoc diff --git a/prdoc/pr_5407.prdoc b/prdoc/1.16.0/pr_5407.prdoc similarity index 100% rename from prdoc/pr_5407.prdoc rename to prdoc/1.16.0/pr_5407.prdoc diff --git a/prdoc/pr_5410.prdoc b/prdoc/1.16.0/pr_5410.prdoc similarity index 100% rename from prdoc/pr_5410.prdoc rename to prdoc/1.16.0/pr_5410.prdoc diff --git a/prdoc/pr_5411.prdoc b/prdoc/1.16.0/pr_5411.prdoc similarity index 100% rename from prdoc/pr_5411.prdoc rename to prdoc/1.16.0/pr_5411.prdoc diff --git a/prdoc/pr_5424.prdoc b/prdoc/1.16.0/pr_5424.prdoc similarity index 100% rename from prdoc/pr_5424.prdoc rename to prdoc/1.16.0/pr_5424.prdoc diff --git a/prdoc/pr_5430.prdoc b/prdoc/1.16.0/pr_5430.prdoc similarity index 100% rename from prdoc/pr_5430.prdoc rename to prdoc/1.16.0/pr_5430.prdoc diff --git a/prdoc/pr_5431.prdoc b/prdoc/1.16.0/pr_5431.prdoc similarity index 100% rename from prdoc/pr_5431.prdoc rename to prdoc/1.16.0/pr_5431.prdoc diff --git a/prdoc/pr_5436.prdoc b/prdoc/1.16.0/pr_5436.prdoc similarity index 100% rename from prdoc/pr_5436.prdoc rename to prdoc/1.16.0/pr_5436.prdoc diff --git a/prdoc/pr_5439.prdoc b/prdoc/1.16.0/pr_5439.prdoc similarity index 100% rename from prdoc/pr_5439.prdoc rename to prdoc/1.16.0/pr_5439.prdoc diff --git a/prdoc/pr_5442.prdoc b/prdoc/1.16.0/pr_5442.prdoc similarity index 100% rename from prdoc/pr_5442.prdoc rename to prdoc/1.16.0/pr_5442.prdoc diff --git a/prdoc/pr_5443.prdoc b/prdoc/1.16.0/pr_5443.prdoc similarity index 100% rename from prdoc/pr_5443.prdoc rename to prdoc/1.16.0/pr_5443.prdoc diff --git a/prdoc/pr_5450.prdoc b/prdoc/1.16.0/pr_5450.prdoc similarity index 100% rename from prdoc/pr_5450.prdoc rename to prdoc/1.16.0/pr_5450.prdoc diff --git a/prdoc/pr_5465.prdoc b/prdoc/1.16.0/pr_5465.prdoc similarity index 100% rename from prdoc/pr_5465.prdoc rename to prdoc/1.16.0/pr_5465.prdoc diff --git a/prdoc/pr_5466.prdoc b/prdoc/1.16.0/pr_5466.prdoc similarity index 100% rename from prdoc/pr_5466.prdoc rename to prdoc/1.16.0/pr_5466.prdoc diff --git a/prdoc/pr_5467.prdoc b/prdoc/1.16.0/pr_5467.prdoc similarity index 100% rename from prdoc/pr_5467.prdoc rename to prdoc/1.16.0/pr_5467.prdoc diff --git a/prdoc/pr_5509.prdoc b/prdoc/1.16.0/pr_5509.prdoc similarity index 100% rename from prdoc/pr_5509.prdoc rename to prdoc/1.16.0/pr_5509.prdoc diff --git a/prdoc/pr_5513.prdoc b/prdoc/1.16.0/pr_5513.prdoc similarity index 100% rename from prdoc/pr_5513.prdoc rename to prdoc/1.16.0/pr_5513.prdoc diff --git a/prdoc/pr_5527.prdoc b/prdoc/1.16.0/pr_5527.prdoc similarity index 100% rename from prdoc/pr_5527.prdoc rename to prdoc/1.16.0/pr_5527.prdoc diff --git a/prdoc/pr_5538.prdoc b/prdoc/1.16.0/pr_5538.prdoc similarity index 100% rename from prdoc/pr_5538.prdoc rename to prdoc/1.16.0/pr_5538.prdoc diff --git a/prdoc/pr_5546.prdoc b/prdoc/1.16.0/pr_5546.prdoc similarity index 100% rename from prdoc/pr_5546.prdoc rename to prdoc/1.16.0/pr_5546.prdoc diff --git a/prdoc/pr_5563.prdoc b/prdoc/1.16.0/pr_5563.prdoc similarity index 100% rename from prdoc/pr_5563.prdoc rename to prdoc/1.16.0/pr_5563.prdoc diff --git a/prdoc/pr_5580.prdoc b/prdoc/1.16.0/pr_5580.prdoc similarity index 100% rename from prdoc/pr_5580.prdoc rename to prdoc/1.16.0/pr_5580.prdoc diff --git a/prdoc/pr_5581.prdoc b/prdoc/1.16.0/pr_5581.prdoc similarity index 100% rename from prdoc/pr_5581.prdoc rename to prdoc/1.16.0/pr_5581.prdoc diff --git a/prdoc/pr_5594.prdoc b/prdoc/1.16.0/pr_5594.prdoc similarity index 100% rename from prdoc/pr_5594.prdoc rename to prdoc/1.16.0/pr_5594.prdoc diff --git a/prdoc/pr_5632.prdoc b/prdoc/1.16.0/pr_5632.prdoc similarity index 100% rename from prdoc/pr_5632.prdoc rename to prdoc/1.16.0/pr_5632.prdoc diff --git a/prdoc/pr_5644.prdoc b/prdoc/1.16.0/pr_5644.prdoc similarity index 100% rename from prdoc/pr_5644.prdoc rename to prdoc/1.16.0/pr_5644.prdoc diff --git a/prdoc/pr_5649.prdoc b/prdoc/1.16.0/pr_5649.prdoc similarity index 100% rename from prdoc/pr_5649.prdoc rename to prdoc/1.16.0/pr_5649.prdoc diff --git a/prdoc/pr_5655.prdoc b/prdoc/1.16.0/pr_5655.prdoc similarity index 100% rename from prdoc/pr_5655.prdoc rename to prdoc/1.16.0/pr_5655.prdoc diff --git a/prdoc/pr_5660.prdoc b/prdoc/1.16.0/pr_5660.prdoc similarity index 100% rename from prdoc/pr_5660.prdoc rename to prdoc/1.16.0/pr_5660.prdoc diff --git a/prdoc/pr_5671.prdoc b/prdoc/1.16.0/pr_5671.prdoc similarity index 100% rename from prdoc/pr_5671.prdoc rename to prdoc/1.16.0/pr_5671.prdoc diff --git a/prdoc/pr_5678.prdoc b/prdoc/1.16.0/pr_5678.prdoc similarity index 100% rename from prdoc/pr_5678.prdoc rename to prdoc/1.16.0/pr_5678.prdoc diff --git a/prdoc/pr_5688.prdoc b/prdoc/1.16.0/pr_5688.prdoc similarity index 100% rename from prdoc/pr_5688.prdoc rename to prdoc/1.16.0/pr_5688.prdoc diff --git a/prdoc/pr_5695.prdoc b/prdoc/1.16.0/pr_5695.prdoc similarity index 100% rename from prdoc/pr_5695.prdoc rename to prdoc/1.16.0/pr_5695.prdoc diff --git a/prdoc/pr_5712.prdoc b/prdoc/1.16.0/pr_5712.prdoc similarity index 100% rename from prdoc/pr_5712.prdoc rename to prdoc/1.16.0/pr_5712.prdoc diff --git a/prdoc/pr_5713.prdoc b/prdoc/1.16.0/pr_5713.prdoc similarity index 100% rename from prdoc/pr_5713.prdoc rename to prdoc/1.16.0/pr_5713.prdoc diff --git a/prdoc/pr_5747.prdoc b/prdoc/1.16.0/pr_5747.prdoc similarity index 100% rename from prdoc/pr_5747.prdoc rename to prdoc/1.16.0/pr_5747.prdoc -- GitLab From a8d8596fd2dc36aa7c4e1bb63536c30fef2855ea Mon Sep 17 00:00:00 2001 From: Joseph Zhao <65984904+programskillforverification@users.noreply.github.com> Date: Mon, 30 Sep 2024 22:15:11 +0800 Subject: [PATCH 124/125] Replace lazy_static with LazyLock (#5716) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description close #5641 --------- Co-authored-by: Bastian Köcher --- Cargo.lock | 17 --- Cargo.toml | 1 - cumulus/pallets/parachain-system/Cargo.toml | 1 - cumulus/pallets/parachain-system/src/tests.rs | 12 +- cumulus/xcm/xcm-emulator/Cargo.toml | 1 - cumulus/xcm/xcm-emulator/src/lib.rs | 46 ++++---- polkadot/node/jaeger/Cargo.toml | 1 - polkadot/node/jaeger/src/lib.rs | 9 +- .../network/dispute-distribution/Cargo.toml | 1 - .../dispute-distribution/src/tests/mock.rs | 103 +++++++++--------- .../node/network/gossip-support/Cargo.toml | 1 - .../node/network/gossip-support/src/tests.rs | 49 ++++----- polkadot/node/primitives/src/lib.rs | 2 +- polkadot/node/subsystem-util/Cargo.toml | 1 - .../node/zombienet-backchannel/Cargo.toml | 1 - .../node/zombienet-backchannel/src/lib.rs | 5 +- prdoc/pr_5716.prdoc | 37 +++++++ substrate/bin/node/bench/Cargo.toml | 1 - substrate/bin/node/bench/src/trie.rs | 13 ++- substrate/client/offchain/Cargo.toml | 6 +- substrate/client/offchain/src/api/http.rs | 8 +- substrate/client/tracing/Cargo.toml | 6 +- substrate/client/utils/Cargo.toml | 1 - substrate/client/utils/src/metrics.rs | 47 ++++---- .../primitives/consensus/beefy/Cargo.toml | 2 - .../consensus/beefy/src/test_utils.rs | 17 +-- substrate/primitives/core/Cargo.toml | 18 ++- substrate/primitives/core/fuzz/Cargo.toml | 1 - .../fuzz/fuzz_targets/fuzz_address_uri.rs | 9 +- substrate/primitives/core/src/address_uri.rs | 9 +- substrate/primitives/panic-handler/Cargo.toml | 1 - substrate/primitives/panic-handler/src/lib.rs | 12 +- substrate/primitives/trie/Cargo.toml | 2 - .../primitives/trie/src/cache/shared_cache.rs | 14 +-- .../utils/frame/benchmarking-cli/Cargo.toml | 1 - .../benchmarking-cli/src/machine/hardware.rs | 20 ++-- 36 files changed, 240 insertions(+), 236 deletions(-) create mode 100644 prdoc/pr_5716.prdoc diff --git a/Cargo.lock b/Cargo.lock index 97bdc935135..7eb88c85bc8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4353,7 +4353,6 @@ dependencies = [ "futures", "hex-literal", "impl-trait-for-tuples", - "lazy_static", "log", "pallet-message-queue", "parity-scale-codec", @@ -6210,7 +6209,6 @@ dependencies = [ "gethostname", "handlebars", "itertools 0.11.0", - "lazy_static", "linked-hash-map", "log", "parity-scale-codec", @@ -10119,7 +10117,6 @@ dependencies = [ "kitchensink-runtime", "kvdb", "kvdb-rocksdb", - "lazy_static", "log", "node-primitives", "node-testing", @@ -14104,7 +14101,6 @@ dependencies = [ "futures", "futures-timer", "indexmap 2.2.3", - "lazy_static", "parity-scale-codec", "polkadot-erasure-coding", "polkadot-node-network-protocol", @@ -14148,7 +14144,6 @@ dependencies = [ "async-trait", "futures", "futures-timer", - "lazy_static", "parking_lot 0.12.3", "polkadot-node-network-protocol", "polkadot-node-subsystem", @@ -14696,7 +14691,6 @@ dependencies = [ name = "polkadot-node-jaeger" version = "7.0.0" dependencies = [ - "lazy_static", "log", "mick-jaeger", "parity-scale-codec", @@ -14863,7 +14857,6 @@ dependencies = [ "kvdb", "kvdb-memorydb", "kvdb-shared-tests", - "lazy_static", "log", "parity-db", "parity-scale-codec", @@ -19432,7 +19425,6 @@ dependencies = [ "futures-timer", "hyper 0.14.29", "hyper-rustls 0.24.2", - "lazy_static", "log", "num_cpus", "once_cell", @@ -19815,7 +19807,6 @@ dependencies = [ "console", "criterion", "is-terminal", - "lazy_static", "libc", "log", "parity-scale-codec", @@ -19904,7 +19895,6 @@ dependencies = [ "async-channel 1.9.0", "futures", "futures-timer", - "lazy_static", "log", "parking_lot 0.12.3", "prometheus", @@ -21752,7 +21742,6 @@ name = "sp-consensus-beefy" version = "13.0.0" dependencies = [ "array-bytes", - "lazy_static", "parity-scale-codec", "scale-info", "serde", @@ -21838,7 +21827,6 @@ dependencies = [ "impl-serde", "itertools 0.11.0", "k256", - "lazy_static", "libsecp256k1", "log", "merlin", @@ -22014,7 +22002,6 @@ dependencies = [ name = "sp-core-fuzz" version = "0.0.0" dependencies = [ - "lazy_static", "libfuzzer-sys", "regex", "sp-core 28.0.0", @@ -22494,7 +22481,6 @@ name = "sp-panic-handler" version = "13.0.0" dependencies = [ "backtrace", - "lazy_static", "regex", ] @@ -23086,7 +23072,6 @@ dependencies = [ "array-bytes", "criterion", "hash-db", - "lazy_static", "memory-db", "nohash-hasher", "parity-scale-codec", @@ -27020,7 +27005,6 @@ dependencies = [ "frame-support", "frame-system", "impl-trait-for-tuples", - "lazy_static", "log", "pallet-balances", "pallet-message-queue", @@ -27270,7 +27254,6 @@ name = "zombienet-backchannel" version = "1.0.0" dependencies = [ "futures-util", - "lazy_static", "parity-scale-codec", "reqwest 0.11.20", "serde", diff --git a/Cargo.toml b/Cargo.toml index a30b5b57d36..155cd2cf5ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -826,7 +826,6 @@ kvdb-memorydb = { version = "0.13.0" } kvdb-rocksdb = { version = "0.19.0" } kvdb-shared-tests = { version = "0.11.0" } landlock = { version = "0.3.0" } -lazy_static = { version = "1.5.0" } libc = { version = "0.2.155" } libfuzzer-sys = { version = "0.4" } libp2p = { version = "0.52.4" } diff --git a/cumulus/pallets/parachain-system/Cargo.toml b/cumulus/pallets/parachain-system/Cargo.toml index 30a232f01b3..66429625d5b 100644 --- a/cumulus/pallets/parachain-system/Cargo.toml +++ b/cumulus/pallets/parachain-system/Cargo.toml @@ -49,7 +49,6 @@ cumulus-primitives-proof-size-hostfunction = { workspace = true } [dev-dependencies] assert_matches = { workspace = true } hex-literal = { workspace = true, default-features = true } -lazy_static = { workspace = true } trie-standardmap = { workspace = true } rand = { workspace = true, default-features = true } futures = { workspace = true } diff --git a/cumulus/pallets/parachain-system/src/tests.rs b/cumulus/pallets/parachain-system/src/tests.rs index 548231966e4..23223627ebc 100755 --- a/cumulus/pallets/parachain-system/src/tests.rs +++ b/cumulus/pallets/parachain-system/src/tests.rs @@ -754,12 +754,8 @@ fn message_queue_chain() { #[test] #[cfg(not(feature = "runtime-benchmarks"))] fn receive_dmp() { - lazy_static::lazy_static! { - static ref MSG: InboundDownwardMessage = InboundDownwardMessage { - sent_at: 1, - msg: b"down".to_vec(), - }; - } + static MSG: std::sync::LazyLock = + std::sync::LazyLock::new(|| InboundDownwardMessage { sent_at: 1, msg: b"down".to_vec() }); BlockTests::new() .with_relay_sproof_builder(|_, relay_block_num, sproof| match relay_block_num { @@ -771,14 +767,14 @@ fn receive_dmp() { }) .with_inherent_data(|_, relay_block_num, data| match relay_block_num { 1 => { - data.downward_messages.push(MSG.clone()); + data.downward_messages.push((*MSG).clone()); }, _ => unreachable!(), }) .add(1, || { HANDLED_DMP_MESSAGES.with(|m| { let mut m = m.borrow_mut(); - assert_eq!(&*m, &[(MSG.msg.clone())]); + assert_eq!(&*m, &[MSG.msg.clone()]); m.clear(); }); }); diff --git a/cumulus/xcm/xcm-emulator/Cargo.toml b/cumulus/xcm/xcm-emulator/Cargo.toml index 6924f11292d..8598481fae7 100644 --- a/cumulus/xcm/xcm-emulator/Cargo.toml +++ b/cumulus/xcm/xcm-emulator/Cargo.toml @@ -13,7 +13,6 @@ workspace = true codec = { workspace = true, default-features = true } paste = { workspace = true, default-features = true } log = { workspace = true } -lazy_static = { workspace = true } impl-trait-for-tuples = { workspace = true } array-bytes = { workspace = true } diff --git a/cumulus/xcm/xcm-emulator/src/lib.rs b/cumulus/xcm/xcm-emulator/src/lib.rs index d393d453773..bb2945dc267 100644 --- a/cumulus/xcm/xcm-emulator/src/lib.rs +++ b/cumulus/xcm/xcm-emulator/src/lib.rs @@ -18,12 +18,16 @@ extern crate alloc; pub use array_bytes; pub use codec::{Decode, Encode, EncodeLike, MaxEncodedLen}; -pub use lazy_static::lazy_static; pub use log; pub use paste; pub use std::{ - any::type_name, collections::HashMap, error::Error, fmt, marker::PhantomData, ops::Deref, - sync::Mutex, + any::type_name, + collections::HashMap, + error::Error, + fmt, + marker::PhantomData, + ops::Deref, + sync::{LazyLock, Mutex}, }; // Substrate @@ -443,10 +447,8 @@ macro_rules! __impl_test_ext_for_relay_chain { = $crate::RefCell::new($crate::TestExternalities::new($genesis)); } - $crate::lazy_static! { - pub static ref $global_ext: $crate::Mutex<$crate::RefCell<$crate::HashMap>> - = $crate::Mutex::new($crate::RefCell::new($crate::HashMap::new())); - } + pub static $global_ext: $crate::LazyLock<$crate::Mutex<$crate::RefCell<$crate::HashMap>>> + = $crate::LazyLock::new(|| $crate::Mutex::new($crate::RefCell::new($crate::HashMap::new()))); impl<$network: $crate::Network> $crate::TestExt for $name<$network> { fn build_new_ext(storage: $crate::Storage) -> $crate::TestExternalities { @@ -478,10 +480,10 @@ macro_rules! __impl_test_ext_for_relay_chain { v.take() }); - // Get TestExternality from lazy_static + // Get TestExternality from LazyLock let global_ext_guard = $global_ext.lock().unwrap(); - // Replace TestExternality in lazy_static by TestExternality from thread_local + // Replace TestExternality in LazyLock by TestExternality from thread_local global_ext_guard.deref().borrow_mut().insert(id.to_string(), local_ext); } @@ -490,10 +492,10 @@ macro_rules! __impl_test_ext_for_relay_chain { let mut global_ext_unlocked = false; - // Keep the mutex unlocked until TesExternality from lazy_static + // Keep the mutex unlocked until TesExternality from LazyLock // has been updated while !global_ext_unlocked { - // Get TesExternality from lazy_static + // Get TesExternality from LazyLock let global_ext_result = $global_ext.try_lock(); if let Ok(global_ext_guard) = global_ext_result { @@ -506,10 +508,10 @@ macro_rules! __impl_test_ext_for_relay_chain { } } - // Now that we know that lazy_static TestExt has been updated, we lock its mutex + // Now that we know that TestExt has been updated, we lock its mutex let mut global_ext_guard = $global_ext.lock().unwrap(); - // and set TesExternality from lazy_static into TesExternality for local_thread + // and set TesExternality from LazyLock into TesExternality for local_thread let global_ext = global_ext_guard.deref(); $local_ext.with(|v| { @@ -744,10 +746,8 @@ macro_rules! __impl_test_ext_for_parachain { = $crate::RefCell::new($crate::TestExternalities::new($genesis)); } - $crate::lazy_static! { - pub static ref $global_ext: $crate::Mutex<$crate::RefCell<$crate::HashMap>> - = $crate::Mutex::new($crate::RefCell::new($crate::HashMap::new())); - } + pub static $global_ext: $crate::LazyLock<$crate::Mutex<$crate::RefCell<$crate::HashMap>>> + = $crate::LazyLock::new(|| $crate::Mutex::new($crate::RefCell::new($crate::HashMap::new()))); impl<$network: $crate::Network> $crate::TestExt for $name<$network> { fn build_new_ext(storage: $crate::Storage) -> $crate::TestExternalities { @@ -777,10 +777,10 @@ macro_rules! __impl_test_ext_for_parachain { v.take() }); - // Get TestExternality from lazy_static + // Get TestExternality from LazyLock let global_ext_guard = $global_ext.lock().unwrap(); - // Replace TestExternality in lazy_static by TestExternality from thread_local + // Replace TestExternality in LazyLock by TestExternality from thread_local global_ext_guard.deref().borrow_mut().insert(id.to_string(), local_ext); } @@ -789,10 +789,10 @@ macro_rules! __impl_test_ext_for_parachain { let mut global_ext_unlocked = false; - // Keep the mutex unlocked until TesExternality from lazy_static + // Keep the mutex unlocked until TesExternality from LazyLock // has been updated while !global_ext_unlocked { - // Get TesExternality from lazy_static + // Get TesExternality from LazyLock let global_ext_result = $global_ext.try_lock(); if let Ok(global_ext_guard) = global_ext_result { @@ -805,10 +805,10 @@ macro_rules! __impl_test_ext_for_parachain { } } - // Now that we know that lazy_static TestExt has been updated, we lock its mutex + // Now that we know that TestExt has been updated, we lock its mutex let mut global_ext_guard = $global_ext.lock().unwrap(); - // and set TesExternality from lazy_static into TesExternality for local_thread + // and set TesExternality from LazyLock into TesExternality for local_thread let global_ext = global_ext_guard.deref(); $local_ext.with(|v| { diff --git a/polkadot/node/jaeger/Cargo.toml b/polkadot/node/jaeger/Cargo.toml index 90a6c80e3d0..8615041bdaf 100644 --- a/polkadot/node/jaeger/Cargo.toml +++ b/polkadot/node/jaeger/Cargo.toml @@ -11,7 +11,6 @@ workspace = true [dependencies] mick-jaeger = { workspace = true } -lazy_static = { workspace = true } parking_lot = { workspace = true, default-features = true } polkadot-primitives = { workspace = true, default-features = true } polkadot-node-primitives = { workspace = true, default-features = true } diff --git a/polkadot/node/jaeger/src/lib.rs b/polkadot/node/jaeger/src/lib.rs index 7de45860681..2ce5b8b6eb6 100644 --- a/polkadot/node/jaeger/src/lib.rs +++ b/polkadot/node/jaeger/src/lib.rs @@ -61,11 +61,12 @@ use self::spans::TraceIdentifier; use sp_core::traits::SpawnNamed; use parking_lot::RwLock; -use std::{result, sync::Arc}; +use std::{ + result, + sync::{Arc, LazyLock}, +}; -lazy_static::lazy_static! { - static ref INSTANCE: RwLock = RwLock::new(Jaeger::None); -} +static INSTANCE: LazyLock> = LazyLock::new(|| RwLock::new(Jaeger::None)); /// Stateful convenience wrapper around [`mick_jaeger`]. pub enum Jaeger { diff --git a/polkadot/node/network/dispute-distribution/Cargo.toml b/polkadot/node/network/dispute-distribution/Cargo.toml index ccf1b5daad7..b4dcafe09eb 100644 --- a/polkadot/node/network/dispute-distribution/Cargo.toml +++ b/polkadot/node/network/dispute-distribution/Cargo.toml @@ -38,5 +38,4 @@ sp-tracing = { workspace = true, default-features = true } sc-keystore = { workspace = true, default-features = true } futures-timer = { workspace = true } assert_matches = { workspace = true } -lazy_static = { workspace = true } polkadot-primitives-test-helpers = { workspace = true } diff --git a/polkadot/node/network/dispute-distribution/src/tests/mock.rs b/polkadot/node/network/dispute-distribution/src/tests/mock.rs index ccc050233e8..baa857e2eb6 100644 --- a/polkadot/node/network/dispute-distribution/src/tests/mock.rs +++ b/polkadot/node/network/dispute-distribution/src/tests/mock.rs @@ -19,12 +19,11 @@ use std::{ collections::{HashMap, HashSet}, - sync::Arc, + sync::{Arc, LazyLock}, time::Instant, }; use async_trait::async_trait; -use lazy_static::lazy_static; use polkadot_node_network_protocol::{authority_discovery::AuthorityDiscovery, PeerId}; use sc_keystore::LocalKeystore; @@ -60,64 +59,60 @@ pub const ALICE_INDEX: ValidatorIndex = ValidatorIndex(1); pub const BOB_INDEX: ValidatorIndex = ValidatorIndex(2); pub const CHARLIE_INDEX: ValidatorIndex = ValidatorIndex(3); -lazy_static! { - /// Mocked `AuthorityDiscovery` service. -pub static ref MOCK_AUTHORITY_DISCOVERY: MockAuthorityDiscovery = MockAuthorityDiscovery::new(); +pub static MOCK_AUTHORITY_DISCOVERY: LazyLock = + LazyLock::new(|| MockAuthorityDiscovery::new()); // Creating an innocent looking `SessionInfo` is really expensive in a debug build. Around // 700ms on my machine, We therefore cache those keys here: -pub static ref MOCK_VALIDATORS_DISCOVERY_KEYS: HashMap = - MOCK_VALIDATORS - .iter() - .chain(MOCK_AUTHORITIES_NEXT_SESSION.iter()) - .map(|v| (*v, v.public().into())) - .collect() -; -pub static ref FERDIE_DISCOVERY_KEY: AuthorityDiscoveryId = - MOCK_VALIDATORS_DISCOVERY_KEYS.get(&Sr25519Keyring::Ferdie).unwrap().clone(); - -pub static ref MOCK_SESSION_INFO: SessionInfo = - SessionInfo { - validators: MOCK_VALIDATORS.iter().take(4).map(|k| k.public().into()).collect(), - discovery_keys: MOCK_VALIDATORS +pub static MOCK_VALIDATORS_DISCOVERY_KEYS: LazyLock> = + LazyLock::new(|| { + MOCK_VALIDATORS .iter() - .map(|k| MOCK_VALIDATORS_DISCOVERY_KEYS.get(&k).unwrap().clone()) - .collect(), - assignment_keys: vec![], - validator_groups: Default::default(), - n_cores: 0, - zeroth_delay_tranche_width: 0, - relay_vrf_modulo_samples: 0, - n_delay_tranches: 0, - no_show_slots: 0, - needed_approvals: 0, - active_validator_indices: vec![], - dispute_period: 6, - random_seed: [0u8; 32], - }; + .chain(MOCK_AUTHORITIES_NEXT_SESSION.iter()) + .map(|v| (*v, v.public().into())) + .collect() + }); +pub static FERDIE_DISCOVERY_KEY: LazyLock = + LazyLock::new(|| MOCK_VALIDATORS_DISCOVERY_KEYS.get(&Sr25519Keyring::Ferdie).unwrap().clone()); + +pub static MOCK_SESSION_INFO: LazyLock = LazyLock::new(|| SessionInfo { + validators: MOCK_VALIDATORS.iter().take(4).map(|k| k.public().into()).collect(), + discovery_keys: MOCK_VALIDATORS + .iter() + .map(|k| MOCK_VALIDATORS_DISCOVERY_KEYS.get(&k).unwrap().clone()) + .collect(), + assignment_keys: vec![], + validator_groups: Default::default(), + n_cores: 0, + zeroth_delay_tranche_width: 0, + relay_vrf_modulo_samples: 0, + n_delay_tranches: 0, + no_show_slots: 0, + needed_approvals: 0, + active_validator_indices: vec![], + dispute_period: 6, + random_seed: [0u8; 32], +}); /// `SessionInfo` for the second session. (No more validators, but two more authorities. -pub static ref MOCK_NEXT_SESSION_INFO: SessionInfo = - SessionInfo { - discovery_keys: - MOCK_AUTHORITIES_NEXT_SESSION - .iter() - .map(|k| MOCK_VALIDATORS_DISCOVERY_KEYS.get(&k).unwrap().clone()) - .collect(), - validators: Default::default(), - assignment_keys: vec![], - validator_groups: Default::default(), - n_cores: 0, - zeroth_delay_tranche_width: 0, - relay_vrf_modulo_samples: 0, - n_delay_tranches: 0, - no_show_slots: 0, - needed_approvals: 0, - active_validator_indices: vec![], - dispute_period: 6, - random_seed: [0u8; 32], - }; -} +pub static MOCK_NEXT_SESSION_INFO: LazyLock = LazyLock::new(|| SessionInfo { + discovery_keys: MOCK_AUTHORITIES_NEXT_SESSION + .iter() + .map(|k| MOCK_VALIDATORS_DISCOVERY_KEYS.get(&k).unwrap().clone()) + .collect(), + validators: Default::default(), + assignment_keys: vec![], + validator_groups: Default::default(), + n_cores: 0, + zeroth_delay_tranche_width: 0, + relay_vrf_modulo_samples: 0, + n_delay_tranches: 0, + no_show_slots: 0, + needed_approvals: 0, + active_validator_indices: vec![], + dispute_period: 6, + random_seed: [0u8; 32], +}); pub fn make_candidate_receipt(relay_parent: Hash) -> CandidateReceipt { CandidateReceipt { diff --git a/polkadot/node/network/gossip-support/Cargo.toml b/polkadot/node/network/gossip-support/Cargo.toml index 83fdc7e2619..c8c19e5de07 100644 --- a/polkadot/node/network/gossip-support/Cargo.toml +++ b/polkadot/node/network/gossip-support/Cargo.toml @@ -39,5 +39,4 @@ polkadot-node-subsystem-test-helpers = { workspace = true } assert_matches = { workspace = true } async-trait = { workspace = true } parking_lot = { workspace = true, default-features = true } -lazy_static = { workspace = true } quickcheck = { workspace = true, default-features = true } diff --git a/polkadot/node/network/gossip-support/src/tests.rs b/polkadot/node/network/gossip-support/src/tests.rs index 09622254f52..399f29db67d 100644 --- a/polkadot/node/network/gossip-support/src/tests.rs +++ b/polkadot/node/network/gossip-support/src/tests.rs @@ -16,12 +16,11 @@ //! Unit tests for Gossip Support Subsystem. -use std::{collections::HashSet, time::Duration}; +use std::{collections::HashSet, sync::LazyLock, time::Duration}; use assert_matches::assert_matches; use async_trait::async_trait; use futures::{executor, future, Future}; -use lazy_static::lazy_static; use quickcheck::quickcheck; use rand::seq::SliceRandom as _; @@ -56,39 +55,29 @@ const AUTHORITY_KEYRINGS: &[Sr25519Keyring] = &[ Sr25519Keyring::Ferdie, ]; -lazy_static! { - static ref AUTHORITIES: Vec = - AUTHORITY_KEYRINGS.iter().map(|k| k.public().into()).collect(); +static AUTHORITIES: LazyLock> = + LazyLock::new(|| AUTHORITY_KEYRINGS.iter().map(|k| k.public().into()).collect()); - static ref AUTHORITIES_WITHOUT_US: Vec = { - let mut a = AUTHORITIES.clone(); - a.pop(); // remove FERDIE. - a - }; - - static ref PAST_PRESENT_FUTURE_AUTHORITIES: Vec = { - (0..50) - .map(|_| AuthorityDiscoveryPair::generate().0.public()) - .chain(AUTHORITIES.clone()) - .collect() - }; +static AUTHORITIES_WITHOUT_US: LazyLock> = LazyLock::new(|| { + let mut a = AUTHORITIES.clone(); + a.pop(); // remove FERDIE. + a +}); - // [2 6] - // [4 5] - // [1 3] - // [0 ] +static PAST_PRESENT_FUTURE_AUTHORITIES: LazyLock> = LazyLock::new(|| { + (0..50) + .map(|_| AuthorityDiscoveryPair::generate().0.public()) + .chain(AUTHORITIES.clone()) + .collect() +}); - static ref EXPECTED_SHUFFLING: Vec = vec![6, 4, 0, 5, 2, 3, 1]; +static EXPECTED_SHUFFLING: LazyLock> = LazyLock::new(|| vec![6, 4, 0, 5, 2, 3, 1]); - static ref ROW_NEIGHBORS: Vec = vec![ - ValidatorIndex::from(2), - ]; +static ROW_NEIGHBORS: LazyLock> = + LazyLock::new(|| vec![ValidatorIndex::from(2)]); - static ref COLUMN_NEIGHBORS: Vec = vec![ - ValidatorIndex::from(3), - ValidatorIndex::from(5), - ]; -} +static COLUMN_NEIGHBORS: LazyLock> = + LazyLock::new(|| vec![ValidatorIndex::from(3), ValidatorIndex::from(5)]); type VirtualOverseer = polkadot_node_subsystem_test_helpers::TestSubsystemContextHandle; diff --git a/polkadot/node/primitives/src/lib.rs b/polkadot/node/primitives/src/lib.rs index 5b6bcdaa9d7..99d3a3e515b 100644 --- a/polkadot/node/primitives/src/lib.rs +++ b/polkadot/node/primitives/src/lib.rs @@ -105,7 +105,7 @@ pub const MAX_FINALITY_LAG: u32 = 500; /// Type of a session window size. /// /// We are not using `NonZeroU32` here because `expect` and `unwrap` are not yet const, so global -/// constants of `SessionWindowSize` would require `lazy_static` in that case. +/// constants of `SessionWindowSize` would require `LazyLock` in that case. /// /// See: #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] diff --git a/polkadot/node/subsystem-util/Cargo.toml b/polkadot/node/subsystem-util/Cargo.toml index a7157d1b5b7..3bd3892ba60 100644 --- a/polkadot/node/subsystem-util/Cargo.toml +++ b/polkadot/node/subsystem-util/Cargo.toml @@ -48,7 +48,6 @@ assert_matches = { workspace = true } futures = { features = ["thread-pool"], workspace = true } log = { workspace = true, default-features = true } polkadot-node-subsystem-test-helpers = { workspace = true } -lazy_static = { workspace = true } polkadot-primitives-test-helpers = { workspace = true } kvdb-shared-tests = { workspace = true } tempfile = { workspace = true } diff --git a/polkadot/node/zombienet-backchannel/Cargo.toml b/polkadot/node/zombienet-backchannel/Cargo.toml index a9bf1f5ef09..56c49a1ec30 100644 --- a/polkadot/node/zombienet-backchannel/Cargo.toml +++ b/polkadot/node/zombienet-backchannel/Cargo.toml @@ -16,7 +16,6 @@ tokio = { features = ["macros", "net", "rt-multi-thread", "sync"], workspace = t url = { workspace = true } tokio-tungstenite = { workspace = true } futures-util = { workspace = true, default-features = true } -lazy_static = { workspace = true } codec = { features = ["derive"], workspace = true, default-features = true } reqwest = { features = ["rustls-tls"], workspace = true } thiserror = { workspace = true } diff --git a/polkadot/node/zombienet-backchannel/src/lib.rs b/polkadot/node/zombienet-backchannel/src/lib.rs index 9068b03399c..080dcf1c2b7 100644 --- a/polkadot/node/zombienet-backchannel/src/lib.rs +++ b/polkadot/node/zombienet-backchannel/src/lib.rs @@ -21,7 +21,6 @@ use codec; use futures_util::{SinkExt, StreamExt}; -use lazy_static::lazy_static; use serde::{Deserialize, Serialize}; use std::{env, sync::Mutex}; use tokio::sync::broadcast; @@ -30,9 +29,7 @@ use tokio_tungstenite::{connect_async, tungstenite::protocol::Message}; mod errors; use errors::BackchannelError; -lazy_static! { - pub static ref ZOMBIENET_BACKCHANNEL: Mutex> = Mutex::new(None); -} +pub static ZOMBIENET_BACKCHANNEL: Mutex> = Mutex::new(None); #[derive(Debug)] pub struct ZombienetBackchannel { diff --git a/prdoc/pr_5716.prdoc b/prdoc/pr_5716.prdoc new file mode 100644 index 00000000000..a9866623372 --- /dev/null +++ b/prdoc/pr_5716.prdoc @@ -0,0 +1,37 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Replace `lazy_static` with `LazyLock` + +doc: + - audience: Node Dev + description: | + Replace all lazy_static usages with LazyLock from the Rust standard library. This will bring us less dependencies. + +crates: + - name: sp-core + bump: patch + - name: sp-panic-handler + bump: patch + - name: sp-trie + bump: patch + - name: sc-utils + bump: major + - name: cumulus-pallet-parachain-system + bump: patch + - name: sp-consensus-beefy + bump: patch + - name: polkadot-node-primitives + bump: patch + - name: polkadot-node-jaeger + bump: patch + - name: frame-benchmarking-cli + bump: major + - name: sc-offchain + bump: patch + - name: polkadot-dispute-distribution + bump: patch + - name: polkadot-gossip-support + bump: patch + - name: xcm-emulator + bump: patch diff --git a/substrate/bin/node/bench/Cargo.toml b/substrate/bin/node/bench/Cargo.toml index 88ea908abc2..8c6556da682 100644 --- a/substrate/bin/node/bench/Cargo.toml +++ b/substrate/bin/node/bench/Cargo.toml @@ -40,7 +40,6 @@ hash-db = { workspace = true, default-features = true } tempfile = { workspace = true } fs_extra = { workspace = true } rand = { features = ["small_rng"], workspace = true, default-features = true } -lazy_static = { workspace = true } parity-db = { workspace = true } sc-transaction-pool = { workspace = true, default-features = true } sc-transaction-pool-api = { workspace = true, default-features = true } diff --git a/substrate/bin/node/bench/src/trie.rs b/substrate/bin/node/bench/src/trie.rs index 09ab405c03b..402a186767e 100644 --- a/substrate/bin/node/bench/src/trie.rs +++ b/substrate/bin/node/bench/src/trie.rs @@ -20,11 +20,14 @@ use hash_db::Prefix; use kvdb::KeyValueDB; -use lazy_static::lazy_static; use rand::Rng; use sp_state_machine::Backend as _; use sp_trie::{trie_types::TrieDBMutBuilderV1, TrieMut as _}; -use std::{borrow::Cow, collections::HashMap, sync::Arc}; +use std::{ + borrow::Cow, + collections::HashMap, + sync::{Arc, LazyLock}, +}; use node_primitives::Hash; @@ -57,10 +60,8 @@ pub enum DatabaseSize { Huge, } -lazy_static! { - static ref KUSAMA_STATE_DISTRIBUTION: SizePool = - SizePool::from_histogram(crate::state_sizes::KUSAMA_STATE_DISTRIBUTION); -} +static KUSAMA_STATE_DISTRIBUTION: LazyLock = + LazyLock::new(|| SizePool::from_histogram(crate::state_sizes::KUSAMA_STATE_DISTRIBUTION)); impl DatabaseSize { /// Should be multiple of SAMPLE_SIZE! diff --git a/substrate/client/offchain/Cargo.toml b/substrate/client/offchain/Cargo.toml index 4b5b04cca62..bbbe7018d10 100644 --- a/substrate/client/offchain/Cargo.toml +++ b/substrate/client/offchain/Cargo.toml @@ -22,7 +22,10 @@ codec = { features = ["derive"], workspace = true, default-features = true } fnv = { workspace = true } futures = { workspace = true } futures-timer = { workspace = true } -hyperv14 = { features = ["http2", "stream"], workspace = true, default-features = true } +hyperv14 = { features = [ + "http2", + "stream", +], workspace = true, default-features = true } hyper-rustls = { features = ["http2"], workspace = true } num_cpus = { workspace = true } once_cell = { workspace = true } @@ -46,7 +49,6 @@ log = { workspace = true, default-features = true } [dev-dependencies] async-trait = { workspace = true } -lazy_static = { workspace = true } tokio = { workspace = true, default-features = true } sc-block-builder = { workspace = true, default-features = true } sc-client-db = { default-features = true, workspace = true } diff --git a/substrate/client/offchain/src/api/http.rs b/substrate/client/offchain/src/api/http.rs index fda5728b0d0..73407b1359d 100644 --- a/substrate/client/offchain/src/api/http.rs +++ b/substrate/client/offchain/src/api/http.rs @@ -763,14 +763,12 @@ mod tests { use crate::api::timestamp; use core::convert::Infallible; use futures::{future, StreamExt}; - use lazy_static::lazy_static; use sp_core::offchain::{Duration, Externalities, HttpError, HttpRequestId, HttpRequestStatus}; + use std::sync::LazyLock; - // Using lazy_static to avoid spawning lots of different SharedClients, + // Using LazyLock to avoid spawning lots of different SharedClients, // as spawning a SharedClient is CPU-intensive and opens lots of fds. - lazy_static! { - static ref SHARED_CLIENT: SharedClient = SharedClient::new(); - } + static SHARED_CLIENT: LazyLock = LazyLock::new(|| SharedClient::new()); // Returns an `HttpApi` whose worker is ran in the background, and a `SocketAddr` to an HTTP // server that runs in the background as well. diff --git a/substrate/client/tracing/Cargo.toml b/substrate/client/tracing/Cargo.toml index 09571610a3a..b8f5e40caf8 100644 --- a/substrate/client/tracing/Cargo.toml +++ b/substrate/client/tracing/Cargo.toml @@ -20,7 +20,6 @@ console = { workspace = true } is-terminal = { workspace = true } chrono = { workspace = true } codec = { workspace = true, default-features = true } -lazy_static = { workspace = true } libc = { workspace = true } log = { workspace = true, default-features = true } parking_lot = { workspace = true, default-features = true } @@ -29,7 +28,10 @@ serde = { workspace = true, default-features = true } thiserror = { workspace = true } tracing = { workspace = true, default-features = true } tracing-log = { workspace = true } -tracing-subscriber = { workspace = true, features = ["env-filter", "parking_lot"] } +tracing-subscriber = { workspace = true, features = [ + "env-filter", + "parking_lot", +] } sc-client-api = { workspace = true, default-features = true } sc-tracing-proc-macro = { workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } diff --git a/substrate/client/utils/Cargo.toml b/substrate/client/utils/Cargo.toml index 6c3a2228952..485261058d5 100644 --- a/substrate/client/utils/Cargo.toml +++ b/substrate/client/utils/Cargo.toml @@ -16,7 +16,6 @@ workspace = true async-channel = { workspace = true } futures = { workspace = true } futures-timer = { workspace = true } -lazy_static = { workspace = true } log = { workspace = true, default-features = true } parking_lot = { workspace = true, default-features = true } prometheus = { workspace = true } diff --git a/substrate/client/utils/src/metrics.rs b/substrate/client/utils/src/metrics.rs index 308e90cb253..9b6e1e47039 100644 --- a/substrate/client/utils/src/metrics.rs +++ b/substrate/client/utils/src/metrics.rs @@ -18,42 +18,49 @@ //! Metering primitives and globals -use lazy_static::lazy_static; use prometheus::{ core::{AtomicU64, GenericCounter, GenericGauge}, Error as PrometheusError, Registry, }; +use std::sync::LazyLock; use prometheus::{ core::{GenericCounterVec, GenericGaugeVec}, Opts, }; -lazy_static! { - pub static ref TOKIO_THREADS_TOTAL: GenericCounter = - GenericCounter::new("substrate_tokio_threads_total", "Total number of threads created") - .expect("Creating of statics doesn't fail. qed"); - pub static ref TOKIO_THREADS_ALIVE: GenericGauge = - GenericGauge::new("substrate_tokio_threads_alive", "Number of threads alive right now") - .expect("Creating of statics doesn't fail. qed"); -} +pub static TOKIO_THREADS_TOTAL: LazyLock> = LazyLock::new(|| { + GenericCounter::new("substrate_tokio_threads_total", "Total number of threads created") + .expect("Creating of statics doesn't fail. qed") +}); -lazy_static! { - pub static ref UNBOUNDED_CHANNELS_COUNTER: GenericCounterVec = GenericCounterVec::new( - Opts::new( - "substrate_unbounded_channel_len", - "Items sent/received/dropped on each mpsc::unbounded instance" - ), - &["entity", "action"], // name of channel, send|received|dropped - ).expect("Creating of statics doesn't fail. qed"); - pub static ref UNBOUNDED_CHANNELS_SIZE: GenericGaugeVec = GenericGaugeVec::new( +pub static TOKIO_THREADS_ALIVE: LazyLock> = LazyLock::new(|| { + GenericGauge::new("substrate_tokio_threads_alive", "Number of threads alive right now") + .expect("Creating of statics doesn't fail. qed") +}); + +pub static UNBOUNDED_CHANNELS_COUNTER: LazyLock> = + LazyLock::new(|| { + GenericCounterVec::new( + Opts::new( + "substrate_unbounded_channel_len", + "Items sent/received/dropped on each mpsc::unbounded instance", + ), + &["entity", "action"], // name of channel, send|received|dropped + ) + .expect("Creating of statics doesn't fail. qed") + }); + +pub static UNBOUNDED_CHANNELS_SIZE: LazyLock> = LazyLock::new(|| { + GenericGaugeVec::new( Opts::new( "substrate_unbounded_channel_size", "Size (number of messages to be processed) of each mpsc::unbounded instance", ), &["entity"], // name of channel - ).expect("Creating of statics doesn't fail. qed"); -} + ) + .expect("Creating of statics doesn't fail. qed") +}); pub static SENT_LABEL: &'static str = "send"; pub static RECEIVED_LABEL: &'static str = "received"; diff --git a/substrate/primitives/consensus/beefy/Cargo.toml b/substrate/primitives/consensus/beefy/Cargo.toml index 57ddab9a70c..13d80683c85 100644 --- a/substrate/primitives/consensus/beefy/Cargo.toml +++ b/substrate/primitives/consensus/beefy/Cargo.toml @@ -28,7 +28,6 @@ sp-runtime = { workspace = true } sp-keystore = { workspace = true } sp-weights = { workspace = true } strum = { features = ["derive"], workspace = true } -lazy_static = { optional = true, workspace = true } [dev-dependencies] array-bytes = { workspace = true, default-features = true } @@ -38,7 +37,6 @@ w3f-bls = { features = ["std"], workspace = true, default-features = true } default = ["std"] std = [ "codec/std", - "dep:lazy_static", "scale-info/std", "serde/std", "sp-api/std", diff --git a/substrate/primitives/consensus/beefy/src/test_utils.rs b/substrate/primitives/consensus/beefy/src/test_utils.rs index bd335ede489..4460bcefd45 100644 --- a/substrate/primitives/consensus/beefy/src/test_utils.rs +++ b/substrate/primitives/consensus/beefy/src/test_utils.rs @@ -26,7 +26,7 @@ use sp_core::{ecdsa, Pair}; use sp_runtime::traits::{BlockNumber, Hash, Header as HeaderT}; use codec::Encode; -use std::{collections::HashMap, marker::PhantomData}; +use std::{collections::HashMap, marker::PhantomData, sync::LazyLock}; use strum::IntoEnumIterator; /// Set of test accounts using [`crate::ecdsa_crypto`] types. @@ -111,12 +111,15 @@ where } } -lazy_static::lazy_static! { - static ref PRIVATE_KEYS: HashMap, ecdsa_crypto::Pair> = - Keyring::iter().map(|i| (i.clone(), i.pair())).collect(); - static ref PUBLIC_KEYS: HashMap, ecdsa_crypto::Public> = - PRIVATE_KEYS.iter().map(|(name, pair)| (name.clone(), sp_application_crypto::Pair::public(pair))).collect(); -} +static PRIVATE_KEYS: LazyLock, ecdsa_crypto::Pair>> = + LazyLock::new(|| Keyring::iter().map(|i| (i.clone(), i.pair())).collect()); +static PUBLIC_KEYS: LazyLock, ecdsa_crypto::Public>> = + LazyLock::new(|| { + PRIVATE_KEYS + .iter() + .map(|(name, pair)| (name.clone(), sp_application_crypto::Pair::public(pair))) + .collect() + }); impl From> for ecdsa_crypto::Pair { fn from(k: Keyring) -> Self { diff --git a/substrate/primitives/core/Cargo.toml b/substrate/primitives/core/Cargo.toml index 51cbfa3bdfb..f6bc17bccac 100644 --- a/substrate/primitives/core/Cargo.toml +++ b/substrate/primitives/core/Cargo.toml @@ -26,10 +26,14 @@ impl-serde = { optional = true, workspace = true } hash-db = { workspace = true } hash256-std-hasher = { workspace = true } bs58 = { optional = true, workspace = true } -rand = { features = ["small_rng"], optional = true, workspace = true, default-features = true } +rand = { features = [ + "small_rng", +], optional = true, workspace = true, default-features = true } substrate-bip39 = { workspace = true } # personal fork here as workaround for: https://github.com/rust-bitcoin/rust-bip39/pull/64 -bip39 = { package = "parity-bip39", version = "2.0.1", default-features = false, features = ["alloc"] } +bip39 = { package = "parity-bip39", version = "2.0.1", default-features = false, features = [ + "alloc", +] } zeroize = { workspace = true } secrecy = { features = ["alloc"], workspace = true } parking_lot = { optional = true, workspace = true, default-features = true } @@ -58,17 +62,21 @@ sp-runtime-interface = { workspace = true } # k256 crate, better portability, intended to be used in substrate-runtimes (no-std) k256 = { features = ["alloc", "ecdsa"], workspace = true } # secp256k1 crate, better performance, intended to be used on host side (std) -secp256k1 = { features = ["alloc", "recovery"], optional = true, workspace = true } +secp256k1 = { features = [ + "alloc", + "recovery", +], optional = true, workspace = true } # bls crypto w3f-bls = { optional = true, workspace = true } # bandersnatch crypto -bandersnatch_vrfs = { git = "https://github.com/w3f/ring-vrf", rev = "0fef826", default-features = false, features = ["substrate-curves"], optional = true } +bandersnatch_vrfs = { git = "https://github.com/w3f/ring-vrf", rev = "0fef826", default-features = false, features = [ + "substrate-curves", +], optional = true } [dev-dependencies] criterion = { workspace = true, default-features = true } serde_json = { workspace = true, default-features = true } -lazy_static = { workspace = true } regex = { workspace = true } [[bench]] diff --git a/substrate/primitives/core/fuzz/Cargo.toml b/substrate/primitives/core/fuzz/Cargo.toml index 46dfe8d483b..b6ef395adf9 100644 --- a/substrate/primitives/core/fuzz/Cargo.toml +++ b/substrate/primitives/core/fuzz/Cargo.toml @@ -11,7 +11,6 @@ workspace = true cargo-fuzz = true [dependencies] -lazy_static = { workspace = true } libfuzzer-sys = { workspace = true } regex = { workspace = true } diff --git a/substrate/primitives/core/fuzz/fuzz_targets/fuzz_address_uri.rs b/substrate/primitives/core/fuzz/fuzz_targets/fuzz_address_uri.rs index e2d9e2fc8b0..ac84faf2d89 100644 --- a/substrate/primitives/core/fuzz/fuzz_targets/fuzz_address_uri.rs +++ b/substrate/primitives/core/fuzz/fuzz_targets/fuzz_address_uri.rs @@ -24,11 +24,12 @@ extern crate sp_core; use libfuzzer_sys::fuzz_target; use regex::Regex; use sp_core::crypto::AddressUri; +use std::sync::LazyLock; -lazy_static::lazy_static! { - static ref SECRET_PHRASE_REGEX: Regex = Regex::new(r"^(?P[a-zA-Z0-9 ]+)?(?P(//?[^/]+)*)(///(?P.*))?$") - .expect("constructed from known-good static value; qed"); -} +static SECRET_PHRASE_REGEX: LazyLock = LazyLock::new(|| { + Regex::new(r"^(?P[a-zA-Z0-9 ]+)?(?P(//?[^/]+)*)(///(?P.*))?$") + .expect("constructed from known-good static value; qed") +}); fuzz_target!(|input: &str| { let regex_result = SECRET_PHRASE_REGEX.captures(input); diff --git a/substrate/primitives/core/src/address_uri.rs b/substrate/primitives/core/src/address_uri.rs index bbe31b7553b..4877250cf3a 100644 --- a/substrate/primitives/core/src/address_uri.rs +++ b/substrate/primitives/core/src/address_uri.rs @@ -196,11 +196,12 @@ impl<'a> AddressUri<'a> { mod tests { use super::*; use regex::Regex; + use std::sync::LazyLock; - lazy_static::lazy_static! { - static ref SECRET_PHRASE_REGEX: Regex = Regex::new(r"^(?P[a-zA-Z0-9 ]+)?(?P(//?[^/]+)*)(///(?P.*))?$") - .expect("constructed from known-good static value; qed"); - } + static SECRET_PHRASE_REGEX: LazyLock = LazyLock::new(|| { + Regex::new(r"^(?P[a-zA-Z0-9 ]+)?(?P(//?[^/]+)*)(///(?P.*))?$") + .expect("constructed from known-good static value; qed") + }); fn check_with_regex(input: &str) { let regex_result = SECRET_PHRASE_REGEX.captures(input); diff --git a/substrate/primitives/panic-handler/Cargo.toml b/substrate/primitives/panic-handler/Cargo.toml index 395e788eb24..012fe08f7cd 100644 --- a/substrate/primitives/panic-handler/Cargo.toml +++ b/substrate/primitives/panic-handler/Cargo.toml @@ -18,5 +18,4 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] backtrace = { workspace = true } -lazy_static = { workspace = true } regex = { workspace = true } diff --git a/substrate/primitives/panic-handler/src/lib.rs b/substrate/primitives/panic-handler/src/lib.rs index e2a9bfa195a..c4a7eb8dc67 100644 --- a/substrate/primitives/panic-handler/src/lib.rs +++ b/substrate/primitives/panic-handler/src/lib.rs @@ -31,6 +31,7 @@ use std::{ io::{self, Write}, marker::PhantomData, panic::{self, PanicInfo}, + sync::LazyLock, thread, }; @@ -128,8 +129,9 @@ impl Drop for AbortGuard { // NOTE: When making any changes here make sure to also change this function in `sc-tracing`. fn strip_control_codes(input: &str) -> std::borrow::Cow { - lazy_static::lazy_static! { - static ref RE: Regex = Regex::new(r#"(?x) + static RE: LazyLock = LazyLock::new(|| { + Regex::new( + r#"(?x) \x1b\[[^m]+m| # VT100 escape codes [ \x00-\x09\x0B-\x1F # ASCII control codes / Unicode C0 control codes, except \n @@ -138,8 +140,10 @@ fn strip_control_codes(input: &str) -> std::borrow::Cow { \u{202A}-\u{202E} # Unicode left-to-right / right-to-left control characters \u{2066}-\u{2069} # Same as above ] - "#).expect("regex parsing doesn't fail; qed"); - } + "#, + ) + .expect("regex parsing doesn't fail; qed") + }); RE.replace_all(input, "") } diff --git a/substrate/primitives/trie/Cargo.toml b/substrate/primitives/trie/Cargo.toml index a28f29b0158..7f27bb09729 100644 --- a/substrate/primitives/trie/Cargo.toml +++ b/substrate/primitives/trie/Cargo.toml @@ -24,7 +24,6 @@ harness = false ahash = { optional = true, workspace = true } codec = { workspace = true } hash-db = { workspace = true } -lazy_static = { optional = true, workspace = true } memory-db = { workspace = true } nohash-hasher = { optional = true, workspace = true } parking_lot = { optional = true, workspace = true, default-features = true } @@ -51,7 +50,6 @@ std = [ "ahash", "codec/std", "hash-db/std", - "lazy_static", "memory-db/std", "nohash-hasher", "parking_lot", diff --git a/substrate/primitives/trie/src/cache/shared_cache.rs b/substrate/primitives/trie/src/cache/shared_cache.rs index e3ba94a2af7..7f6da80fe95 100644 --- a/substrate/primitives/trie/src/cache/shared_cache.rs +++ b/substrate/primitives/trie/src/cache/shared_cache.rs @@ -25,17 +25,15 @@ use schnellru::LruMap; use std::{ collections::{hash_map::Entry as SetEntry, HashMap}, hash::{BuildHasher, Hasher as _}, - sync::Arc, + sync::{Arc, LazyLock}, }; use trie_db::{node::NodeOwned, CachedValue}; -lazy_static::lazy_static! { - static ref RANDOM_STATE: ahash::RandomState = { - use rand::Rng; - let mut rng = rand::thread_rng(); - ahash::RandomState::generate_with(rng.gen(), rng.gen(), rng.gen(), rng.gen()) - }; -} +static RANDOM_STATE: LazyLock = LazyLock::new(|| { + use rand::Rng; + let mut rng = rand::thread_rng(); + ahash::RandomState::generate_with(rng.gen(), rng.gen(), rng.gen(), rng.gen()) +}); pub struct SharedNodeCacheLimiter { /// The maximum size (in bytes) the cache can hold inline. diff --git a/substrate/utils/frame/benchmarking-cli/Cargo.toml b/substrate/utils/frame/benchmarking-cli/Cargo.toml index 4e88e3360e3..ee5522f5bc0 100644 --- a/substrate/utils/frame/benchmarking-cli/Cargo.toml +++ b/substrate/utils/frame/benchmarking-cli/Cargo.toml @@ -24,7 +24,6 @@ comfy-table = { workspace = true } handlebars = { workspace = true } Inflector = { workspace = true } itertools = { workspace = true } -lazy_static = { workspace = true } linked-hash-map = { workspace = true } log = { workspace = true, default-features = true } rand = { features = ["small_rng"], workspace = true, default-features = true } diff --git a/substrate/utils/frame/benchmarking-cli/src/machine/hardware.rs b/substrate/utils/frame/benchmarking-cli/src/machine/hardware.rs index ee1d490b854..f542eb60520 100644 --- a/substrate/utils/frame/benchmarking-cli/src/machine/hardware.rs +++ b/substrate/utils/frame/benchmarking-cli/src/machine/hardware.rs @@ -17,19 +17,17 @@ //! Contains types to define hardware requirements. -use lazy_static::lazy_static; use sc_sysinfo::Requirements; +use std::sync::LazyLock; -lazy_static! { - /// The hardware requirements as measured on reference hardware. - /// - /// These values are provided by Parity, however it is possible - /// to use your own requirements if you are running a custom chain. - pub static ref SUBSTRATE_REFERENCE_HARDWARE: Requirements = { - let raw = include_bytes!("reference_hardware.json").as_slice(); - serde_json::from_slice(raw).expect("Hardcoded data is known good; qed") - }; -} +/// The hardware requirements as measured on reference hardware. +/// +/// These values are provided by Parity, however it is possible +/// to use your own requirements if you are running a custom chain. +pub static SUBSTRATE_REFERENCE_HARDWARE: LazyLock = LazyLock::new(|| { + let raw = include_bytes!("reference_hardware.json").as_slice(); + serde_json::from_slice(raw).expect("Hardcoded data is known good; qed") +}); #[cfg(test)] mod tests { -- GitLab From 8279d1046cca51a317dec15df5a9b29240545163 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Sep 2024 23:19:56 +0200 Subject: [PATCH 125/125] Bump syn from 2.0.77 to 2.0.79 in the known_good_semver group (#5864) Bumps the known_good_semver group with 1 update: [syn](https://github.com/dtolnay/syn). Updates `syn` from 2.0.77 to 2.0.79

Release notes

Sourced from syn's releases.

2.0.79

  • Fix infinite loop on parsing chained ranges (#1741)
  • Fix panic in parsing use items containing absolute paths (#1742)

2.0.78

  • Fix infinite loop on chained comparison (#1739)
Commits
  • 732e6e3 Release 2.0.79
  • af63396 Merge pull request #1742 from dtolnay/usecrateroot
  • 31e8632 Fix construction of UseGroup containing crate roots
  • 037861a Merge pull request #1741 from dtolnay/binoploop
  • 8df4dd0 Force cursor to advance in parse_expr calls
  • 09d020f Release 2.0.78
  • 7eaebfb Merge pull request #1739 from dtolnay/chainedcomparison
  • b3d2886 Fix infinite loop on chained comparison
  • 3f3d0c5 Add regression test for issue 1738
  • 346efae Touch up PR 1737
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=syn&package-manager=cargo&previous-version=2.0.77&new-version=2.0.79)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 188 ++++++++++++++++++++++++++--------------------------- Cargo.toml | 2 +- 2 files changed, 95 insertions(+), 95 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7eb88c85bc8..a572c37a406 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -168,7 +168,7 @@ dependencies = [ "proc-macro-error", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", "syn-solidity", "tiny-keccak", ] @@ -295,7 +295,7 @@ dependencies = [ "proc-macro-error", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -753,7 +753,7 @@ checksum = "7378575ff571966e99a744addeff0bff98b8ada0dedf1956d59e634db95eaac1" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", "synstructure 0.13.1", ] @@ -776,7 +776,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -1380,7 +1380,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -1397,7 +1397,7 @@ checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -1612,7 +1612,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -3031,7 +3031,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -4388,7 +4388,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -4940,7 +4940,7 @@ checksum = "83fdaf97f4804dcebfa5862639bc9ce4121e82140bec2a987ac5140294865b5b" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -4980,7 +4980,7 @@ dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", "scratch", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -4997,7 +4997,7 @@ checksum = "50c49547d73ba8dcfd4ad7325d64c6d5391ff4224d498fc39a6f3f49825a530d" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -5045,7 +5045,7 @@ dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", "strsim 0.11.1", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -5067,7 +5067,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core 0.20.10", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -5184,7 +5184,7 @@ checksum = "d65d7ce8132b7c0e54497a4d9a55a1c2a0912a0d786cf894472ba818fba45762" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -5195,7 +5195,7 @@ checksum = "62d671cc41a825ebabc75757b62d3d168c577f9149b2d49ece1dad1f72119d25" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -5206,7 +5206,7 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -5314,7 +5314,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -5375,7 +5375,7 @@ dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", "regex", - "syn 2.0.77", + "syn 2.0.79", "termcolor", "toml 0.8.12", "walkdir", @@ -5606,7 +5606,7 @@ dependencies = [ "heck 0.4.1", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -5626,7 +5626,7 @@ checksum = "5e9a1f9f7d83e59740248a6e14ecf93929ade55027844dfcea78beafccc15745" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -5637,7 +5637,7 @@ checksum = "6fd000fd6988e73bbe993ea3db9b1aa64906ab88766d654973924340c8cddb42" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -5852,7 +5852,7 @@ dependencies = [ "prettyplease", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -5924,7 +5924,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -6267,7 +6267,7 @@ dependencies = [ "quote 1.0.37", "scale-info", "sp-arithmetic 23.0.0", - "syn 2.0.77", + "syn 2.0.79", "trybuild", ] @@ -6481,7 +6481,7 @@ dependencies = [ "sp-metadata-ir 0.6.0", "sp-runtime 31.0.1", "static_assertions", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -6492,7 +6492,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -6501,7 +6501,7 @@ version = "11.0.0" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -6757,7 +6757,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -8215,7 +8215,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -8947,7 +8947,7 @@ dependencies = [ "proc-macro-warning 0.4.2", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -9364,7 +9364,7 @@ dependencies = [ "macro_magic_core", "macro_magic_macros", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -9378,7 +9378,7 @@ dependencies = [ "macro_magic_core_macros", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -9389,7 +9389,7 @@ checksum = "b02abfe41815b5bd98dbd4260173db2c116dda171dc0fe7838cb206333b83308" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -9400,7 +9400,7 @@ checksum = "73ea28ee64b88876bf45277ed9a5817c1817df061a74f2b988971a12570e5869" dependencies = [ "macro_magic_core", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -9746,7 +9746,7 @@ dependencies = [ "cfg-if", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -10350,7 +10350,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -10526,7 +10526,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -11306,7 +11306,7 @@ version = "18.0.0" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -12410,7 +12410,7 @@ version = "0.1.0" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -12655,7 +12655,7 @@ dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", "sp-runtime 31.0.1", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -13762,7 +13762,7 @@ dependencies = [ "pest_meta", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -13803,7 +13803,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -16281,7 +16281,7 @@ dependencies = [ "polkavm-common 0.8.0", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -16293,7 +16293,7 @@ dependencies = [ "polkavm-common 0.9.0", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -16305,7 +16305,7 @@ dependencies = [ "polkavm-common 0.11.0", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -16315,7 +16315,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15e85319a0d5129dc9f021c62607e0804f5fb777a05cdda44d750ac0732def66" dependencies = [ "polkavm-derive-impl 0.8.0", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -16325,7 +16325,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ba81f7b5faac81e528eb6158a6f3c9e0bb1008e0ffa19653bc8dea925ecb429" dependencies = [ "polkavm-derive-impl 0.9.0", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -16335,7 +16335,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bf952e05bc5ce7d81293bae18cb44c271c78615b201d75e983cdcc40d5c6ef1" dependencies = [ "polkavm-derive-impl 0.11.0", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -16552,7 +16552,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c64d9ba0963cdcea2e1b2230fbae2bab30eb25a174be395c41e764bfb65dd62" dependencies = [ "proc-macro2 1.0.86", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -16643,7 +16643,7 @@ checksum = "3d1eaa7fa0aa1929ffdf7eeb6eac234dde6268914a14ad44d23521ab6a9b258e" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -16654,7 +16654,7 @@ checksum = "9b698b0b09d40e9b7c1a47b132d66a8b54bcd20583d9b6d06e4535e383b4405c" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -16735,7 +16735,7 @@ checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -16817,7 +16817,7 @@ dependencies = [ "prost 0.12.6", "prost-types 0.12.4", "regex", - "syn 2.0.77", + "syn 2.0.79", "tempfile", ] @@ -16838,7 +16838,7 @@ dependencies = [ "prost 0.13.2", "prost-types 0.13.2", "regex", - "syn 2.0.77", + "syn 2.0.79", "tempfile", ] @@ -16865,7 +16865,7 @@ dependencies = [ "itertools 0.12.1", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -16878,7 +16878,7 @@ dependencies = [ "itertools 0.12.1", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -17347,7 +17347,7 @@ checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -17946,7 +17946,7 @@ dependencies = [ "regex", "relative-path", "rustc_version 0.4.0", - "syn 2.0.77", + "syn 2.0.79", "unicode-ident", ] @@ -18474,7 +18474,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -19835,7 +19835,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -20014,7 +20014,7 @@ dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", "scale-info", - "syn 2.0.77", + "syn 2.0.79", "thiserror", ] @@ -20364,7 +20364,7 @@ checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -20466,7 +20466,7 @@ checksum = "91d129178576168c589c9ec973feedf7d3126c01ac2bf08795109aa35b69fb8f" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -21470,7 +21470,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -21485,7 +21485,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -22095,7 +22095,7 @@ version = "0.1.0" dependencies = [ "quote 1.0.37", "sp-crypto-hashing 0.1.0", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -22106,7 +22106,7 @@ checksum = "b85d0f1f1e44bd8617eb2a48203ee854981229e3e79e6f468c7175d5fd37489b" dependencies = [ "quote 1.0.37", "sp-crypto-hashing 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -22124,7 +22124,7 @@ source = "git+https://github.com/paritytech/polkadot-sdk#82912acb33a9030c0ef3bf5 dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -22133,7 +22133,7 @@ version = "14.0.0" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -22144,7 +22144,7 @@ checksum = "48d09fa0a5f7299fb81ee25ae3853d26200f7a348148aed6de76be905c007dbe" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -22705,7 +22705,7 @@ dependencies = [ "proc-macro-crate 1.3.1", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -22717,7 +22717,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -22731,7 +22731,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -23206,7 +23206,7 @@ dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", "sp-version 29.0.0", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -23218,7 +23218,7 @@ dependencies = [ "parity-scale-codec", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -23683,7 +23683,7 @@ dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", "rustversion", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -23696,7 +23696,7 @@ dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", "rustversion", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -24185,7 +24185,7 @@ dependencies = [ "scale-info", "scale-typegen", "subxt-metadata", - "syn 2.0.77", + "syn 2.0.79", "thiserror", "tokio", ] @@ -24248,7 +24248,7 @@ dependencies = [ "quote 1.0.37", "scale-typegen", "subxt-codegen", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -24401,9 +24401,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.77" +version = "2.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", @@ -24419,7 +24419,7 @@ dependencies = [ "paste", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -24448,7 +24448,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -24578,7 +24578,7 @@ checksum = "5999e24eaa32083191ba4e425deb75cdf25efefabe5aaccb7446dd0d4122a3f5" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -24753,7 +24753,7 @@ checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -24929,7 +24929,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -25205,7 +25205,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -25247,7 +25247,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -25887,7 +25887,7 @@ dependencies = [ "once_cell", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", "wasm-bindgen-shared", ] @@ -25921,7 +25921,7 @@ checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -27055,7 +27055,7 @@ dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", "staging-xcm", - "syn 2.0.77", + "syn 2.0.79", "trybuild", ] @@ -27226,7 +27226,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -27246,7 +27246,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 155cd2cf5ba..e2c6d6c8ded 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1304,7 +1304,7 @@ substrate-test-utils = { path = "substrate/test-utils" } substrate-wasm-builder = { path = "substrate/utils/wasm-builder", default-features = false } subxt = { version = "0.37", default-features = false } subxt-signer = { version = "0.37" } -syn = { version = "2.0.77" } +syn = { version = "2.0.79" } sysinfo = { version = "0.30" } tar = { version = "0.4" } tempfile = { version = "3.8.1" } -- GitLab